diff --git a/CHANGELOG.md b/CHANGELOG.md index 790f6de95..912659875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Latest * The normal @mock_ decorators will no longer work with boto. It is suggested that you upgrade to boto3 or use the standalone-server mode. If you would still like to use boto, you must use the @mock__deprecated decorators which will be removed in a future release. * The @mock_s3bucket_path decorator is now deprecated. Use the @mock_s3 decorator instead. + Added + * Reset API: a reset API has been added to flush all of the current data ex: `requests.post("http://motoapi.amazonaws.com/moto-api/reset")` + 0.4.31 ------ diff --git a/Makefile b/Makefile index a7f08b146..58b74b2fb 100644 --- a/Makefile +++ b/Makefile @@ -9,5 +9,8 @@ test: rm -rf cover @nosetests -sv --with-coverage --cover-html ./tests/ +test_server: + @TEST_SERVER_MODE=true nosetests -sv --with-coverage --cover-html ./tests/ + publish: python setup.py sdist bdist_wheel upload diff --git a/README.md b/README.md index ae161dc5c..5485c63cd 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ -# Moto - Mock Boto +# Moto - Mock AWS Services [![Build Status](https://travis-ci.org/spulec/moto.png?branch=master)](https://travis-ci.org/spulec/moto) [![Coverage Status](https://coveralls.io/repos/spulec/moto/badge.png?branch=master)](https://coveralls.io/r/spulec/moto) # In a nutshell -Moto is a library that allows your python tests to easily mock out the boto library. +Moto is a library that allows your tests to easily mock out AWS Services. -Imagine you have the following code that you want to test: +Imagine you have the following python code that you want to test: ```python -import boto -from boto.s3.key import Key +import boto3 class MyModel(object): def __init__(self, name, value): @@ -19,11 +18,9 @@ class MyModel(object): self.value = value def save(self): - conn = boto.connect_s3() - bucket = conn.get_bucket('mybucket') - k = Key(bucket) - k.key = self.name - k.set_contents_from_string(self.value) + s3 = boto3.client('s3', region_name='us-east-1') + s3.put_object(Bucket='mybucket', Key=self.name, Body=self.value) + ``` Take a minute to think how you would have tested that in the past. @@ -31,25 +28,28 @@ Take a minute to think how you would have tested that in the past. Now see how you could test it with Moto: ```python -import boto +import boto3 from moto import mock_s3 from mymodule import MyModel + @mock_s3 def test_my_model_save(): - conn = boto.connect_s3() + conn = boto3.resource('s3', region_name='us-east-1') # We need to create the bucket since this is all in Moto's 'virtual' AWS account - conn.create_bucket('mybucket') + conn.create_bucket(Bucket='mybucket') model_instance = MyModel('steve', 'is awesome') model_instance.save() - assert conn.get_bucket('mybucket').get_key('steve').get_contents_as_string() == 'is awesome' + body = conn.Object('mybucket', 'steve').get()['Body'].read().decode("utf-8") + + assert body == b'is awesome' ``` With the decorator wrapping the test, all the calls to s3 are automatically mocked out. The mock keeps the state of the buckets and keys. -It gets even better! Moto isn't just S3. Here's the status of the other AWS services implemented. +It gets even better! Moto isn't just for Python code and it isn't just for S3. Look at the [standalone server mode](https://github.com/spulec/moto#stand-alone-server-mode) for more information about running Moto with other languages. Here's the status of the other AWS services implemented: ```gherkin |------------------------------------------------------------------------------| @@ -193,11 +193,6 @@ def test_my_model_save(): mock.stop() ``` -## Use with other libraries (boto3) or languages - -In general, Moto doesn't rely on anything specific to Boto. It only mocks AWS endpoints, so there should be no issue with boto3 or using other languages. Feel free to open an issue if something isn't working though. If you are using another language, you will need to either use the stand-alone server mode (more below) or monkey patch the HTTP calls yourself. - - ## Stand-alone Server Mode Moto also has a stand-alone server mode. This allows you to utilize diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index b6fa2df02..4b09f44bc 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -326,8 +326,8 @@ class RestAPI(object): return status_code, {}, response def update_integration_mocks(self, stage_name): - stage_url = STAGE_URL.format(api_id=self.id, region_name=self.region_name, stage_name=stage_name) - responses.add_callback(responses.GET, stage_url.lower(), callback=self.resource_callback) + stage_url = STAGE_URL.format(api_id=self.id.upper(), region_name=self.region_name, stage_name=stage_name) + responses.add_callback(responses.GET, stage_url, callback=self.resource_callback) def create_stage(self, name, deployment_id,variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None): if variables is None: diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index e8595cc22..1fc139eb7 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -154,15 +154,15 @@ class LambdaFunction(object): sys.stderr = sys.__stderr__ return self.convert(result) - def invoke(self, request, headers): + def invoke(self, body, request_headers, response_headers): payload = dict() # Get the invocation type: - r = self._invoke_lambda(code=self.code, event=request.body) - if request.headers.get("x-amz-invocation-type") == "RequestResponse": + r = self._invoke_lambda(code=self.code, event=body) + if request_headers.get("x-amz-invocation-type") == "RequestResponse": encoded = base64.b64encode(r.encode('utf-8')) - headers["x-amz-log-result"] = encoded.decode('utf-8') - payload['result'] = headers["x-amz-log-result"] + response_headers["x-amz-log-result"] = encoded.decode('utf-8') + payload['result'] = response_headers["x-amz-log-result"] result = r.encode('utf-8') else: result = json.dumps(payload) diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index 0cd7c57ea..3fc756efa 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -10,32 +10,32 @@ from .models import lambda_backends class LambdaResponse(BaseResponse): - @classmethod - def root(cls, request, full_url, headers): + def root(self, request, full_url, headers): + self.setup_class(request, full_url, headers) if request.method == 'GET': - return cls()._list_functions(request, full_url, headers) + return self._list_functions(request, full_url, headers) elif request.method == 'POST': - return cls()._create_function(request, full_url, headers) + return self._create_function(request, full_url, headers) else: raise ValueError("Cannot handle request") - @classmethod - def function(cls, request, full_url, headers): + def function(self, request, full_url, headers): + self.setup_class(request, full_url, headers) if request.method == 'GET': - return cls()._get_function(request, full_url, headers) + return self._get_function(request, full_url, headers) elif request.method == 'DELETE': - return cls()._delete_function(request, full_url, headers) + return self._delete_function(request, full_url, headers) else: raise ValueError("Cannot handle request") - @classmethod - def invoke(cls, request, full_url, headers): + def invoke(self, request, full_url, headers): + self.setup_class(request, full_url, headers) if request.method == 'POST': - return cls()._invoke(request, full_url, headers) + return self._invoke(request, full_url) else: raise ValueError("Cannot handle request") - def _invoke(self, request, full_url, headers): + def _invoke(self, request, full_url): response_headers = {} lambda_backend = self.get_lambda_backend(full_url) @@ -44,7 +44,7 @@ class LambdaResponse(BaseResponse): if lambda_backend.has_function(function_name): fn = lambda_backend.get_function(function_name) - payload = fn.invoke(request, response_headers) + payload = fn.invoke(self.body, self.headers, response_headers) response_headers['Content-Length'] = str(len(payload)) return 202, response_headers, payload else: @@ -59,7 +59,7 @@ class LambdaResponse(BaseResponse): def _create_function(self, request, full_url, headers): lambda_backend = self.get_lambda_backend(full_url) - spec = json.loads(request.body.decode('utf-8')) + spec = json.loads(self.body.decode('utf-8')) try: fn = lambda_backend.create_function(spec) except ValueError as e: diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py index 79a99c9f8..c63135766 100644 --- a/moto/awslambda/urls.py +++ b/moto/awslambda/urls.py @@ -5,9 +5,10 @@ url_bases = [ "https?://lambda.(.+).amazonaws.com", ] +response = LambdaResponse() + url_paths = { - # double curly braces because the `format()` method is called on the strings - '{0}/\d{{4}}-\d{{2}}-\d{{2}}/functions/?$': LambdaResponse.root, - '{0}/\d{{4}}-\d{{2}}-\d{{2}}/functions/(?P[\w_-]+)/?$': LambdaResponse.function, - '{0}/\d{{4}}-\d{{2}}-\d{{2}}/functions/(?P[\w_-]+)/invocations?$': LambdaResponse.invoke, + '{0}/(?P[^/]+)/functions/?$': response.root, + '{0}/(?P[^/]+)/functions/(?P[\w_-]+)/?$': response.function, + '{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$': response.invoke, } diff --git a/moto/backends.py b/moto/backends.py index 4cebe560a..5b1695e3b 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -1,67 +1,71 @@ from __future__ import unicode_literals -from moto.apigateway import apigateway_backend -from moto.autoscaling import autoscaling_backend -from moto.awslambda import lambda_backend -from moto.cloudformation import cloudformation_backend -from moto.cloudwatch import cloudwatch_backend -from moto.core import moto_api_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 -from moto.ecs import ecs_backend -from moto.elb import elb_backend -from moto.emr import emr_backend -from moto.events import events_backend -from moto.glacier import glacier_backend -from moto.iam import iam_backend -from moto.kinesis import kinesis_backend -from moto.kms import kms_backend -from moto.opsworks import opsworks_backend -from moto.rds import rds_backend -from moto.redshift import redshift_backend -from moto.route53 import route53_backend -from moto.s3 import s3_backend -from moto.ses import ses_backend -from moto.sns import sns_backend -from moto.sqs import sqs_backend -from moto.sts import sts_backend +from moto.apigateway import apigateway_backends +from moto.autoscaling import autoscaling_backends +from moto.awslambda import lambda_backends +from moto.cloudformation import cloudformation_backends +from moto.cloudwatch import cloudwatch_backends +from moto.core import moto_api_backends +from moto.datapipeline import datapipeline_backends +from moto.dynamodb import dynamodb_backends +from moto.dynamodb2 import dynamodb_backends2 +from moto.ec2 import ec2_backends +from moto.ecs import ecs_backends +from moto.elb import elb_backends +from moto.emr import emr_backends +from moto.events import events_backends +from moto.glacier import glacier_backends +from moto.iam import iam_backends +from moto.instance_metadata import instance_metadata_backends +from moto.kinesis import kinesis_backends +from moto.kms import kms_backends +from moto.opsworks import opsworks_backends +from moto.rds2 import rds2_backends +from moto.redshift import redshift_backends +from moto.route53 import route53_backends +from moto.s3 import s3_backends +from moto.ses import ses_backends +from moto.sns import sns_backends +from moto.sqs import sqs_backends +from moto.sts import sts_backends BACKENDS = { - 'apigateway': apigateway_backend, - 'autoscaling': autoscaling_backend, - 'cloudformation': cloudformation_backend, - 'cloudwatch': cloudwatch_backend, - 'datapipeline': datapipeline_backend, - 'dynamodb': dynamodb_backend, - 'dynamodb2': dynamodb_backend2, - 'ec2': ec2_backend, - 'ecs': ecs_backend, - 'elb': elb_backend, - 'events': events_backend, - 'emr': emr_backend, - 'glacier': glacier_backend, - 'iam': iam_backend, - 'moto_api': moto_api_backend, - 'opsworks': opsworks_backend, - 'kinesis': kinesis_backend, - 'kms': kms_backend, - 'redshift': redshift_backend, - 'rds': rds_backend, - 's3': s3_backend, - 's3bucket_path': s3_backend, - 'ses': ses_backend, - 'sns': sns_backend, - 'sqs': sqs_backend, - 'sts': sts_backend, - 'route53': route53_backend, - 'lambda': lambda_backend, + 'apigateway': apigateway_backends, + 'autoscaling': autoscaling_backends, + 'cloudformation': cloudformation_backends, + 'cloudwatch': cloudwatch_backends, + 'datapipeline': datapipeline_backends, + 'dynamodb': dynamodb_backends, + 'dynamodb2': dynamodb_backends2, + 'ec2': ec2_backends, + 'ecs': ecs_backends, + 'elb': elb_backends, + 'events': events_backends, + 'emr': emr_backends, + 'glacier': glacier_backends, + 'iam': iam_backends, + 'moto_api': moto_api_backends, + 'instance_metadata': instance_metadata_backends, + 'opsworks': opsworks_backends, + 'kinesis': kinesis_backends, + 'kms': kms_backends, + 'redshift': redshift_backends, + 'rds': rds2_backends, + 's3': s3_backends, + 's3bucket_path': s3_backends, + 'ses': ses_backends, + 'sns': sns_backends, + 'sqs': sqs_backends, + 'sts': sts_backends, + 'route53': route53_backends, + 'lambda': lambda_backends, } -def get_model(name): - for backend in BACKENDS.values(): - models = getattr(backend.__class__, '__models__', {}) - if name in models: - return list(getattr(backend, models[name])()) +def get_model(name, region): + for backends in BACKENDS.values(): + for region, backend in backends.items(): + if region == region: + models = getattr(backend.__class__, '__models__', {}) + if name in models: + return list(getattr(backend, models[name])()) diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index d16b3560c..3b8f53895 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -17,8 +17,11 @@ class CloudFormationResponse(BaseResponse): def _get_stack_from_s3_url(self, template_url): template_url_parts = urlparse(template_url) - bucket_name = template_url_parts.netloc.split(".")[0] - key_name = template_url_parts.path.lstrip("/") + if "localhost" in template_url: + bucket_name, key_name = template_url_parts.path.lstrip("/").split("/") + else: + bucket_name = template_url_parts.netloc.split(".")[0] + key_name = template_url_parts.path.lstrip("/") key = s3_backend.get_key(bucket_name, key_name) return key.value.decode("utf-8") diff --git a/moto/core/__init__.py b/moto/core/__init__.py index 664637b76..4f783d46c 100644 --- a/moto/core/__init__.py +++ b/moto/core/__init__.py @@ -1,2 +1,4 @@ from __future__ import unicode_literals from .models import BaseBackend, moto_api_backend # flake8: noqa + +moto_api_backends = {"global": moto_api_backend} diff --git a/moto/core/exceptions.py b/moto/core/exceptions.py index c66b8f257..d3a87e299 100644 --- a/moto/core/exceptions.py +++ b/moto/core/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from werkzeug.exceptions import HTTPException from jinja2 import DictLoader, Environment from six import text_type @@ -47,6 +49,10 @@ class RESTError(HTTPException): error_type=error_type, message=message, **kwargs) +class DryRunClientError(RESTError): + code = 400 + + class JsonRESTError(RESTError): def __init__(self, error_type, message, template='error_json', **kwargs): super(JsonRESTError, self).__init__(error_type, message, template, **kwargs) diff --git a/moto/core/models.py b/moto/core/models.py index 8fac8a990..04ff709e0 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -6,9 +6,9 @@ import inspect import os import re +from moto import settings from moto.packages.responses import responses from moto.packages.httpretty import HTTPretty -from .responses import metadata_response from .utils import ( convert_httpretty_response, convert_regex_to_flask_path, @@ -21,6 +21,15 @@ class BaseMockAWS(object): def __init__(self, backends): self.backends = backends + self.backends_for_urls = {} + from moto.backends import BACKENDS + default_backends = { + "instance_metadata": BACKENDS['instance_metadata']['global'], + "moto_api": BACKENDS['moto_api']['global'], + } + self.backends_for_urls.update(self.backends) + self.backends_for_urls.update(default_backends) + if self.__class__.nested_count == 0: self.reset() @@ -95,20 +104,13 @@ class HttprettyMockAWS(BaseMockAWS): HTTPretty.enable() for method in HTTPretty.METHODS: - backend = list(self.backends.values())[0] - for key, value in backend.urls.items(): - HTTPretty.register_uri( - method=method, - uri=re.compile(key), - body=convert_httpretty_response(value), - ) - - # Mock out localhost instance metadata - HTTPretty.register_uri( - method=method, - uri=re.compile('http://169.254.169.254/latest/meta-data/.*'), - body=convert_httpretty_response(metadata_response), - ) + for backend in self.backends_for_urls.values(): + for key, value in backend.urls.items(): + HTTPretty.register_uri( + method=method, + uri=re.compile(key), + body=convert_httpretty_response(value), + ) def disable_patching(self): HTTPretty.disable() @@ -126,20 +128,14 @@ class ResponsesMockAWS(BaseMockAWS): def enable_patching(self): responses.start() for method in RESPONSES_METHODS: - backend = list(self.backends.values())[0] - for key, value in backend.urls.items(): - responses.add_callback( - method=method, - url=re.compile(key), - callback=convert_flask_to_responses_response(value), - ) + for backend in self.backends_for_urls.values(): + for key, value in backend.urls.items(): + responses.add_callback( + method=method, + url=re.compile(key), + callback=convert_flask_to_responses_response(value), + ) - # Mock out localhost instance metadata - responses.add_callback( - method=method, - url=re.compile('http://169.254.169.254/latest/meta-data/.*'), - callback=convert_flask_to_responses_response(metadata_response), - ) for pattern in responses.mock._urls: pattern['stream'] = True @@ -270,10 +266,15 @@ class BaseBackend(object): return paths def decorator(self, func=None): - if func: - return MockAWS({'global': self})(func) + if settings.TEST_SERVER_MODE: + mocked_backend = ServerModeMockAWS({'global': self}) else: - return MockAWS({'global': self}) + mocked_backend = MockAWS({'global': self}) + + if func: + return mocked_backend(func) + else: + return mocked_backend def deprecated_decorator(self, func=None): if func: @@ -289,13 +290,15 @@ class base_decorator(object): self.backends = backends def __call__(self, func=None): - if self.mock_backend == MockAWS and os.environ.get('TEST_SERVER_MODE', '0').lower() == 'true': - self.mock_backend = ServerModeMockAWS + if self.mock_backend != HttprettyMockAWS and settings.TEST_SERVER_MODE: + mocked_backend = ServerModeMockAWS(self.backends) + else: + mocked_backend = self.mock_backend(self.backends) if func: - return self.mock_backend(self.backends)(func) + return mocked_backend(func) else: - return self.mock_backend(self.backends) + return mocked_backend class deprecated_base_decorator(base_decorator): @@ -303,15 +306,13 @@ class deprecated_base_decorator(base_decorator): class MotoAPIBackend(BaseBackend): - def __init__(self): - super(MotoAPIBackend, self).__init__() - def reset(self): from moto.backends import BACKENDS - for name, backend in BACKENDS.items(): + for name, backends in BACKENDS.items(): if name == "moto_api": continue - backend.reset() + for region_name, backend in backends.items(): + backend.reset() self.__init__() moto_api_backend = MotoAPIBackend() diff --git a/moto/core/responses.py b/moto/core/responses.py index 9b22b58cf..e558eb1dd 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -5,7 +5,7 @@ import logging import re import pytz -from boto.exception import JSONResponseError +from moto.core.exceptions import DryRunClientError from jinja2 import Environment, DictLoader, TemplateNotFound @@ -149,17 +149,19 @@ class BaseResponse(_TemplateEnvironmentMixin): self.path = urlparse(full_url).path self.querystring = querystring self.method = request.method - self.region = self.get_region_from_url(full_url) + self.region = self.get_region_from_url(request, full_url) self.headers = request.headers if 'host' not in self.headers: self.headers['host'] = urlparse(full_url).netloc self.response_headers = {"server": "amazon.com"} - def get_region_from_url(self, full_url): + def get_region_from_url(self, request, full_url): match = re.search(self.region_regex, full_url) if match: region = match.group(1) + elif 'Authorization' in request.headers: + region = request.headers['Authorization'].split(",")[0].split("/")[2] else: region = self.default_region return region @@ -195,6 +197,7 @@ class BaseResponse(_TemplateEnvironmentMixin): if "status" in headers: headers['status'] = str(headers['status']) return status, headers, body + raise NotImplementedError("The {0} action has not been implemented".format(action)) def _get_param(self, param_name, if_none=None): @@ -323,55 +326,19 @@ class BaseResponse(_TemplateEnvironmentMixin): def is_not_dryrun(self, action): if 'true' in self.querystring.get('DryRun', ['false']): - raise JSONResponseError(400, 'DryRunOperation', body={'message': 'An error occurred (DryRunOperation) when calling the %s operation: Request would have succeeded, but DryRun flag is set' % action}) + message = 'An error occurred (DryRunOperation) when calling the %s operation: Request would have succeeded, but DryRun flag is set' % action + raise DryRunClientError(error_type="DryRunOperation", message=message) return True -def metadata_response(request, full_url, headers): - """ - Mock response for localhost metadata - - http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html - """ - - parsed_url = urlparse(full_url) - tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1) - credentials = dict( - AccessKeyId="test-key", - SecretAccessKey="test-secret-key", - Token="test-session-token", - Expiration=tomorrow.strftime("%Y-%m-%dT%H:%M:%SZ") - ) - - path = parsed_url.path - - meta_data_prefix = "/latest/meta-data/" - # Strip prefix if it is there - if path.startswith(meta_data_prefix): - path = path[len(meta_data_prefix):] - - if path == '': - result = 'iam' - elif path == 'iam': - result = json.dumps({ - 'security-credentials': { - 'default-role': credentials - } - }) - elif path == 'iam/security-credentials/': - result = 'default-role' - elif path == 'iam/security-credentials/default-role': - result = json.dumps(credentials) - else: - raise NotImplementedError("The {0} metadata path has not been implemented".format(path)) - return 200, headers, result - class MotoAPIResponse(BaseResponse): def reset_response(self, request, full_url, headers): - from .models import moto_api_backend - moto_api_backend.reset() - return 200, {}, json.dumps({"status": "ok"}) + if request.method == "POST": + from .models import moto_api_backend + moto_api_backend.reset() + return 200, {}, json.dumps({"status": "ok"}) + return 400, {}, json.dumps({"Error": "Need to POST to reset Moto"}) class _RecursiveDictRef(object): diff --git a/moto/core/utils.py b/moto/core/utils.py index 451d1a761..11aafbb89 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -118,12 +118,16 @@ class convert_flask_to_httpretty_response(object): return "{0}.{1}".format(outer, self.callback.__name__) def __call__(self, args=None, **kwargs): - from flask import request + from flask import request, Response result = self.callback(request, request.url, {}) # result is a status, headers, response tuple - status, headers, response = result - return response, status, headers + status, headers, content = result + + response = Response(response=content, status=status, headers=headers) + if request.method == "HEAD" and 'content-length' in headers: + response.headers['Content-Length'] = headers['content-length'] + return response class convert_flask_to_responses_response(object): diff --git a/moto/dynamodb/__init__.py b/moto/dynamodb/__init__.py index 008050317..4c2bc04d9 100644 --- a/moto/dynamodb/__init__.py +++ b/moto/dynamodb/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import dynamodb_backend + +dynamodb_backends = {"global": dynamodb_backend} mock_dynamodb = dynamodb_backend.decorator mock_dynamodb_deprecated = dynamodb_backend.deprecated_decorator diff --git a/moto/dynamodb2/__init__.py b/moto/dynamodb2/__init__.py index f0892d13f..7a1f07352 100644 --- a/moto/dynamodb2/__init__.py +++ b/moto/dynamodb2/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import dynamodb_backend2 + +dynamodb_backends2 = {"global": dynamodb_backend2} mock_dynamodb2 = dynamodb_backend2.decorator mock_dynamodb2_deprecated = dynamodb_backend2.deprecated_decorator \ No newline at end of file diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 10cdcd07b..3c5a087d9 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals from boto.ec2.instancetype import InstanceType -from boto.exception import JSONResponseError 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, \ diff --git a/moto/ec2/responses/ip_addresses.py b/moto/ec2/responses/ip_addresses.py index fd58741e2..995719202 100644 --- a/moto/ec2/responses/ip_addresses.py +++ b/moto/ec2/responses/ip_addresses.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -from boto.exception import JSONResponseError from moto.core.responses import BaseResponse diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py index 321ecd99a..96e5a1ba4 100644 --- a/moto/ec2/responses/spot_instances.py +++ b/moto/ec2/responses/spot_instances.py @@ -35,8 +35,8 @@ class SpotInstances(BaseResponse): def request_spot_instances(self): price = self._get_param('SpotPrice') image_id = self._get_param('LaunchSpecification.ImageId') - count = self._get_int_param('InstanceCount') - type = self._get_param('Type') + count = self._get_int_param('InstanceCount', 1) + type = self._get_param('Type', 'one-time') valid_from = self._get_param('ValidFrom') valid_until = self._get_param('ValidUntil') launch_group = self._get_param('LaunchGroup') @@ -44,7 +44,7 @@ class SpotInstances(BaseResponse): key_name = self._get_param('LaunchSpecification.KeyName') security_groups = self._get_multi_param('LaunchSpecification.SecurityGroup') user_data = self._get_param('LaunchSpecification.UserData') - instance_type = self._get_param('LaunchSpecification.InstanceType') + instance_type = self._get_param('LaunchSpecification.InstanceType', 'm1.small') placement = self._get_param('LaunchSpecification.Placement.AvailabilityZone') kernel_id = self._get_param('LaunchSpecification.KernelId') ramdisk_id = self._get_param('LaunchSpecification.RamdiskId') diff --git a/moto/emr/exceptions.py b/moto/emr/exceptions.py new file mode 100644 index 000000000..1a3398d4f --- /dev/null +++ b/moto/emr/exceptions.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from moto.core.exceptions import RESTError + + +class EmrError(RESTError): + code = 400 diff --git a/moto/emr/models.py b/moto/emr/models.py index f92428331..155e4a898 100644 --- a/moto/emr/models.py +++ b/moto/emr/models.py @@ -299,6 +299,7 @@ class ElasticMapReduceBackend(BaseBackend): created_before = dtparse(created_before) clusters = [c for c in clusters if c.creation_datetime < created_before] + # Amazon EMR can return a maximum of 512 job flow descriptions return sorted(clusters, key=lambda x: x.id)[:512] def describe_step(self, cluster_id, step_id): diff --git a/moto/emr/responses.py b/moto/emr/responses.py index 427ab48c1..3869c33ff 100644 --- a/moto/emr/responses.py +++ b/moto/emr/responses.py @@ -5,15 +5,14 @@ from datetime import datetime from functools import wraps import pytz -from botocore.exceptions import ClientError from moto.compat import urlparse from moto.core.responses import AWSServiceSpec from moto.core.responses import BaseResponse from moto.core.responses import xml_to_json_response +from .exceptions import EmrError from .models import emr_backends -from .utils import steps_from_query_string -from .utils import tags_from_query_string +from .utils import steps_from_query_string, tags_from_query_string def generate_boto3_response(operation): @@ -46,7 +45,7 @@ class ElasticMapReduceResponse(BaseResponse): aws_service_spec = AWSServiceSpec('data/emr/2009-03-31/service-2.json') - def get_region_from_url(self, full_url): + def get_region_from_url(self, request, full_url): parsed = urlparse(full_url) for regex in self.region_regex: match = regex.search(parsed.netloc) @@ -240,9 +239,7 @@ class ElasticMapReduceResponse(BaseResponse): 'Only one AMI version and release label may be specified. ' 'Provided AMI: {0}, release label: {1}.').format( ami_version, release_label) - raise ClientError( - {'Error': {'Code': 'ValidationException', - 'Message': message}}, 'RunJobFlow') + raise EmrError(error_type="ValidationException", message=message, template='single_error') else: if ami_version: kwargs['requested_ami_version'] = ami_version diff --git a/moto/events/__init__.py b/moto/events/__init__.py index 8b15e852a..5c93c59c8 100644 --- a/moto/events/__init__.py +++ b/moto/events/__init__.py @@ -2,4 +2,5 @@ from __future__ import unicode_literals from .models import events_backend +events_backends = {"global": events_backend} mock_events = events_backend.decorator diff --git a/moto/events/urls.py b/moto/events/urls.py index bff05da3f..a6e533b08 100644 --- a/moto/events/urls.py +++ b/moto/events/urls.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from .responses import EventsHandler url_bases = [ - "https://events.(.+).amazonaws.com" + "https?://events.(.+).amazonaws.com" ] url_paths = { diff --git a/moto/iam/__init__.py b/moto/iam/__init__.py index 02519cbc9..c5110b35d 100644 --- a/moto/iam/__init__.py +++ b/moto/iam/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import iam_backend + +iam_backends = {"global": iam_backend} mock_iam = iam_backend.decorator mock_iam_deprecated = iam_backend.deprecated_decorator \ No newline at end of file diff --git a/moto/iam/urls.py b/moto/iam/urls.py index a591e3ebe..46db41e46 100644 --- a/moto/iam/urls.py +++ b/moto/iam/urls.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from .responses import IamResponse url_bases = [ - "https?://iam.amazonaws.com", + "https?://iam(.*).amazonaws.com", ] url_paths = { diff --git a/moto/instance_metadata/__init__.py b/moto/instance_metadata/__init__.py new file mode 100644 index 000000000..9197bcf7c --- /dev/null +++ b/moto/instance_metadata/__init__.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from .models import instance_metadata_backend + +instance_metadata_backends = {"global": instance_metadata_backend} \ No newline at end of file diff --git a/moto/instance_metadata/models.py b/moto/instance_metadata/models.py new file mode 100644 index 000000000..b86f86376 --- /dev/null +++ b/moto/instance_metadata/models.py @@ -0,0 +1,7 @@ +from moto.core.models import BaseBackend + + +class InstanceMetadataBackend(BaseBackend): + pass + +instance_metadata_backend = InstanceMetadataBackend() diff --git a/moto/instance_metadata/responses.py b/moto/instance_metadata/responses.py new file mode 100644 index 000000000..b2de66e7b --- /dev/null +++ b/moto/instance_metadata/responses.py @@ -0,0 +1,47 @@ +from __future__ import unicode_literals +import datetime +import json +from urlparse import urlparse + +from moto.core.responses import BaseResponse + + +class InstanceMetadataResponse(BaseResponse): + def metadata_response(self, request, full_url, headers): + """ + Mock response for localhost metadata + + http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html + """ + + parsed_url = urlparse(full_url) + tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1) + credentials = dict( + AccessKeyId="test-key", + SecretAccessKey="test-secret-key", + Token="test-session-token", + Expiration=tomorrow.strftime("%Y-%m-%dT%H:%M:%SZ") + ) + + path = parsed_url.path + + meta_data_prefix = "/latest/meta-data/" + # Strip prefix if it is there + if path.startswith(meta_data_prefix): + path = path[len(meta_data_prefix):] + + if path == '': + result = 'iam' + elif path == 'iam': + result = json.dumps({ + 'security-credentials': { + 'default-role': credentials + } + }) + elif path == 'iam/security-credentials/': + result = 'default-role' + elif path == 'iam/security-credentials/default-role': + result = json.dumps(credentials) + else: + raise NotImplementedError("The {0} metadata path has not been implemented".format(path)) + return 200, headers, result diff --git a/moto/instance_metadata/urls.py b/moto/instance_metadata/urls.py new file mode 100644 index 000000000..7776b364a --- /dev/null +++ b/moto/instance_metadata/urls.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from .responses import InstanceMetadataResponse + +url_bases = [ + "http://169.254.169.254" +] + +instance_metadata = InstanceMetadataResponse() + +url_paths = { + '{0}/(?P.+)': instance_metadata.metadata_response, +} diff --git a/moto/kinesis/responses.py b/moto/kinesis/responses.py index 9aed719d5..29f6c07ff 100644 --- a/moto/kinesis/responses.py +++ b/moto/kinesis/responses.py @@ -20,7 +20,7 @@ class KinesisResponse(BaseResponse): @property def is_firehose(self): host = self.headers.get('host') or self.headers['Host'] - return host.startswith('firehose') + return host.startswith('firehose') or 'firehose' in self.headers.get('Authorization', '') def create_stream(self): stream_name = self.parameters.get('StreamName') diff --git a/moto/route53/__init__.py b/moto/route53/__init__.py index df629880f..e2bbe4c1a 100644 --- a/moto/route53/__init__.py +++ b/moto/route53/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import route53_backend + +route53_backends = {"global": route53_backend} mock_route53 = route53_backend.decorator mock_route53_deprecated = route53_backend.deprecated_decorator diff --git a/moto/route53/responses.py b/moto/route53/responses.py index 429317dae..d796660e1 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -1,174 +1,186 @@ from __future__ import unicode_literals from jinja2 import Template from six.moves.urllib.parse import parse_qs, urlparse + +from moto.core.responses import BaseResponse from .models import route53_backend import xmltodict -def list_or_create_hostzone_response(request, full_url, headers): +class Route53 (BaseResponse): + def list_or_create_hostzone_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) - if request.method == "POST": - elements = xmltodict.parse(request.body) - if "HostedZoneConfig" in elements["CreateHostedZoneRequest"]: - comment = elements["CreateHostedZoneRequest"]["HostedZoneConfig"]["Comment"] - try: - # in boto3, this field is set directly in the xml - private_zone = elements["CreateHostedZoneRequest"]["HostedZoneConfig"]["PrivateZone"] - except KeyError: - # if a VPC subsection is only included in xmls params when private_zone=True, - # see boto: boto/route53/connection.py - private_zone = 'VPC' in elements["CreateHostedZoneRequest"] - else: - comment = None - private_zone = False + if request.method == "POST": + elements = xmltodict.parse(self.body) + if "HostedZoneConfig" in elements["CreateHostedZoneRequest"]: + comment = elements["CreateHostedZoneRequest"]["HostedZoneConfig"]["Comment"] + try: + # in boto3, this field is set directly in the xml + private_zone = elements["CreateHostedZoneRequest"]["HostedZoneConfig"]["PrivateZone"] + except KeyError: + # if a VPC subsection is only included in xmls params when private_zone=True, + # see boto: boto/route53/connection.py + private_zone = 'VPC' in elements["CreateHostedZoneRequest"] + else: + comment = None + private_zone = False + + name = elements["CreateHostedZoneRequest"]["Name"] + + if name[-1] != ".": + name += "." + + new_zone = route53_backend.create_hosted_zone( + name, + comment=comment, + private_zone=private_zone, + ) + template = Template(CREATE_HOSTED_ZONE_RESPONSE) + return 201, headers, template.render(zone=new_zone) + + elif request.method == "GET": + all_zones = route53_backend.get_all_hosted_zones() + template = Template(LIST_HOSTED_ZONES_RESPONSE) + return 200, headers, template.render(zones=all_zones) - name = elements["CreateHostedZoneRequest"]["Name"] + def get_or_delete_hostzone_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + parsed_url = urlparse(full_url) + zoneid = parsed_url.path.rstrip('/').rsplit('/', 1)[1] + the_zone = route53_backend.get_hosted_zone(zoneid) + if not the_zone: + return 404, headers, "Zone %s not Found" % zoneid - if name[-1] != ".": - name += "." + if request.method == "GET": + template = Template(GET_HOSTED_ZONE_RESPONSE) - new_zone = route53_backend.create_hosted_zone( - name, - comment=comment, - private_zone=private_zone, - ) - template = Template(CREATE_HOSTED_ZONE_RESPONSE) - return 201, headers, template.render(zone=new_zone) - - elif request.method == "GET": - all_zones = route53_backend.get_all_hosted_zones() - template = Template(LIST_HOSTED_ZONES_RESPONSE) - return 200, headers, template.render(zones=all_zones) + return 200, headers, template.render(zone=the_zone) + elif request.method == "DELETE": + route53_backend.delete_hosted_zone(zoneid) + return 200, headers, DELETE_HOSTED_ZONE_RESPONSE -def get_or_delete_hostzone_response(request, full_url, headers): - parsed_url = urlparse(full_url) - zoneid = parsed_url.path.rstrip('/').rsplit('/', 1)[1] - the_zone = route53_backend.get_hosted_zone(zoneid) - if not the_zone: - return 404, headers, "Zone %s not Found" % zoneid + def rrset_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) - if request.method == "GET": - template = Template(GET_HOSTED_ZONE_RESPONSE) + parsed_url = urlparse(full_url) + method = request.method - return 200, headers, template.render(zone=the_zone) - elif request.method == "DELETE": - route53_backend.delete_hosted_zone(zoneid) - return 200, headers, DELETE_HOSTED_ZONE_RESPONSE + zoneid = parsed_url.path.rstrip('/').rsplit('/', 2)[1] + the_zone = route53_backend.get_hosted_zone(zoneid) + if not the_zone: + return 404, headers, "Zone %s Not Found" % zoneid + + if method == "POST": + elements = xmltodict.parse(self.body) + + change_list = elements['ChangeResourceRecordSetsRequest']['ChangeBatch']['Changes']['Change'] + if not isinstance(change_list, list): + change_list = [elements['ChangeResourceRecordSetsRequest']['ChangeBatch']['Changes']['Change']] + + for value in change_list: + action = value['Action'] + record_set = value['ResourceRecordSet'] + if action in ('CREATE', 'UPSERT'): + if 'ResourceRecords' in record_set: + resource_records = list(record_set['ResourceRecords'].values())[0] + if not isinstance(resource_records, list): + # Depending on how many records there are, this may or may not be a list + resource_records = [resource_records] + record_values = [x['Value'] for x in resource_records] + elif 'AliasTarget' in record_set: + record_values = [record_set['AliasTarget']['DNSName']] + record_set['ResourceRecords'] = record_values + if action == 'CREATE': + the_zone.add_rrset(record_set) + else: + the_zone.upsert_rrset(record_set) + elif action == "DELETE": + if 'SetIdentifier' in record_set: + the_zone.delete_rrset_by_id(record_set["SetIdentifier"]) + else: + the_zone.delete_rrset_by_name(record_set["Name"]) + + return 200, headers, CHANGE_RRSET_RESPONSE + + elif method == "GET": + querystring = parse_qs(parsed_url.query) + template = Template(LIST_RRSET_REPONSE) + type_filter = querystring.get("type", [None])[0] + name_filter = querystring.get("name", [None])[0] + record_sets = the_zone.get_record_sets(type_filter, name_filter) + return 200, headers, template.render(record_sets=record_sets) -def rrset_response(request, full_url, headers): - parsed_url = urlparse(full_url) - method = request.method + def health_check_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) - zoneid = parsed_url.path.rstrip('/').rsplit('/', 2)[1] - the_zone = route53_backend.get_hosted_zone(zoneid) - if not the_zone: - return 404, headers, "Zone %s Not Found" % zoneid + parsed_url = urlparse(full_url) + method = request.method - if method == "POST": - elements = xmltodict.parse(request.body) + if method == "POST": + properties = xmltodict.parse(self.body)['CreateHealthCheckRequest']['HealthCheckConfig'] + health_check_args = { + "ip_address": properties.get('IPAddress'), + "port": properties.get('Port'), + "type": properties['Type'], + "resource_path": properties.get('ResourcePath'), + "fqdn": properties.get('FullyQualifiedDomainName'), + "search_string": properties.get('SearchString'), + "request_interval": properties.get('RequestInterval'), + "failure_threshold": properties.get('FailureThreshold'), + } + health_check = route53_backend.create_health_check(health_check_args) + template = Template(CREATE_HEALTH_CHECK_RESPONSE) + return 201, headers, template.render(health_check=health_check) + elif method == "DELETE": + health_check_id = parsed_url.path.split("/")[-1] + route53_backend.delete_health_check(health_check_id) + return 200, headers, DELETE_HEALTH_CHECK_REPONSE + elif method == "GET": + template = Template(LIST_HEALTH_CHECKS_REPONSE) + health_checks = route53_backend.get_health_checks() + return 200, headers, template.render(health_checks=health_checks) - change_list = elements['ChangeResourceRecordSetsRequest']['ChangeBatch']['Changes']['Change'] - if not isinstance(change_list, list): - change_list = [elements['ChangeResourceRecordSetsRequest']['ChangeBatch']['Changes']['Change']] + def not_implemented_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) - for value in change_list: - action = value['Action'] - record_set = value['ResourceRecordSet'] - if action in ('CREATE', 'UPSERT'): - if 'ResourceRecords' in record_set: - resource_records = list(record_set['ResourceRecords'].values())[0] - if not isinstance(resource_records, list): - # Depending on how many records there are, this may or may not be a list - resource_records = [resource_records] - record_values = [x['Value'] for x in resource_records] - elif 'AliasTarget' in record_set: - record_values = [record_set['AliasTarget']['DNSName']] - record_set['ResourceRecords'] = record_values - if action == 'CREATE': - the_zone.add_rrset(record_set) - else: - the_zone.upsert_rrset(record_set) - elif action == "DELETE": - if 'SetIdentifier' in record_set: - the_zone.delete_rrset_by_id(record_set["SetIdentifier"]) - else: - the_zone.delete_rrset_by_name(record_set["Name"]) - - return 200, headers, CHANGE_RRSET_RESPONSE - - elif method == "GET": - querystring = parse_qs(parsed_url.query) - template = Template(LIST_RRSET_REPONSE) - type_filter = querystring.get("type", [None])[0] - name_filter = querystring.get("name", [None])[0] - record_sets = the_zone.get_record_sets(type_filter, name_filter) - return 200, headers, template.render(record_sets=record_sets) + action = '' + if 'tags' in full_url: + action = 'tags' + elif 'trafficpolicyinstances' in full_url: + action = 'policies' + raise NotImplementedError("The action for {0} has not been implemented for route 53".format(action)) -def health_check_response(request, full_url, headers): - parsed_url = urlparse(full_url) - method = request.method + def list_or_change_tags_for_resource_request(self, request, full_url, headers): + self.setup_class(request, full_url, headers) - if method == "POST": - properties = xmltodict.parse(request.body)['CreateHealthCheckRequest']['HealthCheckConfig'] - health_check_args = { - "ip_address": properties.get('IPAddress'), - "port": properties.get('Port'), - "type": properties['Type'], - "resource_path": properties.get('ResourcePath'), - "fqdn": properties.get('FullyQualifiedDomainName'), - "search_string": properties.get('SearchString'), - "request_interval": properties.get('RequestInterval'), - "failure_threshold": properties.get('FailureThreshold'), - } - health_check = route53_backend.create_health_check(health_check_args) - template = Template(CREATE_HEALTH_CHECK_RESPONSE) - return 201, headers, template.render(health_check=health_check) - elif method == "DELETE": - health_check_id = parsed_url.path.split("/")[-1] - route53_backend.delete_health_check(health_check_id) - return 200, headers, DELETE_HEALTH_CHECK_REPONSE - elif method == "GET": - template = Template(LIST_HEALTH_CHECKS_REPONSE) - health_checks = route53_backend.get_health_checks() - return 200, headers, template.render(health_checks=health_checks) + parsed_url = urlparse(full_url) + id_ = parsed_url.path.split("/")[-1] + type_ = parsed_url.path.split("/")[-2] -def not_implemented_response(request, full_url, headers): - action = '' - if 'tags' in full_url: - action = 'tags' - elif 'trafficpolicyinstances' in full_url: - action = 'policies' - raise NotImplementedError("The action for {0} has not been implemented for route 53".format(action)) + if request.method == "GET": + tags = route53_backend.list_tags_for_resource(id_) + template = Template(LIST_TAGS_FOR_RESOURCE_RESPONSE) + return 200, headers, template.render( + resource_type=type_, resource_id=id_, tags=tags) + if request.method == "POST": + tags = xmltodict.parse( + self.body)['ChangeTagsForResourceRequest'] -def list_or_change_tags_for_resource_request(request, full_url, headers): - parsed_url = urlparse(full_url) - id_ = parsed_url.path.split("/")[-1] - type_ = parsed_url.path.split("/")[-2] + if 'AddTags' in tags: + tags = tags['AddTags'] + elif 'RemoveTagKeys' in tags: + tags = tags['RemoveTagKeys'] - if request.method == "GET": - tags = route53_backend.list_tags_for_resource(id_) - template = Template(LIST_TAGS_FOR_RESOURCE_RESPONSE) - return 200, headers, template.render( - resource_type=type_, resource_id=id_, tags=tags) + route53_backend.change_tags_for_resource(id_, tags) + template = Template(CHANGE_TAGS_FOR_RESOURCE_RESPONSE) - if request.method == "POST": - tags = xmltodict.parse( - request.body)['ChangeTagsForResourceRequest'] - - if 'AddTags' in tags: - tags = tags['AddTags'] - elif 'RemoveTagKeys' in tags: - tags = tags['RemoveTagKeys'] - - route53_backend.change_tags_for_resource(id_, tags) - template = Template(CHANGE_TAGS_FOR_RESOURCE_RESPONSE) - - return 200, headers, template.render() + return 200, headers, template.render() LIST_TAGS_FOR_RESOURCE_RESPONSE = """ diff --git a/moto/route53/urls.py b/moto/route53/urls.py index 361c96317..795f7d807 100644 --- a/moto/route53/urls.py +++ b/moto/route53/urls.py @@ -1,15 +1,25 @@ from __future__ import unicode_literals -from . import responses +from .responses import Route53 url_bases = [ - "https://route53.amazonaws.com/201.-..-../", + "https?://route53(.*).amazonaws.com", ] + +def tag_response1(*args, **kwargs): + return Route53().list_or_change_tags_for_resource_request(*args, **kwargs) + + +def tag_response2(*args, **kwargs): + return Route53().list_or_change_tags_for_resource_request(*args, **kwargs) + + url_paths = { - '{0}hostedzone$': responses.list_or_create_hostzone_response, - '{0}hostedzone/[^/]+$': responses.get_or_delete_hostzone_response, - '{0}hostedzone/[^/]+/rrset/?$': responses.rrset_response, - '{0}healthcheck': responses.health_check_response, - '{0}tags/(healthcheck|hostedzone)/*': responses.list_or_change_tags_for_resource_request, - '{0}trafficpolicyinstances/*': responses.not_implemented_response + '{0}/(?P[\d_-]+)/hostedzone$': Route53().list_or_create_hostzone_response, + '{0}/(?P[\d_-]+)/hostedzone/(?P[^/]+)$': Route53().get_or_delete_hostzone_response, + '{0}/(?P[\d_-]+)/hostedzone/(?P[^/]+)/rrset/?$': Route53().rrset_response, + '{0}/(?P[\d_-]+)/healthcheck': Route53().health_check_response, + '{0}/(?P[\d_-]+)/tags/healthcheck/(?P[^/]+)$': tag_response1, + '{0}/(?P[\d_-]+)/tags/hostedzone/(?P[^/]+)$': tag_response2, + '{0}/(?P[\d_-]+)/trafficpolicyinstances/*': Route53().not_implemented_response } diff --git a/moto/s3/__init__.py b/moto/s3/__init__.py index 7d0df53bd..2c54a8d5a 100644 --- a/moto/s3/__init__.py +++ b/moto/s3/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import s3_backend + +s3_backends = {"global": s3_backend} mock_s3 = s3_backend.decorator mock_s3_deprecated = s3_backend.deprecated_decorator \ No newline at end of file diff --git a/moto/s3/models.py b/moto/s3/models.py index 40370b5dd..d5e156498 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -89,21 +89,21 @@ class FakeKey(object): @property def response_dict(self): - r = { + res = { 'etag': self.etag, 'last-modified': self.last_modified_RFC1123, 'content-length': str(len(self.value)), } if self._storage_class != 'STANDARD': - r['x-amz-storage-class'] = self._storage_class + res['x-amz-storage-class'] = self._storage_class if self._expiry is not None: rhdr = 'ongoing-request="false", expiry-date="{0}"' - r['x-amz-restore'] = rhdr.format(self.expiry_date) + res['x-amz-restore'] = rhdr.format(self.expiry_date) if self._is_versioned: - r['x-amz-version-id'] = str(self._version_id) + res['x-amz-version-id'] = str(self._version_id) - return r + return res @property def size(self): diff --git a/moto/server.py b/moto/server.py index 0b5ff7cae..0bb4eb779 100644 --- a/moto/server.py +++ b/moto/server.py @@ -39,21 +39,28 @@ class DomainDispatcherApplication(object): return host for backend_name, backend in BACKENDS.items(): - for url_base in backend.url_bases: + for url_base in backend.values()[0].url_bases: if re.match(url_base, 'http://%s' % host): return backend_name raise RuntimeError('Invalid host: "%s"' % host) def get_application(self, environ): - if environ.get('PATH_INFO', '').startswith("/moto-api"): + path_info = environ.get('PATH_INFO', '') + if path_info.startswith("/moto-api"): host = "moto_api" + elif path_info.startswith("/latest/meta-data/"): + host = "instance_metadata" else: host = environ['HTTP_HOST'].split(':')[0] if host == "localhost": # Fall back to parsing auth header to find service # ['Credential=sdffdsa', '20170220', 'us-east-1', 'sns', 'aws4_request'] - _, _, region, service, _ = environ['HTTP_AUTHORIZATION'].split(",")[0].split()[1].split("/") + try: + _, _, region, service, _ = environ['HTTP_AUTHORIZATION'].split(",")[0].split()[1].split("/") + except ValueError: + region = 'us-east-1' + service = 's3' host = "{service}.{region}.amazonaws.com".format(service=service, region=region) with self.lock: @@ -108,7 +115,7 @@ def create_backend_app(service): backend_app.view_functions = {} backend_app.url_map = Map() backend_app.url_map.converters['regex'] = RegexConverter - backend = BACKENDS[service] + backend = BACKENDS[service].values()[0] for url_path, handler in backend.flask_paths.items(): if handler.__name__ == 'dispatch': endpoint = '{0}.dispatch'.format(handler.__self__.__name__) diff --git a/moto/ses/__init__.py b/moto/ses/__init__.py index e1ec4b41a..e105b9929 100644 --- a/moto/ses/__init__.py +++ b/moto/ses/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import ses_backend + +ses_backends = {"global": ses_backend} mock_ses = ses_backend.decorator mock_ses_deprecated = ses_backend.deprecated_decorator \ No newline at end of file diff --git a/moto/ses/urls.py b/moto/ses/urls.py index 18d5874c4..adfb4c6e4 100644 --- a/moto/ses/urls.py +++ b/moto/ses/urls.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from .responses import EmailResponse url_bases = [ - "https?://email.(.+).amazonaws.com" + "https?://email.(.+).amazonaws.com", + "https?://ses.(.+).amazonaws.com", ] url_paths = { diff --git a/moto/settings.py b/moto/settings.py new file mode 100644 index 000000000..a5240f130 --- /dev/null +++ b/moto/settings.py @@ -0,0 +1,3 @@ +import os + +TEST_SERVER_MODE = os.environ.get('TEST_SERVER_MODE', '0').lower() == 'true' diff --git a/moto/sts/__init__.py b/moto/sts/__init__.py index 57456c1b3..7b46bdfbd 100644 --- a/moto/sts/__init__.py +++ b/moto/sts/__init__.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals from .models import sts_backend + +sts_backends = {"global": sts_backend} mock_sts = sts_backend.decorator mock_sts_deprecated = sts_backend.deprecated_decorator diff --git a/moto/sts/urls.py b/moto/sts/urls.py index c6e310960..2078e0b2c 100644 --- a/moto/sts/urls.py +++ b/moto/sts/urls.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from .responses import TokenResponse url_bases = [ - "https?://sts.amazonaws.com" + "https?://sts(.*).amazonaws.com" ] url_paths = { diff --git a/other_langs/sqsSample.java b/other_langs/sqsSample.java new file mode 100644 index 000000000..23368272c --- /dev/null +++ b/other_langs/sqsSample.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazonaws.samples; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.AmazonServiceException; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClient; + +public class S3Sample { + + public static void main(String[] args) throws IOException { + AmazonSQS sqs = new AmazonSQSClient(); + Region usWest2 = Region.getRegion(Regions.US_WEST_2); + sqs.setRegion(usWest2); + sqs.setEndpoint("http://localhost:8086"); + + String queueName = "my-first-queue"; + sqs.createQueue(queueName); + + System.out.println("Listing queues"); + for (String queue_url: sqs.listQueues().getQueueUrls()) { + System.out.println(" - " + queue_url); + } + System.out.println(); + + } + +} diff --git a/other_langs/test.js b/other_langs/test.js new file mode 100644 index 000000000..65d65ae70 --- /dev/null +++ b/other_langs/test.js @@ -0,0 +1,26 @@ +var AWS = require('aws-sdk'); + +var s3 = new AWS.S3({endpoint: "http://localhost:8086"}); +var myBucket = 'my.unique.bucket.name'; + +var myKey = 'myBucketKey'; + +s3.createBucket({Bucket: myBucket}, function(err, data) { + if (err) { + console.log(err); + } else { + params = {Bucket: myBucket, Key: myKey, Body: 'Hello!'}; + s3.putObject(params, function(err, data) { + if (err) { + console.log(err) + } else { + console.log("Successfully uploaded data to myBucket/myKey"); + } + }); + } +}); + +s3.listBuckets(function(err, data) { + if (err) console.log(err, err.stack); // an error occurred + else console.log(data); // successful response +}); diff --git a/other_langs/test.rb b/other_langs/test.rb new file mode 100644 index 000000000..dc5b7914b --- /dev/null +++ b/other_langs/test.rb @@ -0,0 +1,6 @@ +require 'aws-sdk' + +sqs = Aws::SQS::Resource.new(region: 'us-west-2', endpoint: 'http://localhost:8086') +my_queue = sqs.create_queue(queue_name: 'my-bucket') + +puts sqs.client.list_queues() diff --git a/setup.cfg b/setup.cfg index 3480374bc..3c6e79cf3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_wheel] -universal=1 \ No newline at end of file +universal=1 diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 6bd6eb5e5..e52bfe0d7 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -10,7 +10,7 @@ import sure # noqa from botocore.exceptions import ClientError from moto.packages.responses import responses -from moto import mock_apigateway +from moto import mock_apigateway, settings @freeze_time("2015-01-01") @@ -29,11 +29,11 @@ def test_create_and_get_rest_api(): ) response.pop('ResponseMetadata') + response.pop('createdDate') response.should.equal({ 'id': api_id, 'name': 'my_api', 'description': 'this is my api', - 'createdDate': datetime(2015, 1, 1, tzinfo=tzutc()) }) @@ -930,4 +930,5 @@ def test_http_proxying_integration(): deploy_url = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}".format(api_id=api_id, region_name=region_name, stage_name=stage_name) - requests.get(deploy_url).content.should.equal(b"a fake response") + if not settings.TEST_SERVER_MODE: + requests.get(deploy_url).content.should.equal(b"a fake response") diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index ce8892dc9..74e93c373 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -10,7 +10,7 @@ import zipfile import sure # noqa from freezegun import freeze_time -from moto import mock_lambda, mock_s3, mock_ec2 +from moto import mock_lambda, mock_s3, mock_ec2, settings def _process_lamda(pfunc): @@ -36,16 +36,15 @@ def lambda_handler(event, context): volume_id = event.get('volume_id') print('get volume details for %s' % volume_id) import boto3 - ec2 = boto3.resource('ec2', region_name='us-west-2') + ec2 = boto3.resource('ec2', region_name='us-west-2', endpoint_url="http://{base_url}") vol = ec2.Volume(volume_id) print('Volume - %s state=%s, size=%s' % (volume_id, vol.state, vol.size)) return event -""" +""".format(base_url="localhost:8086" if settings.TEST_SERVER_MODE else "ec2.us-west-2.amazonaws.com") return _process_lamda(pfunc) @mock_lambda -@mock_s3 def test_list_functions(): conn = boto3.client('lambda', 'us-west-2') result = conn.list_functions() @@ -53,7 +52,6 @@ def test_list_functions(): @mock_lambda -@freeze_time('2015-01-01 00:00:00') def test_invoke_requestresponse_function(): conn = boto3.client('lambda', 'us-west-2') conn.create_function( @@ -80,7 +78,6 @@ def test_invoke_requestresponse_function(): @mock_lambda -@freeze_time('2015-01-01 00:00:00') def test_invoke_event_function(): conn = boto3.client('lambda', 'us-west-2') conn.create_function( @@ -111,7 +108,6 @@ def test_invoke_event_function(): @mock_ec2 @mock_lambda -@freeze_time('2015-01-01 00:00:00') def test_invoke_function_get_ec2_volume(): conn = boto3.resource("ec2", "us-west-2") vol = conn.create_volume(Size=99, AvailabilityZone='us-west-2') @@ -141,7 +137,6 @@ def test_invoke_function_get_ec2_volume(): @mock_lambda -@freeze_time('2015-01-01 00:00:00') def test_create_based_on_s3_with_missing_bucket(): conn = boto3.client('lambda', 'us-west-2') @@ -196,6 +191,7 @@ def test_create_function_from_aws_bucket(): ) result['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it result['ResponseMetadata'].pop('RetryAttempts', None) # Botocore inserts retry attempts not seen in Python27 + result.pop('LastModified') result.should.equal({ 'FunctionName': 'testFunction', 'FunctionArn': 'arn:aws:lambda:123456789012:function:testFunction', @@ -207,7 +203,6 @@ def test_create_function_from_aws_bucket(): 'Description': 'test lambda function', 'Timeout': 3, 'MemorySize': 128, - 'LastModified': '2015-01-01 00:00:00', 'Version': '$LATEST', 'VpcConfig': { "SecurityGroupIds": ["sg-123abc"], @@ -238,6 +233,7 @@ def test_create_function_from_zipfile(): ) result['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it result['ResponseMetadata'].pop('RetryAttempts', None) # Botocore inserts retry attempts not seen in Python27 + result.pop('LastModified') result.should.equal({ 'FunctionName': 'testFunction', @@ -249,7 +245,6 @@ def test_create_function_from_zipfile(): 'Description': 'test lambda function', 'Timeout': 3, 'MemorySize': 128, - 'LastModified': '2015-01-01 00:00:00', 'CodeSha256': hashlib.sha256(zip_content).hexdigest(), 'Version': '$LATEST', 'VpcConfig': { @@ -290,6 +285,7 @@ def test_get_function(): result = conn.get_function(FunctionName='testFunction') result['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it result['ResponseMetadata'].pop('RetryAttempts', None) # Botocore inserts retry attempts not seen in Python27 + result['Configuration'].pop('LastModified') result.should.equal({ "Code": { @@ -303,7 +299,6 @@ def test_get_function(): "FunctionArn": "arn:aws:lambda:123456789012:function:testFunction", "FunctionName": "testFunction", "Handler": "lambda_function.handler", - "LastModified": "2015-01-01 00:00:00", "MemorySize": 128, "Role": "test-iam-role", "Runtime": "python2.7", @@ -395,7 +390,6 @@ def test_list_create_list_get_delete_list(): "FunctionArn": "arn:aws:lambda:123456789012:function:testFunction", "FunctionName": "testFunction", "Handler": "lambda_function.handler", - "LastModified": "2015-01-01 00:00:00", "MemorySize": 128, "Role": "test-iam-role", "Runtime": "python2.7", @@ -408,11 +402,14 @@ def test_list_create_list_get_delete_list(): }, 'ResponseMetadata': {'HTTPStatusCode': 200}, } - conn.list_functions()['Functions'].should.equal([expected_function_result['Configuration']]) + func = conn.list_functions()['Functions'][0] + func.pop('LastModified') + func.should.equal(expected_function_result['Configuration']) func = conn.get_function(FunctionName='testFunction') func['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it func['ResponseMetadata'].pop('RetryAttempts', None) # Botocore inserts retry attempts not seen in Python27 + func['Configuration'].pop('LastModified') func.should.equal(expected_function_result) conn.delete_function(FunctionName='testFunction') diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 95ac6ede4..2ee74f886 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -5,7 +5,7 @@ import boto import boto.s3 import boto.s3.key from botocore.exceptions import ClientError -from moto import mock_cloudformation, mock_s3_deprecated +from moto import mock_cloudformation, mock_s3 import json import sure # noqa @@ -118,14 +118,20 @@ def test_create_stack_with_role_arn(): @mock_cloudformation -@mock_s3_deprecated +@mock_s3 def test_create_stack_from_s3_url(): - s3_conn = boto.s3.connect_to_region('us-west-1') - bucket = s3_conn.create_bucket("foobar") - key = boto.s3.key.Key(bucket) - key.key = "template-key" - key.set_contents_from_string(dummy_template_json) - key_url = key.generate_url(expires_in=0, query_auth=False) + s3 = boto3.client('s3') + s3_conn = boto3.resource('s3') + bucket = s3_conn.create_bucket(Bucket="foobar") + + key = s3_conn.Object('foobar', 'template-key').put(Body=dummy_template_json) + key_url = s3.generate_presigned_url( + ClientMethod='get_object', + Params={ + 'Bucket': 'foobar', + 'Key': 'template-key' + } + ) cf_conn = boto3.client('cloudformation', region_name='us-west-1') cf_conn.create_stack( diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 1b9330a9f..609a0b46d 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -701,27 +701,29 @@ def test_vpc_single_instance_in_subnet(): eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0] eip_resource.physical_resource_id.should.equal(eip.allocation_id) -@mock_cloudformation_deprecated() -@mock_ec2_deprecated() +@mock_cloudformation() +@mock_ec2() @mock_rds2() def test_rds_db_parameter_groups(): - ec2_conn = boto.ec2.connect_to_region("us-west-1") - ec2_conn.create_security_group('application', 'Our Application Group') + ec2_conn = boto3.client("ec2", region_name="us-west-1") + ec2_conn.create_security_group(GroupName='application', Description='Our Application Group') template_json = json.dumps(rds_mysql_with_db_parameter_group.template) - conn = boto.cloudformation.connect_to_region("us-west-1") - conn.create_stack( - "test_stack", - template_body=template_json, - parameters=[ - ("DBInstanceIdentifier", "master_db"), - ("DBName", "my_db"), - ("DBUser", "my_user"), - ("DBPassword", "my_password"), - ("DBAllocatedStorage", "20"), - ("DBInstanceClass", "db.m1.medium"), - ("EC2SecurityGroup", "application"), - ("MultiAZ", "true"), + cf_conn = boto3.client('cloudformation', 'us-west-1') + cf_conn.create_stack( + StackName="test_stack", + TemplateBody=template_json, + Parameters=[{'ParameterKey': key, 'ParameterValue': value} for + key, value in [ + ("DBInstanceIdentifier", "master_db"), + ("DBName", "my_db"), + ("DBUser", "my_user"), + ("DBPassword", "my_password"), + ("DBAllocatedStorage", "20"), + ("DBInstanceClass", "db.m1.medium"), + ("EC2SecurityGroup", "application"), + ("MultiAZ", "true"), + ] ], ) @@ -1802,7 +1804,7 @@ def lambda_handler(event, context): return _process_lamda(pfunc) -@mock_cloudformation_deprecated +@mock_cloudformation @mock_lambda def test_lambda_function(): # switch this to python as backend lambda only supports python execution. @@ -1826,10 +1828,10 @@ def test_lambda_function(): } template_json = json.dumps(template) - cf_conn = boto.cloudformation.connect_to_region("us-east-1") + cf_conn = boto3.client('cloudformation', 'us-east-1') cf_conn.create_stack( - "test_stack", - template_body=template_json, + StackName="test_stack", + TemplateBody=template_json, ) conn = boto3.client('lambda', 'us-east-1') diff --git a/tests/test_core/test_instance_metadata.py b/tests/test_core/test_instance_metadata.py index aa86b41b3..80dd501e7 100644 --- a/tests/test_core/test_instance_metadata.py +++ b/tests/test_core/test_instance_metadata.py @@ -3,18 +3,23 @@ import sure # noqa from nose.tools import assert_raises import requests -from moto import mock_ec2 +from moto import mock_ec2, settings + +if settings.TEST_SERVER_MODE: + BASE_URL = 'http://localhost:8086' +else: + BASE_URL = 'http://169.254.169.254' @mock_ec2 def test_latest_meta_data(): - res = requests.get("http://169.254.169.254/latest/meta-data/") + res = requests.get("{0}/latest/meta-data/".format(BASE_URL)) res.content.should.equal(b"iam") @mock_ec2 def test_meta_data_iam(): - res = requests.get("http://169.254.169.254/latest/meta-data/iam") + res = requests.get("{0}/latest/meta-data/iam".format(BASE_URL)) json_response = res.json() default_role = json_response['security-credentials']['default-role'] default_role.should.contain('AccessKeyId') @@ -25,21 +30,15 @@ def test_meta_data_iam(): @mock_ec2 def test_meta_data_security_credentials(): - res = requests.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/") + res = requests.get("{0}/latest/meta-data/iam/security-credentials/".format(BASE_URL)) res.content.should.equal(b"default-role") @mock_ec2 def test_meta_data_default_role(): - res = requests.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/default-role") + res = requests.get("{0}/latest/meta-data/iam/security-credentials/default-role".format(BASE_URL)) json_response = res.json() json_response.should.contain('AccessKeyId') json_response.should.contain('SecretAccessKey') json_response.should.contain('Token') json_response.should.contain('Expiration') - - -@mock_ec2 -def test_meta_data_unknown_path(): - with assert_raises(NotImplementedError): - requests.get("http://169.254.169.254/latest/meta-data/badpath") diff --git a/tests/test_core/test_moto_api.py b/tests/test_core/test_moto_api.py new file mode 100644 index 000000000..3b441a3f1 --- /dev/null +++ b/tests/test_core/test_moto_api.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals +import sure # noqa +from nose.tools import assert_raises +import requests + +import boto3 +from moto import mock_sqs, settings + +base_url = "http://localhost:8086" if settings.TEST_SERVER_MODE else "http://motoapi.amazonaws.com" + + +@mock_sqs +def test_reset_api(): + conn = boto3.client("sqs", region_name='us-west-1') + conn.create_queue(QueueName="queue1") + conn.list_queues()['QueueUrls'].should.have.length_of(1) + + res = requests.post("{base_url}/moto-api/reset".format(base_url=base_url)) + res.content.should.equal(b'{"status": "ok"}') + + conn.list_queues().shouldnt.contain('QueueUrls') # No more queues diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py index 7ea56faa9..f2df39a22 100644 --- a/tests/test_dynamodb/test_dynamodb.py +++ b/tests/test_dynamodb/test_dynamodb.py @@ -42,13 +42,6 @@ def test_describe_missing_table(): conn.describe_table('messages') -@mock_dynamodb -def test_sts_handler(): - res = requests.post("https://sts.amazonaws.com/", data={"GetSessionToken": ""}) - res.ok.should.be.ok - res.text.should.contain("SecretAccessKey") - - @mock_dynamodb_deprecated def test_dynamodb_with_connect_to_region(): # this will work if connected with boto.connect_dynamodb() diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index d66d36d9f..9e92e7985 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -63,11 +63,3 @@ def test_describe_missing_table(): aws_secret_access_key="sk") with assert_raises(JSONResponseError): conn.describe_table('messages') - - -@requires_boto_gte("2.9") -@mock_dynamodb2 -def test_sts_handler(): - res = requests.post("https://sts.amazonaws.com/", data={"GetSessionToken": ""}) - res.ok.should.be.ok - res.text.should.contain("SecretAccessKey") diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py index 9c3fbd40d..4c154ae84 100755 --- a/tests/test_ec2/test_amis.py +++ b/tests/test_ec2/test_amis.py @@ -5,7 +5,7 @@ from nose.tools import assert_raises import boto import boto.ec2 -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError, EC2ResponseError import sure # noqa @@ -19,9 +19,9 @@ def test_ami_create_and_delete(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: image_id = conn.create_image(instance.id, "test-ami", "this is a test ami", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateImage operation: Request would have succeeded, but DryRun flag is set') @@ -52,9 +52,9 @@ def test_ami_create_and_delete(): snapshot.volume_id.should.equal(volume.id) # Deregister - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: success = conn.deregister_image(image_id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeregisterImage operation: Request would have succeeded, but DryRun flag is set') @@ -80,9 +80,9 @@ def test_ami_copy(): source_image = conn.get_all_images(image_ids=[source_image_id])[0] # Boto returns a 'CopyImage' object with an image_id attribute here. Use the image_id to fetch the full info. - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: copy_image_ref = conn.copy_image(source_image.region.name, source_image.id, "test-copy-ami", "this is a test copy ami", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CopyImage operation: Request would have succeeded, but DryRun flag is set') @@ -127,9 +127,9 @@ def test_ami_tagging(): conn.create_image(instance.id, "test-ami", "this is a test ami") image = conn.get_all_images()[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: image.add_tag("a key", "some value", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') @@ -289,9 +289,9 @@ def test_ami_attribute_group_permissions(): 'groups': 'all'} # Add 'all' group and confirm - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.modify_image_attribute(**dict(ADD_GROUP_ARGS, **{'dry_run': True})) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyImageAttribute operation: Request would have succeeded, but DryRun flag is set') diff --git a/tests/test_ec2/test_ec2_core.py b/tests/test_ec2/test_ec2_core.py index 53c7d6480..baffc4882 100644 --- a/tests/test_ec2/test_ec2_core.py +++ b/tests/test_ec2/test_ec2_core.py @@ -1,11 +1 @@ from __future__ import unicode_literals -import requests -from moto import mock_ec2 - - -@mock_ec2 -def test_not_implemented_method(): - requests.post.when.called_with( - "https://ec2.us-east-1.amazonaws.com/", - data={'Action': ['foobar']} - ).should.throw(NotImplementedError) diff --git a/tests/test_ec2/test_elastic_block_store.py b/tests/test_ec2/test_elastic_block_store.py index c4794b1c8..6491412e3 100644 --- a/tests/test_ec2/test_elastic_block_store.py +++ b/tests/test_ec2/test_elastic_block_store.py @@ -5,7 +5,7 @@ from nose.tools import assert_raises from moto.ec2 import ec2_backends import boto -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError import sure # noqa from moto import mock_ec2_deprecated @@ -24,9 +24,9 @@ def test_create_and_delete_volume(): volume = all_volumes[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: volume.delete(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteVolume operation: Request would have succeeded, but DryRun flag is set') @@ -46,9 +46,9 @@ def test_create_and_delete_volume(): @mock_ec2_deprecated def test_create_encrypted_volume_dryrun(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.create_volume(80, "us-east-1a", encrypted=True, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateVolume operation: Request would have succeeded, but DryRun flag is set') @@ -58,9 +58,9 @@ def test_create_encrypted_volume(): conn = boto.connect_ec2('the_key', 'the_secret') conn.create_volume(80, "us-east-1a", encrypted=True) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.create_volume(80, "us-east-1a", encrypted=True, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateVolume operation: Request would have succeeded, but DryRun flag is set') @@ -165,9 +165,9 @@ def test_volume_attach_and_detach(): volume.update() volume.volume_state().should.equal('available') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: volume.attach(instance.id, "/dev/sdh", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AttachVolume operation: Request would have succeeded, but DryRun flag is set') @@ -179,9 +179,9 @@ def test_volume_attach_and_detach(): volume.attach_data.instance_id.should.equal(instance.id) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: volume.detach(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DetachVolume operation: Request would have succeeded, but DryRun flag is set') @@ -214,9 +214,9 @@ def test_create_snapshot(): conn = boto.connect_ec2('the_key', 'the_secret') volume = conn.create_volume(80, "us-east-1a") - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: snapshot = volume.create_snapshot('a dryrun snapshot', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateSnapshot operation: Request would have succeeded, but DryRun flag is set') @@ -347,9 +347,9 @@ def test_snapshot_attribute(): # Add 'all' group and confirm - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.modify_snapshot_attribute(**dict(ADD_GROUP_ARGS, **{'dry_run': True})) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifySnapshotAttribute operation: Request would have succeeded, but DryRun flag is set') @@ -363,9 +363,9 @@ def test_snapshot_attribute(): conn.modify_snapshot_attribute.when.called_with(**ADD_GROUP_ARGS).should_not.throw(EC2ResponseError) # Remove 'all' group and confirm - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.modify_snapshot_attribute(**dict(REMOVE_GROUP_ARGS, **{'dry_run': True})) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifySnapshotAttribute operation: Request would have succeeded, but DryRun flag is set') @@ -424,9 +424,9 @@ def test_create_volume_from_snapshot(): volume = conn.create_volume(80, "us-east-1a") snapshot = volume.create_snapshot('a test snapshot') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: snapshot = volume.create_snapshot('a test snapshot', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateSnapshot operation: Request would have succeeded, but DryRun flag is set') @@ -468,9 +468,9 @@ def test_modify_attribute_blockDeviceMapping(): instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.modify_attribute('blockDeviceMapping', {'/dev/sda1': True}, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set') @@ -487,9 +487,9 @@ def test_volume_tag_escaping(): vol = conn.create_volume(10, 'us-east-1a') snapshot = conn.create_snapshot(vol.id, 'Desc') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: snapshot.add_tags({'key': ''}, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') dict(conn.get_all_snapshots()[0].tags).should_not.be.equal({'key': ''}) diff --git a/tests/test_ec2/test_elastic_ip_addresses.py b/tests/test_ec2/test_elastic_ip_addresses.py index dc7910379..f92c4df8b 100644 --- a/tests/test_ec2/test_elastic_ip_addresses.py +++ b/tests/test_ec2/test_elastic_ip_addresses.py @@ -5,7 +5,7 @@ from nose.tools import assert_raises import boto import boto3 -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError import six import sure # noqa @@ -20,9 +20,9 @@ def test_eip_allocate_classic(): """Allocate/release Classic EIP""" conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: standard = conn.allocate_address(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AllocateAddress operation: Request would have succeeded, but DryRun flag is set') @@ -32,9 +32,9 @@ def test_eip_allocate_classic(): standard.instance_id.should.be.none standard.domain.should.be.equal("standard") - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: standard.release(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ReleaseAddress operation: Request would have succeeded, but DryRun flag is set') @@ -47,9 +47,9 @@ def test_eip_allocate_vpc(): """Allocate/release VPC EIP""" conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: vpc = conn.allocate_address(domain="vpc", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AllocateAddress operation: Request would have succeeded, but DryRun flag is set') @@ -89,9 +89,9 @@ def test_eip_associate_classic(): cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.associate_address(instance_id=instance.id, public_ip=eip.public_ip, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AssociateAddress operation: Request would have succeeded, but DryRun flag is set') @@ -99,9 +99,9 @@ def test_eip_associate_classic(): eip = conn.get_all_addresses(addresses=[eip.public_ip])[0] # no .update() on address ): eip.instance_id.should.be.equal(instance.id) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.disassociate_address(public_ip=eip.public_ip, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DisAssociateAddress operation: Request would have succeeded, but DryRun flag is set') @@ -139,9 +139,9 @@ def test_eip_associate_vpc(): eip.instance_id.should.be.equal(u'') eip.association_id.should.be.none - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: eip.release(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ReleaseAddress operation: Request would have succeeded, but DryRun flag is set') @@ -153,9 +153,8 @@ def test_eip_associate_vpc(): @mock_ec2 def test_eip_boto3_vpc_association(): """Associate EIP to VPC instance in a new subnet with boto3""" - session = boto3.session.Session(region_name='us-west-1') - service = session.resource('ec2') - client = session.client('ec2') + service = boto3.resource('ec2', region_name='us-west-1') + client = boto3.client('ec2', region_name='us-west-1') vpc_res = client.create_vpc(CidrBlock='10.0.0.0/24') subnet_res = client.create_subnet( VpcId=vpc_res['Vpc']['VpcId'], CidrBlock='10.0.0.0/24') diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py index 6f60c85a8..9027e0448 100644 --- a/tests/test_ec2/test_elastic_network_interfaces.py +++ b/tests/test_ec2/test_elastic_network_interfaces.py @@ -4,10 +4,11 @@ import tests.backport_assert_raises from nose.tools import assert_raises import boto3 +from botocore.exceptions import ClientError import boto import boto.cloudformation import boto.ec2 -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError import sure # noqa from moto import mock_ec2, mock_cloudformation_deprecated, mock_ec2_deprecated @@ -22,9 +23,9 @@ def test_elastic_network_interfaces(): vpc = conn.create_vpc("10.0.0.0/16") subnet = conn.create_subnet(vpc.id, "10.0.0.0/18") - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: eni = conn.create_network_interface(subnet.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateNetworkInterface operation: Request would have succeeded, but DryRun flag is set') @@ -36,9 +37,9 @@ def test_elastic_network_interfaces(): eni.groups.should.have.length_of(0) eni.private_ip_addresses.should.have.length_of(0) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.delete_network_interface(eni.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteNetworkInterface operation: Request would have succeeded, but DryRun flag is set') @@ -49,7 +50,7 @@ def test_elastic_network_interfaces(): with assert_raises(EC2ResponseError) as cm: conn.delete_network_interface(eni.id) - cm.exception.code.should.equal('InvalidNetworkInterfaceID.NotFound') + cm.exception.error_code.should.equal('InvalidNetworkInterfaceID.NotFound') cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none @@ -60,7 +61,7 @@ def test_elastic_network_interfaces_subnet_validation(): with assert_raises(EC2ResponseError) as cm: conn.create_network_interface("subnet-abcd1234") - cm.exception.code.should.equal('InvalidSubnetID.NotFound') + cm.exception.error_code.should.equal('InvalidSubnetID.NotFound') cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none @@ -117,9 +118,9 @@ def test_elastic_network_interfaces_modify_attribute(): eni.groups.should.have.length_of(1) eni.groups[0].id.should.equal(security_group1.id) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.modify_network_interface_attribute(eni.id, 'groupset', [security_group2.id], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyNetworkInterface operation: Request would have succeeded, but DryRun flag is set') @@ -183,11 +184,11 @@ def test_elastic_network_interfaces_get_by_tag_name(): eni1 = ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress='10.0.10.5') - with assert_raises(JSONResponseError) as ex: + with assert_raises(ClientError) as ex: eni1.create_tags(Tags=[{'Key': 'Name', 'Value': 'eni1'}], DryRun=True) - ex.exception.reason.should.equal('DryRunOperation') - ex.exception.status.should.equal(400) - ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') + ex.exception.response['Error']['Code'].should.equal('DryRunOperation') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') eni1.create_tags(Tags=[{'Key': 'Name', 'Value': 'eni1'}]) diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index a310c05a4..b6601e87f 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -8,7 +8,7 @@ import datetime import boto from boto.ec2.instance import Reservation, InstanceAttribute -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError, EC2ResponseError from freezegun import freeze_time import sure # noqa @@ -41,9 +41,9 @@ def test_add_servers(): def test_instance_launch_and_terminate(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: reservation = conn.run_instances('ami-1234abcd', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the RunInstance operation: Request would have succeeded, but DryRun flag is set') @@ -74,9 +74,9 @@ def test_instance_launch_and_terminate(): volume.attach_data.instance_id.should.equal(instance.id) volume.status.should.equal('in-use') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.terminate_instances([instance.id], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the TerminateInstance operation: Request would have succeeded, but DryRun flag is set') @@ -427,9 +427,9 @@ def test_instance_start_and_stop(): instance_ids = [instance.id for instance in instances] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: stopped_instances = conn.stop_instances(instance_ids, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the StopInstance operation: Request would have succeeded, but DryRun flag is set') @@ -438,9 +438,9 @@ def test_instance_start_and_stop(): for instance in stopped_instances: instance.state.should.equal('stopping') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: started_instances = conn.start_instances([instances[0].id], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the StartInstance operation: Request would have succeeded, but DryRun flag is set') @@ -454,9 +454,9 @@ def test_instance_reboot(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.reboot(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the RebootInstance operation: Request would have succeeded, but DryRun flag is set') @@ -470,9 +470,9 @@ def test_instance_attribute_instance_type(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.modify_attribute("instanceType", "m1.small", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyInstanceType operation: Request would have succeeded, but DryRun flag is set') @@ -491,9 +491,9 @@ def test_modify_instance_attribute_security_groups(): sg_id = 'sg-1234abcd' sg_id2 = 'sg-abcd4321' - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.modify_attribute("groupSet", [sg_id, sg_id2], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set') @@ -512,9 +512,9 @@ def test_instance_attribute_user_data(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.modify_attribute("userData", "this is my user data", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyUserData operation: Request would have succeeded, but DryRun flag is set') @@ -540,9 +540,9 @@ def test_instance_attribute_source_dest_check(): # Set to false (note: Boto converts bool to string, eg 'false') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.modify_attribute("sourceDestCheck", False, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifySourceDestCheck operation: Request would have succeeded, but DryRun flag is set') @@ -584,9 +584,9 @@ def test_user_data_with_run_instance(): def test_run_instance_with_security_group_name(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: group = conn.create_security_group('group1', "some description", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateSecurityGroup operation: Request would have succeeded, but DryRun flag is set') @@ -745,9 +745,9 @@ def test_instance_with_nic_attach_detach(): set([group.id for group in eni.groups]).should.equal(set([security_group2.id])) # Attach - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.attach_network_interface(eni.id, instance.id, device_index=1, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AttachNetworkInterface operation: Request would have succeeded, but DryRun flag is set') @@ -766,9 +766,9 @@ def test_instance_with_nic_attach_detach(): set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id])) # Detach - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.detach_network_interface(instance_eni.attachment.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DetachNetworkInterface operation: Request would have succeeded, but DryRun flag is set') @@ -886,9 +886,9 @@ def test_get_instance_by_security_group(): security_group = conn.create_security_group('test', 'test') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.modify_instance_attribute(instance.id, "groupSet", [security_group.id], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set') diff --git a/tests/test_ec2/test_internet_gateways.py b/tests/test_ec2/test_internet_gateways.py index 12b37860e..fe5e4945d 100644 --- a/tests/test_ec2/test_internet_gateways.py +++ b/tests/test_ec2/test_internet_gateways.py @@ -6,7 +6,7 @@ from nose.tools import assert_raises import re import boto -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError import sure # noqa @@ -24,9 +24,9 @@ def test_igw_create(): conn.get_all_internet_gateways().should.have.length_of(0) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: igw = conn.create_internet_gateway(dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateInternetGateway operation: Request would have succeeded, but DryRun flag is set') @@ -44,9 +44,9 @@ def test_igw_attach(): igw = conn.create_internet_gateway() vpc = conn.create_vpc(VPC_CIDR) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.attach_internet_gateway(igw.id, vpc.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the AttachInternetGateway operation: Request would have succeeded, but DryRun flag is set') @@ -90,9 +90,9 @@ def test_igw_detach(): vpc = conn.create_vpc(VPC_CIDR) conn.attach_internet_gateway(igw.id, vpc.id) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.detach_internet_gateway(igw.id, vpc.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DetachInternetGateway operation: Request would have succeeded, but DryRun flag is set') @@ -151,9 +151,9 @@ def test_igw_delete(): igw = conn.create_internet_gateway() conn.get_all_internet_gateways().should.have.length_of(1) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.delete_internet_gateway(igw.id, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteInternetGateway operation: Request would have succeeded, but DryRun flag is set') diff --git a/tests/test_ec2/test_key_pairs.py b/tests/test_ec2/test_key_pairs.py index a35f0b962..6c4773200 100644 --- a/tests/test_ec2/test_key_pairs.py +++ b/tests/test_ec2/test_key_pairs.py @@ -7,7 +7,7 @@ import boto import six import sure # noqa -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError from moto import mock_ec2_deprecated @@ -32,9 +32,9 @@ def test_key_pairs_invalid_id(): def test_key_pairs_create(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: kp = conn.create_key_pair('foo', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateKeyPair operation: Request would have succeeded, but DryRun flag is set') @@ -87,9 +87,9 @@ def test_key_pairs_delete_exist(): conn = boto.connect_ec2('the_key', 'the_secret') conn.create_key_pair('foo') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: r = conn.delete_key_pair('foo', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteKeyPair operation: Request would have succeeded, but DryRun flag is set') @@ -102,9 +102,9 @@ def test_key_pairs_delete_exist(): def test_key_pairs_import(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: kp = conn.import_key_pair('foo', b'content', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ImportKeyPair operation: Request would have succeeded, but DryRun flag is set') diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 3968d9151..3056331be 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -9,7 +9,7 @@ from nose.tools import assert_raises import boto3 import boto from botocore.exceptions import ClientError -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError import sure # noqa from moto import mock_ec2, mock_ec2_deprecated @@ -19,9 +19,9 @@ from moto import mock_ec2, mock_ec2_deprecated def test_create_and_describe_security_group(): conn = boto.connect_ec2('the_key', 'the_secret') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: security_group = conn.create_security_group('test security group', 'this is a test security group', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateSecurityGroup operation: Request would have succeeded, but DryRun flag is set') @@ -121,9 +121,9 @@ def test_deleting_security_groups(): cm.exception.request_id.should_not.be.none # Delete by name - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.delete_security_group('test2', dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteSecurityGroup operation: Request would have succeeded, but DryRun flag is set') @@ -150,9 +150,9 @@ def test_authorize_ip_range_and_revoke(): conn = boto.connect_ec2('the_key', 'the_secret') security_group = conn.create_security_group('test', 'test') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: success = security_group.authorize(ip_protocol="tcp", from_port="22", to_port="2222", cidr_ip="123.123.123.123/32", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the GrantSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set') @@ -171,9 +171,9 @@ def test_authorize_ip_range_and_revoke(): cm.exception.request_id.should_not.be.none # Actually revoke - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: security_group.revoke(ip_protocol="tcp", from_port="22", to_port="2222", cidr_ip="123.123.123.123/32", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the RevokeSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set') @@ -185,9 +185,9 @@ def test_authorize_ip_range_and_revoke(): # Test for egress as well egress_security_group = conn.create_security_group('testegress', 'testegress', vpc_id='vpc-3432589') - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: success = conn.authorize_security_group_egress(egress_security_group.id, "tcp", from_port="22", to_port="2222", cidr_ip="123.123.123.123/32", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the GrantSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set') @@ -203,9 +203,9 @@ def test_authorize_ip_range_and_revoke(): egress_security_group.revoke.when.called_with(ip_protocol="tcp", from_port="22", to_port="2222", cidr_ip="123.123.123.122/32").should.throw(EC2ResponseError) # Actually revoke - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.revoke_security_group_egress(egress_security_group.id, "tcp", from_port="22", to_port="2222", cidr_ip="123.123.123.123/32", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the RevokeSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set') @@ -339,9 +339,9 @@ def test_security_group_tagging(): sg = conn.create_security_group("test-sg", "Test SG", vpc.id) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: sg.add_tag("Test", "Tag", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') @@ -540,11 +540,11 @@ def test_security_group_tagging_boto3(): sg = conn.create_security_group(GroupName="test-sg", Description="Test SG") - with assert_raises(JSONResponseError) as ex: + with assert_raises(ClientError) as ex: conn.create_tags(Resources=[sg['GroupId']], Tags=[{'Key': 'Test', 'Value': 'Tag'}], DryRun=True) - ex.exception.reason.should.equal('DryRunOperation') - ex.exception.status.should.equal(400) - ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') + ex.exception.response['Error']['Code'].should.equal('DryRunOperation') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') conn.create_tags(Resources=[sg['GroupId']], Tags=[{'Key': 'Test', 'Value': 'Tag'}]) describe = conn.describe_security_groups(Filters=[{'Name': 'tag-value', 'Values': ['Tag']}]) diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index 1933613e8..2d3cb3036 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -4,8 +4,10 @@ import datetime import boto import boto3 +from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError +import pytz import sure # noqa -from boto.exception import JSONResponseError from moto import mock_ec2, mock_ec2_deprecated from moto.backends import get_model @@ -13,98 +15,130 @@ from moto.core.utils import iso_8601_datetime_with_milliseconds @mock_ec2 -@mock_ec2_deprecated def test_request_spot_instances(): conn = boto3.client('ec2', 'us-east-1') vpc = conn.create_vpc(CidrBlock="10.0.0.0/8")['Vpc'] subnet = conn.create_subnet(VpcId=vpc['VpcId'], CidrBlock='10.0.0.0/16', AvailabilityZone='us-east-1a')['Subnet'] subnet_id = subnet['SubnetId'] - conn = boto.connect_ec2() + conn.create_security_group(GroupName='group1', Description='description') + conn.create_security_group(GroupName='group2', Description='description') - conn.create_security_group('group1', 'description') - conn.create_security_group('group2', 'description') + start_dt = datetime.datetime(2013, 1, 1).replace(tzinfo=pytz.utc) + end_dt = datetime.datetime(2013, 1, 2).replace(tzinfo=pytz.utc) + start = iso_8601_datetime_with_milliseconds(start_dt) + end = iso_8601_datetime_with_milliseconds(end_dt) - start = iso_8601_datetime_with_milliseconds(datetime.datetime(2013, 1, 1)) - end = iso_8601_datetime_with_milliseconds(datetime.datetime(2013, 1, 2)) - - with assert_raises(JSONResponseError) as ex: + with assert_raises(ClientError) as ex: request = conn.request_spot_instances( - price=0.5, image_id='ami-abcd1234', count=1, type='one-time', - valid_from=start, valid_until=end, launch_group="the-group", - availability_zone_group='my-group', key_name="test", - security_groups=['group1', 'group2'], user_data=b"some test data", - instance_type='m1.small', placement='us-east-1c', - kernel_id="test-kernel", ramdisk_id="test-ramdisk", - monitoring_enabled=True, subnet_id=subnet_id, dry_run=True + SpotPrice="0.5", InstanceCount=1, Type='one-time', + ValidFrom=start, ValidUntil=end, LaunchGroup="the-group", + AvailabilityZoneGroup='my-group', + LaunchSpecification={ + "ImageId": 'ami-abcd1234', + "KeyName": "test", + "SecurityGroups": ['group1', 'group2'], + "UserData": b"some test data", + "InstanceType": 'm1.small', + "Placement": { + "AvailabilityZone": 'us-east-1c', + }, + "KernelId": "test-kernel", + "RamdiskId": "test-ramdisk", + "Monitoring": { + "Enabled": True, + }, + "SubnetId": subnet_id, + }, + DryRun=True, ) - ex.exception.reason.should.equal('DryRunOperation') - ex.exception.status.should.equal(400) - ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the RequestSpotInstance operation: Request would have succeeded, but DryRun flag is set') + ex.exception.response['Error']['Code'].should.equal('DryRunOperation') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal('An error occurred (DryRunOperation) when calling the RequestSpotInstance operation: Request would have succeeded, but DryRun flag is set') request = conn.request_spot_instances( - price=0.5, image_id='ami-abcd1234', count=1, type='one-time', - valid_from=start, valid_until=end, launch_group="the-group", - availability_zone_group='my-group', key_name="test", - security_groups=['group1', 'group2'], user_data=b"some test data", - instance_type='m1.small', placement='us-east-1c', - kernel_id="test-kernel", ramdisk_id="test-ramdisk", - monitoring_enabled=True, subnet_id=subnet_id, + SpotPrice="0.5", InstanceCount=1, Type='one-time', + ValidFrom=start, ValidUntil=end, LaunchGroup="the-group", + AvailabilityZoneGroup='my-group', + LaunchSpecification={ + "ImageId": 'ami-abcd1234', + "KeyName": "test", + "SecurityGroups": ['group1', 'group2'], + "UserData": b"some test data", + "InstanceType": 'm1.small', + "Placement": { + "AvailabilityZone": 'us-east-1c', + }, + "KernelId": "test-kernel", + "RamdiskId": "test-ramdisk", + "Monitoring": { + "Enabled": True, + }, + "SubnetId": subnet_id, + }, ) - requests = conn.get_all_spot_instance_requests() + requests = conn.describe_spot_instance_requests()['SpotInstanceRequests'] requests.should.have.length_of(1) request = requests[0] - request.state.should.equal("open") - request.price.should.equal(0.5) - request.launch_specification.image_id.should.equal('ami-abcd1234') - request.type.should.equal('one-time') - request.valid_from.should.equal(start) - request.valid_until.should.equal(end) - request.launch_group.should.equal("the-group") - request.availability_zone_group.should.equal('my-group') - request.launch_specification.key_name.should.equal("test") - security_group_names = [group.name for group in request.launch_specification.groups] + request['State'].should.equal("open") + request['SpotPrice'].should.equal("0.5") + request['Type'].should.equal('one-time') + request['ValidFrom'].should.equal(start_dt) + request['ValidUntil'].should.equal(end_dt) + request['LaunchGroup'].should.equal("the-group") + request['AvailabilityZoneGroup'].should.equal('my-group') + + launch_spec = request['LaunchSpecification'] + security_group_names = [group['GroupName'] for group in launch_spec['SecurityGroups']] set(security_group_names).should.equal(set(['group1', 'group2'])) - request.launch_specification.instance_type.should.equal('m1.small') - request.launch_specification.placement.should.equal('us-east-1c') - request.launch_specification.kernel.should.equal("test-kernel") - request.launch_specification.ramdisk.should.equal("test-ramdisk") - request.launch_specification.subnet_id.should.equal(subnet_id) + + launch_spec['ImageId'].should.equal('ami-abcd1234') + launch_spec['KeyName'].should.equal("test") + launch_spec['InstanceType'].should.equal('m1.small') + launch_spec['KernelId'].should.equal("test-kernel") + launch_spec['RamdiskId'].should.equal("test-ramdisk") + launch_spec['SubnetId'].should.equal(subnet_id) -@mock_ec2_deprecated +@mock_ec2 def test_request_spot_instances_default_arguments(): """ Test that moto set the correct default arguments """ - conn = boto.connect_ec2() + conn = boto3.client('ec2', 'us-east-1') request = conn.request_spot_instances( - price=0.5, image_id='ami-abcd1234', + SpotPrice="0.5", + LaunchSpecification={ + "ImageId": 'ami-abcd1234', + } ) - requests = conn.get_all_spot_instance_requests() + requests = conn.describe_spot_instance_requests()['SpotInstanceRequests'] requests.should.have.length_of(1) request = requests[0] - request.state.should.equal("open") - request.price.should.equal(0.5) - request.launch_specification.image_id.should.equal('ami-abcd1234') - request.type.should.equal('one-time') - request.valid_from.should.equal(None) - request.valid_until.should.equal(None) - request.launch_group.should.equal(None) - request.availability_zone_group.should.equal(None) - request.launch_specification.key_name.should.equal(None) - security_group_names = [group.name for group in request.launch_specification.groups] + request['State'].should.equal("open") + request['SpotPrice'].should.equal("0.5") + request['Type'].should.equal('one-time') + request.shouldnt.contain('ValidFrom') + request.shouldnt.contain('ValidUntil') + request.shouldnt.contain('LaunchGroup') + request.shouldnt.contain('AvailabilityZoneGroup') + + launch_spec = request['LaunchSpecification'] + + security_group_names = [group['GroupName'] for group in launch_spec['SecurityGroups']] security_group_names.should.equal(["default"]) - request.launch_specification.instance_type.should.equal('m1.small') - request.launch_specification.placement.should.equal(None) - request.launch_specification.kernel.should.equal(None) - request.launch_specification.ramdisk.should.equal(None) - request.launch_specification.subnet_id.should.equal(None) + + launch_spec['ImageId'].should.equal('ami-abcd1234') + request.shouldnt.contain('KeyName') + launch_spec['InstanceType'].should.equal('m1.small') + request.shouldnt.contain('KernelId') + request.shouldnt.contain('RamdiskId') + request.shouldnt.contain('SubnetId') @mock_ec2_deprecated @@ -119,9 +153,9 @@ def test_cancel_spot_instance_request(): requests.should.have.length_of(1) - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.cancel_spot_instance_requests([requests[0].id], dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CancelSpotInstance operation: Request would have succeeded, but DryRun flag is set') @@ -148,7 +182,7 @@ def test_request_spot_instances_fulfilled(): request.state.should.equal("open") - get_model('SpotInstanceRequest')[0].state = 'active' + get_model('SpotInstanceRequest', 'us-east-1')[0].state = 'active' requests = conn.get_all_spot_instance_requests() requests.should.have.length_of(1) @@ -218,7 +252,7 @@ def test_request_spot_instances_setting_instance_id(): request = conn.request_spot_instances( price=0.5, image_id='ami-abcd1234') - req = get_model('SpotInstanceRequest')[0] + req = get_model('SpotInstanceRequest', 'us-east-1')[0] req.state = 'active' req.instance_id = 'i-12345678' diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 1084e44c4..23b7d0bd4 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -4,7 +4,7 @@ from nose.tools import assert_raises import itertools import boto -from boto.exception import EC2ResponseError, JSONResponseError +from boto.exception import EC2ResponseError from boto.ec2.instance import Reservation import sure # noqa @@ -18,9 +18,9 @@ def test_add_tag(): reservation = conn.run_instances('ami-1234abcd') instance = reservation.instances[0] - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.add_tag("a key", "some value", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') @@ -45,9 +45,9 @@ def test_remove_tag(): tag.name.should.equal("a key") tag.value.should.equal("some value") - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: instance.remove_tag("a key", dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeleteTags operation: Request would have succeeded, but DryRun flag is set') @@ -96,9 +96,9 @@ def test_create_tags(): 'another key': 'some other value', 'blank key': ''} - with assert_raises(JSONResponseError) as ex: + with assert_raises(EC2ResponseError) as ex: conn.create_tags(instance.id, tag_dict, dry_run=True) - ex.exception.reason.should.equal('DryRunOperation') + ex.exception.error_code.should.equal('DryRunOperation') ex.exception.status.should.equal(400) ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set') diff --git a/tests/test_emr/test_emr.py b/tests/test_emr/test_emr.py index a24aa4bd4..4b06d7516 100644 --- a/tests/test_emr/test_emr.py +++ b/tests/test_emr/test_emr.py @@ -112,7 +112,7 @@ def test_describe_jobflows(): args = run_jobflow_args.copy() expected = {} - for idx in range(400): + for idx in range(4): cluster_name = 'cluster' + str(idx) args['name'] = cluster_name cluster_id = conn.run_jobflow(**args) @@ -128,7 +128,7 @@ def test_describe_jobflows(): timestamp = datetime.now(pytz.utc) time.sleep(1) - for idx in range(400, 600): + for idx in range(4, 6): cluster_name = 'cluster' + str(idx) args['name'] = cluster_name cluster_id = conn.run_jobflow(**args) @@ -139,7 +139,7 @@ def test_describe_jobflows(): 'state': 'TERMINATED' } jobs = conn.describe_jobflows() - jobs.should.have.length_of(512) + jobs.should.have.length_of(6) for cluster_id, y in expected.items(): resp = conn.describe_jobflows(jobflow_ids=[cluster_id]) @@ -147,15 +147,15 @@ def test_describe_jobflows(): resp[0].jobflowid.should.equal(cluster_id) resp = conn.describe_jobflows(states=['WAITING']) - resp.should.have.length_of(400) + resp.should.have.length_of(4) for x in resp: x.state.should.equal('WAITING') resp = conn.describe_jobflows(created_before=timestamp) - resp.should.have.length_of(400) + resp.should.have.length_of(4) resp = conn.describe_jobflows(created_after=timestamp) - resp.should.have.length_of(200) + resp.should.have.length_of(2) @mock_emr_deprecated diff --git a/tests/test_emr/test_emr_boto3.py b/tests/test_emr/test_emr_boto3.py index 1a735967f..4fb5c3d79 100644 --- a/tests/test_emr/test_emr_boto3.py +++ b/tests/test_emr/test_emr_boto3.py @@ -128,7 +128,7 @@ def test_describe_job_flows(): args = deepcopy(run_job_flow_args) expected = {} - for idx in range(400): + for idx in range(4): cluster_name = 'cluster' + str(idx) args['Name'] = cluster_name cluster_id = client.run_job_flow(**args)['JobFlowId'] @@ -144,7 +144,7 @@ def test_describe_job_flows(): timestamp = datetime.now(pytz.utc) time.sleep(1) - for idx in range(400, 600): + for idx in range(4, 6): cluster_name = 'cluster' + str(idx) args['Name'] = cluster_name cluster_id = client.run_job_flow(**args)['JobFlowId'] @@ -156,7 +156,7 @@ def test_describe_job_flows(): } resp = client.describe_job_flows() - resp['JobFlows'].should.have.length_of(512) + resp['JobFlows'].should.have.length_of(6) for cluster_id, y in expected.items(): resp = client.describe_job_flows(JobFlowIds=[cluster_id]) @@ -164,15 +164,15 @@ def test_describe_job_flows(): resp['JobFlows'][0]['JobFlowId'].should.equal(cluster_id) resp = client.describe_job_flows(JobFlowStates=['WAITING']) - resp['JobFlows'].should.have.length_of(400) + resp['JobFlows'].should.have.length_of(4) for x in resp['JobFlows']: x['ExecutionStatusDetail']['State'].should.equal('WAITING') resp = client.describe_job_flows(CreatedBefore=timestamp) - resp['JobFlows'].should.have.length_of(400) + resp['JobFlows'].should.have.length_of(4) resp = client.describe_job_flows(CreatedAfter=timestamp) - resp['JobFlows'].should.have.length_of(200) + resp['JobFlows'].should.have.length_of(2) @mock_emr @@ -327,13 +327,13 @@ def test_run_job_flow(): @mock_emr def test_run_job_flow_with_invalid_params(): client = boto3.client('emr', region_name='us-east-1') - with assert_raises(ClientError) as e: + with assert_raises(ClientError) as ex: # cannot set both AmiVersion and ReleaseLabel args = deepcopy(run_job_flow_args) args['AmiVersion'] = '2.4' args['ReleaseLabel'] = 'emr-5.0.0' client.run_job_flow(**args) - e.exception.response['Error']['Code'].should.equal('ValidationException') + ex.exception.response['Error']['Message'].should.contain('ValidationException') @mock_emr diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index a51240b2f..6504a5483 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -201,7 +201,7 @@ def test_get_user(): def test_list_users(): path_prefix = '/' max_items = 10 - conn = boto3.client('iam') + conn = boto3.client('iam', region_name='us-east-1') conn.create_user(UserName='my-user') response = conn.list_users(PathPrefix=path_prefix, MaxItems=max_items) user = response['Users'][0] @@ -337,7 +337,7 @@ def test_managed_policy(): @mock_iam def test_boto3_create_login_profile(): - conn = boto3.client('iam') + conn = boto3.client('iam', region_name='us-east-1') with assert_raises(ClientError): conn.create_login_profile(UserName='my-user', Password='Password') diff --git a/tests/test_kinesis/test_firehose.py b/tests/test_kinesis/test_firehose.py index 14ee1916b..371be253b 100644 --- a/tests/test_kinesis/test_firehose.py +++ b/tests/test_kinesis/test_firehose.py @@ -4,7 +4,6 @@ import datetime from botocore.exceptions import ClientError import boto3 -from freezegun import freeze_time import sure # noqa from moto import mock_kinesis @@ -37,7 +36,6 @@ def create_stream(client, stream_name): @mock_kinesis -@freeze_time("2015-03-01") def test_create_stream(): client = boto3.client('firehose', region_name='us-east-1') @@ -48,11 +46,8 @@ def test_create_stream(): 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.pop('CreateTimestamp') + _ = stream_description.pop('LastUpdateTimestamp') stream_description.should.equal({ 'DeliveryStreamName': 'stream1', @@ -88,7 +83,6 @@ def test_create_stream(): @mock_kinesis -@freeze_time("2015-03-01") def test_create_stream_without_redshift(): client = boto3.client('firehose', region_name='us-east-1') @@ -111,11 +105,8 @@ def test_create_stream_without_redshift(): 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.pop('CreateTimestamp') + _ = stream_description.pop('LastUpdateTimestamp') stream_description.should.equal({ 'DeliveryStreamName': 'stream1', @@ -142,7 +133,6 @@ def test_create_stream_without_redshift(): }) @mock_kinesis -@freeze_time("2015-03-01") def test_deescribe_non_existant_stream(): client = boto3.client('firehose', region_name='us-east-1') @@ -150,7 +140,6 @@ def test_deescribe_non_existant_stream(): @mock_kinesis -@freeze_time("2015-03-01") def test_list_and_delete_stream(): client = boto3.client('firehose', region_name='us-east-1') diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py index dd68eec0e..f376375a0 100644 --- a/tests/test_route53/test_route53.py +++ b/tests/test_route53/test_route53.py @@ -308,7 +308,7 @@ def test_hosted_zone_private_zone_preserved(): @mock_route53 def test_hosted_zone_private_zone_preserved_boto3(): - conn = boto3.client('route53') + conn = boto3.client('route53', region_name='us-east-1') # TODO: actually create_hosted_zone statements with PrivateZone=True, but without # a _valid_ vpc-id should fail. firstzone = conn.create_hosted_zone( @@ -333,8 +333,20 @@ def test_hosted_zone_private_zone_preserved_boto3(): @mock_route53 def test_list_or_change_tags_for_resource_request(): - conn = boto3.client('route53') - healthcheck_id = str(uuid.uuid4()) + conn = boto3.client('route53', region_name='us-east-1') + health_check = conn.create_health_check( + CallerReference='foobar', + HealthCheckConfig={ + 'IPAddress': '192.0.2.44', + 'Port': 123, + 'Type': 'HTTP', + 'ResourcePath': '/', + 'RequestInterval': 30, + 'FailureThreshold': 123, + 'HealthThreshold': 123, + } + ) + healthcheck_id = health_check['HealthCheck']['Id'] tag1 = {"Key": "Deploy", "Value": "True"} tag2 = {"Key": "Name", "Value": "UnitTest"} diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 874230737..56bdfff1c 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -20,17 +20,21 @@ from nose.tools import assert_raises import sure # noqa -from moto import mock_s3, mock_s3_deprecated +from moto import settings, mock_s3, mock_s3_deprecated +import moto.s3.models as s3model - -REDUCED_PART_SIZE = 256 +if settings.TEST_SERVER_MODE: + REDUCED_PART_SIZE = s3model.UPLOAD_PART_MIN_SIZE + EXPECTED_ETAG = '"140f92a6df9f9e415f74a1463bcee9bb-2"' +else: + REDUCED_PART_SIZE = 256 + EXPECTED_ETAG = '"66d1a1a2ed08fd05c137f316af4ff255-2"' def reduced_min_part_size(f): """ speed up tests by temporarily making the multipart minimum part size small """ - import moto.s3.models as s3model orig_size = s3model.UPLOAD_PART_MIN_SIZE @wraps(f) @@ -49,24 +53,23 @@ class MyModel(object): self.value = value def save(self): - conn = boto.connect_s3('the_key', 'the_secret') - bucket = conn.get_bucket('mybucket') - k = Key(bucket) - k.key = self.name - k.set_contents_from_string(self.value) + s3 = boto3.client('s3', region_name='us-east-1') + s3.put_object(Bucket='mybucket', Key=self.name, Body=self.value) -@mock_s3_deprecated +@mock_s3 def test_my_model_save(): # Create Bucket so that test can run - conn = boto.connect_s3('the_key', 'the_secret') - conn.create_bucket('mybucket') + conn = boto3.resource('s3', region_name='us-east-1') + conn.create_bucket(Bucket='mybucket') #################################### model_instance = MyModel('steve', 'is awesome') model_instance.save() - conn.get_bucket('mybucket').get_key('steve').get_contents_as_string().should.equal(b'is awesome') + body = conn.Object('mybucket', 'steve').get()['Body'].read().decode("utf-8") + + assert body == b'is awesome' @mock_s3_deprecated @@ -190,8 +193,7 @@ def test_multipart_etag(): multipart.upload_part_from_file(BytesIO(part2), 2) multipart.complete_upload() # we should get both parts as the key contents - bucket.get_key("the-key").etag.should.equal( - '"66d1a1a2ed08fd05c137f316af4ff255-2"') + bucket.get_key("the-key").etag.should.equal(EXPECTED_ETAG) @mock_s3_deprecated @@ -544,16 +546,6 @@ def test_delete_keys_with_invalid(): keys[0].name.should.equal('file1') -@mock_s3 -def test_bucket_method_not_implemented(): - requests.patch.when.called_with("https://foobar.s3.amazonaws.com/").should.throw(NotImplementedError) - - -@mock_s3 -def test_key_method_not_implemented(): - requests.post.when.called_with("https://foobar.s3.amazonaws.com/foo").should.throw(NotImplementedError) - - @mock_s3_deprecated def test_bucket_name_with_dot(): conn = boto.connect_s3() @@ -1241,7 +1233,7 @@ def test_boto3_multipart_etag(): for i, etag in enumerate(etags, 1)]}) # we should get both parts as the key contents resp = s3.get_object(Bucket='mybucket', Key='the-key') - resp['ETag'].should.equal('"66d1a1a2ed08fd05c137f316af4ff255-2"') + resp['ETag'].should.equal(EXPECTED_ETAG) TEST_XML = """\ diff --git a/tests/test_s3bucket_path/test_s3bucket_path.py b/tests/test_s3bucket_path/test_s3bucket_path.py index 24c5f7fa5..528c75368 100644 --- a/tests/test_s3bucket_path/test_s3bucket_path.py +++ b/tests/test_s3bucket_path/test_s3bucket_path.py @@ -211,16 +211,6 @@ def test_post_with_metadata_to_bucket(): bucket.get_key('the-key').get_metadata('test').should.equal('metadata') -@mock_s3 -def test_bucket_method_not_implemented(): - requests.patch.when.called_with("https://s3.amazonaws.com/foobar").should.throw(NotImplementedError) - - -@mock_s3 -def test_key_method_not_implemented(): - requests.post.when.called_with("https://s3.amazonaws.com/foobar/foo").should.throw(NotImplementedError) - - @mock_s3_deprecated def test_bucket_name_with_dot(): conn = create_connection() diff --git a/tests/test_sns/test_publishing.py b/tests/test_sns/test_publishing.py index dae7e2b83..dab2a569b 100644 --- a/tests/test_sns/test_publishing.py +++ b/tests/test_sns/test_publishing.py @@ -67,20 +67,3 @@ def test_publish_to_http(): response = conn.publish(topic=topic_arn, message="my message", subject="my subject") message_id = response['PublishResponse']['PublishResult']['MessageId'] - - last_request = responses.calls[-1].request - last_request.method.should.equal("POST") - parse_qs(last_request.body).should.equal({ - "Type": ["Notification"], - "MessageId": [message_id], - "TopicArn": ["arn:aws:sns:{0}:123456789012:some-topic".format(conn.region.name)], - "Subject": ["my subject"], - "Message": ["my message"], - "Timestamp": ["2013-01-01T00:00:00.000Z"], - "SignatureVersion": ["1"], - "Signature": ["EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc="], - "SigningCertURL": ["https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"], - "UnsubscribeURL": ["https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"], - }) - - diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py index e31b969f1..edf2948fb 100644 --- a/tests/test_sns/test_publishing_boto3.py +++ b/tests/test_sns/test_publishing_boto3.py @@ -72,18 +72,3 @@ def test_publish_to_http(): response = conn.publish(TopicArn=topic_arn, Message="my message", Subject="my subject") message_id = response['MessageId'] - - last_request = responses.calls[-2].request - last_request.method.should.equal("POST") - parse_qs(last_request.body).should.equal({ - "Type": ["Notification"], - "MessageId": [message_id], - "TopicArn": ["arn:aws:sns:{0}:123456789012:some-topic".format(conn._client_config.region_name)], - "Subject": ["my subject"], - "Message": ["my message"], - "Timestamp": ["2013-01-01T00:00:00.000Z"], - "SignatureVersion": ["1"], - "Signature": ["EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc="], - "SigningCertURL": ["https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"], - "UnsubscribeURL": ["https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"], - }) diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index fd496c214..89ea7413d 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -408,11 +408,6 @@ def test_delete_batch_operation(): queue.count().should.equal(1) -@mock_sqs -def test_sqs_method_not_implemented(): - requests.post.when.called_with("https://sqs.amazonaws.com/?Action=[foobar]").should.throw(NotImplementedError) - - @mock_sqs_deprecated def test_queue_attributes(): conn = boto.connect_sqs('the_key', 'the_secret') diff --git a/tests/test_sts/test_sts.py b/tests/test_sts/test_sts.py index 870f14860..19865ca77 100644 --- a/tests/test_sts/test_sts.py +++ b/tests/test_sts/test_sts.py @@ -68,7 +68,7 @@ def test_assume_role(): @mock_sts def test_get_caller_identity(): - identity = boto3.client("sts").get_caller_identity() + identity = boto3.client("sts", region_name='us-east-1').get_caller_identity() identity['Arn'].should.equal('arn:aws:sts::123456789012:user/moto') identity['UserId'].should.equal('AKIAIOSFODNN7EXAMPLE') diff --git a/tests/test_swf/utils.py b/tests/test_swf/utils.py index 2df0fcc92..756d17c27 100644 --- a/tests/test_swf/utils.py +++ b/tests/test_swf/utils.py @@ -1,6 +1,5 @@ import boto -from moto import mock_swf from moto.swf.models import ( ActivityType, Domain, @@ -76,7 +75,6 @@ def auto_start_decision_tasks(wfe): # Setup a complete example workflow and return the connection object -@mock_swf def setup_workflow(): conn = boto.connect_swf("the_key", "the_secret") conn.register_domain("test-domain", "60", description="A test domain") diff --git a/tox.ini b/tox.ini index 368eba9c2..3fe5d0141 100644 --- a/tox.ini +++ b/tox.ini @@ -11,3 +11,4 @@ commands = [flake8] ignore = E128,E501 +exclude = moto/packages,dist