Get standalone server mode working for all tests.
This commit is contained in:
parent
cb28eeefbb
commit
81836b6981
78 changed files with 957 additions and 783 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue