remove proxy to ec2

Change-Id: I7c20d19ba1d608c84fbcf9602fcd4be61e9f64ea
This commit is contained in:
Andrey Pavlov 2014-12-25 21:48:42 +03:00
parent a53c96d231
commit 28756f3ad6
21 changed files with 67 additions and 851 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
.pydevproject
ec2_api.egg-info
.tox
.testrepository

View File

@ -421,7 +421,7 @@ def ec2_error_ex(ex, req, code=None, message=None, unexpected=False):
log_msg = _("%(ex_name)s raised: %(ex_str)s")
# NOTE(jruzicka): For compatibility with EC2 API, treat expected
# exceptions as client (4xx) errors. The exception error code is 500
# by default and most exceptions inherit this from NovaException even
# by default and most exceptions inherit this from EC2Exception even
# though they are actually client errors in most cases.
if status >= 500:
status = 400
@ -468,34 +468,9 @@ class Executor(wsgi.Application):
ec2_id = ec2utils.id_to_ec2_inst_id(ex.kwargs['instance_id'])
message = ex.msg_fmt % {'instance_id': ec2_id}
return ec2_error_ex(ex, req, message=message)
except exception.NovaDbVolumeNotFound as ex:
ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id'])
message = ex.msg_fmt % {'volume_id': ec2_id}
return ec2_error_ex(ex, req, message=message)
except exception.NovaDbSnapshotNotFound as ex:
ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id'])
message = ex.msg_fmt % {'snapshot_id': ec2_id}
return ec2_error_ex(ex, req, message=message)
except exception.MethodNotFound:
try:
http, response = api_request.proxy(req)
resp = webob.Response()
resp.status = http["status"]
resp.headers["content-type"] = http["content-type"]
resp.body = str(response)
return resp
except Exception as ex:
return ec2_error_ex(ex, req, unexpected=True)
except exception.EC2ServerError as ex:
resp = webob.Response()
resp.status = ex.response['status']
resp.headers['Content-Type'] = ex.response['content-type']
resp.body = ex.content
return resp
except Exception as ex:
return ec2_error_ex(ex, req,
unexpected=not isinstance(
ex, exception.EC2Exception))
return ec2_error_ex(
ex, req, unexpected=not isinstance(ex, exception.EC2Exception))
else:
resp = webob.Response()
resp.status = 200

View File

@ -22,7 +22,6 @@ from oslo.config import cfg
from ec2api.api import clients
from ec2api.api import common
from ec2api.api import ec2client
from ec2api.api import ec2utils
from ec2api.api import utils
from ec2api.db import api as db_api
@ -275,7 +274,6 @@ class AddressEngineNeutron(object):
msg = _("The address '%(public_ip)s' does not belong to you.")
raise exception.AuthFailure(msg % {'public_ip': public_ip})
ec2 = ec2client.ec2client(context)
# NOTE(ft): in fact only the first two parameters are used to
# associate an address in EC2 Classic mode. Other parameters are
# sent to validate them for EC2 Classic mode and raise an error.

View File

@ -25,11 +25,10 @@ from oslo.config import cfg
from ec2api.api import cloud
from ec2api.api import ec2utils
from ec2api.api import proxy
from ec2api import exception
from ec2api.openstack.common.gettextutils import _
from ec2api.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@ -58,14 +57,15 @@ class APIRequest(object):
self.controller = cloud.VpcCloudController()
else:
self.controller = cloud.CloudController()
self.proxyController = proxy.ProxyController()
def invoke(self, context):
try:
method = getattr(self.controller,
ec2utils.camelcase_to_underscore(self.action))
except AttributeError:
raise exception.MethodNotFound(name=self.action)
LOG.exception(_('Unsupported API request: action = %(action)s'),
{'action': self.action})
raise exception.InvalidRequest()
args = ec2utils.dict_from_dotted_str(self.args.items())
@ -91,9 +91,6 @@ class APIRequest(object):
result = method(context, **args)
return self._render_response(result, context.request_id)
def proxy(self, req):
return self.proxyController.proxy(req, self.args)
def _render_response(self, response_data, request_id):
xml = minidom.Document()

View File

