1136 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1136 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # vim: tabstop=4 shiftwidth=4 softtabstop=4
 | |
| 
 | |
| # Copyright 2010 United States Government as represented by the
 | |
| # Administrator of the National Aeronautics and Space Administration.
 | |
| # Copyright 2011 Justin Santa Barbara
 | |
| # 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.
 | |
| 
 | |
| """Utilities and helper functions."""
 | |
| 
 | |
| import contextlib
 | |
| import datetime
 | |
| import functools
 | |
| import inspect
 | |
| import os
 | |
| import pyclbr
 | |
| import random
 | |
| import re
 | |
| import shutil
 | |
| import socket
 | |
| import struct
 | |
| import sys
 | |
| import tempfile
 | |
| from xml.sax import saxutils
 | |
| 
 | |
| import eventlet
 | |
| import netaddr
 | |
| from oslo.config import cfg
 | |
| 
 | |
| from nova import exception
 | |
| from nova.openstack.common import excutils
 | |
| from nova.openstack.common import gettextutils
 | |
| from nova.openstack.common.gettextutils import _
 | |
| from nova.openstack.common import importutils
 | |
| from nova.openstack.common import lockutils
 | |
| from nova.openstack.common import log as logging
 | |
| from nova.openstack.common import processutils
 | |
| from nova.openstack.common.rpc import common as rpc_common
 | |
| from nova.openstack.common import timeutils
 | |
| 
 | |
| notify_decorator = 'nova.notifications.notify_decorator'
 | |
| 
 | |
| monkey_patch_opts = [
 | |
|     cfg.BoolOpt('monkey_patch',
 | |
|                 default=False,
 | |
|                 help='Whether to log monkey patching'),
 | |
|     cfg.ListOpt('monkey_patch_modules',
 | |
|                 default=[
 | |
|                   'nova.api.ec2.cloud:%s' % (notify_decorator),
 | |
|                   'nova.compute.api:%s' % (notify_decorator)
 | |
|                   ],
 | |
|                 help='List of modules/decorators to monkey patch'),
 | |
| ]
 | |
| utils_opts = [
 | |
|     cfg.IntOpt('password_length',
 | |
|                default=12,
 | |
|                help='Length of generated instance admin passwords'),
 | |
|     cfg.StrOpt('instance_usage_audit_period',
 | |
|                default='month',
 | |
|                help='time period to generate instance usages for.  '
 | |
|                     'Time period must be hour, day, month or year'),
 | |
|     cfg.StrOpt('rootwrap_config',
 | |
|                default="/etc/nova/rootwrap.conf",
 | |
|                help='Path to the rootwrap configuration file to use for '
 | |
|                     'running commands as root'),
 | |
|     cfg.StrOpt('tempdir',
 | |
|                help='Explicitly specify the temporary working directory'),
 | |
| ]
 | |
| CONF = cfg.CONF
 | |
| CONF.register_opts(monkey_patch_opts)
 | |
| CONF.register_opts(utils_opts)
 | |
| CONF.import_opt('network_api_class', 'nova.network')
 | |
| 
 | |
| LOG = logging.getLogger(__name__)
 | |
| 
 | |
| # Used for looking up extensions of text
 | |
| # to their 'multiplied' byte amount
 | |
| BYTE_MULTIPLIERS = {
 | |
|     '': 1,
 | |
|     't': 1024 ** 4,
 | |
|     'g': 1024 ** 3,
 | |
|     'm': 1024 ** 2,
 | |
|     'k': 1024,
 | |
| }
 | |
| 
 | |
| # used in limits
 | |
| TIME_UNITS = {
 | |
|     'SECOND': 1,
 | |
|     'MINUTE': 60,
 | |
|     'HOUR': 3600,
 | |
|     'DAY': 84400
 | |
| }
 | |
| 
 | |
| 
 | |
| _IS_NEUTRON_ATTEMPTED = False
 | |
| _IS_NEUTRON = False
 | |
| 
 | |
| synchronized = lockutils.synchronized_with_prefix('nova-')
 | |
| 
 | |
| SM_IMAGE_PROP_PREFIX = "image_"
 | |
| SM_INHERITABLE_KEYS = (
 | |
|     'min_ram', 'min_disk', 'disk_format', 'container_format',
 | |
| )
 | |
| 
 | |
| 
 | |
| def vpn_ping(address, port, timeout=0.05, session_id=None):
 | |
|     """Sends a vpn negotiation packet and returns the server session.
 | |
| 
 | |
|     Returns False on a failure. Basic packet structure is below.
 | |
| 
 | |
|     Client packet (14 bytes)::
 | |
| 
 | |
|          0 1      8 9  13
 | |
|         +-+--------+-----+
 | |
|         |x| cli_id |?????|
 | |
|         +-+--------+-----+
 | |
|         x = packet identifier 0x38
 | |
|         cli_id = 64 bit identifier
 | |
|         ? = unknown, probably flags/padding
 | |
| 
 | |
|     Server packet (26 bytes)::
 | |
| 
 | |
|          0 1      8 9  13 14    21 2225
 | |
|         +-+--------+-----+--------+----+
 | |
|         |x| srv_id |?????| cli_id |????|
 | |
|         +-+--------+-----+--------+----+
 | |
|         x = packet identifier 0x40
 | |
|         cli_id = 64 bit identifier
 | |
|         ? = unknown, probably flags/padding
 | |
|         bit 9 was 1 and the rest were 0 in testing
 | |
| 
 | |
|     """
 | |
|     if session_id is None:
 | |
|         session_id = random.randint(0, 0xffffffffffffffff)
 | |
|     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 | |
|     data = struct.pack('!BQxxxxx', 0x38, session_id)
 | |
|     sock.sendto(data, (address, port))
 | |
|     sock.settimeout(timeout)
 | |
|     try:
 | |
|         received = sock.recv(2048)
 | |
|     except socket.timeout:
 | |
|         return False
 | |
