text: # vim: tabstop=4 shiftwidth=4 softtabstop=4
  
  # 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
  