@ -1,222 +0,0 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
import base64
import hashlib
import hmac
import re
import time
import types
import urllib
import urlparse
import httplib2
from lxml import etree
from oslo.config import cfg
from ec2api.api import ec2utils
from ec2api import exception
from ec2api.openstack.common import log as logging
ec2_opts = [
cfg.StrOpt('base_ec2_host',
default="localhost",
help='The IP address of the EC2 API server'),
cfg.IntOpt('base_ec2_port',
default=8773,
help='The port of the EC2 API server'),
cfg.StrOpt('base_ec2_scheme',
default='http',
help='The protocol to use when connecting to the EC2 API '
'server (http, https)'),
cfg.StrOpt('base_ec2_path',
default='/services/Cloud',
help='The path prefix used to call the ec2 API server'),
]
CONF = cfg.CONF
CONF.register_opts(ec2_opts)
LOG = logging.getLogger(__name__)
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
def ec2client(context):
return EC2Client(context)
class EC2Requester(object):
def __init__(self, version, http_method):
self.http_obj = httplib2.Http(
disable_ssl_certificate_validation=True)
self.version = version
self.method = http_method
def request(self, context, action, args):
headers = {
'content-type': 'application/x-www-form-urlencoded',
'connection': 'close',
}
params = args
params['Action'] = action
params['Version'] = self.version
self._add_auth(context, params)
params = self._get_query_string(params)
if self.method == 'POST':
url = self._ec2_url
body = params
else:
url = '?'.join((self._ec2_url, params,))
body = None
response, content = self.http_obj.request(url, self.method,
body=body, headers=headers)
return response, content
_ec2_url = '%s://%s:%s%s' % (CONF.base_ec2_scheme,
CONF.base_ec2_host,
CONF.base_ec2_port,
CONF.base_ec2_path)
@staticmethod
def _get_query_string(params):
pairs = []
for key in sorted(params):
value = params[key]
pairs.append(urllib.quote(key.encode('utf-8'), safe='') + '=' +
urllib.quote(value.encode('utf-8'), safe='-_~'))
return '&'.join(pairs)
def _calc_signature(self, context, params):
LOG.debug('Calculating signature using v2 auth.')
split = urlparse.urlsplit(self._ec2_url)
path = split.path
if len(path) == 0:
path = '/'
string_to_sign = '%s\n%s\n%s\n' % (self.method,
split.netloc,
path)
secret = context.secret_key
lhmac = hmac.new(secret.encode('utf-8'), digestmod=hashlib.sha256)
string_to_sign += self._get_query_string(params)
LOG.debug('String to sign: %s', string_to_sign)
lhmac.update(string_to_sign.encode('utf-8'))
b64 = base64.b64encode(lhmac.digest()).strip().decode('utf-8')
return b64
def _add_auth(self, context, params):
params['AWSAccessKeyId'] = context.access_key
params['SignatureVersion'] = '2'
params['SignatureMethod'] = 'HmacSHA256'
params['Timestamp'] = time.strftime(ISO8601, time.gmtime())
signature = self._calc_signature(context, params)
params['Signature'] = signature
class EC2Client(object):
def __init__(self, context):
self.context = context
self.requester = EC2Requester(context.api_version, 'POST')
def __getattr__(self, name):
ec2_name = self._underscore_to_camelcase(name)
def func(self, **kwargs):
params = self._build_params(**kwargs)
response, content = self.requester.request(self.context, ec2_name,
params)
return self._process_response(response, content)
func.__name__ = name
setattr(self, name, types.MethodType(func, self, self.__class__))
setattr(self.__class__, name,
types.MethodType(func, None, self.__class__))
return getattr(self, name)
@staticmethod
def _process_response(response, content):
if response.status > 200:
raise exception.EC2ServerError(response, content)
res = EC2Client._parse_xml(content)
res = next(res.itervalues())
if 'return' in res:
return res['return']
else:
res.pop('requestId')
return res
@staticmethod
def _build_params(**kwargs):
def add_list_param(params, items, label):
for i in range(1, len(items) + 1):
item = items[i - 1]
item_label = '%s.%d' % (label, i)
if isinstance(item, dict):
add_dict_param(params, item, item_label)
else:
params[item_label] = str(item)
def add_dict_param(params, items, label=None):
for key, value in items.iteritems():
ec2_key = EC2Client._underscore_to_camelcase(key)
item_label = '%s.%s' % (label, ec2_key) if label else ec2_key
if isinstance(value, dict):
add_dict_param(params, value, item_label)
elif isinstance(value, list):
add_list_param(params, value, item_label)
else:
params[item_label] = str(value)
params = {}
add_dict_param(params, kwargs)
return params
_xml_scheme = re.compile('\sxmlns=".*"')
@staticmethod
# NOTE(ft): this function is used in unit tests until it be moved to one
# of utils module
def _parse_xml(xml_string):
xml_string = EC2Client._xml_scheme.sub('', xml_string)
xml = etree.fromstring(xml_string)
def convert_node(node):
children = list(node)
if len(children):
if children[0].tag == 'item':
val = list(convert_node(child)[1] for child in children)
else:
val = dict(convert_node(child) for child in children)
elif node.tag.endswith('Set'):
val = []
else:
# TODO(ft): do not use private function
val = (ec2utils._try_convert(node.text)
if node.text
else node.text)
return node.tag, val
return dict([convert_node(xml)])
@staticmethod
# NOTE(ft): this function is copied from apirequest to avoid circular
# module reference. It should be moved to one of utils module
def _underscore_to_camelcase(st):
return ''.join([x[:1].upper() + x[1:] for x in st.split('_')])

View File

