435 lines
13 KiB
Python
435 lines
13 KiB
Python
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# 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. 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 functools
|
|
import re
|
|
|
|
from nova import availability_zones
|
|
from nova import context
|
|
from nova import db
|
|
from nova import exception
|
|
from nova.network import model as network_model
|
|
from nova.objects import instance as instance_obj
|
|
from nova.openstack.common.gettextutils import _
|
|
from nova.openstack.common import log as logging
|
|
from nova.openstack.common import memorycache
|
|
from nova.openstack.common import timeutils
|
|
from nova.openstack.common import uuidutils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
# NOTE(vish): cache mapping for one week
|
|
_CACHE_TIME = 7 * 24 * 60 * 60
|
|
_CACHE = None
|
|
|
|
|
|
def memoize(func):
|
|
@functools.wraps(func)
|
|
def memoizer(context, reqid):
|
|
global _CACHE
|
|
if not _CACHE:
|
|
_CACHE = memorycache.get_client()
|
|
key = "%s:%s" % (func.__name__, reqid)
|
|
key = str(key)
|
|
value = _CACHE.get(key)
|
|
if value is None:
|
|
value = func(context, reqid)
|
|
_CACHE.set(key, value, time=_CACHE_TIME)
|
|
return value
|
|
return memoizer
|
|
|
|
|
|
def reset_cache():
|
|
global _CACHE
|
|
_CACHE = None
|
|
|
|
|
|
def image_type(image_type):
|
|
"""Converts to a three letter image type.
|
|
|
|
aki, kernel => aki
|
|
ari, ramdisk => ari
|
|
anything else => ami
|
|
|
|
"""
|
|
if image_type == 'kernel':
|
|
return 'aki'
|
|
if image_type == 'ramdisk':
|
|
return 'ari'
|
|
if image_type not in ['aki', 'ari']:
|
|
return 'ami'
|
|
return image_type
|
|
|
|
|
|
def resource_type_from_id(context, resource_id):
|
|
"""Get resource type by ID
|
|
|
|
Returns a string representation of the Amazon resource type, if known.
|
|
Returns None on failure.
|
|
|
|
:param context: context under which the method is called
|
|
:param resource_id: resource_id to evaluate
|
|
"""
|
|
|
|
known_types = {
|
|
'i': 'instance',
|
|
'r': 'reservation',
|
|
'vol': 'volume',
|
|
'snap': 'snapshot',
|
|
'ami': 'image',
|
|
'aki': 'image',
|
|
'ari': 'image'
|
|
}
|
|
|
|
type_marker = resource_id.split('-')[0]
|
|
|
|
return known_types.get(type_marker)
|
|
|
|
|
|
@memoize
|
|
def id_to_glance_id(context, image_id):
|
|
"""Convert an internal (db) id to a glance id."""
|
|
return db.s3_image_get(context, image_id)['uuid']
|
|
|
|
|
|
@memoize
|
|
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 db.s3_image_get_by_uuid(context, glance_id)['id']
|
|
except exception.NotFound:
|
|
return db.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)
|
|
|
|
|
|
def ec2_id_to_id(ec2_id):
|
|
"""Convert an ec2 ID (i-[base 16 number]) to an instance id (int)."""
|
|
try:
|
|
return int(ec2_id.split('-')[-1], 16)
|
|
except ValueError:
|
|
raise exception.InvalidEc2Id(ec2_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 get_ip_info_for_instance_from_nw_info(nw_info):
|
|
if not isinstance(nw_info, network_model.NetworkInfo):
|
|
nw_info = network_model.NetworkInfo.hydrate(nw_info)
|
|
|
|
ip_info = {}
|
|
fixed_ips = nw_info.fixed_ips()
|
|
ip_info['fixed_ips'] = [ip['address'] for ip in fixed_ips
|
|
if ip['version'] == 4]
|
|
ip_info['fixed_ip6s'] = [ip['address'] for ip in fixed_ips
|
|
if ip['version'] == 6]
|
|
ip_info['floating_ips'] = [ip['address'] for ip in nw_info.floating_ips()]
|
|
|
|
return ip_info
|
|
|
|
|
|
def get_ip_info_for_instance(context, instance):
|
|
"""Return a dictionary of IP information for an instance."""
|
|
|
|
if isinstance(instance, instance_obj.Instance):
|
|
nw_info = instance.info_cache.network_info
|
|
else:
|
|
# FIXME(comstud): Temporary as we transition to objects.
|
|
info_cache = instance['info_cache'] or {}
|
|
nw_info = info_cache.get('network_info')
|
|
# Make sure empty response is turned into the model
|
|
if not nw_info:
|
|
nw_info = []
|
|
return get_ip_info_for_instance_from_nw_info(nw_info)
|
|
|
|
|
|
def get_availability_zone_by_host(host, conductor_api=None):
|
|
return availability_zones.get_host_availability_zone(
|
|
context.get_admin_context(), host, conductor_api)
|
|
|
|
|
|
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)
|
|
|
|
|
|
def id_to_ec2_inst_id(instance_id):
|
|
"""Get or create an ec2 instance ID (i-[base 16 number]) from uuid."""
|
|
if instance_id is None:
|
|
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)
|
|
return id_to_ec2_id(int_id)
|
|
else:
|
|
return id_to_ec2_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)
|
|
|
|
|
|
@memoize
|
|
def get_instance_uuid_from_int_id(context, int_id):
|
|
return db.get_instance_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 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)
|
|
|
|
|
|
_ms_time_regex = re.compile('^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,6}Z$')
|
|
|
|
|
|
def is_ec2_timestamp_expired(request, expires=None):
|
|
"""Checks the timestamp or expiry time included in an EC2 request
|
|
and returns true if the request is expired
|
|
"""
|
|
query_time = None
|
|
timestamp = request.get('Timestamp')
|
|
expiry_time = request.get('Expires')
|
|
|
|
def parse_strtime(strtime):
|
|
if _ms_time_regex.match(strtime):
|
|
# NOTE(MotoKen): time format for aws-sdk-java contains millisecond
|
|
time_format = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
else:
|
|
time_format = "%Y-%m-%dT%H:%M:%SZ"
|
|
return timeutils.parse_strtime(strtime, time_format)
|
|
|
|
try:
|
|
if timestamp and expiry_time:
|
|
msg = _("Request must include either Timestamp or Expires,"
|
|
" but cannot contain both")
|
|
LOG.error(msg)
|
|
raise exception.InvalidRequest(msg)
|
|
elif expiry_time:
|
|
query_time = parse_strtime(expiry_time)
|
|
return timeutils.is_older_than(query_time, -1)
|
|
elif timestamp:
|
|
query_time = parse_strtime(timestamp)
|
|
|
|
# Check if the difference between the timestamp in the request
|
|
# and the time on our servers is larger than 5 minutes, the
|
|
# request is too old (or too new).
|
|
if query_time and expires:
|
|
return timeutils.is_older_than(query_time, expires) or \
|
|
timeutils.is_newer_than(query_time, expires)
|
|
return False
|
|
except ValueError:
|
|
LOG.audit(_("Timestamp is invalid."))
|
|
return True
|
|
|
|
|
|
@memoize
|
|
def get_int_id_from_instance_uuid(context, instance_uuid):
|
|
if instance_uuid is None:
|
|
return
|
|
try:
|
|
return db.get_ec2_instance_id_by_uuid(context, instance_uuid)
|
|
except exception.NotFound:
|
|
return db.ec2_instance_create(context, instance_uuid)['id']
|
|
|
|
|
|
@memoize
|
|
def get_int_id_from_volume_uuid(context, volume_uuid):
|
|
if volume_uuid is None:
|
|
return
|
|
try:
|
|
return db.get_ec2_volume_id_by_uuid(context, volume_uuid)
|
|
except exception.NotFound:
|
|
return db.ec2_volume_create(context, volume_uuid)['id']
|
|
|
|
|
|
@memoize
|
|
def get_volume_uuid_from_int_id(context, int_id):
|
|
return db.get_volume_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)
|
|
|
|
|
|
@memoize
|
|
def get_int_id_from_snapshot_uuid(context, snapshot_uuid):
|
|
if snapshot_uuid is None:
|
|
return
|
|
try:
|
|
return db.get_ec2_snapshot_id_by_uuid(context, snapshot_uuid)
|
|
except exception.NotFound:
|
|
return db.ec2_snapshot_create(context, snapshot_uuid)['id']
|
|
|
|
|
|
@memoize
|
|
def get_snapshot_uuid_from_int_id(context, int_id):
|
|
return db.get_snapshot_uuid_by_ec2_id(context, int_id)
|
|
|
|
|
|
_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
|
|
|
|
|
|
def camelcase_to_underscore(str):
|
|
return _c2u.sub(r'_\1', str).lower().strip('_')
|
|
|
|
|
|
def _try_convert(value):
|
|
"""Return a non-string from a string or unicode, if possible.
|
|
|
|
============= =====================================================
|
|
When value is returns
|
|
============= =====================================================
|
|
zero-length ''
|
|
'None' None
|
|
'True' True case insensitive
|
|
'False' False case insensitive
|
|
'0', '-0' 0
|
|
0xN, -0xN int from hex (positive) (N is any number)
|
|
0bN, -0bN int from binary (positive) (N is any number)
|
|
* try conversion to int, float, complex, fallback value
|
|
|
|
"""
|
|
def _negative_zero(value):
|
|
epsilon = 1e-7
|
|
return 0 if abs(value) < epsilon else value
|
|
|
|
if len(value) == 0:
|
|
return ''
|
|
if value == 'None':
|
|
return None
|
|
lowered_value = value.lower()
|
|
if lowered_value == 'true':
|
|
return True
|
|
if lowered_value == 'false':
|
|
return False
|
|
for prefix, base in [('0x', 16), ('0b', 2), ('0', 8), ('', 10)]:
|
|
try:
|
|
if lowered_value.startswith((prefix, "-" + prefix)):
|
|
return int(lowered_value, base)
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
return _negative_zero(float(value))
|
|
except ValueError:
|
|
return value
|
|
|
|
|
|
def dict_from_dotted_str(items):
|
|
"""parse multi dot-separated argument into dict.
|
|
EBS boot uses multi dot-separated arguments like
|
|
BlockDeviceMapping.1.DeviceName=snap-id
|
|
Convert the above into
|
|
{'block_device_mapping': {'1': {'device_name': snap-id}}}
|
|
"""
|
|
args = {}
|
|
for key, value in items:
|
|
parts = key.split(".")
|
|
key = str(camelcase_to_underscore(parts[0]))
|
|
if isinstance(value, str) or isinstance(value, unicode):
|
|
# NOTE(vish): Automatically convert strings back
|
|
# into their respective values
|
|
value = _try_convert(value)
|
|
|
|
if len(parts) > 1:
|
|
d = args.get(key, {})
|
|
args[key] = d
|
|
for k in parts[1:-1]:
|
|
k = camelcase_to_underscore(k)
|
|
v = d.get(k, {})
|
|
d[k] = v
|
|
d = v
|
|
d[camelcase_to_underscore(parts[-1])] = value
|
|
else:
|
|
args[key] = value
|
|
|
|
return args
|
|
|
|
|
|
def search_opts_from_filters(filters):
|
|
return dict((f['name'].replace('-', '_'), f['value']['1'])
|
|
for f in filters if f['value']['1']) if filters else {}
|
|
|
|
|
|
def regex_from_ec2_regex(ec2_re):
|
|
"""Converts an EC2-style regex to a python regex.
|
|
Approach is based on python fnmatch.
|
|
"""
|
|
|
|
iter_ec2_re = iter(ec2_re)
|
|
|
|
py_re = ''
|
|
for char in iter_ec2_re:
|
|
if char == '*':
|
|
py_re += '.*'
|
|
elif char == '?':
|
|
py_re += '.'
|
|
elif char == '\\':
|
|
try:
|
|
next_char = iter_ec2_re.next()
|
|
except StopIteration:
|
|
next_char = ''
|
|
if next_char == '*' or next_char == '?':
|
|
py_re += '[%s]' % next_char
|
|
else:
|
|
py_re += '\\\\' + next_char
|
|
else:
|
|
py_re += re.escape(char)
|
|
return '\A%s\Z(?s)' % py_re
|