|     finally:
 | |
|         sock.close()
 | |
|     fmt = '!BQxxxxxQxxxx'
 | |
|     if len(received) != struct.calcsize(fmt):
 | |
|         LOG.warn(_('Expected to receive %(exp)s bytes, but actually %(act)s') %
 | |
|                  dict(exp=struct.calcsize(fmt), act=len(received)))
 | |
|         return False
 | |
|     (identifier, server_sess, client_sess) = struct.unpack(fmt, received)
 | |
|     if identifier == 0x40 and client_sess == session_id:
 | |
|         return server_sess
 | |
| 
 | |
| 
 | |
| def _get_root_helper():
 | |
|     return 'sudo nova-rootwrap %s' % CONF.rootwrap_config
 | |
| 
 | |
| 
 | |
| def execute(*cmd, **kwargs):
 | |
|     """Convenience wrapper around oslo's execute() method."""
 | |
|     if 'run_as_root' in kwargs and not 'root_helper' in kwargs:
 | |
|         kwargs['root_helper'] = _get_root_helper()
 | |
|     return processutils.execute(*cmd, **kwargs)
 | |
| 
 | |
| 
 | |
| def trycmd(*args, **kwargs):
 | |
|     """Convenience wrapper around oslo's trycmd() method."""
 | |
|     if 'run_as_root' in kwargs and not 'root_helper' in kwargs:
 | |
|         kwargs['root_helper'] = _get_root_helper()
 | |
|     return processutils.trycmd(*args, **kwargs)
 | |
| 
 | |
| 
 | |
| def novadir():
 | |
|     import nova
 | |
|     return os.path.abspath(nova.__file__).split('nova/__init__.py')[0]
 | |
| 
 | |
| 
 | |
| def generate_uid(topic, size=8):
 | |
|     characters = '01234567890abcdefghijklmnopqrstuvwxyz'
 | |
|     choices = [random.choice(characters) for _x in xrange(size)]
 | |
|     return '%s-%s' % (topic, ''.join(choices))
 | |
| 
 | |
| 
 | |
| # Default symbols to use for passwords. Avoids visually confusing characters.
 | |
| # ~6 bits per symbol
 | |
| DEFAULT_PASSWORD_SYMBOLS = ('23456789',  # Removed: 0,1
 | |
|                             'ABCDEFGHJKLMNPQRSTUVWXYZ',   # Removed: I, O
 | |
|                             'abcdefghijkmnopqrstuvwxyz')  # Removed: l
 | |
| 
 | |
| 
 | |
| # ~5 bits per symbol
 | |
| EASIER_PASSWORD_SYMBOLS = ('23456789',  # Removed: 0, 1
 | |
|                            'ABCDEFGHJKLMNPQRSTUVWXYZ')  # Removed: I, O
 | |
| 
 | |
| 
 | |
| def last_completed_audit_period(unit=None, before=None):
 | |
|     """This method gives you the most recently *completed* audit period.
 | |
| 
 | |
|     arguments:
 | |
|             units: string, one of 'hour', 'day', 'month', 'year'
 | |
|                     Periods normally begin at the beginning (UTC) of the
 | |
|                     period unit (So a 'day' period begins at midnight UTC,
 | |
|                     a 'month' unit on the 1st, a 'year' on Jan, 1)
 | |
|                     unit string may be appended with an optional offset
 | |
|                     like so:  'day@18'  This will begin the period at 18:00
 | |
|                     UTC.  'month@15' starts a monthly period on the 15th,
 | |
|                     and year@3 begins a yearly one on March 1st.
 | |
|             before: Give the audit period most recently completed before
 | |
|                     <timestamp>. Defaults to now.
 | |
| 
 | |
| 
 | |
|     returns:  2 tuple of datetimes (begin, end)
 | |
|               The begin timestamp of this audit period is the same as the
 | |
|               end of the previous.
 | |
|     """
 | |
|     if not unit:
 | |
|         unit = CONF.instance_usage_audit_period
 | |
| 
 | |
|     offset = 0
 | |
|     if '@' in unit:
 | |
|         unit, offset = unit.split("@", 1)
 | |
|         offset = int(offset)
 | |
| 
 | |
|     if before is not None:
 | |
|         rightnow = before
 | |
|     else:
 | |
|         rightnow = timeutils.utcnow()
 | |
|     if unit not in ('month', 'day', 'year', 'hour'):
 | |
|         raise ValueError('Time period must be hour, day, month or year')
 | |
|     if unit == 'month':
 | |
|         if offset == 0:
 | |
|             offset = 1
 | |
|         end = datetime.datetime(day=offset,
 | |
|                                 month=rightnow.month,
 | |
|                                 year=rightnow.year)
 | |
|         if end >= rightnow:
 | |
|             year = rightnow.year
 | |
|             if 1 >= rightnow.month:
 | |
|                 year -= 1
 | |
|                 month = 12 + (rightnow.month - 1)
 | |
|             else:
 | |
|                 month = rightnow.month - 1
 | |
|             end = datetime.datetime(day=offset,
 | |
|                                     month=month,
 | |
|                                     year=year)
 | |
|         year = end.year
 | |
|         if 1 >= end.month:
 | |
|             year -= 1
 | |
|             month = 12 + (end.month - 1)
 | |
|         else:
 | |
|             month = end.month - 1
 | |
|         begin = datetime.datetime(day=offset, month=month, year=year)
 | |
| 
 | |
|     elif unit == 'year':
 | |
|         if offset == 0:
 | |
|             offset = 1
 | |
|         end = datetime.datetime(day=1, month=offset, year=rightnow.year)
 | |
|         if end >= rightnow:
 | |
|             end = datetime.datetime(day=1,
 | |
|                                     month=offset,
 | |
|                                     year=rightnow.year - 1)
 | |
|             begin = datetime.datetime(day=1,
 | |
|                                       month=offset,
 | |
|                                       year=rightnow.year - 2)
 | |