@ -152,29 +152,9 @@ def is_ec2_timestamp_expired(request, expires=None):
return True
def id_to_glance_id(context, image_id):
"""Convert an internal (db) id to a glance id."""
return novadb.s3_image_get(context, image_id)['uuid']
def glance_id_to_id(context, glance_id):
"""Convert a glance id to an internal (db) id."""
if glance_id is None:
return
try:
return novadb.s3_image_get_by_uuid(context, glance_id)['id']
except exception.NotFound:
return novadb.s3_image_create(context, glance_id)['id']
def ec2_id_to_glance_id(context, ec2_id):
image_id = ec2_id_to_id(ec2_id)
return id_to_glance_id(context, image_id)
def glance_id_to_ec2_id(context, glance_id, image_type='ami'):
image_id = glance_id_to_id(context, glance_id)
return image_ec2_id(image_id, image_type=image_type)
return novadb.s3_image_get(context, image_id)['uuid']
# TODO(Alex) This function is copied as is from original cloud.py. It doesn't
@ -187,12 +167,6 @@ def ec2_id_to_id(ec2_id):
raise exception.InvalidId(id=ec2_id)
def image_ec2_id(image_id, image_type='ami'):
"""Returns image ec2_id using id and three letter type."""
template = image_type + '-%08x'
return id_to_ec2_id(image_id, template=template)
def id_to_ec2_id(instance_id, template='i-%08x'):
"""Convert an instance ID (int) to an ec2 ID (i-[base 16 number])."""
return template % int(instance_id)
@ -204,7 +178,7 @@ def id_to_ec2_inst_id(instance_id):
return None
elif uuidutils.is_uuid_like(instance_id):
ctxt = context.get_admin_context()
int_id = get_int_id_from_instance_uuid(ctxt, instance_id)
int_id = _get_int_id_from_instance_uuid(ctxt, instance_id)
return id_to_ec2_id(int_id)
else:
return id_to_ec2_id(instance_id)
@ -213,14 +187,14 @@ def id_to_ec2_inst_id(instance_id):
def ec2_inst_id_to_uuid(context, ec2_id):
""""Convert an instance id to uuid."""
int_id = ec2_id_to_id(ec2_id)
return get_instance_uuid_from_int_id(context, int_id)
return _get_instance_uuid_from_int_id(context, int_id)
def get_instance_uuid_from_int_id(context, int_id):
def _get_instance_uuid_from_int_id(context, int_id):
return novadb.get_instance_uuid_by_ec2_id(context, int_id)
def get_int_id_from_instance_uuid(context, instance_uuid):
def _get_int_id_from_instance_uuid(context, instance_uuid):
if instance_uuid is None:
return
try:
@ -229,69 +203,6 @@ def get_int_id_from_instance_uuid(context, instance_uuid):
return novadb.ec2_instance_create(context, instance_uuid)['id']
def get_volume_uuid_from_int_id(context, int_id):
return novadb.get_volume_uuid_by_ec2_id(context, int_id)
def id_to_ec2_snap_id(snapshot_id):
"""Get or create an ec2 volume ID (vol-[base 16 number]) from uuid."""
if uuidutils.is_uuid_like(snapshot_id):
ctxt = context.get_admin_context()
int_id = get_int_id_from_snapshot_uuid(ctxt, snapshot_id)
return id_to_ec2_id(int_id, 'snap-%08x')
else:
return id_to_ec2_id(snapshot_id, 'snap-%08x')
def id_to_ec2_vol_id(volume_id):
"""Get or create an ec2 volume ID (vol-[base 16 number]) from uuid."""
if uuidutils.is_uuid_like(volume_id):
ctxt = context.get_admin_context()
int_id = get_int_id_from_volume_uuid(ctxt, volume_id)
return id_to_ec2_id(int_id, 'vol-%08x')
else:
return id_to_ec2_id(volume_id, 'vol-%08x')
def get_int_id_from_volume_uuid(context, volume_uuid):
if volume_uuid is None:
return
try:
return novadb.get_ec2_volume_id_by_uuid(context, volume_uuid)
except exception.NotFound:
return novadb.ec2_volume_create(context, volume_uuid)['id']
def ec2_vol_id_to_uuid(ec2_id):
"""Get the corresponding UUID for the given ec2-id."""
ctxt = context.get_admin_context()
# NOTE(jgriffith) first strip prefix to get just the numeric
int_id = ec2_id_to_id(ec2_id)
return get_volume_uuid_from_int_id(ctxt, int_id)
def get_snapshot_uuid_from_int_id(context, int_id):
return novadb.get_snapshot_uuid_by_ec2_id(context, int_id)
def ec2_snap_id_to_uuid(ec2_id):
"""Get the corresponding UUID for the given ec2-id."""
ctxt = context.get_admin_context()
# NOTE(jgriffith) first strip prefix to get just the numeric
int_id = ec2_id_to_id(ec2_id)
return get_snapshot_uuid_from_int_id(ctxt, int_id)
def get_int_id_from_snapshot_uuid(context, snapshot_uuid):
if snapshot_uuid is None:
return
try:
return novadb.get_ec2_snapshot_id_by_uuid(context, snapshot_uuid)
except exception.NotFound:
return novadb.ec2_snapshot_create(context, snapshot_uuid)['id']
# NOTE(ft): extra functions to use in vpc specific code or instead of
# malformed existed functions

View File

@ -1,27 +0,0 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
from ec2api.api import ec2client
class ProxyController(object):
def __str__(self):
return 'ProxyController'
def proxy(self, req, args):
requester = ec2client.EC2Requester(req.params["Version"],
req.environ["REQUEST_METHOD"])
return requester.request(req.environ['ec2api.context'],
req.params["Action"], args)

View File

@ -28,7 +28,8 @@ def create_volume(context, availability_zone=None, size=None,
description=None, metadata=None, iops=None, encrypted=None,
kms_key_id=None):
if snapshot_id is not None:
os_snapshot_id = ec2utils.ec2_snap_id_to_uuid(snapshot_id)
snapshot = ec2utils.get_db_item(context, 'snap', snapshot_id)
os_snapshot_id = snapshot['os_id']
else:
os_snapshot_id = None

View File

@ -18,12 +18,12 @@ from ec2api.openstack.common.db import options
from ec2api import paths
from ec2api import version
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('nova.sqlite')
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('ec2api.sqlite')
def parse_args(argv, default_config_files=None):
options.set_defaults(sql_connection=_DEFAULT_SQL_CONNECTION,
sqlite_db='nova.sqlite')
sqlite_db='ec2api.sqlite')
cfg.CONF(argv[1:],
project='ec2api',
version=version.version_info.version_string(),

View File

@ -25,7 +25,6 @@ from sqlalchemy import and_
from sqlalchemy import or_
from sqlalchemy.sql import bindparam
from ec2api.api import ec2utils
import ec2api.context
from ec2api.db.sqlalchemy import models
from ec2api.openstack.common.db import exception as db_exception
@ -91,20 +90,8 @@ def model_query(context, model, *args, **kwargs):
def _new_id(kind, os_id):
# NOTE(ft): obtaining new id from Nova DB is temporary solution
# while we don't implmenet all Nova EC2 methods
if kind == 'i':
obj_id = ec2utils.id_to_ec2_inst_id(os_id)
elif kind == 'vol':
obj_id = ec2utils.id_to_ec2_vol_id(os_id)
elif kind == 'snap':
obj_id = ec2utils.id_to_ec2_snap_id(os_id)
elif kind in ('ami', 'ari', 'aki'):
obj_id = ec2utils.glance_id_to_ec2_id(
ec2api.context.get_admin_context(), os_id, kind)
else:
obj_id = "%(kind)s-%(id)08x" % {"kind": kind,
"id": random.randint(1, 0xffffffff)}
obj_id = "%(kind)s-%(id)08x" % {"kind": kind,
"id": random.randint(1, 0xffffffff)}
return obj_id

View File

@ -133,10 +133,6 @@ class PasteAppNotFound(EC2Exception):
msg_fmt = _("Could not load paste app '%(name)s' from %(path)s")
class MethodNotFound(EC2Exception):
msg_fmt = _("Could not find method '%(name)s'")
class Forbidden(EC2Exception):
ec2_code = 'AuthFailure'
msg_fmt = _("Not authorized.")
@ -166,16 +162,6 @@ class NovaDbImageNotFound(EC2NotFound):
msg_fmt = _("The image id '[%(image_id)s]' does not exist")
class NovaDbVolumeNotFound(EC2NotFound):
ec2_code = 'InvalidVolume.NotFound'
msg_fmt = _("Volume %(volume_id)s could not be found.")
class NovaDbSnapshotNotFound(EC2NotFound):
ec2_code = 'InvalidSnapshot.NotFound'
msg_fmt = _("Snapshot %(snapshot_id)s could not be found.")
class NovaDbInstanceNotFound(EC2NotFound):
ec2_code = 'InvalidInstanceID.NotFound'
msg_fmt = _("Instance %(instance_id)s could not be found.")

View File

@ -85,43 +85,6 @@ def s3_image_get(context, image_id):
return IMPL.s3_image_get(context, image_id)
def s3_image_get_by_uuid(context, image_uuid):
"""Find local s3 image represented by the provided uuid."""
return IMPL.s3_image_get_by_uuid(context, image_uuid)
def s3_image_create(context, image_uuid):
"""Create local s3 image represented by provided uuid."""
return IMPL.s3_image_create(context, image_uuid)
###################
def get_ec2_volume_id_by_uuid(context, volume_id):
return IMPL.get_ec2_volume_id_by_uuid(context, volume_id)
def get_volume_uuid_by_ec2_id(context, ec2_id):
return IMPL.get_volume_uuid_by_ec2_id(context, ec2_id)
def ec2_volume_create(context, volume_id, forced_id=None):
return IMPL.ec2_volume_create(context, volume_id, forced_id)
def get_snapshot_uuid_by_ec2_id(context, ec2_id):
return IMPL.get_snapshot_uuid_by_ec2_id(context, ec2_id)
def get_ec2_snapshot_id_by_uuid(context, snapshot_id):
return IMPL.get_ec2_snapshot_id_by_uuid(context, snapshot_id)
def ec2_snapshot_create(context, snapshot_id, forced_id=None):
return IMPL.ec2_snapshot_create(context, snapshot_id, forced_id)
###################
@ -140,14 +103,6 @@ def ec2_instance_create(context, instance_uuid, id=None):
return IMPL.ec2_instance_create(context, instance_uuid, id)
def ec2_instance_get_by_uuid(context, instance_uuid):
return IMPL.ec2_instance_get_by_uuid(context, instance_uuid)
def ec2_instance_get_by_id(context, instance_id):
return IMPL.ec2_instance_get_by_id(context, instance_id)
def instance_get_by_uuid(context, uuid, columns_to_join=None, use_slave=False):
"""Get an instance or raise if it does not exist."""
return IMPL.instance_get_by_uuid(context, uuid,

View File

@ -26,7 +26,6 @@ from sqlalchemy import or_
import ec2api.context
from ec2api import exception
from ec2api.novadb.sqlalchemy import models
from ec2api.openstack.common.db import exception as db_exc
from ec2api.openstack.common.db.sqlalchemy import session as db_session
from ec2api.openstack.common.gettextutils import _
from ec2api.openstack.common import log as logging
@ -179,118 +178,6 @@ def s3_image_get(context, image_id):
return result
def s3_image_get_by_uuid(context, image_uuid):
"""Find local s3 image represented by the provided uuid."""
result = (model_query(context, models.S3Image, read_deleted="yes").
filter_by(uuid=image_uuid).
first())
if not result:
raise exception.NovaDbImageNotFound(image_id=image_uuid)
return result
def s3_image_create(context, image_uuid):
"""Create local s3 image represented by provided uuid."""
try:
s3_image_ref = models.S3Image()
s3_image_ref.update({'uuid': image_uuid})
s3_image_ref.save()
except Exception as e:
raise db_exc.DBError(e)
return s3_image_ref
##################
def _ec2_volume_get_query(context, session=None):
return model_query(context, models.VolumeIdMapping,
session=session, read_deleted='yes')
def _ec2_snapshot_get_query(context, session=None):
return model_query(context, models.SnapshotIdMapping,
session=session, read_deleted='yes')
@require_context
def ec2_volume_create(context, volume_uuid, id=None):
"""Create ec2 compatible volume by provided uuid."""
ec2_volume_ref = models.VolumeIdMapping()
ec2_volume_ref.update({'uuid': volume_uuid})
if id is not None:
ec2_volume_ref.update({'id': id})
ec2_volume_ref.save()
return ec2_volume_ref
@require_context
def get_ec2_volume_id_by_uuid(context, volume_id):
result = (_ec2_volume_get_query(context).
filter_by(uuid=volume_id).
first())
if not result:
raise exception.NovaDbVolumeNotFound(volume_id=volume_id)
return result['id']
@require_context
def get_volume_uuid_by_ec2_id(context, ec2_id):
result = (model_query(context, models.VolumeIdMapping, read_deleted='yes').
filter_by(id=ec2_id).
first())
if not result:
raise exception.NovaDbVolumeNotFound(volume_id=ec2_id)
return result['uuid']
@require_context
def ec2_snapshot_create(context, snapshot_uuid, id=None):
"""Create ec2 compatible snapshot by provided uuid."""
ec2_snapshot_ref = models.SnapshotIdMapping()
ec2_snapshot_ref.update({'uuid': snapshot_uuid})
if id is not None:
ec2_snapshot_ref.update({'id': id})
ec2_snapshot_ref.save()
return ec2_snapshot_ref
@require_context
def get_ec2_snapshot_id_by_uuid(context, snapshot_id):
result = (_ec2_snapshot_get_query(context).
filter_by(uuid=snapshot_id).
first())
if not result:
raise exception.NovaDbSnapshotNotFound(snapshot_id=snapshot_id)
return result['id']
@require_context
def get_snapshot_uuid_by_ec2_id(context, ec2_id):
result = (model_query(context, models.SnapshotIdMapping,
read_deleted='yes').
filter_by(id=ec2_id).
first())
if not result:
raise exception.NovaDbSnapshotNotFound(snapshot_id=ec2_id)
return result['uuid']
###################
@ -308,7 +195,7 @@ def ec2_instance_create(context, instance_uuid, id=None):
@require_context
def ec2_instance_get_by_uuid(context, instance_uuid):
def _ec2_instance_get_by_uuid(context, instance_uuid):
result = (_ec2_instance_get_query(context).
filter_by(uuid=instance_uuid).
first())
@ -321,12 +208,12 @@ def ec2_instance_get_by_uuid(context, instance_uuid):
@require_context
def get_ec2_instance_id_by_uuid(context, instance_id):
result = ec2_instance_get_by_uuid(context, instance_id)
result = _ec2_instance_get_by_uuid(context, instance_id)
return result['id']
@require_context
def ec2_instance_get_by_id(context, instance_id):
def _ec2_instance_get_by_id(context, instance_id):
result = (_ec2_instance_get_query(context).
filter_by(id=instance_id).
first())
@ -339,7 +226,7 @@ def ec2_instance_get_by_id(context, instance_id):
@require_context
def get_instance_uuid_by_ec2_id(context, ec2_id):
result = ec2_instance_get_by_id(context, ec2_id)
result = _ec2_instance_get_by_id(context, ec2_id)
return result['uuid']

View File

@ -59,22 +59,6 @@ class S3Image(BASE, NovaBase):
uuid = Column(String(36), nullable=False)
class VolumeIdMapping(BASE, NovaBase):
"""Compatibility layer for the EC2 volume service."""
__tablename__ = 'volume_id_mappings'
__table_args__ = ()
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), nullable=False)
class SnapshotIdMapping(BASE, NovaBase):
"""Compatibility layer for the EC2 snapshot service."""
__tablename__ = 'snapshot_id_mappings'
__table_args__ = ()
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), nullable=False)
class InstanceIdMapping(BASE, NovaBase):
"""Compatibility layer for the EC2 instance service."""
__tablename__ = 'instance_id_mappings'