|         else:
 | |
|             begin = datetime.datetime(day=1,
 | |
|                                       month=offset,
 | |
|                                       year=rightnow.year - 1)
 | |
| 
 | |
|     elif unit == 'day':
 | |
|         end = datetime.datetime(hour=offset,
 | |
|                                day=rightnow.day,
 | |
|                                month=rightnow.month,
 | |
|                                year=rightnow.year)
 | |
|         if end >= rightnow:
 | |
|             end = end - datetime.timedelta(days=1)
 | |
|         begin = end - datetime.timedelta(days=1)
 | |
| 
 | |
|     elif unit == 'hour':
 | |
|         end = rightnow.replace(minute=offset, second=0, microsecond=0)
 | |
|         if end >= rightnow:
 | |
|             end = end - datetime.timedelta(hours=1)
 | |
|         begin = end - datetime.timedelta(hours=1)
 | |
| 
 | |
|     return (begin, end)
 | |
| 
 | |
| 
 | |
| def generate_password(length=None, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
 | |
|     """Generate a random password from the supplied symbol groups.
 | |
| 
 | |
|     At least one symbol from each group will be included. Unpredictable
 | |
|     results if length is less than the number of symbol groups.
 | |
| 
 | |
|     Believed to be reasonably secure (with a reasonable password length!)
 | |
| 
 | |
|     """
 | |
|     if length is None:
 | |
|         length = CONF.password_length
 | |
| 
 | |
|     r = random.SystemRandom()
 | |
| 
 | |
|     # NOTE(jerdfelt): Some password policies require at least one character
 | |
|     # from each group of symbols, so start off with one random character
 | |
|     # from each symbol group
 | |
|     password = [r.choice(s) for s in symbolgroups]
 | |
|     # If length < len(symbolgroups), the leading characters will only
 | |
|     # be from the first length groups. Try our best to not be predictable
 | |
|     # by shuffling and then truncating.
 | |
|     r.shuffle(password)
 | |
|     password = password[:length]
 | |
|     length -= len(password)
 | |
| 
 | |
|     # then fill with random characters from all symbol groups
 | |
|     symbols = ''.join(symbolgroups)
 | |
|     password.extend([r.choice(symbols) for _i in xrange(length)])
 | |
| 
 | |
|     # finally shuffle to ensure first x characters aren't from a
 | |
|     # predictable group
 | |
|     r.shuffle(password)
 | |
| 
 | |
|     return ''.join(password)
 | |
| 
 | |
| 
 | |
| def get_my_ipv4_address():
 | |
|     """Run ip route/addr commands to figure out the best ipv4
 | |
|     """
 | |
|     LOCALHOST = '127.0.0.1'
 | |
|     try:
 | |
|         out = execute('ip', '-f', 'inet', '-o', 'route', 'show')
 | |
| 
 | |
|         # Find the default route
 | |
|         regex_default = ('default\s*via\s*'
 | |
|                          '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
 | |
|                          '\s*dev\s*(\w*)\s*')
 | |
|         default_routes = re.findall(regex_default, out[0])
 | |
|         if not default_routes:
 | |
|             return LOCALHOST
 | |
|         gateway, iface = default_routes[0]
 | |
| 
 | |
|         # Find the right subnet for the gateway/interface for
 | |
|         # the default route
 | |
|         route = ('(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})'
 | |
|               '\s*dev\s*(\w*)\s*')
 | |
|         for match in re.finditer(route, out[0]):
 | |
|             subnet = netaddr.IPNetwork(match.group(1) + "/" + match.group(2))
 | |
|             if (match.group(3) == iface and
 | |
|                     netaddr.IPAddress(gateway) in subnet):
 | |
|                 try:
 | |
|                     return _get_ipv4_address_for_interface(iface)
 | |
|                 except exception.NovaException:
 | |
|                     pass
 | |
|     except Exception as ex:
 | |
|         LOG.error(_("Couldn't get IPv4 : %(ex)s") % {'ex': ex})
 | |
|     return LOCALHOST
 | |
| 
 | |
| 
 | |
| def _get_ipv4_address_for_interface(iface):
 | |
|     """Run ip addr show for an interface and grab its ipv4 addresses
 | |
|     """
 | |
|     try:
 | |
|         out = execute('ip', '-f', 'inet', '-o', 'addr', 'show', iface)
 | |
|         regexp_address = re.compile('inet\s*'
 | |
|                                     '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
 | |
|         address = [m.group(1) for m in regexp_address.finditer(out[0])
 | |
|                    if m.group(1) != '127.0.0.1']
 | |
|         if address:
 | |
|             return address[0]
 | |
|         else:
 | |
|             msg = _('IPv4 address is not found.: %s') % out[0]
 | |
|             raise exception.NovaException(msg)
 | |
|     except Exception as ex:
 | |
|         msg = _("Couldn't get IPv4 of %(interface)s"
 | |
|                 " : %(ex)s") % {'interface': iface, 'ex': ex}
 | |
|         LOG.error(msg)
 | |
|         raise exception.NovaException(msg)
 | |
| 
 | |
| 
 | |
| def get_my_linklocal(interface):
 | |
|     try:
 | |
|         if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
 | |
|         condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link'
 | |
|         links = [re.search(condition, x) for x in if_str[0].split('\n')]
 | |
|         address = [w.group(1) for w in links if w is not None]
 | |
|         if address[0] is not None:
 | |
|             return address[0]
 | |
|         else:
 | |
|             msg = _('Link Local address is not found.:%s') % if_str
 | |
|             raise exception.NovaException(msg)
 | |
|     except Exception as ex:
 | |
|         msg = _("Couldn't get Link Local IP of %(interface)s"
 | |
|                 " :%(ex)s") % {'interface': interface, 'ex': ex}
 | |
|         raise exception.NovaException(msg)
 | |
| 
 | |
| 
 | |
| class LazyPluggable(object):
 | |
|     """A pluggable backend loaded lazily based on some value."""
 | |
| 
 | |
|     def __init__(self, pivot, config_group=None, **backends):
 | |
|         self.__backends = backends
 | |
|         self.__pivot = pivot
 | |
|         self.__backend = None
 | |
|         self.__config_group = config_group
 | |
| 
 | |
|     def __get_backend(self):
 | |
|         if not self.__backend:
 | |
|             if self.__config_group is None:
 | |
|                 backend_name = CONF[self.__pivot]
 | |
|             else:
 | |
|                 backend_name = CONF[self.__config_group][self.__pivot]
 | |
|             if backend_name not in self.__backends:
 | |
|                 msg = _('Invalid backend: %s') % backend_name
 | |
|                 raise exception.NovaException(msg)
 | |
| 
 | |
|             backend = self.__backends[backend_name]
 | |
|             if isinstance(backend, tuple):
 | |
|                 name = backend[0]
 | |
|                 fromlist = backend[1]
 | |
|             else:
 | |
|                 name = backend
 | |
|                 fromlist = backend
 | |
| 
 | |
|             self.__backend = __import__(name, None, None, fromlist)
 | |
|         return self.__backend
 | |
| 
 | |
|     def __getattr__(self, key):
 | |
|         backend = self.__get_backend()
 | |
|         return getattr(backend, key)
 | |
| 
 | |
| 
 | |
| def xhtml_escape(value):
 | |
|     """Escapes a string so it is valid within XML or XHTML.
 | |
| 
 | |
|     """
 | |
|     return saxutils.escape(value, {'"': '"', "'": '''})
 | |
| 
 | |
| 
 | |
| def utf8(value):
 | |
|     """Try to turn a string into utf-8 if possible.
 | |
| 
 | |
|     Code is directly from the utf8 function in
 | |
|     http://github.com/facebook/tornado/blob/master/tornado/escape.py
 | |
| 
 | |
|     """
 | |
|     if isinstance(value, unicode):
 | |
|         return value.encode('utf-8')
 | |
|     elif isinstance(value, gettextutils.Message):
 | |
|         return unicode(value).encode('utf-8')
 | |
|     assert isinstance(value, str)
 | |
|     return value
 | |
| 
 | |
| 
 | |
| def diff_dict(orig, new):
 | |
|     """
 | |
|     Return a dict describing how to change orig to new.  The keys
 | |
|     correspond to values that have changed; the value will be a list
 | |
|     of one or two elements.  The first element of the list will be
 | |
|     either '+' or '-', indicating whether the key was updated or
 | |
|     deleted; if the key was updated, the list will contain a second
 | |
|     element, giving the updated value.
 | |
|     """
 | |
|     # Figure out what keys went away
 | |
|     result = dict((k, ['-']) for k in set(orig.keys()) - set(new.keys()))
 | |
|     # Compute the updates
 | |
|     for key, value in new.items():
 | |
|         if key not in orig or value != orig[key]:
 | |
|             result[key] = ['+', value]
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def check_isinstance(obj, cls):
 | |
|     """Checks that obj is of type cls, and lets PyLint infer types."""
 | |
|     if isinstance(obj, cls):
 | |
|         return obj
 | |
|     raise Exception(_('Expected object of type: %s') % (str(cls)))
 | |
| 
 | |
| 
 | |
| def parse_server_string(server_str):
 | |
|     """
 | |
|     Parses the given server_string and returns a list of host and port.
 | |
|     If it's not a combination of host part and port, the port element
 | |
|     is a null string. If the input is invalid expression, return a null
 | |
|     list.
 | |
|     """
 | |
|     try:
 | |
|         # First of all, exclude pure IPv6 address (w/o port).
 | |
|         if netaddr.valid_ipv6(server_str):
 | |
|             return (server_str, '')
 | |
| 
 | |
|         # Next, check if this is IPv6 address with a port number combination.
 | |
|         if server_str.find("]:") != -1:
 | |
|             (address, port) = server_str.replace('[', '', 1).split(']:')
 | |
|             return (address, port)
 | |
| 
 | |
|         # Third, check if this is a combination of an address and a port
 | |
|         if server_str.find(':') == -1:
 | |
|             return (server_str, '')
 | |
| 
 | |
|         # This must be a combination of an address and a port
 | |
|         (address, port) = server_str.split(':')
 | |
|         return (address, port)
 | |
| 
 | |
|     except Exception:
 | |
|         LOG.error(_('Invalid server_string: %s'), server_str)
 | |
|         return ('', '')
 | |
| 
 | |
| 
 | |
| def is_int_like(val):
 | |
|     """Check if a value looks like an int."""
 | |
|     try:
 | |
|         return str(int(val)) == str(val)
 | |
|     except Exception:
 | |
|         return False
 | |
| 
 | |
| 
 | |
| def is_valid_ipv4(address):
 | |
|     """Verify that address represents a valid IPv4 address."""
 | |
|     try:
 | |
|         return netaddr.valid_ipv4(address)
 | |
|     except Exception:
 | |
|         return False
 | |
| 
 | |
| 
 | |
| def is_valid_ipv6(address):
 | |
|     try:
 | |
|         return netaddr.valid_ipv6(address)
 | |
|     except Exception:
 | |
|         return False
 | |
| 
 | |
| 
 | |
| def is_valid_ipv6_cidr(address):
 | |
|     try:
 | |
|         str(netaddr.IPNetwork(address, version=6).cidr)
 | |
|         return True
 | |
|     except Exception:
 | |
|         return False
 | |
| 
 | |
| 
 | |
| def get_shortened_ipv6(address):
 | |
|     addr = netaddr.IPAddress(address, version=6)
 | |
|     return str(addr.ipv6())
 | |
| 
 | |
| 
 | |
| def get_shortened_ipv6_cidr(address):
 | |
|     net = netaddr.IPNetwork(address, version=6)
 | |
|     return str(net.cidr)
 | |
| 
 | |
| 
 | |
| def is_valid_cidr(address):
 | |
|     """Check if address is valid
 | |
| 
 | |
|     The provided address can be a IPv6 or a IPv4
 | |
|     CIDR address.
 | |
|     """
 | |
|     try:
 | |
|         # Validate the correct CIDR Address
 | |
|         netaddr.IPNetwork(address)
 | |
|     except netaddr.core.AddrFormatError:
 | |
|         return False
 | |
|     except UnboundLocalError:
 | |
|         # NOTE(MotoKen): work around bug in netaddr 0.7.5 (see detail in
 | |
|         # https://github.com/drkjam/netaddr/issues/2)
 | |
|         return False
 | |
| 
 | |
|     # Prior validation partially verify /xx part
 | |
|     # Verify it here
 | |
|     ip_segment = address.split('/')
 | |
| 
 | |
|     if (len(ip_segment) <= 1 or
 | |
|             ip_segment[1] == ''):
 | |
|         return False
 | |
| 
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def get_ip_version(network):
 | |
|     """Returns the IP version of a network (IPv4 or IPv6).
 | |
| 
 | |
|     Raises AddrFormatError if invalid network.
 | |
|     """
 | |
|     if netaddr.IPNetwork(network).version == 6:
 | |
|         return "IPv6"
 | |
|     elif netaddr.IPNetwork(network).version == 4:
 | |
|         return "IPv4"
 | |
| 
 | |
| 
 | |
| def monkey_patch():
 | |
|     """If the Flags.monkey_patch set as True,
 | |
|     this function patches a decorator
 | |
|     for all functions in specified modules.
 | |
|     You can set decorators for each modules
 | |
|     using CONF.monkey_patch_modules.
 | |
|     The format is "Module path:Decorator function".
 | |
|     Example:
 | |
|       'nova.api.ec2.cloud:nova.notifications.notify_decorator'
 | |
| 
 | |
|     Parameters of the decorator is as follows.
 | |
|     (See nova.notifications.notify_decorator)
 | |
| 
 | |
|     name - name of the function
 | |
|     function - object of the function
 | |
|     """
 | |
|     # If CONF.monkey_patch is not True, this function do nothing.
 | |
|     if not CONF.monkey_patch:
 | |
|         return
 | |
|     # Get list of modules and decorators
 | |
|     for module_and_decorator in CONF.monkey_patch_modules:
 | |
|         module, decorator_name = module_and_decorator.split(':')
 | |
|         # import decorator function
 | |
|         decorator = importutils.import_class(decorator_name)
 | |
|         __import__(module)
 | |
|         # Retrieve module information using pyclbr
 | |
|         module_data = pyclbr.readmodule_ex(module)
 | |
|         for key in module_data.keys():
 | |
|             # set the decorator for the class methods
 | |
|             if isinstance(module_data[key], pyclbr.Class):
 | |
|                 clz = importutils.import_class("%s.%s" % (module, key))
 | |
|                 for method, func in inspect.getmembers(clz, inspect.ismethod):
 | |
|                     setattr(clz, method,
 | |
|                         decorator("%s.%s.%s" % (module, key, method), func))
 | |
|             # set the decorator for the function
 | |
|             if isinstance(module_data[key], pyclbr.Function):
 | |
|                 func = importutils.import_class("%s.%s" % (module, key))
 | |
|                 setattr(sys.modules[module], key,
 | |
|                     decorator("%s.%s" % (module, key), func))
 | |
| 
 | |
| 
 | |
| def convert_to_list_dict(lst, label):
 | |
|     """Convert a value or list into a list of dicts."""
 | |
|     if not lst:
 | |
|         return None
 | |
|     if not isinstance(lst, list):
 | |
|         lst = [lst]
 | |
|     return [{label: x} for x in lst]
 | |
| 
 | |
| 
 | |
| def make_dev_path(dev, partition=None, base='/dev'):
 | |
|     """Return a path to a particular device.
 | |
| 
 | |
|     >>> make_dev_path('xvdc')
 | |
|     /dev/xvdc
 | |
| 
 | |
|     >>> make_dev_path('xvdc', 1)
 | |
|     /dev/xvdc1
 | |
|     """
 | |
|     path = os.path.join(base, dev)
 | |
|     if partition:
 | |
|         path += str(partition)
 | |
|     return path
 | |
| 
 | |
| 
 | |
| def sanitize_hostname(hostname):
 | |
|     """Return a hostname which conforms to RFC-952 and RFC-1123 specs."""
 | |
|     if isinstance(hostname, unicode):
 | |
|         hostname = hostname.encode('latin-1', 'ignore')
 | |
| 
 | |
|     hostname = re.sub('[ _]', '-', hostname)
 | |
|     hostname = re.sub('[^\w.-]+', '', hostname)
 | |
|     hostname = hostname.lower()
 | |
|     hostname = hostname.strip('.-')
 | |
| 
 | |
|     return hostname
 | |
| 
 | |
| 
 | |
| def read_cached_file(filename, cache_info, reload_func=None):
 | |
|     """Read from a file if it has been modified.
 | |
| 
 | |
|     :param cache_info: dictionary to hold opaque cache.
 | |
|     :param reload_func: optional function to be called with data when
 | |
|                         file is reloaded due to a modification.
 | |
| 
 | |
|     :returns: data from file
 | |
| 
 | |
|     """
 | |
|     mtime = os.path.getmtime(filename)
 | |
|     if not cache_info or mtime != cache_info.get('mtime'):
 | |
|         LOG.debug(_("Reloading cached file %s") % filename)
 | |
|         with open(filename) as fap:
 | |
|             cache_info['data'] = fap.read()
 | |
|         cache_info['mtime'] = mtime
 | |
|         if reload_func:
 | |
|             reload_func(cache_info['data'])
 | |
|     return cache_info['data']
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def temporary_mutation(obj, **kwargs):
 | |
|     """Temporarily set the attr on a particular object to a given value then
 | |
|     revert when finished.
 | |
| 
 | |
|     One use of this is to temporarily set the read_deleted flag on a context
 | |
|     object:
 | |
| 
 | |
|         with temporary_mutation(context, read_deleted="yes"):
 | |
|             do_something_that_needed_deleted_objects()
 | |
|     """
 | |
|     def is_dict_like(thing):
 | |
|         return hasattr(thing, 'has_key')
 | |
| 
 | |
|     def get(thing, attr, default):
 | |
|         if is_dict_like(thing):
 | |
|             return thing.get(attr, default)
 | |
|         else:
 | |
|             return getattr(thing, attr, default)
 | |
| 
 | |
|     def set_value(thing, attr, val):
 | |
|         if is_dict_like(thing):
 | |
|             thing[attr] = val
 | |
|         else:
 | |
|             setattr(thing, attr, val)
 | |
| 
 | |
|     def delete(thing, attr):
 | |
|         if is_dict_like(thing):
 | |
|             del thing[attr]
 | |
|         else:
 | |
|             delattr(thing, attr)
 | |
| 
 | |
|     NOT_PRESENT = object()
 | |
| 
 | |
|     old_values = {}
 | |
|     for attr, new_value in kwargs.items():
 | |
|         old_values[attr] = get(obj, attr, NOT_PRESENT)
 | |
|         set_value(obj, attr, new_value)
 | |
| 
 | |
|     try:
 | |
|         yield
 | |
|     finally:
 | |
|         for attr, old_value in old_values.items():
 | |
|             if old_value is NOT_PRESENT:
 | |
|                 delete(obj, attr)
 | |
|             else:
 | |
|                 set_value(obj, attr, old_value)
 | |
| 
 | |
| 
 | |
| def generate_mac_address():
 | |
|     """Generate an Ethernet MAC address."""
 | |
|     # NOTE(vish): We would prefer to use 0xfe here to ensure that linux
 | |
|     #             bridge mac addresses don't change, but it appears to
 | |
|     #             conflict with libvirt, so we use the next highest octet
 | |
|     #             that has the unicast and locally administered bits set
 | |
|     #             properly: 0xfa.
 | |
|     #             Discussion: https://bugs.launchpad.net/nova/+bug/921838
 | |
|     mac = [0xfa, 0x16, 0x3e,
 | |
|            random.randint(0x00, 0xff),
 | |
|            random.randint(0x00, 0xff),
 | |
|            random.randint(0x00, 0xff)]
 | |
|     return ':'.join(map(lambda x: "%02x" % x, mac))
 | |
| 
 | |
| 
 | |
| def read_file_as_root(file_path):
 | |
|     """Secure helper to read file as root."""
 | |
|     try:
 | |
|         out, _err = execute('cat', file_path, run_as_root=True)
 | |
|         return out
 | |
|     except processutils.ProcessExecutionError:
 | |
|         raise exception.FileNotFound(file_path=file_path)
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def temporary_chown(path, owner_uid=None):
 | |
|     """Temporarily chown a path.
 | |
| 
 | |
|     :params owner_uid: UID of temporary owner (defaults to current user)
 | |
|     """
 | |
|     if owner_uid is None:
 | |
|         owner_uid = os.getuid()
 | |
| 
 | |
|     orig_uid = os.stat(path).st_uid
 | |
| 
 | |
|     if orig_uid != owner_uid:
 | |
|         execute('chown', owner_uid, path, run_as_root=True)
 | |
|     try:
 | |
|         yield
 | |
|     finally:
 | |
|         if orig_uid != owner_uid:
 | |
|             execute('chown', orig_uid, path, run_as_root=True)
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def tempdir(**kwargs):
 | |
|     argdict = kwargs.copy()
 | |
|     if 'dir' not in argdict:
 | |
|         argdict['dir'] = CONF.tempdir
 | |
|     tmpdir = tempfile.mkdtemp(**argdict)
 | |
|     try:
 | |
|         yield tmpdir
 | |
|     finally:
 | |
|         try:
 | |
|             shutil.rmtree(tmpdir)
 | |
|         except OSError as e:
 | |
|             LOG.error(_('Could not remove tmpdir: %s'), str(e))
 | |
| 
 | |
| 
 | |
| def walk_class_hierarchy(clazz, encountered=None):
 | |
|     """Walk class hierarchy, yielding most derived classes first."""
 | |
|     if not encountered:
 | |
|         encountered = []
 | |
|     for subclass in clazz.__subclasses__():
 | |
|         if subclass not in encountered:
 | |
|             encountered.append(subclass)
 | |
|             # drill down to leaves first
 | |
|             for subsubclass in walk_class_hierarchy(subclass, encountered):
 | |
|                 yield subsubclass
 | |
|             yield subclass
 | |
| 
 | |
| 
 | |
| class UndoManager(object):
 | |
|     """Provides a mechanism to facilitate rolling back a series of actions
 | |
|     when an exception is raised.
 | |
|     """
 | |
|     def __init__(self):
 | |
|         self.undo_stack = []
 | |
| 
 | |
|     def undo_with(self, undo_func):
 | |
|         self.undo_stack.append(undo_func)
 | |
| 
 | |
|     def _rollback(self):
 | |
|         for undo_func in reversed(self.undo_stack):
 | |
|             undo_func()
 | |
| 
 | |
|     def rollback_and_reraise(self, msg=None, **kwargs):
 | |
|         """Rollback a series of actions then re-raise the exception.
 | |
| 
 | |
|         .. note:: (sirp) This should only be called within an
 | |
|                   exception handler.
 | |
|         """
 | |
|         with excutils.save_and_reraise_exception():
 | |
|             if msg:
 | |
|                 LOG.exception(msg, **kwargs)
 | |
| 
 | |
|             self._rollback()
 | |
| 
 | |
| 
 | |
| def mkfs(fs, path, label=None, run_as_root=False):
 | |
|     """Format a file or block device
 | |
| 
 | |
|     :param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4'
 | |
|                'btrfs', etc.)
 | |
|     :param path: Path to file or block device to format
 | |
|     :param label: Volume label to use
 | |
|     """
 | |
|     if fs == 'swap':
 | |
|         args = ['mkswap']
 | |
|     else:
 | |
|         args = ['mkfs', '-t', fs]
 | |
|     #add -F to force no interactive execute on non-block device.
 | |
|     if fs in ('ext3', 'ext4', 'ntfs'):
 | |
|         args.extend(['-F'])
 | |
|     if label:
 | |
|         if fs in ('msdos', 'vfat'):
 | |
|             label_opt = '-n'
 | |
|         else:
 | |
|             label_opt = '-L'
 | |
|         args.extend([label_opt, label])
 | |
|     args.append(path)
 | |
|     execute(*args, run_as_root=run_as_root)
 | |
| 
 | |
| 
 | |
| def last_bytes(file_like_object, num):
 | |
|     """Return num bytes from the end of the file, and remaining byte count.
 | |
| 
 | |
|     :param file_like_object: The file to read
 | |
|     :param num: The number of bytes to return
 | |
| 
 | |
|     :returns (data, remaining)
 | |
|     """
 | |
| 
 | |
|     try:
 | |
|         file_like_object.seek(-num, os.SEEK_END)
 | |
|     except IOError as e:
 | |
|         if e.errno == 22:
 | |
|             file_like_object.seek(0, os.SEEK_SET)
 | |
|         else:
 | |
|             raise
 | |
| 
 | |
|     remaining = file_like_object.tell()
 | |
|     return (file_like_object.read(), remaining)
 | |
| 
 | |
| 
 | |
| def metadata_to_dict(metadata):
 | |
|     result = {}
 | |
|     for item in metadata:
 | |
|         if not item.get('deleted'):
 | |
|             result[item['key']] = item['value']
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def dict_to_metadata(metadata):
 | |
|     result = []
 | |
|     for key, value in metadata.iteritems():
 | |
|         result.append(dict(key=key, value=value))
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def instance_meta(instance):
 | |
|     if isinstance(instance['metadata'], dict):
 | |
|         return instance['metadata']
 | |
|     else:
 | |
|         return metadata_to_dict(instance['metadata'])
 | |
| 
 | |
| 
 | |
| def instance_sys_meta(instance):
 | |
|     if not instance.get('system_metadata'):
 | |
|         return {}
 | |
|     if isinstance(instance['system_metadata'], dict):
 | |
|         return instance['system_metadata']
 | |
|     else:
 | |
|         return metadata_to_dict(instance['system_metadata'])
 | |
| 
 | |
| 
 | |
| def get_wrapped_function(function):
 | |
|     """Get the method at the bottom of a stack of decorators."""
 | |
|     if not hasattr(function, 'func_closure') or not function.func_closure:
 | |
|         return function
 | |
| 
 | |
|     def _get_wrapped_function(function):
 | |
|         if not hasattr(function, 'func_closure') or not function.func_closure:
 | |
|             return None
 | |
| 
 | |
|         for closure in function.func_closure:
 | |
|             func = closure.cell_contents
 | |
| 
 | |
|             deeper_func = _get_wrapped_function(func)
 | |
|             if deeper_func:
 | |
|                 return deeper_func
 | |
|             elif hasattr(closure.cell_contents, '__call__'):
 | |
|                 return closure.cell_contents
 | |
| 
 | |
|     return _get_wrapped_function(function)
 | |
| 
 | |
| 
 | |
| class ExceptionHelper(object):
 | |
|     """Class to wrap another and translate the ClientExceptions raised by its
 | |
|     function calls to the actual ones.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, target):
 | |
|         self._target = target
 | |
| 
 | |
|     def __getattr__(self, name):
 | |
|         func = getattr(self._target, name)
 | |
| 
 | |
|         @functools.wraps(func)
 | |
|         def wrapper(*args, **kwargs):
 | |
|             try:
 | |
|                 return func(*args, **kwargs)
 | |
|             except rpc_common.ClientException as e:
 | |
|                 raise (e._exc_info[1], None, e._exc_info[2])
 | |
|         return wrapper
 | |
| 
 | |
| 
 | |
| def check_string_length(value, name, min_length=0, max_length=None):
 | |
|     """Check the length of specified string
 | |
|     :param value: the value of the string
 | |
|     :param name: the name of the string
 | |
|     :param min_length: the min_length of the string
 | |
|     :param max_length: the max_length of the string
 | |
|     """
 | |
|     if not isinstance(value, basestring):
 | |
|         msg = _("%s is not a string or unicode") % name
 | |
|         raise exception.InvalidInput(message=msg)
 | |
| 
 | |
|     if len(value) < min_length:
 | |
|         msg = _("%(name)s has a minimum character requirement of "
 | |
|                 "%(min_length)s.") % {'name': name, 'min_length': min_length}
 | |
|         raise exception.InvalidInput(message=msg)
 | |
| 
 | |
|     if max_length and len(value) > max_length:
 | |
|         msg = _("%(name)s has more than %(max_length)s "
 | |
|                 "characters.") % {'name': name, 'max_length': max_length}
 | |
|         raise exception.InvalidInput(message=msg)
 | |
| 
 | |
| 
 | |
| def validate_integer(value, name, min_value=None, max_value=None):
 | |
|     """Make sure that value is a valid integer, potentially within range."""
 | |
|     try:
 | |
|         value = int(str(value))
 | |
|     except ValueError:
 | |
|         msg = _('%(value_name)s must be an integer')
 | |
|         raise exception.InvalidInput(reason=(
 | |
|             msg % {'value_name': name}))
 | |
| 
 | |
|     if min_value is not None:
 | |
|         if value < min_value:
 | |
|             msg = _('%(value_name)s must be >= %(min_value)d')
 | |
|             raise exception.InvalidInput(
 | |
|                 reason=(msg % {'value_name': name,
 | |
|                                'min_value': min_value}))
 | |
|     if max_value is not None:
 | |
|         if value > max_value:
 | |
|             msg = _('%(value_name)s must be <= %(max_value)d')
 | |
|             raise exception.InvalidInput(
 | |
|                 reason=(
 | |
|                     msg % {'value_name': name,
 | |
|                            'max_value': max_value})
 | |
|             )
 | |
|     return value
 | |
| 
 | |
| 
 | |
| def spawn_n(func, *args, **kwargs):
 | |
|     """Passthrough method for eventlet.spawn_n.
 | |
| 
 | |
|     This utility exists so that it can be stubbed for testing without
 | |
|     interfering with the service spawns.
 | |
|     """
 | |
|     eventlet.spawn_n(func, *args, **kwargs)
 | |
| 
 | |
| 
 | |
| def is_none_string(val):
 | |
|     """
 | |
|     Check if a string represents a None value.
 | |
|     """
 | |
|     if not isinstance(val, basestring):
 | |
|         return False
 | |
| 
 | |
|     return val.lower() == 'none'
 | |
| 
 | |
| 
 | |
| def convert_version_to_int(version):
 | |
|     return version[0] * 1000000 + version[1] * 1000 + version[2]
 | |
| 
 | |
| 
 | |
| def is_neutron():
 | |
|     global _IS_NEUTRON_ATTEMPTED
 | |
|     global _IS_NEUTRON
 | |
| 
 | |
|     if _IS_NEUTRON_ATTEMPTED:
 | |
|         return _IS_NEUTRON
 | |
| 
 | |
|     try:
 | |
|         # compatibility with Folsom/Grizzly configs
 | |
|         cls_name = CONF.network_api_class
 | |
|         if cls_name == 'nova.network.quantumv2.api.API':
 | |
|             cls_name = 'nova.network.neutronv2.api.API'
 | |
|         _IS_NEUTRON_ATTEMPTED = True
 | |
| 
 | |
|         from nova.network.neutronv2 import api as neutron_api
 | |
|         _IS_NEUTRON = issubclass(importutils.import_class(cls_name),
 | |
|                                  neutron_api.API)
 | |
|     except ImportError:
 | |
|         _IS_NEUTRON = False
 | |
| 
 | |
|     return _IS_NEUTRON
 | |
| 
 | |
| 
 | |
| def reset_is_neutron():
 | |
|     global _IS_NEUTRON_ATTEMPTED
 | |
|     global _IS_NEUTRON
 | |
| 
 | |
|     _IS_NEUTRON_ATTEMPTED = False
 | |
|     _IS_NEUTRON = False
 | |
| 
 | |
| 
 | |
| def is_auto_disk_config_disabled(auto_disk_config_raw):
 | |
|     auto_disk_config_disabled = False
 | |
|     if auto_disk_config_raw is not None:
 | |
|         adc_lowered = auto_disk_config_raw.strip().lower()
 | |
|         if adc_lowered == "disabled":
 | |
|             auto_disk_config_disabled = True
 | |
|     return auto_disk_config_disabled
 | |
| 
 | |
| 
 | |
| def get_auto_disk_config_from_instance(instance=None, sys_meta=None):
 | |
|     if sys_meta is None:
 | |
|         sys_meta = instance_sys_meta(instance)
 | |
|     return sys_meta.get("image_auto_disk_config")
 | |
| 
 | |
| 
 | |
| def get_auto_disk_config_from_image_props(image_properties):
 | |
|     return image_properties.get("auto_disk_config")
 | |
| 
 | |
| 
 | |
| def get_system_metadata_from_image(image_meta, instance_type=None):
 | |
|     system_meta = {}
 | |
|     prefix_format = SM_IMAGE_PROP_PREFIX + '%s'
 | |
| 
 | |
|     for key, value in image_meta.get('properties', {}).iteritems():
 | |
|         new_value = unicode(value)[:255]
 | |
|         system_meta[prefix_format % key] = new_value
 | |
| 
 | |
|     for key in SM_INHERITABLE_KEYS:
 | |
|         value = image_meta.get(key)
 | |
| 
 | |
|         if key == 'min_disk' and instance_type:
 | |
|             if image_meta.get('disk_format') == 'vhd':
 | |
|                 value = instance_type['root_gb']
 | |
|             else:
 | |
|                 value = max(value, instance_type['root_gb'])
 | |
| 
 | |
|         if value is None:
 | |
|             continue
 | |
| 
 | |
|         system_meta[prefix_format % key] = value
 | |
| 
 | |
|     return system_meta
 | |
| 
 | |
| 
 | |
| def get_image_from_system_metadata(system_meta):
 | |
|     image_meta = {}
 | |
|     properties = {}
 | |
| 
 | |
|     if not isinstance(system_meta, dict):
 | |
|         system_meta = metadata_to_dict(system_meta)
 | |
| 
 | |
|     for key, value in system_meta.iteritems():
 | |
|         if value is None:
 | |
|             continue
 | |
| 
 | |
|         # NOTE(xqueralt): Not sure this has to inherit all the properties or
 | |
|         # just the ones we need. Leaving it for now to keep the old behaviour.
 | |
|         if key.startswith(SM_IMAGE_PROP_PREFIX):
 | |
|             key = key[len(SM_IMAGE_PROP_PREFIX):]
 | |
| 
 | |
|         if key in SM_INHERITABLE_KEYS:
 | |
|             image_meta[key] = value
 | |
|         else:
 | |
|             # Skip properties that are non-inheritable
 | |
|             if key in CONF.non_inheritable_image_properties:
 | |
|                 continue
 | |
|             properties[key] = value
 | |
| 
 | |
|     if properties:
 | |
|         image_meta['properties'] = properties
 | |
| 
 | |
|     return image_meta
 | 