View File

@ -17,9 +17,9 @@ import mock
from oslotest import base as test_base
import ec2api.api.apirequest
from ec2api.api import ec2client
from ec2api.tests import fakes
from ec2api.tests import matchers
from ec2api.tests import tools
import ec2api.wsgi
@ -58,14 +58,6 @@ class ApiTestCase(test_base.BaseTestCase):
mock.patch('ec2api.api.ec2utils.ec2_inst_id_to_uuid'))
self.ec2_inst_id_to_uuid = ec2_inst_id_to_uuid_patcher.start()
self.addCleanup(ec2_inst_id_to_uuid_patcher.stop)
# TODO(ft): patch EC2Client object instead of ec2client function
# to make this similar to other patchers (neutron)
# Now it's impossible since tests use EC2Client._parse_xml
# Or patch neutron client function too, and make tests on client
# functions
ec2_patcher = mock.patch('ec2api.api.ec2client.ec2client')
self.ec2 = ec2_patcher.start().return_value
self.addCleanup(ec2_patcher.stop)
isotime_patcher = mock.patch('ec2api.openstack.common.timeutils.'
'isotime')
self.isotime = isotime_patcher.start()
@ -90,7 +82,7 @@ class ApiTestCase(test_base.BaseTestCase):
'endpoints': [{'publicUrl': 'fake_url'}]}])
def _check_and_transform_response(self, response, action):
body = ec2client.EC2Client._parse_xml(response.body)
body = tools.parse_xml(response.body)
if response.status_code == 200:
action_tag = '%sResponse' % action
self.assertIn(action_tag, body)

View File

@ -20,7 +20,6 @@ from oslotest import base as test_base
from ec2api import api
from ec2api.api import apirequest
from ec2api.api import cloud
from ec2api import exception
from ec2api.tests import fakes_request_response as fakes
from ec2api.tests import matchers
@ -35,10 +34,6 @@ class ApiInitTestCase(test_base.BaseTestCase):
def setUp(self):
super(ApiInitTestCase, self).setUp()
requester_patcher = mock.patch('ec2api.api.ec2client.EC2Requester')
self.requester_class = requester_patcher.start()
self.requester = self.requester_class.return_value
self.addCleanup(requester_patcher.stop)
controller_patcher = mock.patch('ec2api.api.cloud.VpcCloudController')
self.controller_class = controller_patcher.start()
@ -94,36 +89,3 @@ class ApiInitTestCase(test_base.BaseTestCase):
'KeyError', 'Unknown error occurred.')
do_check(exception.InvalidVpcIDNotFound('fake_msg'), 400,
'InvalidVpcID.NotFound', 'fake_msg')
def test_execute_proxy(self):
self.controller_class.return_value = mock.create_autospec(
cloud.CloudController, instance=True)
# NOTE(ft): recreate APIRequest to use mock with autospec
ec2_request = apirequest.APIRequest('FakeAction', 'fake_v1',
{'Param': 'fake_param'})
self.environ['ec2.request'] = ec2_request
self.environ['QUERY_STRING'] = 'Version=fake_v1&Action=FakeAction'
self.requester.request.return_value = ({'status': 200,
'content-type': 'fake_type'},
'fake_data')
res = self.request.send(self.application)
self.requester_class.assert_called_once_with('fake_v1', 'FAKE')
self.requester.request.assert_called_once_with(self.fake_context,
'FakeAction',
{'Param': 'fake_param'})
self.assertEqual(200, res.status_code)
self.assertEqual('fake_type', res.content_type)
self.assertEqual('fake_data', res.body)
def test_execute_proxy_error(self):
self.controller.fake_action.side_effect = exception.EC2ServerError(
{'status': 400, 'content-type': 'fake_type'},
'fake_content')
res = self.request.send(self.application)
self.assertEqual(400, res.status_code)
self.assertEqual('fake_type', res.content_type)
self.assertEqual('fake_content', res.body)

View File

@ -20,7 +20,6 @@ import mock
from oslotest import base as test_base
from ec2api.api import apirequest
from ec2api.api import ec2client
from ec2api.tests import fakes_request_response as fakes
from ec2api.tests import matchers
from ec2api.tests import tools
@ -35,9 +34,6 @@ class EC2RequesterTestCase(test_base.BaseTestCase):
def setUp(self):
super(EC2RequesterTestCase, self).setUp()
requester_patcher = mock.patch('ec2api.api.ec2client.EC2Requester')
self.requester = requester_patcher.start().return_value
self.addCleanup(requester_patcher.stop)
controller_patcher = mock.patch('ec2api.api.cloud.VpcCloudController')
self.controller = controller_patcher.start().return_value
@ -89,7 +85,7 @@ class EC2RequesterTestCase(test_base.BaseTestCase):
# based on the order of tags
xml = etree.fromstring(observed)
self.assertEqual(xmlns, xml.nsmap.get(None))
observed_data = ec2client.EC2Client._parse_xml(observed)
observed_data = tools.parse_xml(observed)
expected = {root_tag: tools.update_dict(dict_data,
{'requestId': request_id})}
self.assertThat(observed_data, matchers.DictMatches(expected))

View File

@ -1,172 +0,0 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
import collections
import time
import mock
from oslotest import base as test_base
from ec2api.api import ec2client
from ec2api import exception
from ec2api.tests import fakes_request_response as fakes
from ec2api.tests import matchers
class EC2RequesterTestCase(test_base.BaseTestCase):
fake_context_class = collections.namedtuple('FakeContext', ['access_key',
'secret_key'])
def setUp(self):
super(EC2RequesterTestCase, self).setUp()
httplib2_patcher = mock.patch('ec2api.api.ec2client.httplib2')
self.httplib2 = httplib2_patcher.start()
self.addCleanup(httplib2_patcher.stop)
gmtime_patcher = mock.patch('ec2api.api.ec2client.time.gmtime')
self.gmtime = gmtime_patcher.start()
self.addCleanup(gmtime_patcher.stop)
def test_post_request(self):
http_obj = self.httplib2.Http.return_value
http_obj.request.return_value = ('fake_response', 'fake_context',)
self.gmtime.return_value = time.struct_time((2014, 6, 13,
7, 43, 54, 4, 164, 0,))
requester = ec2client.EC2Requester('fake_v1', 'POST')
requester._ec2_url = 'http://fake.host.com:1234/fake_Service'
context = self.fake_context_class('caeafa52dda845d78a54786aa2ad355b',
'f889ec080e094a92badb6f6ba0253393')
result = requester.request(context, 'FakeAction',
{'Arg1': 'Val1', 'Arg2': 'Val2'})
http_obj.request.assert_called_once_with(
'http://fake.host.com:1234/fake_Service',
'POST',
body='AWSAccessKeyId=caeafa52dda845d78a54786aa2ad355b&'
'Action=FakeAction&Arg1=Val1&Arg2=Val2&Signature='
'uBRxsBHetogWlgv%2FHJnJLK0vBMEChm1LFX%2BH9U1kjHo%3D&'
'SignatureMethod=HmacSHA256&SignatureVersion=2&'
'Timestamp=2014-06-13T07%3A43%3A54Z&Version=fake_v1',
headers={'content-type': 'application/x-www-form-urlencoded',
'connection': 'close'})
self.assertEqual(('fake_response', 'fake_context',), result)
def test_get_request(self):
http_obj = self.httplib2.Http.return_value
http_obj.request.return_value = ('fake_response', 'fake_context',)
self.gmtime.return_value = time.struct_time((2014, 6, 14,
10, 6, 16, 5, 165, 0,))
requester = ec2client.EC2Requester('fake_v1', 'GET')
requester._ec2_url = 'http://fake.host.com'
context = self.fake_context_class('c1ba55bbcaeb4b41bc9a6d5344392825',
'24aaf70906fe4d799f6360d7cd6320ba')
result = requester.request(context, 'FakeAction',
{'Arg1': 'Val1', 'Arg2': 'Val2'})
http_obj.request.assert_called_once_with(
'http://fake.host.com?'
'AWSAccessKeyId=c1ba55bbcaeb4b41bc9a6d5344392825&'
'Action=FakeAction&Arg1=Val1&Arg2=Val2&Signature='
'puCc5v7kjOLibLTaT5bDp%2FPcgtbWMGt3kvh54z%2BpedE%3D&'
'SignatureMethod=HmacSHA256&SignatureVersion=2&'
'Timestamp=2014-06-14T10%3A06%3A16Z&Version=fake_v1',
'GET',
body=None,
headers={'content-type': 'application/x-www-form-urlencoded',
'connection': 'close'})
self.assertEqual(('fake_response', 'fake_context',), result)
class EC2ClientTestCase(test_base.BaseTestCase):
fake_response_class = collections.namedtuple('response', ['status'])
def test_ec2_xml_to_json_on_fake_result(self):
json = ec2client.EC2Client._parse_xml(fakes.XML_FAKE_RESULT)
self.assertIsInstance(json, dict)
self.assertThat(fakes.DICT_FAKE_RESULT, matchers.DictMatches(json))
def test_ec2_xml_to_json_on_single_result(self):
json = ec2client.EC2Client._parse_xml(fakes.XML_SINGLE_RESULT)
self.assertIsInstance(json, dict)
self.assertThat(fakes.DICT_SINGLE_RESULT, matchers.DictMatches(json))
def test_ec2_xml_to_json_on_result_set(self):
json = ec2client.EC2Client._parse_xml(fakes.XML_RESULT_SET)
self.assertIsInstance(json, dict)
self.assertThat(fakes.DICT_RESULT_SET, matchers.DictMatches(json))
def test_ec2_xml_to_json_on_empty_result_set(self):
json = ec2client.EC2Client._parse_xml(fakes.XML_EMPTY_RESULT_SET)
self.assertIsInstance(json, dict)
self.assertThat(fakes.DICT_EMPTY_RESULT_SET,
matchers.DictMatches(json))
def test_ec2_xml_to_json_on_error(self):
json = ec2client.EC2Client._parse_xml(fakes.XML_ERROR)
self.assertIsInstance(json, dict)
self.assertThat(fakes.DICT_ERROR, matchers.DictMatches(json))
def test_process_response_on_data_result(self):
response = self.fake_response_class(200)
json = ec2client.EC2Client._process_response(response,
fakes.XML_FAKE_RESULT)
self.assertThat(json,
matchers.DictMatches(fakes.DICT_FAKE_RESULT_DATA))
def test_process_response_on_ok_result(self):
response = self.fake_response_class(200)
result = ec2client.EC2Client._process_response(
response, fakes.XML_SILENT_OPERATIN_RESULT)
self.assertEqual(True, result)
def test_process_response_on_error(self):
response = self.fake_response_class(400)
try:
ec2client.EC2Client._process_response(response, fakes.XML_ERROR)
except exception.EC2ServerError as ex:
self.assertEqual(response, ex.response)
self.assertEqual(fakes.XML_ERROR, ex.content)
except Exception as ex:
self.fail('%s was raised instead of '
'ec2api.exception.EC2ServerError' % str(ex))
else:
self.fail('No ec2api.exception.EC2ServerError was raised')
def test_build_params(self):
ec2_params = ec2client.EC2Client._build_params(
**fakes.DICT_FAKE_PARAMS)
self.assertThat(ec2_params,
matchers.DictMatches(fakes.DOTTED_FAKE_PARAMS))
@mock.patch('ec2api.api.ec2client.EC2Requester')
def test_call_action(self, requester_class):
requester = requester_class.return_value
fake_response = self.fake_response_class(200)
requester.request.return_value = (fake_response,
fakes.XML_FAKE_RESULT,)
fake_context_class = collections.namedtuple('FakeContext',
['api_version'])
fake_context = fake_context_class('fake_v1')
ec2 = ec2client.ec2client(fake_context)
json = ec2.fake_action(fake_int=1234, fake_str='fake')
self.assertThat(json,
matchers.DictMatches(fakes.DICT_FAKE_RESULT_DATA))
requester_class.assert_called_once_with('fake_v1', 'POST')
requester.request.assert_called_once_with(
fake_context, 'FakeAction',
{'FakeInt': '1234', 'FakeStr': 'fake'})

View File

@ -52,10 +52,6 @@ class InstanceTestCase(base.ApiTestCase):
novadb_patcher = (mock.patch('ec2api.api.instance.novadb'))
self.novadb = novadb_patcher.start()
self.addCleanup(novadb_patcher.stop)
glance_id_to_ec2_id_patcher = (
mock.patch('ec2api.api.instance.ec2utils.glance_id_to_ec2_id'))
self.glance_id_to_ec2_id = glance_id_to_ec2_id_patcher.start()
self.addCleanup(glance_id_to_ec2_id_patcher.stop)
self.fake_image_class = collections.namedtuple(
'FakeImage', ['id', 'status', 'properties'])
@ -83,8 +79,6 @@ class InstanceTestCase(base.ApiTestCase):
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1)
# self.ec2_id_to_glance_id.return_value = 'fake_image_id'
# self.glance_id_to_ec2_id.return_value = None
fake_flavor = self.fake_flavor_class('fake_flavor')
self.nova_flavors.list.return_value = [fake_flavor]
self.nova_servers.create.return_value = (
@ -156,7 +150,6 @@ class InstanceTestCase(base.ApiTestCase):
self.create_network_interface.reset_mock()
self.nova_servers.reset_mock()
self.ec2.reset_mock()
self.db_api.reset_mock()
self.isotime.reset_mock()
@ -217,13 +210,6 @@ class InstanceTestCase(base.ApiTestCase):
self.create_network_interface.side_effect = (
[{'networkInterface': eni}
for eni in self.EC2_DETACHED_ENIS])
self.ec2.describe_instances.return_value = {
'reservationSet': [
fakes.gen_ec2_reservation(
fakes.ID_EC2_RESERVATION_1,
[fakes.gen_ec2_instance(ec2_instance_id,
private_ip_address=None)
for ec2_instance_id in self.IDS_EC2_INSTANCE])]}
self.nova_servers.create.side_effect = [
self.fake_instance_class(os_instance_id)
for os_instance_id in self.IDS_OS_INSTANCE]
@ -422,8 +408,6 @@ class InstanceTestCase(base.ApiTestCase):
fakes.ID_EC2_INSTANCE_2: fakes.ID_OS_INSTANCE_2}
self.ec2_inst_id_to_uuid.side_effect = (
lambda _, inst_id: os_instance_ids_dict[inst_id])
self.ec2.terminate_instances.return_value = (
ec2_terminate_instances_result)
def do_check(mock_port_list=[], mock_eni_list=[],
updated_ports=[], deleted_ports=[]):
@ -443,8 +427,6 @@ class InstanceTestCase(base.ApiTestCase):
self.ec2_inst_id_to_uuid.assert_any_call(
mock.ANY, inst_id)
self._assert_list_ports_is_called_with_filter(self.IDS_OS_INSTANCE)
self.ec2.terminate_instances.assert_called_once_with(
instance_id=self.IDS_EC2_INSTANCE)
self.assertEqual(len(updated_ports),
self.neutron.update_port.call_count)
self.assertEqual(len(updated_ports),
@ -469,7 +451,6 @@ class InstanceTestCase(base.ApiTestCase):
self.ec2_inst_id_to_uuid.reset_mock()
self.neutron.list_ports.reset_mock()
self.neutron.update_port.reset_mock()
self.ec2.terminate_instances.reset_mock()
self.db_api.delete_item.reset_mock()
self.db_api.update_item.reset_mock()
@ -566,9 +547,6 @@ class InstanceTestCase(base.ApiTestCase):
self.IDS_EC2_INSTANCE, ips_instance)]
reservation_set = gen_reservation_set([instances[0], instances[1]])
self.ec2.describe_instances.return_value = (
{'reservationSet': reservation_set,
'fakeKey': 'fakeValue'})
self.ec2_inst_id_to_uuid.side_effect = self.IDS_OS_INSTANCE
self.neutron.list_ports.return_value = {'ports': mock_port_list}
self.db_api.get_items.return_value = (
@ -591,14 +569,11 @@ class InstanceTestCase(base.ApiTestCase):
self.assertThat({'reservationSet': reservation_set,
'fakeKey': 'fakeValue'},
matchers.DictMatches(resp), verbose=True)
self.ec2.describe_instances.assert_called_once_with(
instance_id=None, filter=None)
for inst_id in self.IDS_EC2_INSTANCE:
self.ec2_inst_id_to_uuid.assert_any_call(
mock.ANY, inst_id)
self._assert_list_ports_is_called_with_filter(self.IDS_OS_INSTANCE)
self.ec2.describe_instances.reset_mock()
self.neutron.list_ports.reset_mock()
# NOTE(ft): 2 instances; the first has 2 correct ports;

View File

@ -14,9 +14,13 @@
import copy
import re
from lxml import etree
import mock
from ec2api.api import ec2utils
def update_dict(dict1, dict2):
"""Get a copy of union of two dicts."""
@ -51,3 +55,29 @@ class CopyingMock(mock.MagicMock):
args = copy.deepcopy(args)
kwargs = copy.deepcopy(kwargs)
return super(CopyingMock, self).__call__(*args, **kwargs)
_xml_scheme = re.compile('\sxmlns=".*"')
def parse_xml(xml_string):
xml_string = _xml_scheme.sub('', xml_string)
xml = etree.fromstring(xml_string)
def convert_node(node):
children = list(node)
if len(children):
if children[0].tag == 'item':
val = list(convert_node(child)[1] for child in children)
else:
val = dict(convert_node(child) for child in children)
elif node.tag.endswith('Set'):
val = []
else:
# TODO(ft): do not use private function
val = (ec2utils._try_convert(node.text)
if node.text
else node.text)
return node.tag, val
return dict([convert_node(xml)])

View File

@ -16,8 +16,8 @@
'''
find_unused_options.py
Compare the nova.conf file with the nova.conf.sample file to find any unused
options or default values in nova.conf
Compare the ec2api.conf file with the ec2api.conf.sample file to find any
unused options or default values in ec2api.conf
'''
from __future__ import print_function
@ -56,17 +56,17 @@ class PropertyCollecter(iniparser.BaseParser):
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''Compare the nova.conf
file with the nova.conf.sample file to find any unused options or
default values in nova.conf''')
parser = argparse.ArgumentParser(description='''Compare the ec2api.conf
file with the ec2api.conf.sample file to find any unused options or
default values in ec2api.conf''')
parser.add_argument('-c', action='store',
default='/etc/nova/nova.conf',
help='path to nova.conf\
(defaults to /etc/nova/nova.conf)')
parser.add_argument('-s', default='./etc/nova/nova.conf.sample',
help='path to nova.conf.sample\
(defaults to ./etc/nova/nova.conf.sample')
default='/etc/ec2api/ec2api.conf',
help='path to ec2api.conf\
(defaults to /etc/ec2api/ec2api.conf)')
parser.add_argument('-s', default='./etc/ec2api/ec2api.conf.sample',
help='path to ec2api.conf.sample\
(defaults to ./etc/ec2api/ec2api.conf.sample')
options = parser.parse_args()
conf_file_options = PropertyCollecter.collect_properties(open(options.c))