merge trunk, resolved conflict

This commit is contained in:
Ken Pepple 2011-04-22 00:14:11 -07:00
commit 40ef1240c3
52 changed files with 1144 additions and 986 deletions

View File

@ -30,6 +30,7 @@ Ilya Alekseyev <ialekseev@griddynamics.com>
Jason Koelker <jason@koelker.net> Jason Koelker <jason@koelker.net>
Jay Pipes <jaypipes@gmail.com> Jay Pipes <jaypipes@gmail.com>
Jesse Andrews <anotherjesse@gmail.com> Jesse Andrews <anotherjesse@gmail.com>
Jimmy Bergman <jimmy@sigint.se>
Joe Heck <heckj@mac.com> Joe Heck <heckj@mac.com>
Joel Moore <joelbm24@gmail.com> Joel Moore <joelbm24@gmail.com>
Johannes Erdfelt <johannes.erdfelt@rackspace.com> Johannes Erdfelt <johannes.erdfelt@rackspace.com>

17
HACKING
View File

@ -50,17 +50,24 @@ Human Alphabetical Order Examples
Docstrings Docstrings
---------- ----------
"""Summary of the function, class or method, less than 80 characters. """A one line docstring looks like this and ends in a period."""
New paragraph after newline that explains in more detail any general
information about the function, class or method. After this, if defining """A multiline docstring has a one-line summary, less than 80 characters.
parameters and return types use the Sphinx format. After that an extra
newline then close the quotations. Then a new paragraph after a newline that explains in more detail any
general information about the function, class or method. Example usages
are also great to have here if it is a complex class for function. After
you have finished your descriptions add an extra newline and close the
quotations.
When writing the docstring for a class, an extra line should be placed When writing the docstring for a class, an extra line should be placed
after the closing quotations. For more in-depth explanations for these after the closing quotations. For more in-depth explanations for these
decisions see http://www.python.org/dev/peps/pep-0257/ decisions see http://www.python.org/dev/peps/pep-0257/
If you are going to describe parameters and return values, use Sphinx, the
appropriate syntax is as follows.
:param foo: the foo parameter :param foo: the foo parameter
:param bar: the bar parameter :param bar: the bar parameter
:returns: description of the return value :returns: description of the return value

View File

@ -28,11 +28,11 @@ import sys
# If ../nova/__init__.py exists, add ../ to Python search path, so that # If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python... # it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir, os.pardir,
os.pardir)) os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1) gettext.install('nova', unicode=1)

View File

@ -58,7 +58,6 @@ import gettext
import glob import glob
import json import json
import os import os
import re
import sys import sys
import time import time
@ -66,11 +65,11 @@ import IPy
# If ../nova/__init__.py exists, add ../ to Python search path, so that # If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python... # it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir, os.pardir,
os.pardir)) os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1) gettext.install('nova', unicode=1)
@ -809,12 +808,11 @@ class VolumeCommands(object):
class InstanceTypeCommands(object): class InstanceTypeCommands(object):
"""Class for managing instance types / flavors.""" """Class for managing instance types / flavors."""
def _print_instance_types(self, n, val): def _print_instance_types(self, name, val):
"""helper method to print out instance_types values"""
deleted = ('', ', inactive')[val["deleted"] == 1] deleted = ('', ', inactive')[val["deleted"] == 1]
print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, " print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, "
"Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % ( "Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % (
n, val["memory_mb"], val["vcpus"], val["local_gb"], name, val["memory_mb"], val["vcpus"], val["local_gb"],
val["flavorid"], val["swap"], val["rxtx_quota"], val["flavorid"], val["swap"], val["rxtx_quota"],
val["rxtx_cap"], deleted) val["rxtx_cap"], deleted)
@ -1028,7 +1026,7 @@ class ImageCommands(object):
machine_images[image_path] = image_metadata machine_images[image_path] = image_metadata
else: else:
other_images[image_path] = image_metadata other_images[image_path] = image_metadata
except Exception as exc: except Exception:
print _("Failed to load %(fn)s.") % locals() print _("Failed to load %(fn)s.") % locals()
# NOTE(vish): do kernels and ramdisks first so images # NOTE(vish): do kernels and ramdisks first so images
self._convert_images(other_images) self._convert_images(other_images)

View File

@ -62,12 +62,18 @@ Making a cloudpipe image is relatively easy.
:language: bash :language: bash
:linenos: :linenos:
# download and run the payload on boot from /etc/rc.local. # download and run the payload on boot from /etc/rc.local
.. literalinclude:: rc.local .. literalinclude:: rc.local
:language: bash :language: bash
:linenos: :linenos:
# setup /etc/network/interfaces
.. literalinclude:: interfaces
:language: bash
:linenos:
# register the image and set the image id in your flagfile:: # register the image and set the image id in your flagfile::
--vpn_image_id=ami-xxxxxxxx --vpn_image_id=ami-xxxxxxxx

View File

@ -0,0 +1,17 @@
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto eth0
iface eth0 inet manual
up ifconfig $IFACE 0.0.0.0 up
down ifconfig $IFACE down
auto br0
iface br0 inet dhcp
bridge_ports eth0

View File

@ -21,7 +21,7 @@ NAME=$1
SUBJ=$2 SUBJ=$2
mkdir -p projects/$NAME mkdir -p projects/$NAME
cd projects/$NAME cd projects/$NAME
cp ../../openssl.cnf.tmpl openssl.cnf cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf
sed -i -e s/%USERNAME%/$NAME/g openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
mkdir -p certs crl newcerts private mkdir -p certs crl newcerts private
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes

View File

@ -266,7 +266,7 @@ class AdminController(object):
def _vpn_for(self, context, project_id): def _vpn_for(self, context, project_id):
"""Get the VPN instance for a project ID.""" """Get the VPN instance for a project ID."""
for instance in db.instance_get_all_by_project(context, project_id): for instance in db.instance_get_all_by_project(context, project_id):
if (instance['image_id'] == FLAGS.vpn_image_id if (instance['image_id'] == str(FLAGS.vpn_image_id)
and not instance['state_description'] in and not instance['state_description'] in
['shutting_down', 'shutdown']): ['shutting_down', 'shutdown']):
return instance return instance

View File

@ -159,7 +159,7 @@ class CloudController(object):
floating_ip = db.instance_get_floating_address(ctxt, floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id']) instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') image_ec2_id = self.image_ec2_id(instance_ref['image_id'])
data = { data = {
'user-data': base64.b64decode(instance_ref['user_data']), 'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': { 'meta-data': {
@ -187,9 +187,9 @@ class CloudController(object):
'mpi': mpi}} 'mpi': mpi}}
for image_type in ['kernel', 'ramdisk']: for image_type in ['kernel', 'ramdisk']:
if '%s_id' % image_type in instance_ref: if instance_ref.get('%s_id' % image_type):
ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type],
self._image_type(image_type)) self._image_type(image_type))
data['meta-data']['%s-id' % image_type] = ec2_id data['meta-data']['%s-id' % image_type] = ec2_id
if False: # TODO(vish): store ancestor ids if False: # TODO(vish): store ancestor ids
@ -613,7 +613,7 @@ class CloudController(object):
# TODO(vish): Instance should be None at db layer instead of # TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into # trying to lazy load, but for now we turn it into
# a dict to avoid an error. # a dict to avoid an error.
return {'volumeSet': [self._format_volume(context, dict(volume))]} return self._format_volume(context, dict(volume))
def delete_volume(self, context, volume_id, **kwargs): def delete_volume(self, context, volume_id, **kwargs):
volume_id = ec2utils.ec2_id_to_id(volume_id) volume_id = ec2utils.ec2_id_to_id(volume_id)
@ -703,13 +703,13 @@ class CloudController(object):
instances = self.compute_api.get_all(context, **kwargs) instances = self.compute_api.get_all(context, **kwargs)
for instance in instances: for instance in instances:
if not context.is_admin: if not context.is_admin:
if instance['image_id'] == FLAGS.vpn_image_id: if instance['image_id'] == str(FLAGS.vpn_image_id):
continue continue
i = {} i = {}
instance_id = instance['id'] instance_id = instance['id']
ec2_id = ec2utils.id_to_ec2_id(instance_id) ec2_id = ec2utils.id_to_ec2_id(instance_id)
i['instanceId'] = ec2_id i['instanceId'] = ec2_id
i['imageId'] = self._image_ec2_id(instance['image_id']) i['imageId'] = self.image_ec2_id(instance['image_id'])
i['instanceState'] = { i['instanceState'] = {
'code': instance['state'], 'code': instance['state'],
'name': instance['state_description']} 'name': instance['state_description']}
@ -726,7 +726,9 @@ class CloudController(object):
instance['mac_address']) instance['mac_address'])
i['privateDnsName'] = fixed_addr i['privateDnsName'] = fixed_addr
i['privateIpAddress'] = fixed_addr
i['publicDnsName'] = floating_addr i['publicDnsName'] = floating_addr
i['ipAddress'] = floating_addr or fixed_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName'] i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name'] i['keyName'] = instance['key_name']
@ -898,7 +900,7 @@ class CloudController(object):
return image_type return image_type
@staticmethod @staticmethod
def _image_ec2_id(image_id, image_type='ami'): def image_ec2_id(image_id, image_type='ami'):
"""Returns image ec2_id using id and three letter type.""" """Returns image ec2_id using id and three letter type."""
template = image_type + '-%08x' template = image_type + '-%08x'
return ec2utils.id_to_ec2_id(int(image_id), template=template) return ec2utils.id_to_ec2_id(int(image_id), template=template)
@ -917,15 +919,15 @@ class CloudController(object):
"""Convert from format defined by BaseImageService to S3 format.""" """Convert from format defined by BaseImageService to S3 format."""
i = {} i = {}
image_type = self._image_type(image.get('container_format')) image_type = self._image_type(image.get('container_format'))
ec2_id = self._image_ec2_id(image.get('id'), image_type) ec2_id = self.image_ec2_id(image.get('id'), image_type)
name = image.get('name') name = image.get('name')
i['imageId'] = ec2_id i['imageId'] = ec2_id
kernel_id = image['properties'].get('kernel_id') kernel_id = image['properties'].get('kernel_id')
if kernel_id: if kernel_id:
i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') i['kernelId'] = self.image_ec2_id(kernel_id, 'aki')
ramdisk_id = image['properties'].get('ramdisk_id') ramdisk_id = image['properties'].get('ramdisk_id')
if ramdisk_id: if ramdisk_id:
i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') i['ramdiskId'] = self.image_ec2_id(ramdisk_id, 'ari')
i['imageOwnerId'] = image['properties'].get('owner_id') i['imageOwnerId'] = image['properties'].get('owner_id')
if name: if name:
i['imageLocation'] = "%s (%s)" % (image['properties']. i['imageLocation'] = "%s (%s)" % (image['properties'].
@ -976,8 +978,8 @@ class CloudController(object):
metadata = {'properties': {'image_location': image_location}} metadata = {'properties': {'image_location': image_location}}
image = self.image_service.create(context, metadata) image = self.image_service.create(context, metadata)
image_type = self._image_type(image.get('container_format')) image_type = self._image_type(image.get('container_format'))
image_id = self._image_ec2_id(image['id'], image_id = self.image_ec2_id(image['id'],
image_type) image_type)
msg = _("Registered image %(image_location)s with" msg = _("Registered image %(image_location)s with"
" id %(image_id)s") % locals() " id %(image_id)s") % locals()
LOG.audit(msg, context=context) LOG.audit(msg, context=context)

View File

@ -40,7 +40,7 @@ import nova.api.openstack
from nova.scheduler import api as scheduler_api from nova.scheduler import api as scheduler_api
LOG = logging.getLogger('server') LOG = logging.getLogger('nova.api.openstack.servers')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -321,6 +321,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
def _action_rebuild(self, input_dict, req, id): def _action_rebuild(self, input_dict, req, id):
LOG.debug(_("Rebuild server action is not implemented"))
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
def _action_resize(self, input_dict, req, id): def _action_resize(self, input_dict, req, id):
@ -336,18 +337,20 @@ class Controller(common.OpenstackController):
except Exception, e: except Exception, e:
LOG.exception(_("Error in resize %s"), e) LOG.exception(_("Error in resize %s"), e)
return faults.Fault(exc.HTTPBadRequest()) return faults.Fault(exc.HTTPBadRequest())
return faults.Fault(exc.HTTPAccepted()) return exc.HTTPAccepted()
def _action_reboot(self, input_dict, req, id): def _action_reboot(self, input_dict, req, id):
try: if 'reboot' in input_dict and 'type' in input_dict['reboot']:
reboot_type = input_dict['reboot']['type'] reboot_type = input_dict['reboot']['type']
except Exception: else:
raise faults.Fault(exc.HTTPNotImplemented()) LOG.exception(_("Missing argument 'type' for reboot"))
return faults.Fault(exc.HTTPUnprocessableEntity())
try: try:
# TODO(gundlach): pass reboot_type, support soft reboot in # TODO(gundlach): pass reboot_type, support soft reboot in
# virt driver # virt driver
self.compute_api.reboot(req.environ['nova.context'], id) self.compute_api.reboot(req.environ['nova.context'], id)
except: except Exception, e:
LOG.exception(_("Error in reboot %s"), e)
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()

View File

@ -101,12 +101,13 @@ class CloudPipe(object):
key_name = self.setup_key_pair(ctxt) key_name = self.setup_key_pair(ctxt)
group_name = self.setup_security_group(ctxt) group_name = self.setup_security_group(ctxt)
ec2_id = self.controller.image_ec2_id(FLAGS.vpn_image_id)
reservation = self.controller.run_instances(ctxt, reservation = self.controller.run_instances(ctxt,
user_data=self.get_encoded_zip(project_id), user_data=self.get_encoded_zip(project_id),
max_count=1, max_count=1,
min_count=1, min_count=1,
instance_type='m1.tiny', instance_type='m1.tiny',
image_id=FLAGS.vpn_image_id, image_id=ec2_id,
key_name=key_name, key_name=key_name,
security_group=[group_name]) security_group=[group_name])

View File

@ -17,8 +17,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Handles all processes relating to instances (guest vms).
Handles all processes relating to instances (guest vms).
The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that
handles RPC calls relating to creating instances. It is responsible for handles RPC calls relating to creating instances. It is responsible for
@ -33,12 +32,11 @@ terminating it.
by :func:`nova.utils.import_object` by :func:`nova.utils.import_object`
:volume_manager: Name of class that handles persistent storage, loaded by :volume_manager: Name of class that handles persistent storage, loaded by
:func:`nova.utils.import_object` :func:`nova.utils.import_object`
""" """
import datetime import datetime
import os import os
import random
import string
import socket import socket
import sys import sys
import tempfile import tempfile
@ -50,11 +48,14 @@ from nova import exception
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import manager from nova import manager
from nova import network
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova import volume
from nova.compute import power_state from nova.compute import power_state
from nova.virt import driver from nova.virt import driver
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', '$state_path/instances', flags.DEFINE_string('instances_path', '$state_path/instances',
'where instances are stored on disk') 'where instances are stored on disk')
@ -73,20 +74,17 @@ flags.DEFINE_integer('live_migration_retry_count', 30,
flags.DEFINE_integer("rescue_timeout", 0, flags.DEFINE_integer("rescue_timeout", 0,
"Automatically unrescue an instance after N seconds." "Automatically unrescue an instance after N seconds."
" Set to 0 to disable.") " Set to 0 to disable.")
flags.DEFINE_bool('auto_assign_floating_ip', False,
'Autoassigning floating ip to VM')
LOG = logging.getLogger('nova.compute.manager') LOG = logging.getLogger('nova.compute.manager')
def checks_instance_lock(function): def checks_instance_lock(function):
""" """Decorator to prevent action against locked instances for non-admins."""
decorator used for preventing action against locked instances
unless, of course, you happen to be admin
"""
@functools.wraps(function) @functools.wraps(function)
def decorated_function(self, context, instance_id, *args, **kwargs): def decorated_function(self, context, instance_id, *args, **kwargs):
LOG.info(_("check_instance_lock: decorating: |%s|"), function, LOG.info(_("check_instance_lock: decorating: |%s|"), function,
context=context) context=context)
LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|" LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|"
@ -112,7 +110,6 @@ def checks_instance_lock(function):
class ComputeManager(manager.SchedulerDependentManager): class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction.""" """Manages the running instances from creation to destruction."""
def __init__(self, compute_driver=None, *args, **kwargs): def __init__(self, compute_driver=None, *args, **kwargs):
@ -132,13 +129,12 @@ class ComputeManager(manager.SchedulerDependentManager):
self.network_manager = utils.import_object(FLAGS.network_manager) self.network_manager = utils.import_object(FLAGS.network_manager)
self.volume_manager = utils.import_object(FLAGS.volume_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager)
self.network_api = network.API()
super(ComputeManager, self).__init__(service_name="compute", super(ComputeManager, self).__init__(service_name="compute",
*args, **kwargs) *args, **kwargs)
def init_host(self): def init_host(self):
"""Do any initialization that needs to be run if this is a """Initialization for a standalone compute service."""
standalone service.
"""
self.driver.init_host(host=self.host) self.driver.init_host(host=self.host)
def _update_state(self, context, instance_id): def _update_state(self, context, instance_id):
@ -153,16 +149,18 @@ class ComputeManager(manager.SchedulerDependentManager):
self.db.instance_set_state(context, instance_id, state) self.db.instance_set_state(context, instance_id, state)
def get_console_topic(self, context, **kwargs): def get_console_topic(self, context, **kwargs):
"""Retrieves the console host for a project on this host """Retrieves the console host for a project on this host.
Currently this is just set in the flags for each compute
host.""" Currently this is just set in the flags for each compute host.
"""
#TODO(mdragon): perhaps make this variable by console_type? #TODO(mdragon): perhaps make this variable by console_type?
return self.db.queue_get_for(context, return self.db.queue_get_for(context,
FLAGS.console_topic, FLAGS.console_topic,
FLAGS.console_host) FLAGS.console_host)
def get_network_topic(self, context, **kwargs): def get_network_topic(self, context, **kwargs):
"""Retrieves the network host for a project on this host""" """Retrieves the network host for a project on this host."""
# TODO(vish): This method should be memoized. This will make # TODO(vish): This method should be memoized. This will make
# the call to get_network_host cheaper, so that # the call to get_network_host cheaper, so that
# it can pas messages instead of checking the db # it can pas messages instead of checking the db
@ -179,15 +177,23 @@ class ComputeManager(manager.SchedulerDependentManager):
return self.driver.get_console_pool_info(console_type) return self.driver.get_console_pool_info(console_type)
@exception.wrap_exception @exception.wrap_exception
def refresh_security_group_rules(self, context, def refresh_security_group_rules(self, context, security_group_id,
security_group_id, **kwargs): **kwargs):
"""This call passes straight through to the virtualization driver.""" """Tell the virtualization driver to refresh security group rules.
Passes straight through to the virtualization driver.
"""
return self.driver.refresh_security_group_rules(security_group_id) return self.driver.refresh_security_group_rules(security_group_id)
@exception.wrap_exception @exception.wrap_exception
def refresh_security_group_members(self, context, def refresh_security_group_members(self, context,
security_group_id, **kwargs): security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver.""" """Tell the virtualization driver to refresh security group members.
Passes straight through to the virtualization driver.
"""
return self.driver.refresh_security_group_members(security_group_id) return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception @exception.wrap_exception
@ -209,7 +215,7 @@ class ComputeManager(manager.SchedulerDependentManager):
power_state.NOSTATE, power_state.NOSTATE,
'networking') 'networking')
is_vpn = instance_ref['image_id'] == FLAGS.vpn_image_id is_vpn = instance_ref['image_id'] == str(FLAGS.vpn_image_id)
# NOTE(vish): This could be a cast because we don't do anything # NOTE(vish): This could be a cast because we don't do anything
# with the address currently, but I'm leaving it as # with the address currently, but I'm leaving it as
# a call to ensure that network setup completes. We # a call to ensure that network setup completes. We
@ -244,12 +250,24 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_id, instance_id,
power_state.SHUTDOWN) power_state.SHUTDOWN)
if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip:
public_ip = self.network_api.allocate_floating_ip(context)
self.db.floating_ip_set_auto_assigned(context, public_ip)
fixed_ip = self.db.fixed_ip_get_by_address(context, address)
floating_ip = self.db.floating_ip_get_by_address(context,
public_ip)
self.network_api.associate_floating_ip(context,
floating_ip,
fixed_ip,
affect_auto_assigned=True)
self._update_state(context, instance_id) self._update_state(context, instance_id)
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def terminate_instance(self, context, instance_id): def terminate_instance(self, context, instance_id):
"""Terminate an instance on this machine.""" """Terminate an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Terminating instance %s"), instance_id, context=context) LOG.audit(_("Terminating instance %s"), instance_id, context=context)
@ -264,13 +282,17 @@ class ComputeManager(manager.SchedulerDependentManager):
# NOTE(vish): Right now we don't really care if the ip is # NOTE(vish): Right now we don't really care if the ip is
# disassociated. We may need to worry about # disassociated. We may need to worry about
# checking this later. # checking this later.
network_topic = self.db.queue_get_for(context, self.network_api.disassociate_floating_ip(context,
FLAGS.network_topic, address,
floating_ip['host']) True)
rpc.cast(context, if (FLAGS.auto_assign_floating_ip
network_topic, and floating_ip.get('auto_assigned')):
{"method": "disassociate_floating_ip", LOG.debug(_("Deallocating floating ip %s"),
"args": {"floating_address": address}}) floating_ip['address'],
context=context)
self.network_api.release_floating_ip(context,
address,
True)
address = fixed_ip['address'] address = fixed_ip['address']
if address: if address:
@ -297,7 +319,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def reboot_instance(self, context, instance_id): def reboot_instance(self, context, instance_id):
"""Reboot an instance on this server.""" """Reboot an instance on this host."""
context = context.elevated() context = context.elevated()
self._update_state(context, instance_id) self._update_state(context, instance_id)
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
@ -321,7 +343,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def snapshot_instance(self, context, instance_id, image_id): def snapshot_instance(self, context, instance_id, image_id):
"""Snapshot an instance on this server.""" """Snapshot an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
@ -344,7 +366,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def set_admin_password(self, context, instance_id, new_pass=None): def set_admin_password(self, context, instance_id, new_pass=None):
"""Set the root/admin password for an instance on this server.""" """Set the root/admin password for an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id'] instance_id = instance_ref['id']
@ -365,7 +387,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def inject_file(self, context, instance_id, path, file_contents): def inject_file(self, context, instance_id, path, file_contents):
"""Write a file to the specified path on an instance on this server""" """Write a file to the specified path in an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id'] instance_id = instance_ref['id']
@ -383,44 +405,34 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def rescue_instance(self, context, instance_id): def rescue_instance(self, context, instance_id):
"""Rescue an instance on this server.""" """Rescue an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: rescuing'), instance_id, context=context) LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
self.db.instance_set_state( self.db.instance_set_state(context,
context, instance_id,
instance_id, power_state.NOSTATE,
power_state.NOSTATE, 'rescuing')
'rescuing')
self.network_manager.setup_compute_network(context, instance_id) self.network_manager.setup_compute_network(context, instance_id)
self.driver.rescue( _update_state = lambda result: self._update_state_callback(
instance_ref, self, context, instance_id, result)
lambda result: self._update_state_callback( self.driver.rescue(instance_ref, _update_state)
self,
context,
instance_id,
result))
self._update_state(context, instance_id) self._update_state(context, instance_id)
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def unrescue_instance(self, context, instance_id): def unrescue_instance(self, context, instance_id):
"""Rescue an instance on this server.""" """Rescue an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context)
self.db.instance_set_state( self.db.instance_set_state(context,
context, instance_id,
instance_id, power_state.NOSTATE,
power_state.NOSTATE, 'unrescuing')
'unrescuing') _update_state = lambda result: self._update_state_callback(
self.driver.unrescue( self, context, instance_id, result)
instance_ref, self.driver.unrescue(instance_ref, _update_state)
lambda result: self._update_state_callback(
self,
context,
instance_id,
result))
self._update_state(context, instance_id) self._update_state(context, instance_id)
@staticmethod @staticmethod
@ -431,7 +443,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def confirm_resize(self, context, instance_id, migration_id): def confirm_resize(self, context, instance_id, migration_id):
"""Destroys the source instance""" """Destroys the source instance."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
self.driver.destroy(instance_ref) self.driver.destroy(instance_ref)
@ -439,9 +451,12 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def revert_resize(self, context, instance_id, migration_id): def revert_resize(self, context, instance_id, migration_id):
"""Destroys the new instance on the destination machine, """Destroys the new instance on the destination machine.
reverts the model changes, and powers on the old
instance on the source machine""" Reverts the model changes, and powers on the old instance on the
source machine.
"""
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id) migration_ref = self.db.migration_get(context, migration_id)
@ -458,9 +473,12 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def finish_revert_resize(self, context, instance_id, migration_id): def finish_revert_resize(self, context, instance_id, migration_id):
"""Finishes the second half of reverting a resize, powering back on """Finishes the second half of reverting a resize.
the source instance and reverting the resized attributes in the
database""" Power back on the source instance and revert the resized attributes
in the database.
"""
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id) migration_ref = self.db.migration_get(context, migration_id)
instance_type = self.db.instance_type_get_by_flavor_id(context, instance_type = self.db.instance_type_get_by_flavor_id(context,
@ -480,8 +498,11 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def prep_resize(self, context, instance_id, flavor_id): def prep_resize(self, context, instance_id, flavor_id):
"""Initiates the process of moving a running instance to another """Initiates the process of moving a running instance to another host.
host, possibly changing the RAM and disk size in the process"""
Possibly changes the RAM and disk size in the process.
"""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
if instance_ref['host'] == FLAGS.host: if instance_ref['host'] == FLAGS.host:
@ -513,35 +534,38 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def resize_instance(self, context, instance_id, migration_id): def resize_instance(self, context, instance_id, migration_id):
"""Starts the migration of a running instance to another host""" """Starts the migration of a running instance to another host."""
migration_ref = self.db.migration_get(context, migration_id) migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
self.db.migration_update(context, migration_id, self.db.migration_update(context,
{'status': 'migrating', }) migration_id,
{'status': 'migrating'})
disk_info = self.driver.migrate_disk_and_power_off(instance_ref, disk_info = self.driver.migrate_disk_and_power_off(
migration_ref['dest_host']) instance_ref, migration_ref['dest_host'])
self.db.migration_update(context, migration_id, self.db.migration_update(context,
{'status': 'post-migrating', }) migration_id,
{'status': 'post-migrating'})
# Make sure the service exists before sending a message. service = self.db.service_get_by_host_and_topic(
_service = self.db.service_get_by_host_and_topic(context, context, migration_ref['dest_compute'], FLAGS.compute_topic)
migration_ref['dest_compute'], FLAGS.compute_topic) topic = self.db.queue_get_for(context,
topic = self.db.queue_get_for(context, FLAGS.compute_topic, FLAGS.compute_topic,
migration_ref['dest_compute']) migration_ref['dest_compute'])
rpc.cast(context, topic, rpc.cast(context, topic, {'method': 'finish_resize',
{'method': 'finish_resize', 'args': {'migration_id': migration_id,
'args': { 'instance_id': instance_id,
'migration_id': migration_id, 'disk_info': disk_info}})
'instance_id': instance_id,
'disk_info': disk_info, },
})
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def finish_resize(self, context, instance_id, migration_id, disk_info): def finish_resize(self, context, instance_id, migration_id, disk_info):
"""Completes the migration process by setting up the newly transferred """Completes the migration process.
disk and turning on the instance on its new host machine"""
Sets up the newly transferred disk and turns on the instance at its
new host machine.
"""
migration_ref = self.db.migration_get(context, migration_id) migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context, instance_ref = self.db.instance_get(context,
migration_ref['instance_id']) migration_ref['instance_id'])
@ -566,7 +590,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def pause_instance(self, context, instance_id): def pause_instance(self, context, instance_id):
"""Pause an instance on this server.""" """Pause an instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: pausing'), instance_id, context=context) LOG.audit(_('instance %s: pausing'), instance_id, context=context)
@ -583,7 +607,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def unpause_instance(self, context, instance_id): def unpause_instance(self, context, instance_id):
"""Unpause a paused instance on this server.""" """Unpause a paused instance on this host."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unpausing'), instance_id, context=context) LOG.audit(_('instance %s: unpausing'), instance_id, context=context)
@ -599,7 +623,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def get_diagnostics(self, context, instance_id): def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for an instance on this server.""" """Retrieve diagnostics for an instance on this host."""
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
if instance_ref["state"] == power_state.RUNNING: if instance_ref["state"] == power_state.RUNNING:
@ -610,10 +634,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def suspend_instance(self, context, instance_id): def suspend_instance(self, context, instance_id):
""" """Suspend the given instance."""
suspend the instance with instance_id
"""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: suspending'), instance_id, context=context) LOG.audit(_('instance %s: suspending'), instance_id, context=context)
@ -629,10 +650,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def resume_instance(self, context, instance_id): def resume_instance(self, context, instance_id):
""" """Resume the given suspended instance."""
resume the suspended instance with instance_id
"""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: resuming'), instance_id, context=context) LOG.audit(_('instance %s: resuming'), instance_id, context=context)
@ -647,10 +665,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def lock_instance(self, context, instance_id): def lock_instance(self, context, instance_id):
""" """Lock the given instance."""
lock the instance with instance_id
"""
context = context.elevated() context = context.elevated()
LOG.debug(_('instance %s: locking'), instance_id, context=context) LOG.debug(_('instance %s: locking'), instance_id, context=context)
@ -658,10 +673,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def unlock_instance(self, context, instance_id): def unlock_instance(self, context, instance_id):
""" """Unlock the given instance."""
unlock the instance with instance_id
"""
context = context.elevated() context = context.elevated()
LOG.debug(_('instance %s: unlocking'), instance_id, context=context) LOG.debug(_('instance %s: unlocking'), instance_id, context=context)
@ -669,10 +681,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def get_lock(self, context, instance_id): def get_lock(self, context, instance_id):
""" """Return the boolean state of the given instance's lock."""
return the boolean state of (instance with instance_id)'s lock
"""
context = context.elevated() context = context.elevated()
LOG.debug(_('instance %s: getting locked state'), instance_id, LOG.debug(_('instance %s: getting locked state'), instance_id,
context=context) context=context)
@ -681,10 +690,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock @checks_instance_lock
def reset_network(self, context, instance_id): def reset_network(self, context, instance_id):
""" """Reset networking on the given instance."""
Reset networking on the instance.
"""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: reset network'), instance_id, LOG.debug(_('instance %s: reset network'), instance_id,
@ -693,10 +699,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock @checks_instance_lock
def inject_network_info(self, context, instance_id): def inject_network_info(self, context, instance_id):
""" """Inject network info for the given instance."""
Inject network info for the instance.
"""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: inject network info'), instance_id, LOG.debug(_('instance %s: inject network info'), instance_id,
@ -705,7 +708,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def get_console_output(self, context, instance_id): def get_console_output(self, context, instance_id):
"""Send the console output for an instance.""" """Send the console output for the given instance."""
context = context.elevated() context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Get console output for instance %s"), instance_id, LOG.audit(_("Get console output for instance %s"), instance_id,
@ -714,20 +717,18 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception @exception.wrap_exception
def get_ajax_console(self, context, instance_id): def get_ajax_console(self, context, instance_id):
"""Return connection information for an ajax console""" """Return connection information for an ajax console."""
context = context.elevated() context = context.elevated()
LOG.debug(_("instance %s: getting ajax console"), instance_id) LOG.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_ajax_console(instance_ref) return self.driver.get_ajax_console(instance_ref)
@exception.wrap_exception @exception.wrap_exception
def get_vnc_console(self, context, instance_id): def get_vnc_console(self, context, instance_id):
"""Return connection information for an vnc console.""" """Return connection information for a vnc console."""
context = context.elevated() context = context.elevated()
LOG.debug(_("instance %s: getting vnc console"), instance_id) LOG.debug(_("instance %s: getting vnc console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_vnc_console(instance_ref) return self.driver.get_vnc_console(instance_ref)
@checks_instance_lock @checks_instance_lock
@ -779,9 +780,17 @@ class ComputeManager(manager.SchedulerDependentManager):
self.db.volume_detached(context, volume_id) self.db.volume_detached(context, volume_id)
return True return True
def remove_volume(self, context, volume_id):
"""Remove volume on compute host.
:param context: security context
:param volume_id: volume ID
"""
self.volume_manager.remove_compute_volume(context, volume_id)
@exception.wrap_exception @exception.wrap_exception
def compare_cpu(self, context, cpu_info): def compare_cpu(self, context, cpu_info):
"""Checks the host cpu is compatible to a cpu given by xml. """Checks that the host cpu is compatible with a cpu given by xml.
:param context: security context :param context: security context
:param cpu_info: json string obtained from virConnect.getCapabilities :param cpu_info: json string obtained from virConnect.getCapabilities
@ -802,7 +811,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: tmpfile name(basename) :returns: tmpfile name(basename)
""" """
dirpath = FLAGS.instances_path dirpath = FLAGS.instances_path
fd, tmp_file = tempfile.mkstemp(dir=dirpath) fd, tmp_file = tempfile.mkstemp(dir=dirpath)
LOG.debug(_("Creating tmpfile %s to notify to other " LOG.debug(_("Creating tmpfile %s to notify to other "
@ -819,7 +827,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: confirm existence of FLAGS.instances_path/thisfile :param filename: confirm existence of FLAGS.instances_path/thisfile
""" """
tmp_file = os.path.join(FLAGS.instances_path, filename) tmp_file = os.path.join(FLAGS.instances_path, filename)
if not os.path.exists(tmp_file): if not os.path.exists(tmp_file):
raise exception.NotFound(_('%s not found') % tmp_file) raise exception.NotFound(_('%s not found') % tmp_file)
@ -832,7 +839,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: remove existence of FLAGS.instances_path/thisfile :param filename: remove existence of FLAGS.instances_path/thisfile
""" """
tmp_file = os.path.join(FLAGS.instances_path, filename) tmp_file = os.path.join(FLAGS.instances_path, filename)
os.remove(tmp_file) os.remove(tmp_file)
@ -844,7 +850,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: See driver.update_available_resource() :returns: See driver.update_available_resource()
""" """
return self.driver.update_available_resource(context, self.host) return self.driver.update_available_resource(context, self.host)
def pre_live_migration(self, context, instance_id, time=None): def pre_live_migration(self, context, instance_id, time=None):
@ -854,7 +859,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param instance_id: nova.db.sqlalchemy.models.Instance.Id :param instance_id: nova.db.sqlalchemy.models.Instance.Id
""" """
if not time: if not time:
time = greenthread time = greenthread
@ -913,7 +917,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param dest: destination host :param dest: destination host
""" """
# Get instance for error handling. # Get instance for error handling.
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
i_name = instance_ref.name i_name = instance_ref.name
@ -1004,17 +1007,15 @@ class ComputeManager(manager.SchedulerDependentManager):
"Domain not found: no domain with matching name.\" " "Domain not found: no domain with matching name.\" "
"This error can be safely ignored.")) "This error can be safely ignored."))
def recover_live_migration(self, ctxt, instance_ref, host=None): def recover_live_migration(self, ctxt, instance_ref, host=None, dest=None):
"""Recovers Instance/volume state from migrating -> running. """Recovers Instance/volume state from migrating -> running.
:param ctxt: security context :param ctxt: security context
:param instance_id: nova.db.sqlalchemy.models.Instance.Id :param instance_id: nova.db.sqlalchemy.models.Instance.Id
:param host: :param host: DB column value is updated by this hostname.
DB column value is updated by this hostname. If none, the host instance currently running is selected.
if none, the host instance currently running is selected.
""" """
if not host: if not host:
host = instance_ref['host'] host = instance_ref['host']
@ -1024,8 +1025,13 @@ class ComputeManager(manager.SchedulerDependentManager):
'state': power_state.RUNNING, 'state': power_state.RUNNING,
'host': host}) 'host': host})
for volume in instance_ref['volumes']: if dest:
self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'}) volume_api = volume.API()
for volume_ref in instance_ref['volumes']:
volume_id = volume_ref['id']
self.db.volume_update(ctxt, volume_id, {'status': 'in-use'})
if dest:
volume_api.remove_from_compute(ctxt, volume_id, dest)
def periodic_tasks(self, context=None): def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval.""" """Tasks to be run at a periodic interval."""

View File

@ -15,23 +15,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Handles ConsoleProxy API requests."""
Handles ConsoleProxy API requests
"""
from nova import exception from nova import exception
from nova.db import base
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova.db import base
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class API(base.Base): class API(base.Base):
"""API for spining up or down console proxy connections""" """API for spinning up or down console proxy connections."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(API, self).__init__(**kwargs) super(API, self).__init__(**kwargs)
@ -51,8 +47,8 @@ class API(base.Base):
self.db.queue_get_for(context, self.db.queue_get_for(context,
FLAGS.console_topic, FLAGS.console_topic,
pool['host']), pool['host']),
{"method": "remove_console", {'method': 'remove_console',
"args": {"console_id": console['id']}}) 'args': {'console_id': console['id']}})
def create_console(self, context, instance_id): def create_console(self, context, instance_id):
instance = self.db.instance_get(context, instance_id) instance = self.db.instance_get(context, instance_id)
@ -63,13 +59,12 @@ class API(base.Base):
# here. # here.
rpc.cast(context, rpc.cast(context,
self._get_console_topic(context, instance['host']), self._get_console_topic(context, instance['host']),
{"method": "add_console", {'method': 'add_console',
"args": {"instance_id": instance_id}}) 'args': {'instance_id': instance_id}})
def _get_console_topic(self, context, instance_host): def _get_console_topic(self, context, instance_host):
topic = self.db.queue_get_for(context, topic = self.db.queue_get_for(context,
FLAGS.compute_topic, FLAGS.compute_topic,
instance_host) instance_host)
return rpc.call(context, return rpc.call(context, topic, {'method': 'get_console_topic',
topic, 'args': {'fake': 1}})
{"method": "get_console_topic", "args": {'fake': 1}})

View File

@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Fake ConsoleProxy driver for tests."""
Fake ConsoleProxy driver for tests.
"""
from nova import exception from nova import exception
@ -27,32 +25,32 @@ class FakeConsoleProxy(object):
@property @property
def console_type(self): def console_type(self):
return "fake" return 'fake'
def setup_console(self, context, console): def setup_console(self, context, console):
"""Sets up actual proxies""" """Sets up actual proxies."""
pass pass
def teardown_console(self, context, console): def teardown_console(self, context, console):
"""Tears down actual proxies""" """Tears down actual proxies."""
pass pass
def init_host(self): def init_host(self):
"""Start up any config'ed consoles on start""" """Start up any config'ed consoles on start."""
pass pass
def generate_password(self, length=8): def generate_password(self, length=8):
"""Returns random console password""" """Returns random console password."""
return "fakepass" return 'fakepass'
def get_port(self, context): def get_port(self, context):
"""get available port for consoles that need one""" """Get available port for consoles that need one."""
return 5999 return 5999
def fix_pool_password(self, password): def fix_pool_password(self, password):
"""Trim password to length, and any other massaging""" """Trim password to length, and any other massaging."""
return password return password
def fix_console_password(self, password): def fix_console_password(self, password):
"""Trim password to length, and any other massaging""" """Trim password to length, and any other massaging."""
return password return password

View File

@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Console Proxy Service."""
Console Proxy Service
"""
import functools import functools
import socket import socket
@ -29,6 +27,7 @@ from nova import manager
from nova import rpc from nova import rpc
from nova import utils from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('console_driver', flags.DEFINE_string('console_driver',
'nova.console.xvp.XVPConsoleProxy', 'nova.console.xvp.XVPConsoleProxy',
@ -41,9 +40,11 @@ flags.DEFINE_string('console_public_hostname',
class ConsoleProxyManager(manager.Manager): class ConsoleProxyManager(manager.Manager):
"""Sets up and tears down any console proxy connections.
""" Sets up and tears down any proxy connections needed for accessing Needed for accessing instance consoles securely.
instance consoles securely"""
"""
def __init__(self, console_driver=None, *args, **kwargs): def __init__(self, console_driver=None, *args, **kwargs):
if not console_driver: if not console_driver:
@ -67,7 +68,7 @@ class ConsoleProxyManager(manager.Manager):
pool['id'], pool['id'],
instance_id) instance_id)
except exception.NotFound: except exception.NotFound:
logging.debug(_("Adding console")) logging.debug(_('Adding console'))
if not password: if not password:
password = utils.generate_password(8) password = utils.generate_password(8)
if not port: if not port:
@ -115,8 +116,8 @@ class ConsoleProxyManager(manager.Manager):
self.db.queue_get_for(context, self.db.queue_get_for(context,
FLAGS.compute_topic, FLAGS.compute_topic,
instance_host), instance_host),
{"method": "get_console_pool_info", {'method': 'get_console_pool_info',
"args": {"console_type": console_type}}) 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password( pool_info['password'] = self.driver.fix_pool_password(
pool_info['password']) pool_info['password'])
pool_info['host'] = self.host pool_info['host'] = self.host

View File

@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """VMRC console drivers."""
VMRC console drivers.
"""
import base64 import base64
import json import json
@ -27,6 +25,8 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi import vim_util
FLAGS = flags.FLAGS
flags.DEFINE_integer('console_vmrc_port', flags.DEFINE_integer('console_vmrc_port',
443, 443,
"port for VMware VMRC connections") "port for VMware VMRC connections")
@ -34,8 +34,6 @@ flags.DEFINE_integer('console_vmrc_error_retries',
10, 10,
"number of retries for retrieving VMRC information") "number of retries for retrieving VMRC information")
FLAGS = flags.FLAGS
class VMRCConsole(object): class VMRCConsole(object):
"""VMRC console driver with ESX credentials.""" """VMRC console driver with ESX credentials."""
@ -69,34 +67,34 @@ class VMRCConsole(object):
return password return password
def generate_password(self, vim_session, pool, instance_name): def generate_password(self, vim_session, pool, instance_name):
""" """Returns VMRC Connection credentials.
Returns VMRC Connection credentials.
Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'. Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'.
""" """
username, password = pool['username'], pool['password'] username, password = pool['username'], pool['password']
vms = vim_session._call_method(vim_util, "get_objects", vms = vim_session._call_method(vim_util, 'get_objects',
"VirtualMachine", ["name", "config.files.vmPathName"]) 'VirtualMachine', ['name', 'config.files.vmPathName'])
vm_ds_path_name = None vm_ds_path_name = None
vm_ref = None vm_ref = None
for vm in vms: for vm in vms:
vm_name = None vm_name = None
ds_path_name = None ds_path_name = None
for prop in vm.propSet: for prop in vm.propSet:
if prop.name == "name": if prop.name == 'name':
vm_name = prop.val vm_name = prop.val
elif prop.name == "config.files.vmPathName": elif prop.name == 'config.files.vmPathName':
ds_path_name = prop.val ds_path_name = prop.val
if vm_name == instance_name: if vm_name == instance_name:
vm_ref = vm.obj vm_ref = vm.obj
vm_ds_path_name = ds_path_name vm_ds_path_name = ds_path_name
break break
if vm_ref is None: if vm_ref is None:
raise exception.NotFound(_("instance - %s not present") % raise exception.NotFound(_('instance - %s not present') %
instance_name) instance_name)
json_data = json.dumps({"vm_id": vm_ds_path_name, json_data = json.dumps({'vm_id': vm_ds_path_name,
"username": username, 'username': username,
"password": password}) 'password': password})
return base64.b64encode(json_data) return base64.b64encode(json_data)
def is_otp(self): def is_otp(self):
@ -115,28 +113,28 @@ class VMRCSessionConsole(VMRCConsole):
return 'vmrc+session' return 'vmrc+session'
def generate_password(self, vim_session, pool, instance_name): def generate_password(self, vim_session, pool, instance_name):
""" """Returns a VMRC Session.
Returns a VMRC Session.
Return string is of the form '<VM MOID>:<VMRC Ticket>'. Return string is of the form '<VM MOID>:<VMRC Ticket>'.
""" """
vms = vim_session._call_method(vim_util, "get_objects", vms = vim_session._call_method(vim_util, 'get_objects',
"VirtualMachine", ["name"]) 'VirtualMachine', ['name'])
vm_ref = None vm_ref = NoneV
for vm in vms: for vm in vms:
if vm.propSet[0].val == instance_name: if vm.propSet[0].val == instance_name:
vm_ref = vm.obj vm_ref = vm.obj
if vm_ref is None: if vm_ref is None:
raise exception.NotFound(_("instance - %s not present") % raise exception.NotFound(_('instance - %s not present') %
instance_name) instance_name)
virtual_machine_ticket = \ virtual_machine_ticket = \
vim_session._call_method( vim_session._call_method(
vim_session._get_vim(), vim_session._get_vim(),
"AcquireCloneTicket", 'AcquireCloneTicket',
vim_session._get_vim().get_service_content().sessionManager) vim_session._get_vim().get_service_content().sessionManager)
json_data = json.dumps({"vm_id": str(vm_ref.value), json_data = json.dumps({'vm_id': str(vm_ref.value),
"username": virtual_machine_ticket, 'username': virtual_machine_ticket,
"password": virtual_machine_ticket}) 'password': virtual_machine_ticket})
return base64.b64encode(json_data) return base64.b64encode(json_data)
def is_otp(self): def is_otp(self):

View File

@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """VMRC Console Manager."""
VMRC Console Manager.
"""
from nova import exception from nova import exception
from nova import flags from nova import flags
@ -25,24 +23,21 @@ from nova import log as logging
from nova import manager from nova import manager
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova.virt.vmwareapi_conn import VMWareAPISession from nova.virt import vmwareapi_conn
LOG = logging.getLogger("nova.console.vmrc_manager") LOG = logging.getLogger("nova.console.vmrc_manager")
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('console_public_hostname', flags.DEFINE_string('console_public_hostname', '',
'',
'Publicly visible name for this console host') 'Publicly visible name for this console host')
flags.DEFINE_string('console_driver', flags.DEFINE_string('console_driver', 'nova.console.vmrc.VMRCConsole',
'nova.console.vmrc.VMRCConsole',
'Driver to use for the console') 'Driver to use for the console')
class ConsoleVMRCManager(manager.Manager): class ConsoleVMRCManager(manager.Manager):
"""Manager to handle VMRC connections for accessing instance consoles."""
"""
Manager to handle VMRC connections needed for accessing instance consoles.
"""
def __init__(self, console_driver=None, *args, **kwargs): def __init__(self, console_driver=None, *args, **kwargs):
self.driver = utils.import_object(FLAGS.console_driver) self.driver = utils.import_object(FLAGS.console_driver)
@ -56,16 +51,17 @@ class ConsoleVMRCManager(manager.Manager):
"""Get VIM session for the pool specified.""" """Get VIM session for the pool specified."""
vim_session = None vim_session = None
if pool['id'] not in self.sessions.keys(): if pool['id'] not in self.sessions.keys():
vim_session = VMWareAPISession(pool['address'], vim_session = vmwareapi_conn.VMWareAPISession(
pool['username'], pool['address'],
pool['password'], pool['username'],
FLAGS.console_vmrc_error_retries) pool['password'],
FLAGS.console_vmrc_error_retries)
self.sessions[pool['id']] = vim_session self.sessions[pool['id']] = vim_session
return self.sessions[pool['id']] return self.sessions[pool['id']]
def _generate_console(self, context, pool, name, instance_id, instance): def _generate_console(self, context, pool, name, instance_id, instance):
"""Sets up console for the instance.""" """Sets up console for the instance."""
LOG.debug(_("Adding console")) LOG.debug(_('Adding console'))
password = self.driver.generate_password( password = self.driver.generate_password(
self._get_vim_session(pool), self._get_vim_session(pool),
@ -84,9 +80,10 @@ class ConsoleVMRCManager(manager.Manager):
@exception.wrap_exception @exception.wrap_exception
def add_console(self, context, instance_id, password=None, def add_console(self, context, instance_id, password=None,
port=None, **kwargs): port=None, **kwargs):
""" """Adds a console for the instance.
Adds a console for the instance. If it is one time password, then we
generate new console credentials. If it is one time password, then we generate new console credentials.
""" """
instance = self.db.instance_get(context, instance_id) instance = self.db.instance_get(context, instance_id)
host = instance['host'] host = instance['host']
@ -97,19 +94,17 @@ class ConsoleVMRCManager(manager.Manager):
pool['id'], pool['id'],
instance_id) instance_id)
if self.driver.is_otp(): if self.driver.is_otp():
console = self._generate_console( console = self._generate_console(context,
context, pool,
pool, name,
name, instance_id,
instance_id, instance)
instance)
except exception.NotFound: except exception.NotFound:
console = self._generate_console( console = self._generate_console(context,
context, pool,
pool, name,
name, instance_id,
instance_id, instance)
instance)
return console['id'] return console['id']
@exception.wrap_exception @exception.wrap_exception
@ -118,13 +113,11 @@ class ConsoleVMRCManager(manager.Manager):
try: try:
console = self.db.console_get(context, console_id) console = self.db.console_get(context, console_id)
except exception.NotFound: except exception.NotFound:
LOG.debug(_("Tried to remove non-existent console " LOG.debug(_('Tried to remove non-existent console '
"%(console_id)s.") % '%(console_id)s.') % {'console_id': console_id})
{'console_id': console_id})
return return
LOG.debug(_("Removing console " LOG.debug(_('Removing console '
"%(console_id)s.") % '%(console_id)s.') % {'console_id': console_id})
{'console_id': console_id})
self.db.console_delete(context, console_id) self.db.console_delete(context, console_id)
self.driver.teardown_console(context, console) self.driver.teardown_console(context, console)
@ -139,11 +132,11 @@ class ConsoleVMRCManager(manager.Manager):
console_type) console_type)
except exception.NotFound: except exception.NotFound:
pool_info = rpc.call(context, pool_info = rpc.call(context,
self.db.queue_get_for(context, self.db.queue_get_for(context,
FLAGS.compute_topic, FLAGS.compute_topic,
instance_host), instance_host),
{"method": "get_console_pool_info", {'method': 'get_console_pool_info',
"args": {"console_type": console_type}}) 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password( pool_info['password'] = self.driver.fix_pool_password(
pool_info['password']) pool_info['password'])
pool_info['host'] = self.host pool_info['host'] = self.host

View File

@ -15,16 +15,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """XVP (Xenserver VNC Proxy) driver."""
XVP (Xenserver VNC Proxy) driver.
"""
import fcntl import fcntl
import os import os
import signal import signal
import subprocess import subprocess
from Cheetah.Template import Template from Cheetah import Template
from nova import context from nova import context
from nova import db from nova import db
@ -33,6 +31,8 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova import utils from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('console_xvp_conf_template', flags.DEFINE_string('console_xvp_conf_template',
utils.abspath('console/xvp.conf.template'), utils.abspath('console/xvp.conf.template'),
'XVP conf template') 'XVP conf template')
@ -47,12 +47,11 @@ flags.DEFINE_string('console_xvp_log',
'XVP log file') 'XVP log file')
flags.DEFINE_integer('console_xvp_multiplex_port', flags.DEFINE_integer('console_xvp_multiplex_port',
5900, 5900,
"port for XVP to multiplex VNC connections on") 'port for XVP to multiplex VNC connections on')
FLAGS = flags.FLAGS
class XVPConsoleProxy(object): class XVPConsoleProxy(object):
"""Sets up XVP config, and manages xvp daemon""" """Sets up XVP config, and manages XVP daemon."""
def __init__(self): def __init__(self):
self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
@ -61,50 +60,51 @@ class XVPConsoleProxy(object):
@property @property
def console_type(self): def console_type(self):
return "vnc+xvp" return 'vnc+xvp'
def get_port(self, context): def get_port(self, context):
"""get available port for consoles that need one""" """Get available port for consoles that need one."""
#TODO(mdragon): implement port selection for non multiplex ports, #TODO(mdragon): implement port selection for non multiplex ports,
# we are not using that, but someone else may want # we are not using that, but someone else may want
# it. # it.
return FLAGS.console_xvp_multiplex_port return FLAGS.console_xvp_multiplex_port
def setup_console(self, context, console): def setup_console(self, context, console):
"""Sets up actual proxies""" """Sets up actual proxies."""
self._rebuild_xvp_conf(context.elevated()) self._rebuild_xvp_conf(context.elevated())
def teardown_console(self, context, console): def teardown_console(self, context, console):
"""Tears down actual proxies""" """Tears down actual proxies."""
self._rebuild_xvp_conf(context.elevated()) self._rebuild_xvp_conf(context.elevated())
def init_host(self): def init_host(self):
"""Start up any config'ed consoles on start""" """Start up any config'ed consoles on start."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
self._rebuild_xvp_conf(ctxt) self._rebuild_xvp_conf(ctxt)
def fix_pool_password(self, password): def fix_pool_password(self, password):
"""Trim password to length, and encode""" """Trim password to length, and encode."""
return self._xvp_encrypt(password, is_pool_password=True) return self._xvp_encrypt(password, is_pool_password=True)
def fix_console_password(self, password): def fix_console_password(self, password):
"""Trim password to length, and encode""" """Trim password to length, and encode."""
return self._xvp_encrypt(password) return self._xvp_encrypt(password)
def _rebuild_xvp_conf(self, context): def _rebuild_xvp_conf(self, context):
logging.debug(_("Rebuilding xvp conf")) logging.debug(_('Rebuilding xvp conf'))
pools = [pool for pool in pools = [pool for pool in
db.console_pool_get_all_by_host_type(context, self.host, db.console_pool_get_all_by_host_type(context, self.host,
self.console_type) self.console_type)
if pool['consoles']] if pool['consoles']]
if not pools: if not pools:
logging.debug("No console pools!") logging.debug('No console pools!')
self._xvp_stop() self._xvp_stop()
return return
conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
'pools': pools, 'pools': pools,
'pass_encode': self.fix_console_password} 'pass_encode': self.fix_console_password}
config = str(Template(self.xvpconf_template, searchList=[conf_data])) config = str(Template.Template(self.xvpconf_template,
searchList=[conf_data]))
self._write_conf(config) self._write_conf(config)
self._xvp_restart() self._xvp_restart()
@ -114,7 +114,7 @@ class XVPConsoleProxy(object):
cfile.write(config) cfile.write(config)
def _xvp_stop(self): def _xvp_stop(self):
logging.debug(_("Stopping xvp")) logging.debug(_('Stopping xvp'))
pid = self._xvp_pid() pid = self._xvp_pid()
if not pid: if not pid:
return return
@ -127,19 +127,19 @@ class XVPConsoleProxy(object):
def _xvp_start(self): def _xvp_start(self):
if self._xvp_check_running(): if self._xvp_check_running():
return return
logging.debug(_("Starting xvp")) logging.debug(_('Starting xvp'))
try: try:
utils.execute('xvp', utils.execute('xvp',
'-p', FLAGS.console_xvp_pid, '-p', FLAGS.console_xvp_pid,
'-c', FLAGS.console_xvp_conf, '-c', FLAGS.console_xvp_conf,
'-l', FLAGS.console_xvp_log) '-l', FLAGS.console_xvp_log)
except exception.ProcessExecutionError, err: except exception.ProcessExecutionError, err:
logging.error(_("Error starting xvp: %s") % err) logging.error(_('Error starting xvp: %s') % err)
def _xvp_restart(self): def _xvp_restart(self):
logging.debug(_("Restarting xvp")) logging.debug(_('Restarting xvp'))
if not self._xvp_check_running(): if not self._xvp_check_running():
logging.debug(_("xvp not running...")) logging.debug(_('xvp not running...'))
self._xvp_start() self._xvp_start()
else: else:
pid = self._xvp_pid() pid = self._xvp_pid()
@ -178,7 +178,9 @@ class XVPConsoleProxy(object):
Note that xvp's obfuscation should not be considered 'real' encryption. Note that xvp's obfuscation should not be considered 'real' encryption.
It simply DES encrypts the passwords with static keys plainly viewable It simply DES encrypts the passwords with static keys plainly viewable
in the xvp source code.""" in the xvp source code.
"""
maxlen = 8 maxlen = 8
flag = '-e' flag = '-e'
if is_pool_password: if is_pool_password:

View File

@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """RequestContext: context for requests that persist through all of nova."""
RequestContext: context for requests that persist through all of nova.
"""
import datetime import datetime
import random import random
@ -28,6 +26,12 @@ from nova import utils
class RequestContext(object): class RequestContext(object):
"""Security context and request information.
Represents the user taking a given action within the system.
"""
def __init__(self, user, project, is_admin=None, read_deleted=False, def __init__(self, user, project, is_admin=None, read_deleted=False,
remote_address=None, timestamp=None, request_id=None): remote_address=None, timestamp=None, request_id=None):
if hasattr(user, 'id'): if hasattr(user, 'id'):

View File

@ -15,10 +15,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""
Wrappers around standard crypto data elements. """Wrappers around standard crypto data elements.
Includes root and intermediate CAs, SSH key_pairs and x509 certificates. Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
""" """
import base64 import base64
@ -43,6 +44,8 @@ from nova import log as logging
LOG = logging.getLogger("nova.crypto") LOG = logging.getLogger("nova.crypto")
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA')) flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA'))
flags.DEFINE_string('key_file', flags.DEFINE_string('key_file',
@ -90,13 +93,13 @@ def key_path(project_id=None):
def fetch_ca(project_id=None, chain=True): def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_project_ca: if not FLAGS.use_project_ca:
project_id = None project_id = None
buffer = "" buffer = ''
if project_id: if project_id:
with open(ca_path(project_id), "r") as cafile: with open(ca_path(project_id), 'r') as cafile:
buffer += cafile.read() buffer += cafile.read()
if not chain: if not chain:
return buffer return buffer
with open(ca_path(None), "r") as cafile: with open(ca_path(None), 'r') as cafile:
buffer += cafile.read() buffer += cafile.read()
return buffer return buffer
@ -143,7 +146,7 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
def revoke_cert(project_id, file_name): def revoke_cert(project_id, file_name):
"""Revoke a cert by file name""" """Revoke a cert by file name."""
start = os.getcwd() start = os.getcwd()
os.chdir(ca_folder(project_id)) os.chdir(ca_folder(project_id))
# NOTE(vish): potential race condition here # NOTE(vish): potential race condition here
@ -155,14 +158,14 @@ def revoke_cert(project_id, file_name):
def revoke_certs_by_user(user_id): def revoke_certs_by_user(user_id):
"""Revoke all user certs""" """Revoke all user certs."""
admin = context.get_admin_context() admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id): for cert in db.certificate_get_all_by_user(admin, user_id):
revoke_cert(cert['project_id'], cert['file_name']) revoke_cert(cert['project_id'], cert['file_name'])
def revoke_certs_by_project(project_id): def revoke_certs_by_project(project_id):
"""Revoke all project certs""" """Revoke all project certs."""
# NOTE(vish): This is somewhat useless because we can just shut down # NOTE(vish): This is somewhat useless because we can just shut down
# the vpn. # the vpn.
admin = context.get_admin_context() admin = context.get_admin_context()
@ -171,29 +174,29 @@ def revoke_certs_by_project(project_id):
def revoke_certs_by_user_and_project(user_id, project_id): def revoke_certs_by_user_and_project(user_id, project_id):
"""Revoke certs for user in project""" """Revoke certs for user in project."""
admin = context.get_admin_context() admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id, project_id): for cert in db.certificate_get_all_by_user(admin, user_id, project_id):
revoke_cert(cert['project_id'], cert['file_name']) revoke_cert(cert['project_id'], cert['file_name'])
def _project_cert_subject(project_id): def _project_cert_subject(project_id):
"""Helper to generate user cert subject""" """Helper to generate user cert subject."""
return FLAGS.project_cert_subject % (project_id, utils.isotime()) return FLAGS.project_cert_subject % (project_id, utils.isotime())
def _vpn_cert_subject(project_id): def _vpn_cert_subject(project_id):
"""Helper to generate user cert subject""" """Helper to generate user cert subject."""
return FLAGS.vpn_cert_subject % (project_id, utils.isotime()) return FLAGS.vpn_cert_subject % (project_id, utils.isotime())
def _user_cert_subject(user_id, project_id): def _user_cert_subject(user_id, project_id):
"""Helper to generate user cert subject""" """Helper to generate user cert subject."""
return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime()) return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime())
def generate_x509_cert(user_id, project_id, bits=1024): def generate_x509_cert(user_id, project_id, bits=1024):
"""Generate and sign a cert for user in project""" """Generate and sign a cert for user in project."""
subject = _user_cert_subject(user_id, project_id) subject = _user_cert_subject(user_id, project_id)
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key')) keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
@ -205,7 +208,7 @@ def generate_x509_cert(user_id, project_id, bits=1024):
csr = open(csrfile).read() csr = open(csrfile).read()
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
(serial, signed_csr) = sign_csr(csr, project_id) (serial, signed_csr) = sign_csr(csr, project_id)
fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial) fname = os.path.join(ca_folder(project_id), 'newcerts/%s.pem' % serial)
cert = {'user_id': user_id, cert = {'user_id': user_id,
'project_id': project_id, 'project_id': project_id,
'file_name': fname} 'file_name': fname}
@ -227,8 +230,8 @@ def _ensure_project_folder(project_id):
def generate_vpn_files(project_id): def generate_vpn_files(project_id):
project_folder = ca_folder(project_id) project_folder = ca_folder(project_id)
csr_fn = os.path.join(project_folder, "server.csr") csr_fn = os.path.join(project_folder, 'server.csr')
crt_fn = os.path.join(project_folder, "server.crt") crt_fn = os.path.join(project_folder, 'server.crt')
genvpn_sh_path = os.path.join(os.path.dirname(__file__), genvpn_sh_path = os.path.join(os.path.dirname(__file__),
'CA', 'CA',
@ -241,10 +244,10 @@ def generate_vpn_files(project_id):
# TODO(vish): the shell scripts could all be done in python # TODO(vish): the shell scripts could all be done in python
utils.execute('sh', genvpn_sh_path, utils.execute('sh', genvpn_sh_path,
project_id, _vpn_cert_subject(project_id)) project_id, _vpn_cert_subject(project_id))
with open(csr_fn, "r") as csrfile: with open(csr_fn, 'r') as csrfile:
csr_text = csrfile.read() csr_text = csrfile.read()
(serial, signed_csr) = sign_csr(csr_text, project_id) (serial, signed_csr) = sign_csr(csr_text, project_id)
with open(crt_fn, "w") as crtfile: with open(crt_fn, 'w') as crtfile:
crtfile.write(signed_csr) crtfile.write(signed_csr)
os.chdir(start) os.chdir(start)
@ -261,12 +264,12 @@ def sign_csr(csr_text, project_id=None):
def _sign_csr(csr_text, ca_folder): def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp() tmpfolder = tempfile.mkdtemp()
inbound = os.path.join(tmpfolder, "inbound.csr") inbound = os.path.join(tmpfolder, 'inbound.csr')
outbound = os.path.join(tmpfolder, "outbound.csr") outbound = os.path.join(tmpfolder, 'outbound.csr')
csrfile = open(inbound, "w") csrfile = open(inbound, 'w')
csrfile.write(csr_text) csrfile.write(csr_text)
csrfile.close() csrfile.close()
LOG.debug(_("Flags path: %s"), ca_folder) LOG.debug(_('Flags path: %s'), ca_folder)
start = os.getcwd() start = os.getcwd()
# Change working dir to CA # Change working dir to CA
if not os.path.exists(ca_folder): if not os.path.exists(ca_folder):
@ -276,13 +279,13 @@ def _sign_csr(csr_text, ca_folder):
'./openssl.cnf', '-infiles', inbound) './openssl.cnf', '-infiles', inbound)
out, _err = utils.execute('openssl', 'x509', '-in', outbound, out, _err = utils.execute('openssl', 'x509', '-in', outbound,
'-serial', '-noout') '-serial', '-noout')
serial = string.strip(out.rpartition("=")[2]) serial = string.strip(out.rpartition('=')[2])
os.chdir(start) os.chdir(start)
with open(outbound, "r") as crtfile: with open(outbound, 'r') as crtfile:
return (serial, crtfile.read()) return (serial, crtfile.read())
def mkreq(bits, subject="foo", ca=0): def mkreq(bits, subject='foo', ca=0):
pk = M2Crypto.EVP.PKey() pk = M2Crypto.EVP.PKey()
req = M2Crypto.X509.Request() req = M2Crypto.X509.Request()
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None) rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
@ -314,7 +317,7 @@ def mkcacert(subject='nova', years=1):
cert.set_not_before(now) cert.set_not_before(now)
cert.set_not_after(nowPlusYear) cert.set_not_after(nowPlusYear)
issuer = M2Crypto.X509.X509_Name() issuer = M2Crypto.X509.X509_Name()
issuer.C = "US" issuer.C = 'US'
issuer.CN = subject issuer.CN = subject
cert.set_issuer(issuer) cert.set_issuer(issuer)
cert.set_pubkey(pkey) cert.set_pubkey(pkey)
@ -352,13 +355,15 @@ def mkcacert(subject='nova', years=1):
# http://code.google.com/p/boto # http://code.google.com/p/boto
def compute_md5(fp): def compute_md5(fp):
""" """Compute an md5 hash.
:type fp: file :type fp: file
:param fp: File pointer to the file to MD5 hash. The file pointer will be :param fp: File pointer to the file to MD5 hash. The file pointer will be
reset to the beginning of the file before the method returns. reset to the beginning of the file before the method returns.
:rtype: tuple :rtype: tuple
:return: the hex digest version of the MD5 hash :returns: the hex digest version of the MD5 hash
""" """
m = hashlib.md5() m = hashlib.md5()
fp.seek(0) fp.seek(0)

View File

@ -15,8 +15,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""
Defines interface for DB access. """Defines interface for DB access.
The underlying driver is loaded as a :class:`LazyPluggable`. The underlying driver is loaded as a :class:`LazyPluggable`.
@ -30,6 +30,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`.
:enable_new_services: when adding a new service to the database, is it in the :enable_new_services: when adding a new service to the database, is it in the
pool of available hardware (Default: True) pool of available hardware (Default: True)
""" """
from nova import exception from nova import exception
@ -86,7 +87,7 @@ def service_get(context, service_id):
def service_get_by_host_and_topic(context, host, topic): def service_get_by_host_and_topic(context, host, topic):
"""Get a service by host it's on and topic it listens to""" """Get a service by host it's on and topic it listens to."""
return IMPL.service_get_by_host_and_topic(context, host, topic) return IMPL.service_get_by_host_and_topic(context, host, topic)
@ -113,7 +114,7 @@ def service_get_all_compute_by_host(context, host):
def service_get_all_compute_sorted(context): def service_get_all_compute_sorted(context):
"""Get all compute services sorted by instance count. """Get all compute services sorted by instance count.
Returns a list of (Service, instance_count) tuples. :returns: a list of (Service, instance_count) tuples.
""" """
return IMPL.service_get_all_compute_sorted(context) return IMPL.service_get_all_compute_sorted(context)
@ -122,7 +123,7 @@ def service_get_all_compute_sorted(context):
def service_get_all_network_sorted(context): def service_get_all_network_sorted(context):
"""Get all network services sorted by network count. """Get all network services sorted by network count.
Returns a list of (Service, network_count) tuples. :returns: a list of (Service, network_count) tuples.
""" """
return IMPL.service_get_all_network_sorted(context) return IMPL.service_get_all_network_sorted(context)
@ -131,7 +132,7 @@ def service_get_all_network_sorted(context):
def service_get_all_volume_sorted(context): def service_get_all_volume_sorted(context):
"""Get all volume services sorted by volume count. """Get all volume services sorted by volume count.
Returns a list of (Service, volume_count) tuples. :returns: a list of (Service, volume_count) tuples.
""" """
return IMPL.service_get_all_volume_sorted(context) return IMPL.service_get_all_volume_sorted(context)
@ -241,7 +242,7 @@ def floating_ip_count_by_project(context, project_id):
def floating_ip_deallocate(context, address): def floating_ip_deallocate(context, address):
"""Deallocate an floating ip by address""" """Deallocate an floating ip by address."""
return IMPL.floating_ip_deallocate(context, address) return IMPL.floating_ip_deallocate(context, address)
@ -253,7 +254,7 @@ def floating_ip_destroy(context, address):
def floating_ip_disassociate(context, address): def floating_ip_disassociate(context, address):
"""Disassociate an floating ip from a fixed ip by address. """Disassociate an floating ip from a fixed ip by address.
Returns the address of the existing fixed ip. :returns: the address of the existing fixed ip.
""" """
return IMPL.floating_ip_disassociate(context, address) return IMPL.floating_ip_disassociate(context, address)
@ -291,25 +292,30 @@ def floating_ip_update(context, address, values):
return IMPL.floating_ip_update(context, address, values) return IMPL.floating_ip_update(context, address, values)
def floating_ip_set_auto_assigned(context, address):
"""Set auto_assigned flag to floating ip"""
return IMPL.floating_ip_set_auto_assigned(context, address)
#################### ####################
def migration_update(context, id, values): def migration_update(context, id, values):
"""Update a migration instance""" """Update a migration instance."""
return IMPL.migration_update(context, id, values) return IMPL.migration_update(context, id, values)
def migration_create(context, values): def migration_create(context, values):
"""Create a migration record""" """Create a migration record."""
return IMPL.migration_create(context, values) return IMPL.migration_create(context, values)
def migration_get(context, migration_id): def migration_get(context, migration_id):
"""Finds a migration by the id""" """Finds a migration by the id."""
return IMPL.migration_get(context, migration_id) return IMPL.migration_get(context, migration_id)
def migration_get_by_instance_and_status(context, instance_id, status): def migration_get_by_instance_and_status(context, instance_id, status):
"""Finds a migration by the instance id its migrating""" """Finds a migration by the instance id its migrating."""
return IMPL.migration_get_by_instance_and_status(context, instance_id, return IMPL.migration_get_by_instance_and_status(context, instance_id,
status) status)
@ -455,11 +461,6 @@ def instance_get_project_vpn(context, project_id):
return IMPL.instance_get_project_vpn(context, project_id) return IMPL.instance_get_project_vpn(context, project_id)
def instance_is_vpn(context, instance_id):
"""True if instance is a vpn."""
return IMPL.instance_is_vpn(context, instance_id)
def instance_set_state(context, instance_id, state, description=None): def instance_set_state(context, instance_id, state, description=None):
"""Set the state of an instance.""" """Set the state of an instance."""
return IMPL.instance_set_state(context, instance_id, state, description) return IMPL.instance_set_state(context, instance_id, state, description)
@ -579,7 +580,9 @@ def network_create_safe(context, values):
def network_delete_safe(context, network_id): def network_delete_safe(context, network_id):
"""Delete network with key network_id. """Delete network with key network_id.
This method assumes that the network is not associated with any project This method assumes that the network is not associated with any project
""" """
return IMPL.network_delete_safe(context, network_id) return IMPL.network_delete_safe(context, network_id)
@ -674,7 +677,6 @@ def project_get_network(context, project_id, associate=True):
network if one is not found, otherwise it returns None. network if one is not found, otherwise it returns None.
""" """
return IMPL.project_get_network(context, project_id, associate) return IMPL.project_get_network(context, project_id, associate)
@ -722,7 +724,9 @@ def iscsi_target_create_safe(context, values):
The device is not returned. If the create violates the unique The device is not returned. If the create violates the unique
constraints because the iscsi_target and host already exist, constraints because the iscsi_target and host already exist,
no exception is raised.""" no exception is raised.
"""
return IMPL.iscsi_target_create_safe(context, values) return IMPL.iscsi_target_create_safe(context, values)
@ -1050,10 +1054,7 @@ def project_delete(context, project_id):
def host_get_networks(context, host): def host_get_networks(context, host):
"""Return all networks for which the given host is the designated """All networks for which the given host is the network host."""
network host.
"""
return IMPL.host_get_networks(context, host) return IMPL.host_get_networks(context, host)
@ -1115,38 +1116,40 @@ def console_get(context, console_id, instance_id=None):
def instance_type_create(context, values): def instance_type_create(context, values):
"""Create a new instance type""" """Create a new instance type."""
return IMPL.instance_type_create(context, values) return IMPL.instance_type_create(context, values)
def instance_type_get_all(context, inactive=False): def instance_type_get_all(context, inactive=False):
"""Get all instance types""" """Get all instance types."""
return IMPL.instance_type_get_all(context, inactive) return IMPL.instance_type_get_all(context, inactive)
def instance_type_get_by_id(context, id): def instance_type_get_by_id(context, id):
"""Get instance type by id""" """Get instance type by id."""
return IMPL.instance_type_get_by_id(context, id) return IMPL.instance_type_get_by_id(context, id)
def instance_type_get_by_name(context, name): def instance_type_get_by_name(context, name):
"""Get instance type by name""" """Get instance type by name."""
return IMPL.instance_type_get_by_name(context, name) return IMPL.instance_type_get_by_name(context, name)
def instance_type_get_by_flavor_id(context, id): def instance_type_get_by_flavor_id(context, id):
"""Get instance type by name""" """Get instance type by name."""
return IMPL.instance_type_get_by_flavor_id(context, id) return IMPL.instance_type_get_by_flavor_id(context, id)
def instance_type_destroy(context, name): def instance_type_destroy(context, name):
"""Delete a instance type""" """Delete a instance type."""
return IMPL.instance_type_destroy(context, name) return IMPL.instance_type_destroy(context, name)
def instance_type_purge(context, name): def instance_type_purge(context, name):
"""Purges (removes) an instance type from DB """Purges (removes) an instance type from DB.
Use instance_type_destroy for most cases
Use instance_type_destroy for most cases
""" """
return IMPL.instance_type_purge(context, name) return IMPL.instance_type_purge(context, name)
@ -1183,15 +1186,15 @@ def zone_get_all(context):
def instance_metadata_get(context, instance_id): def instance_metadata_get(context, instance_id):
"""Get all metadata for an instance""" """Get all metadata for an instance."""
return IMPL.instance_metadata_get(context, instance_id) return IMPL.instance_metadata_get(context, instance_id)
def instance_metadata_delete(context, instance_id, key): def instance_metadata_delete(context, instance_id, key):
"""Delete the given metadata item""" """Delete the given metadata item."""
IMPL.instance_metadata_delete(context, instance_id, key) IMPL.instance_metadata_delete(context, instance_id, key)
def instance_metadata_update_or_create(context, instance_id, metadata): def instance_metadata_update_or_create(context, instance_id, metadata):
"""Create or update instance metadata""" """Create or update instance metadata."""
IMPL.instance_metadata_update_or_create(context, instance_id, metadata) IMPL.instance_metadata_update_or_create(context, instance_id, metadata)

View File

@ -16,20 +16,20 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Base class for classes that need modular database access."""
Base class for classes that need modular database access.
"""
from nova import utils from nova import utils
from nova import flags from nova import flags
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('db_driver', 'nova.db.api', flags.DEFINE_string('db_driver', 'nova.db.api',
'driver to use for database access') 'driver to use for database access')
class Base(object): class Base(object):
"""DB driver is injected in the init method""" """DB driver is injected in the init method."""
def __init__(self, db_driver=None): def __init__(self, db_driver=None):
if not db_driver: if not db_driver:
db_driver = FLAGS.db_driver db_driver = FLAGS.db_driver

View File

@ -15,11 +15,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Database setup and migration commands.""" """Database setup and migration commands."""
from nova import flags from nova import flags
from nova import utils from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DECLARE('db_backend', 'nova.db.api') flags.DECLARE('db_backend', 'nova.db.api')

View File

@ -461,6 +461,7 @@ def floating_ip_count_by_project(context, project_id):
session = get_session() session = get_session()
return session.query(models.FloatingIp).\ return session.query(models.FloatingIp).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id).\
filter_by(auto_assigned=False).\
filter_by(deleted=False).\ filter_by(deleted=False).\
count() count()
@ -489,6 +490,7 @@ def floating_ip_deallocate(context, address):
address, address,
session=session) session=session)
floating_ip_ref['project_id'] = None floating_ip_ref['project_id'] = None
floating_ip_ref['auto_assigned'] = False
floating_ip_ref.save(session=session) floating_ip_ref.save(session=session)
@ -522,6 +524,17 @@ def floating_ip_disassociate(context, address):
return fixed_ip_address return fixed_ip_address
@require_context
def floating_ip_set_auto_assigned(context, address):
session = get_session()
with session.begin():
floating_ip_ref = floating_ip_get_by_address(context,
address,
session=session)
floating_ip_ref.auto_assigned = True
floating_ip_ref.save(session=session)
@require_admin_context @require_admin_context
def floating_ip_get_all(context): def floating_ip_get_all(context):
session = get_session() session = get_session()
@ -548,6 +561,7 @@ def floating_ip_get_all_by_project(context, project_id):
return session.query(models.FloatingIp).\ return session.query(models.FloatingIp).\
options(joinedload_all('fixed_ip.instance')).\ options(joinedload_all('fixed_ip.instance')).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id).\
filter_by(auto_assigned=False).\
filter_by(deleted=False).\ filter_by(deleted=False).\
all() all()
@ -941,7 +955,7 @@ def instance_get_project_vpn(context, project_id):
options(joinedload('security_groups')).\ options(joinedload('security_groups')).\
options(joinedload('instance_type')).\ options(joinedload('instance_type')).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id).\
filter_by(image_id=FLAGS.vpn_image_id).\ filter_by(image_id=str(FLAGS.vpn_image_id)).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
first() first()
@ -980,13 +994,6 @@ def instance_get_floating_address(context, instance_id):
return instance_ref.fixed_ip.floating_ips[0]['address'] return instance_ref.fixed_ip.floating_ips[0]['address']
@require_admin_context
def instance_is_vpn(context, instance_id):
# TODO(vish): Move this into image code somewhere
instance_ref = instance_get(context, instance_id)
return instance_ref['image_id'] == FLAGS.vpn_image_id
@require_admin_context @require_admin_context
def instance_set_state(context, instance_id, state, description=None): def instance_set_state(context, instance_id, state, description=None):
# TODO(devcamcar): Move this out of models and into driver # TODO(devcamcar): Move this out of models and into driver

View File

@ -54,10 +54,12 @@ def upgrade(migrate_engine):
instances.create_column(c_instance_type_id) instances.create_column(c_instance_type_id)
type_names = {}
recs = migrate_engine.execute(instance_types.select()) recs = migrate_engine.execute(instance_types.select())
for row in recs: for row in recs:
type_id = row[0] type_names[row[0]] = row[1]
type_name = row[1]
for type_id, type_name in type_names.iteritems():
migrate_engine.execute(instances.update()\ migrate_engine.execute(instances.update()\
.where(instances.c.instance_type == type_name)\ .where(instances.c.instance_type == type_name)\
.values(instance_type_id=type_id)) .values(instance_type_id=type_id))

View File

@ -0,0 +1,39 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Grid Dynamics
#
# 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 sqlalchemy import *
from sqlalchemy.sql import text
from migrate import *
meta = MetaData()
c_auto_assigned = Column('auto_assigned', Boolean, default=False)
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
floating_ips = Table('floating_ips',
meta,
autoload=True,
autoload_with=migrate_engine)
floating_ips.create_column(c_auto_assigned)

View File

@ -592,6 +592,7 @@ class FloatingIp(BASE, NovaBase):
'FloatingIp.deleted == False)') 'FloatingIp.deleted == False)')
project_id = Column(String(255)) project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id')) host = Column(String(255)) # , ForeignKey('hosts.id'))
auto_assigned = Column(Boolean, default=False, nullable=False)
class ConsolePool(BASE, NovaBase): class ConsolePool(BASE, NovaBase):

View File

@ -16,31 +16,34 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Nova base exception handling.
Nova base exception handling, including decorator for re-raising
Nova-type exceptions. SHOULD include dedicated exception logging. Includes decorator for re-raising Nova-type exceptions.
SHOULD include dedicated exception logging.
""" """
from nova import log as logging from nova import log as logging
LOG = logging.getLogger('nova.exception') LOG = logging.getLogger('nova.exception')
class ProcessExecutionError(IOError): class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None): description=None):
if description is None: if description is None:
description = _("Unexpected error while running command.") description = _('Unexpected error while running command.')
if exit_code is None: if exit_code is None:
exit_code = '-' exit_code = '-'
message = _("%(description)s\nCommand: %(cmd)s\n" message = _('%(description)s\nCommand: %(cmd)s\n'
"Exit code: %(exit_code)s\nStdout: %(stdout)r\n" 'Exit code: %(exit_code)s\nStdout: %(stdout)r\n'
"Stderr: %(stderr)r") % locals() 'Stderr: %(stderr)r') % locals()
IOError.__init__(self, message) IOError.__init__(self, message)
class Error(Exception): class Error(Exception):
def __init__(self, message=None): def __init__(self, message=None):
super(Error, self).__init__(message) super(Error, self).__init__(message)
@ -97,7 +100,7 @@ class TimeoutException(Error):
class DBError(Error): class DBError(Error):
"""Wraps an implementation specific exception""" """Wraps an implementation specific exception."""
def __init__(self, inner_exception): def __init__(self, inner_exception):
self.inner_exception = inner_exception self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception)) super(DBError, self).__init__(str(inner_exception))
@ -108,7 +111,7 @@ def wrap_db_error(f):
try: try:
return f(*args, **kwargs) return f(*args, **kwargs)
except Exception, e: except Exception, e:
LOG.exception(_('DB exception wrapped')) LOG.exception(_('DB exception wrapped.'))
raise DBError(e) raise DBError(e)
return _wrap return _wrap
_wrap.func_name = f.func_name _wrap.func_name = f.func_name

View File

@ -18,14 +18,14 @@
"""Super simple fake memcache client.""" """Super simple fake memcache client."""
import utils from nova import utils
class Client(object): class Client(object):
"""Replicates a tiny subset of memcached client interface.""" """Replicates a tiny subset of memcached client interface."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Ignores the passed in args""" """Ignores the passed in args."""
self.cache = {} self.cache = {}
def get(self, key): def get(self, key):

View File

@ -16,9 +16,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Command-line flag library.
Wraps gflags.
Package-level global flags are defined here, the rest are defined Package-level global flags are defined here, the rest are defined
where they're used. where they're used.
""" """
import getopt import getopt
@ -145,10 +149,12 @@ class FlagValues(gflags.FlagValues):
class StrWrapper(object): class StrWrapper(object):
"""Wrapper around FlagValues objects """Wrapper around FlagValues objects.
Wraps FlagValues objects for string.Template so that we're Wraps FlagValues objects for string.Template so that we're
sure to return strings.""" sure to return strings.
"""
def __init__(self, context_objs): def __init__(self, context_objs):
self.context_objs = context_objs self.context_objs = context_objs
@ -169,6 +175,7 @@ def _GetCallingModule():
We generally use this function to get the name of the module calling a We generally use this function to get the name of the module calling a
DEFINE_foo... function. DEFINE_foo... function.
""" """
# Walk down the stack to find the first globals dict that's not ours. # Walk down the stack to find the first globals dict that's not ours.
for depth in range(1, sys.getrecursionlimit()): for depth in range(1, sys.getrecursionlimit()):
@ -192,6 +199,7 @@ def __GetModuleName(globals_dict):
Returns: Returns:
A string (the name of the module) or None (if the module could not A string (the name of the module) or None (if the module could not
be identified. be identified.
""" """
for name, module in sys.modules.iteritems(): for name, module in sys.modules.iteritems():
if getattr(module, '__dict__', None) is globals_dict: if getattr(module, '__dict__', None) is globals_dict:
@ -316,7 +324,7 @@ DEFINE_string('null_kernel', 'nokernel',
'kernel image that indicates not to use a kernel,' 'kernel image that indicates not to use a kernel,'
' but to use a raw disk image instead') ' but to use a raw disk image instead')
DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server') DEFINE_integer('vpn_image_id', 0, 'integer id for cloudpipe vpn server')
DEFINE_string('vpn_key_suffix', DEFINE_string('vpn_key_suffix',
'-vpn', '-vpn',
'Suffix to add to project name for vpn key and secgroups') 'Suffix to add to project name for vpn key and secgroups')
@ -326,7 +334,7 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'), DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
"Top-level directory for maintaining nova's state") "Top-level directory for maintaining nova's state")
DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'), DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
"Directory for lock files") 'Directory for lock files')
DEFINE_string('logdir', None, 'output to a per-service log file in named ' DEFINE_string('logdir', None, 'output to a per-service log file in named '
'directory') 'directory')

View File

@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Implementation of an fake image service""" """Implementation of an fake image service"""
import copy import copy
@ -69,14 +70,14 @@ class FakeImageService(service.BaseImageService):
image = self.images.get(image_id) image = self.images.get(image_id)
if image: if image:
return copy.deepcopy(image) return copy.deepcopy(image)
LOG.warn("Unable to find image id %s. Have images: %s", LOG.warn('Unable to find image id %s. Have images: %s',
image_id, self.images) image_id, self.images)
raise exception.NotFound raise exception.NotFound
def create(self, context, data): def create(self, context, data):
"""Store the image data and return the new image id. """Store the image data and return the new image id.
:raises Duplicate if the image already exist. :raises: Duplicate if the image already exist.
""" """
image_id = int(data['id']) image_id = int(data['id'])
@ -88,7 +89,7 @@ class FakeImageService(service.BaseImageService):
def update(self, context, image_id, data): def update(self, context, image_id, data):
"""Replace the contents of the given image with the new data. """Replace the contents of the given image with the new data.
:raises NotFound if the image does not exist. :raises: NotFound if the image does not exist.
""" """
image_id = int(image_id) image_id = int(image_id)
@ -99,7 +100,7 @@ class FakeImageService(service.BaseImageService):
def delete(self, context, image_id): def delete(self, context, image_id):
"""Delete the given image. """Delete the given image.
:raises NotFound if the image does not exist. :raises: NotFound if the image does not exist.
""" """
image_id = int(image_id) image_id = int(image_id)

View File

@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Implementation of an image service that uses Glance as the backend""" """Implementation of an image service that uses Glance as the backend"""
from __future__ import absolute_import from __future__ import absolute_import
@ -31,16 +32,18 @@ from nova.image import service
LOG = logging.getLogger('nova.image.glance') LOG = logging.getLogger('nova.image.glance')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
GlanceClient = utils.import_class('glance.client.Client') GlanceClient = utils.import_class('glance.client.Client')
class GlanceImageService(service.BaseImageService): class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance.""" """Provides storage and retrieval of disk image objects within Glance."""
GLANCE_ONLY_ATTRS = ["size", "location", "disk_format", GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
"container_format"] 'container_format']
# NOTE(sirp): Overriding to use _translate_to_service provided by # NOTE(sirp): Overriding to use _translate_to_service provided by
# BaseImageService # BaseImageService
@ -56,9 +59,7 @@ class GlanceImageService(service.BaseImageService):
self.client = client self.client = client
def index(self, context): def index(self, context):
""" """Calls out to Glance for a list of images available."""
Calls out to Glance for a list of images available
"""
# NOTE(sirp): We need to use `get_images_detailed` and not # NOTE(sirp): We need to use `get_images_detailed` and not
# `get_images` here because we need `is_public` and `properties` # `get_images` here because we need `is_public` and `properties`
# included so we can filter by user # included so we can filter by user
@ -71,9 +72,7 @@ class GlanceImageService(service.BaseImageService):
return filtered return filtered
def detail(self, context): def detail(self, context):
""" """Calls out to Glance for a list of detailed image information."""
Calls out to Glance for a list of detailed image information
"""
filtered = [] filtered = []
image_metas = self.client.get_images_detailed() image_metas = self.client.get_images_detailed()
for image_meta in image_metas: for image_meta in image_metas:
@ -83,9 +82,7 @@ class GlanceImageService(service.BaseImageService):
return filtered return filtered
def show(self, context, image_id): def show(self, context, image_id):
""" """Returns a dict with image data for the given opaque image id."""
Returns a dict containing image data for the given opaque image id.
"""
try: try:
image_meta = self.client.get_image_meta(image_id) image_meta = self.client.get_image_meta(image_id)
except glance_exception.NotFound: except glance_exception.NotFound:
@ -98,9 +95,7 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta return base_image_meta
def show_by_name(self, context, name): def show_by_name(self, context, name):
""" """Returns a dict containing image data for the given name."""
Returns a dict containing image data for the given name.
"""
# TODO(vish): replace this with more efficient call when glance # TODO(vish): replace this with more efficient call when glance
# supports it. # supports it.
image_metas = self.detail(context) image_metas = self.detail(context)
@ -110,9 +105,7 @@ class GlanceImageService(service.BaseImageService):
raise exception.NotFound raise exception.NotFound
def get(self, context, image_id, data): def get(self, context, image_id, data):
""" """Calls out to Glance for metadata and data and writes data."""
Calls out to Glance for metadata and data and writes data.
"""
try: try:
image_meta, image_chunks = self.client.get_image(image_id) image_meta, image_chunks = self.client.get_image(image_id)
except glance_exception.NotFound: except glance_exception.NotFound:
@ -125,16 +118,16 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta return base_image_meta
def create(self, context, image_meta, data=None): def create(self, context, image_meta, data=None):
""" """Store the image data and return the new image id.
Store the image data and return the new image id.
:raises: AlreadyExists if the image already exist.
:raises AlreadyExists if the image already exist.
""" """
# Translate Base -> Service # Translate Base -> Service
LOG.debug(_("Creating image in Glance. Metadata passed in %s"), LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta) image_meta)
sent_service_image_meta = self._translate_to_service(image_meta) sent_service_image_meta = self._translate_to_service(image_meta)
LOG.debug(_("Metadata after formatting for Glance %s"), LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta) sent_service_image_meta)
recv_service_image_meta = self.client.add_image( recv_service_image_meta = self.client.add_image(
@ -142,14 +135,15 @@ class GlanceImageService(service.BaseImageService):
# Translate Service -> Base # Translate Service -> Base
base_image_meta = self._translate_to_base(recv_service_image_meta) base_image_meta = self._translate_to_base(recv_service_image_meta)
LOG.debug(_("Metadata returned from Glance formatted for Base %s"), LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
base_image_meta) base_image_meta)
return base_image_meta return base_image_meta
def update(self, context, image_id, image_meta, data=None): def update(self, context, image_id, image_meta, data=None):
"""Replace the contents of the given image with the new data. """Replace the contents of the given image with the new data.
:raises NotFound if the image does not exist. :raises: NotFound if the image does not exist.
""" """
# NOTE(vish): show is to check if image is available # NOTE(vish): show is to check if image is available
self.show(context, image_id) self.show(context, image_id)
@ -162,10 +156,10 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta return base_image_meta
def delete(self, context, image_id): def delete(self, context, image_id):
""" """Delete the given image.
Delete the given image.
:raises: NotFound if the image does not exist.
:raises NotFound if the image does not exist.
""" """
# NOTE(vish): show is to check if image is available # NOTE(vish): show is to check if image is available
self.show(context, image_id) self.show(context, image_id)
@ -176,16 +170,12 @@ class GlanceImageService(service.BaseImageService):
return result return result
def delete_all(self): def delete_all(self):
""" """Clears out all images."""
Clears out all images
"""
pass pass
@classmethod @classmethod
def _translate_to_base(cls, image_meta): def _translate_to_base(cls, image_meta):
"""Overriding the base translation to handle conversion to datetime """Override translation to handle conversion to datetime objects."""
objects
"""
image_meta = service.BaseImageService._propertify_metadata( image_meta = service.BaseImageService._propertify_metadata(
image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta, cls.SERVICE_IMAGE_ATTRS)
image_meta = _convert_timestamps_to_datetimes(image_meta) image_meta = _convert_timestamps_to_datetimes(image_meta)
@ -194,9 +184,7 @@ class GlanceImageService(service.BaseImageService):
# utility functions # utility functions
def _convert_timestamps_to_datetimes(image_meta): def _convert_timestamps_to_datetimes(image_meta):
""" """Returns image with timestamp fields converted to datetime objects."""
Returns image with known timestamp fields converted to datetime objects
"""
for attr in ['created_at', 'updated_at', 'deleted_at']: for attr in ['created_at', 'updated_at', 'deleted_at']:
if image_meta.get(attr): if image_meta.get(attr):
image_meta[attr] = _parse_glance_iso8601_timestamp( image_meta[attr] = _parse_glance_iso8601_timestamp(
@ -205,10 +193,8 @@ def _convert_timestamps_to_datetimes(image_meta):
def _parse_glance_iso8601_timestamp(timestamp): def _parse_glance_iso8601_timestamp(timestamp):
""" """Parse a subset of iso8601 timestamps into datetime objects."""
Parse a subset of iso8601 timestamps into datetime objects iso_formats = ['%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S']
"""
iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"]
for iso_format in iso_formats: for iso_format in iso_formats:
try: try:
@ -216,5 +202,5 @@ def _parse_glance_iso8601_timestamp(timestamp):
except ValueError: except ValueError:
pass pass
raise ValueError(_("%(timestamp)s does not follow any of the " raise ValueError(_('%(timestamp)s does not follow any of the '
"signatures: %(ISO_FORMATS)s") % locals()) 'signatures: %(ISO_FORMATS)s') % locals())

View File

@ -23,14 +23,15 @@ import shutil
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova.image import service
from nova import utils from nova import utils
from nova.image import service
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('images_path', '$state_path/images', flags.DEFINE_string('images_path', '$state_path/images',
'path to decrypted images') 'path to decrypted images')
LOG = logging.getLogger('nova.image.local') LOG = logging.getLogger('nova.image.local')
@ -56,9 +57,8 @@ class LocalImageService(service.BaseImageService):
try: try:
unhexed_image_id = int(image_dir, 16) unhexed_image_id = int(image_dir, 16)
except ValueError: except ValueError:
LOG.error( LOG.error(_('%s is not in correct directory naming format')
_("%s is not in correct directory naming format"\ % image_dir)
% image_dir))
else: else:
images.append(unhexed_image_id) images.append(unhexed_image_id)
return images return images
@ -148,7 +148,8 @@ class LocalImageService(service.BaseImageService):
def delete(self, context, image_id): def delete(self, context, image_id):
"""Delete the given image. """Delete the given image.
Raises NotFound if the image does not exist.
:raises: NotFound if the image does not exist.
""" """
# NOTE(vish): show is to check if image is available # NOTE(vish): show is to check if image is available

View File

@ -16,13 +16,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Proxy AMI-related calls from cloud controller to objectstore service."""
Proxy AMI-related calls from the cloud controller, to the running
objectstore service.
"""
import binascii import binascii
import eventlet
import os import os
import shutil import shutil
import tarfile import tarfile
@ -30,6 +26,7 @@ import tempfile
from xml.etree import ElementTree from xml.etree import ElementTree
import boto.s3.connection import boto.s3.connection
import eventlet
from nova import crypto from nova import crypto
from nova import exception from nova import exception
@ -46,7 +43,8 @@ flags.DEFINE_string('image_decryption_dir', '/tmp',
class S3ImageService(service.BaseImageService): class S3ImageService(service.BaseImageService):
"""Wraps an existing image service to support s3 based register""" """Wraps an existing image service to support s3 based register."""
def __init__(self, service=None, *args, **kwargs): def __init__(self, service=None, *args, **kwargs):
if service is None: if service is None:
service = utils.import_object(FLAGS.image_service) service = utils.import_object(FLAGS.image_service)
@ -54,7 +52,11 @@ class S3ImageService(service.BaseImageService):
self.service.__init__(*args, **kwargs) self.service.__init__(*args, **kwargs)
def create(self, context, metadata, data=None): def create(self, context, metadata, data=None):
"""metadata['properties'] should contain image_location""" """Create an image.
metadata['properties'] should contain image_location.
"""
image = self._s3_create(context, metadata) image = self._s3_create(context, metadata)
return image return image
@ -100,12 +102,12 @@ class S3ImageService(service.BaseImageService):
return local_filename return local_filename
def _s3_create(self, context, metadata): def _s3_create(self, context, metadata):
"""Gets a manifext from s3 and makes an image""" """Gets a manifext from s3 and makes an image."""
image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir) image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir)
image_location = metadata['properties']['image_location'] image_location = metadata['properties']['image_location']
bucket_name = image_location.split("/")[0] bucket_name = image_location.split('/')[0]
manifest_path = image_location[len(bucket_name) + 1:] manifest_path = image_location[len(bucket_name) + 1:]
bucket = self._conn(context).get_bucket(bucket_name) bucket = self._conn(context).get_bucket(bucket_name)
key = bucket.get_key(manifest_path) key = bucket.get_key(manifest_path)
@ -116,7 +118,7 @@ class S3ImageService(service.BaseImageService):
image_type = 'machine' image_type = 'machine'
try: try:
kernel_id = manifest.find("machine_configuration/kernel_id").text kernel_id = manifest.find('machine_configuration/kernel_id').text
if kernel_id == 'true': if kernel_id == 'true':
image_format = 'aki' image_format = 'aki'
image_type = 'kernel' image_type = 'kernel'
@ -125,7 +127,7 @@ class S3ImageService(service.BaseImageService):
kernel_id = None kernel_id = None
try: try:
ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text
if ramdisk_id == 'true': if ramdisk_id == 'true':
image_format = 'ari' image_format = 'ari'
image_type = 'ramdisk' image_type = 'ramdisk'
@ -134,7 +136,7 @@ class S3ImageService(service.BaseImageService):
ramdisk_id = None ramdisk_id = None
try: try:
arch = manifest.find("machine_configuration/architecture").text arch = manifest.find('machine_configuration/architecture').text
except Exception: except Exception:
arch = 'x86_64' arch = 'x86_64'
@ -160,7 +162,7 @@ class S3ImageService(service.BaseImageService):
def delayed_create(): def delayed_create():
"""This handles the fetching and decrypting of the part files.""" """This handles the fetching and decrypting of the part files."""
parts = [] parts = []
for fn_element in manifest.find("image").getiterator("filename"): for fn_element in manifest.find('image').getiterator('filename'):
part = self._download_file(bucket, fn_element.text, image_path) part = self._download_file(bucket, fn_element.text, image_path)
parts.append(part) parts.append(part)
@ -174,9 +176,9 @@ class S3ImageService(service.BaseImageService):
metadata['properties']['image_state'] = 'decrypting' metadata['properties']['image_state'] = 'decrypting'
self.service.update(context, image_id, metadata) self.service.update(context, image_id, metadata)
hex_key = manifest.find("image/ec2_encrypted_key").text hex_key = manifest.find('image/ec2_encrypted_key').text
encrypted_key = binascii.a2b_hex(hex_key) encrypted_key = binascii.a2b_hex(hex_key)
hex_iv = manifest.find("image/ec2_encrypted_iv").text hex_iv = manifest.find('image/ec2_encrypted_iv').text
encrypted_iv = binascii.a2b_hex(hex_iv) encrypted_iv = binascii.a2b_hex(hex_iv)
# FIXME(vish): grab key from common service so this can run on # FIXME(vish): grab key from common service so this can run on
@ -214,7 +216,7 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_key, process_input=encrypted_key,
check_exit_code=False) check_exit_code=False)
if err: if err:
raise exception.Error(_("Failed to decrypt private key: %s") raise exception.Error(_('Failed to decrypt private key: %s')
% err) % err)
iv, err = utils.execute('openssl', iv, err = utils.execute('openssl',
'rsautl', 'rsautl',
@ -223,8 +225,8 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_iv, process_input=encrypted_iv,
check_exit_code=False) check_exit_code=False)
if err: if err:
raise exception.Error(_("Failed to decrypt initialization " raise exception.Error(_('Failed to decrypt initialization '
"vector: %s") % err) 'vector: %s') % err)
_out, err = utils.execute('openssl', 'enc', _out, err = utils.execute('openssl', 'enc',
'-d', '-aes-128-cbc', '-d', '-aes-128-cbc',
@ -234,14 +236,14 @@ class S3ImageService(service.BaseImageService):
'-out', '%s' % (decrypted_filename,), '-out', '%s' % (decrypted_filename,),
check_exit_code=False) check_exit_code=False)
if err: if err:
raise exception.Error(_("Failed to decrypt image file " raise exception.Error(_('Failed to decrypt image file '
"%(image_file)s: %(err)s") % '%(image_file)s: %(err)s') %
{'image_file': encrypted_filename, {'image_file': encrypted_filename,
'err': err}) 'err': err})
@staticmethod @staticmethod
def _untarzip_image(path, filename): def _untarzip_image(path, filename):
tar_file = tarfile.open(filename, "r|gz") tar_file = tarfile.open(filename, 'r|gz')
tar_file.extractall(path) tar_file.extractall(path)
image_file = tar_file.getnames()[0] image_file = tar_file.getnames()[0]
tar_file.close() tar_file.close()

View File

@ -20,7 +20,7 @@ from nova import utils
class BaseImageService(object): class BaseImageService(object):
"""Base class for providing image search and retrieval services """Base class for providing image search and retrieval services.
ImageService exposes two concepts of metadata: ImageService exposes two concepts of metadata:
@ -35,7 +35,9 @@ class BaseImageService(object):
This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
metadata dict, all other attributes will be returned as keys in the nested metadata dict, all other attributes will be returned as keys in the nested
'properties' dict. 'properties' dict.
""" """
BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at', BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status', 'is_public'] 'deleted_at', 'deleted', 'status', 'is_public']
@ -45,23 +47,18 @@ class BaseImageService(object):
SERVICE_IMAGE_ATTRS = [] SERVICE_IMAGE_ATTRS = []
def index(self, context): def index(self, context):
""" """List images.
Returns a sequence of mappings of id and name information about
images.
:rtype: array :returns: a sequence of mappings with the following signature
:retval: a sequence of mappings with the following signature {'id': opaque id of image, 'name': name of image}
{'id': opaque id of image, 'name': name of image}
""" """
raise NotImplementedError raise NotImplementedError
def detail(self, context): def detail(self, context):
""" """Detailed information about an images.
Returns a sequence of mappings of detailed information about images.
:rtype: array :returns: a sequence of mappings with the following signature
:retval: a sequence of mappings with the following signature
{'id': opaque id of image, {'id': opaque id of image,
'name': name of image, 'name': name of image,
'created_at': creation datetime object, 'created_at': creation datetime object,
@ -77,15 +74,14 @@ class BaseImageService(object):
NotImplementedError, in which case Nova will emulate this method NotImplementedError, in which case Nova will emulate this method
with repeated calls to show() for each image received from the with repeated calls to show() for each image received from the
index() method. index() method.
""" """
raise NotImplementedError raise NotImplementedError
def show(self, context, image_id): def show(self, context, image_id):
""" """Detailed information about an image.
Returns a dict containing image metadata for the given opaque image id.
:retval a mapping with the following signature:
:returns: a mapping with the following signature:
{'id': opaque id of image, {'id': opaque id of image,
'name': name of image, 'name': name of image,
'created_at': creation datetime object, 'created_at': creation datetime object,
@ -96,54 +92,56 @@ class BaseImageService(object):
'is_public': boolean indicating if image is public 'is_public': boolean indicating if image is public
}, ... }, ...
:raises NotFound if the image does not exist :raises: NotFound if the image does not exist
""" """
raise NotImplementedError raise NotImplementedError
def get(self, context, data): def get(self, context, data):
""" """Get an image.
Returns a dict containing image metadata and writes image data to data.
:param data: a file-like object to hold binary image data :param data: a file-like object to hold binary image data
:returns: a dict containing image metadata, writes image data to data.
:raises: NotFound if the image does not exist
:raises NotFound if the image does not exist
""" """
raise NotImplementedError raise NotImplementedError
def create(self, context, metadata, data=None): def create(self, context, metadata, data=None):
""" """Store the image metadata and data.
Store the image metadata and data and return the new image metadata.
:raises AlreadyExists if the image already exist. :returns: the new image metadata.
:raises: AlreadyExists if the image already exist.
""" """
raise NotImplementedError raise NotImplementedError
def update(self, context, image_id, metadata, data=None): def update(self, context, image_id, metadata, data=None):
"""Update the given image metadata and data and return the metadata """Update the given image metadata and data and return the metadata.
:raises NotFound if the image does not exist. :raises: NotFound if the image does not exist.
""" """
raise NotImplementedError raise NotImplementedError
def delete(self, context, image_id): def delete(self, context, image_id):
""" """Delete the given image.
Delete the given image.
:raises NotFound if the image does not exist. :raises: NotFound if the image does not exist.
""" """
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def _is_image_available(context, image_meta): def _is_image_available(context, image_meta):
""" """Check image availability.
Images are always available if they are public or if the user is an Images are always available if they are public or if the user is an
admin. admin.
Otherwise, we filter by project_id (if present) and then fall-back to Otherwise, we filter by project_id (if present) and then fall-back to
images owned by user. images owned by user.
""" """
# FIXME(sirp): We should be filtering by user_id on the Glance side # FIXME(sirp): We should be filtering by user_id on the Glance side
# for security; however, we can't do that until we get authn/authz # for security; however, we can't do that until we get authn/authz
@ -169,29 +167,32 @@ class BaseImageService(object):
This is used by subclasses to expose only a metadata dictionary that This is used by subclasses to expose only a metadata dictionary that
is the same across ImageService implementations. is the same across ImageService implementations.
""" """
return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS) return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
@classmethod @classmethod
def _translate_to_service(cls, metadata): def _translate_to_service(cls, metadata):
"""Return a metadata dictionary that is usable by the ImageService """Return a metadata dict that is usable by the ImageService subclass.
subclass.
As an example, Glance has additional attributes (like 'location'); the As an example, Glance has additional attributes (like 'location'); the
BaseImageService considers these properties, but we need to translate BaseImageService considers these properties, but we need to translate
these back to first-class attrs for sending to Glance. This method these back to first-class attrs for sending to Glance. This method
handles this by allowing you to specify the attributes an ImageService handles this by allowing you to specify the attributes an ImageService
considers first-class. considers first-class.
""" """
if not cls.SERVICE_IMAGE_ATTRS: if not cls.SERVICE_IMAGE_ATTRS:
raise NotImplementedError(_("Cannot use this without specifying " raise NotImplementedError(_('Cannot use this without specifying '
"SERVICE_IMAGE_ATTRS for subclass")) 'SERVICE_IMAGE_ATTRS for subclass'))
return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS) return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
@staticmethod @staticmethod
def _propertify_metadata(metadata, keys): def _propertify_metadata(metadata, keys):
"""Return a dict with any unrecognized keys placed in the nested """Move unknown keys to a nested 'properties' dict.
'properties' dict.
:returns: a new dict with the keys moved.
""" """
flattened = utils.flatten_dict(metadata) flattened = utils.flatten_dict(metadata)
attributes, properties = utils.partition_dict(flattened, keys) attributes, properties = utils.partition_dict(flattened, keys)

View File

@ -16,16 +16,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Nova logging handler.
Nova logging handler.
This module adds to logging functionality by adding the option to specify This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object a context object when calling the various log methods. If the context object
is not specified, default formatting is used. is not specified, default formatting is used.
It also allows setting of formatting information through flags. It also allows setting of formatting information through flags.
"""
"""
import cStringIO import cStringIO
import inspect import inspect
@ -41,34 +40,28 @@ from nova import version
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('logging_context_format_string', flags.DEFINE_string('logging_context_format_string',
'%(asctime)s %(levelname)s %(name)s ' '%(asctime)s %(levelname)s %(name)s '
'[%(request_id)s %(user)s ' '[%(request_id)s %(user)s '
'%(project)s] %(message)s', '%(project)s] %(message)s',
'format string to use for log messages with context') 'format string to use for log messages with context')
flags.DEFINE_string('logging_default_format_string', flags.DEFINE_string('logging_default_format_string',
'%(asctime)s %(levelname)s %(name)s [-] ' '%(asctime)s %(levelname)s %(name)s [-] '
'%(message)s', '%(message)s',
'format string to use for log messages without context') 'format string to use for log messages without context')
flags.DEFINE_string('logging_debug_format_suffix', flags.DEFINE_string('logging_debug_format_suffix',
'from (pid=%(process)d) %(funcName)s' 'from (pid=%(process)d) %(funcName)s'
' %(pathname)s:%(lineno)d', ' %(pathname)s:%(lineno)d',
'data to append to log format when level is DEBUG') 'data to append to log format when level is DEBUG')
flags.DEFINE_string('logging_exception_prefix', flags.DEFINE_string('logging_exception_prefix',
'(%(name)s): TRACE: ', '(%(name)s): TRACE: ',
'prefix each line of exception output with this format') 'prefix each line of exception output with this format')
flags.DEFINE_list('default_log_levels', flags.DEFINE_list('default_log_levels',
['amqplib=WARN', ['amqplib=WARN',
'sqlalchemy=WARN', 'sqlalchemy=WARN',
'boto=WARN', 'boto=WARN',
'eventlet.wsgi.server=WARN'], 'eventlet.wsgi.server=WARN'],
'list of logger=LEVEL pairs') 'list of logger=LEVEL pairs')
flags.DEFINE_bool('use_syslog', False, 'output to syslog') flags.DEFINE_bool('use_syslog', False, 'output to syslog')
flags.DEFINE_string('logfile', None, 'output to named file') flags.DEFINE_string('logfile', None, 'output to named file')
@ -83,6 +76,8 @@ WARN = logging.WARN
INFO = logging.INFO INFO = logging.INFO
DEBUG = logging.DEBUG DEBUG = logging.DEBUG
NOTSET = logging.NOTSET NOTSET = logging.NOTSET
# methods # methods
getLogger = logging.getLogger getLogger = logging.getLogger
debug = logging.debug debug = logging.debug
@ -93,6 +88,8 @@ error = logging.error
exception = logging.exception exception = logging.exception
critical = logging.critical critical = logging.critical
log = logging.log log = logging.log
# handlers # handlers
StreamHandler = logging.StreamHandler StreamHandler = logging.StreamHandler
WatchedFileHandler = logging.handlers.WatchedFileHandler WatchedFileHandler = logging.handlers.WatchedFileHandler
@ -127,17 +124,18 @@ def _get_log_file_path(binary=None):
class NovaLogger(logging.Logger): class NovaLogger(logging.Logger):
""" """NovaLogger manages request context and formatting.
NovaLogger manages request context and formatting.
This becomes the class that is instanciated by logging.getLogger. This becomes the class that is instanciated by logging.getLogger.
""" """
def __init__(self, name, level=NOTSET): def __init__(self, name, level=NOTSET):
logging.Logger.__init__(self, name, level) logging.Logger.__init__(self, name, level)
self.setup_from_flags() self.setup_from_flags()
def setup_from_flags(self): def setup_from_flags(self):
"""Setup logger from flags""" """Setup logger from flags."""
level = NOTSET level = NOTSET
for pair in FLAGS.default_log_levels: for pair in FLAGS.default_log_levels:
logger, _sep, level_name = pair.partition('=') logger, _sep, level_name = pair.partition('=')
@ -148,7 +146,7 @@ class NovaLogger(logging.Logger):
self.setLevel(level) self.setLevel(level)
def _log(self, level, msg, args, exc_info=None, extra=None, context=None): def _log(self, level, msg, args, exc_info=None, extra=None, context=None):
"""Extract context from any log call""" """Extract context from any log call."""
if not extra: if not extra:
extra = {} extra = {}
if context: if context:
@ -157,17 +155,17 @@ class NovaLogger(logging.Logger):
return logging.Logger._log(self, level, msg, args, exc_info, extra) return logging.Logger._log(self, level, msg, args, exc_info, extra)
def addHandler(self, handler): def addHandler(self, handler):
"""Each handler gets our custom formatter""" """Each handler gets our custom formatter."""
handler.setFormatter(_formatter) handler.setFormatter(_formatter)
return logging.Logger.addHandler(self, handler) return logging.Logger.addHandler(self, handler)
def audit(self, msg, *args, **kwargs): def audit(self, msg, *args, **kwargs):
"""Shortcut for our AUDIT level""" """Shortcut for our AUDIT level."""
if self.isEnabledFor(AUDIT): if self.isEnabledFor(AUDIT):
self._log(AUDIT, msg, args, **kwargs) self._log(AUDIT, msg, args, **kwargs)
def exception(self, msg, *args, **kwargs): def exception(self, msg, *args, **kwargs):
"""Logging.exception doesn't handle kwargs, so breaks context""" """Logging.exception doesn't handle kwargs, so breaks context."""
if not kwargs.get('exc_info'): if not kwargs.get('exc_info'):
kwargs['exc_info'] = 1 kwargs['exc_info'] = 1
self.error(msg, *args, **kwargs) self.error(msg, *args, **kwargs)
@ -181,14 +179,13 @@ class NovaLogger(logging.Logger):
for k in env.keys(): for k in env.keys():
if not isinstance(env[k], str): if not isinstance(env[k], str):
env.pop(k) env.pop(k)
message = "Environment: %s" % json.dumps(env) message = 'Environment: %s' % json.dumps(env)
kwargs.pop('exc_info') kwargs.pop('exc_info')
self.error(message, **kwargs) self.error(message, **kwargs)
class NovaFormatter(logging.Formatter): class NovaFormatter(logging.Formatter):
""" """A nova.context.RequestContext aware formatter configured through flags.
A nova.context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_foramt_string The flags used to set format strings are: logging_context_foramt_string
and logging_default_format_string. You can also specify and logging_default_format_string. You can also specify
@ -197,10 +194,11 @@ class NovaFormatter(logging.Formatter):
For information about what variables are available for the formatter see: For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter http://docs.python.org/library/logging.html#formatter
""" """
def format(self, record): def format(self, record):
"""Uses contextstring if request_id is set, otherwise default""" """Uses contextstring if request_id is set, otherwise default."""
if record.__dict__.get('request_id', None): if record.__dict__.get('request_id', None):
self._fmt = FLAGS.logging_context_format_string self._fmt = FLAGS.logging_context_format_string
else: else:
@ -214,20 +212,21 @@ class NovaFormatter(logging.Formatter):
return logging.Formatter.format(self, record) return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None): def formatException(self, exc_info, record=None):
"""Format exception output with FLAGS.logging_exception_prefix""" """Format exception output with FLAGS.logging_exception_prefix."""
if not record: if not record:
return logging.Formatter.formatException(self, exc_info) return logging.Formatter.formatException(self, exc_info)
stringbuffer = cStringIO.StringIO() stringbuffer = cStringIO.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer) None, stringbuffer)
lines = stringbuffer.getvalue().split("\n") lines = stringbuffer.getvalue().split('\n')
stringbuffer.close() stringbuffer.close()
formatted_lines = [] formatted_lines = []
for line in lines: for line in lines:
pl = FLAGS.logging_exception_prefix % record.__dict__ pl = FLAGS.logging_exception_prefix % record.__dict__
fl = "%s%s" % (pl, line) fl = '%s%s' % (pl, line)
formatted_lines.append(fl) formatted_lines.append(fl)
return "\n".join(formatted_lines) return '\n'.join(formatted_lines)
_formatter = NovaFormatter() _formatter = NovaFormatter()
@ -241,7 +240,7 @@ class NovaRootLogger(NovaLogger):
NovaLogger.__init__(self, name, level) NovaLogger.__init__(self, name, level)
def setup_from_flags(self): def setup_from_flags(self):
"""Setup logger from flags""" """Setup logger from flags."""
global _filelog global _filelog
if FLAGS.use_syslog: if FLAGS.use_syslog:
self.syslog = SysLogHandler(address='/dev/log') self.syslog = SysLogHandler(address='/dev/log')

View File

@ -16,7 +16,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Base Manager class.
Managers are responsible for a certain aspect of the sytem. It is a logical Managers are responsible for a certain aspect of the sytem. It is a logical
grouping of code relating to a portion of the system. In general other grouping of code relating to a portion of the system. In general other
components should be using the manager to make changes to the components that components should be using the manager to make changes to the components that
@ -49,16 +50,19 @@ Managers will often provide methods for initial setup of a host or periodic
tasksto a wrapping service. tasksto a wrapping service.
This module provides Manager, a base class for managers. This module provides Manager, a base class for managers.
""" """
from nova import utils
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import utils
from nova.db import base from nova.db import base
from nova.scheduler import api from nova.scheduler import api
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.manager') LOG = logging.getLogger('nova.manager')
@ -70,23 +74,29 @@ class Manager(base.Base):
super(Manager, self).__init__(db_driver) super(Manager, self).__init__(db_driver)
def periodic_tasks(self, context=None): def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval""" """Tasks to be run at a periodic interval."""
pass pass
def init_host(self): def init_host(self):
"""Do any initialization that needs to be run if this is a standalone """Handle initialization if this is a standalone service.
service. Child classes should override this method."""
Child classes should override this method.
"""
pass pass
class SchedulerDependentManager(Manager): class SchedulerDependentManager(Manager):
"""Periodically send capability updates to the Scheduler services. """Periodically send capability updates to the Scheduler services.
Services that need to update the Scheduler of their capabilities
should derive from this class. Otherwise they can derive from
manager.Manager directly. Updates are only sent after
update_service_capabilities is called with non-None values."""
def __init__(self, host=None, db_driver=None, service_name="undefined"): Services that need to update the Scheduler of their capabilities
should derive from this class. Otherwise they can derive from
manager.Manager directly. Updates are only sent after
update_service_capabilities is called with non-None values.
"""
def __init__(self, host=None, db_driver=None, service_name='undefined'):
self.last_capabilities = None self.last_capabilities = None
self.service_name = service_name self.service_name = service_name
super(SchedulerDependentManager, self).__init__(host, db_driver) super(SchedulerDependentManager, self).__init__(host, db_driver)
@ -96,9 +106,9 @@ class SchedulerDependentManager(Manager):
self.last_capabilities = capabilities self.last_capabilities = capabilities
def periodic_tasks(self, context=None): def periodic_tasks(self, context=None):
"""Pass data back to the scheduler at a periodic interval""" """Pass data back to the scheduler at a periodic interval."""
if self.last_capabilities: if self.last_capabilities:
LOG.debug(_("Notifying Schedulers of capabilities ...")) LOG.debug(_('Notifying Schedulers of capabilities ...'))
api.update_service_capabilities(context, self.service_name, api.update_service_capabilities(context, self.service_name,
self.host, self.last_capabilities) self.host, self.last_capabilities)

View File

@ -51,8 +51,11 @@ class API(base.Base):
{"method": "allocate_floating_ip", {"method": "allocate_floating_ip",
"args": {"project_id": context.project_id}}) "args": {"project_id": context.project_id}})
def release_floating_ip(self, context, address): def release_floating_ip(self, context, address,
affect_auto_assigned=False):
floating_ip = self.db.floating_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, address)
if not affect_auto_assigned and floating_ip.get('auto_assigned'):
return
# NOTE(vish): We don't know which network host should get the ip # NOTE(vish): We don't know which network host should get the ip
# when we deallocate, so just send it to any one. This # when we deallocate, so just send it to any one. This
# will probably need to move into a network supervisor # will probably need to move into a network supervisor
@ -62,10 +65,13 @@ class API(base.Base):
{"method": "deallocate_floating_ip", {"method": "deallocate_floating_ip",
"args": {"floating_address": floating_ip['address']}}) "args": {"floating_address": floating_ip['address']}})
def associate_floating_ip(self, context, floating_ip, fixed_ip): def associate_floating_ip(self, context, floating_ip, fixed_ip,
affect_auto_assigned=False):
if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode):
fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip)
floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip)
if not affect_auto_assigned and floating_ip.get('auto_assigned'):
return
# Check if the floating ip address is allocated # Check if the floating ip address is allocated
if floating_ip['project_id'] is None: if floating_ip['project_id'] is None:
raise exception.ApiError(_("Address (%s) is not allocated") % raise exception.ApiError(_("Address (%s) is not allocated") %
@ -90,8 +96,11 @@ class API(base.Base):
"args": {"floating_address": floating_ip['address'], "args": {"floating_address": floating_ip['address'],
"fixed_address": fixed_ip['address']}}) "fixed_address": fixed_ip['address']}})
def disassociate_floating_ip(self, context, address): def disassociate_floating_ip(self, context, address,
affect_auto_assigned=False):
floating_ip = self.db.floating_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, address)
if not affect_auto_assigned and floating_ip.get('auto_assigned'):
return
if not floating_ip.get('fixed_ip'): if not floating_ip.get('fixed_ip'):
raise exception.ApiError('Address is not associated.') raise exception.ApiError('Address is not associated.')
# NOTE(vish): Get the topic from the host name of the network of # NOTE(vish): Get the topic from the host name of the network of

View File

@ -15,16 +15,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""
Quotas for instances, volumes, and floating ips """Quotas for instances, volumes, and floating ips."""
"""
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
FLAGS = flags.FLAGS
FLAGS = flags.FLAGS
flags.DEFINE_integer('quota_instances', 10, flags.DEFINE_integer('quota_instances', 10,
'number of instances allowed per project') 'number of instances allowed per project')
flags.DEFINE_integer('quota_cores', 20, flags.DEFINE_integer('quota_cores', 20,
@ -64,7 +63,7 @@ def get_quota(context, project_id):
def allowed_instances(context, num_instances, instance_type): def allowed_instances(context, num_instances, instance_type):
"""Check quota and return min(num_instances, allowed_instances)""" """Check quota and return min(num_instances, allowed_instances)."""
project_id = context.project_id project_id = context.project_id
context = context.elevated() context = context.elevated()
used_instances, used_cores = db.instance_data_get_for_project(context, used_instances, used_cores = db.instance_data_get_for_project(context,
@ -79,7 +78,7 @@ def allowed_instances(context, num_instances, instance_type):
def allowed_volumes(context, num_volumes, size): def allowed_volumes(context, num_volumes, size):
"""Check quota and return min(num_volumes, allowed_volumes)""" """Check quota and return min(num_volumes, allowed_volumes)."""
project_id = context.project_id project_id = context.project_id
context = context.elevated() context = context.elevated()
used_volumes, used_gigabytes = db.volume_data_get_for_project(context, used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
@ -95,7 +94,7 @@ def allowed_volumes(context, num_volumes, size):
def allowed_floating_ips(context, num_floating_ips): def allowed_floating_ips(context, num_floating_ips):
"""Check quota and return min(num_floating_ips, allowed_floating_ips)""" """Check quota and return min(num_floating_ips, allowed_floating_ips)."""
project_id = context.project_id project_id = context.project_id
context = context.elevated() context = context.elevated()
used_floating_ips = db.floating_ip_count_by_project(context, project_id) used_floating_ips = db.floating_ip_count_by_project(context, project_id)
@ -105,7 +104,7 @@ def allowed_floating_ips(context, num_floating_ips):
def allowed_metadata_items(context, num_metadata_items): def allowed_metadata_items(context, num_metadata_items):
"""Check quota; return min(num_metadata_items,allowed_metadata_items)""" """Check quota; return min(num_metadata_items,allowed_metadata_items)."""
project_id = context.project_id project_id = context.project_id
context = context.elevated() context = context.elevated()
quota = get_quota(context, project_id) quota = get_quota(context, project_id)
@ -114,20 +113,20 @@ def allowed_metadata_items(context, num_metadata_items):
def allowed_injected_files(context): def allowed_injected_files(context):
"""Return the number of injected files allowed""" """Return the number of injected files allowed."""
return FLAGS.quota_max_injected_files return FLAGS.quota_max_injected_files
def allowed_injected_file_content_bytes(context): def allowed_injected_file_content_bytes(context):
"""Return the number of bytes allowed per injected file content""" """Return the number of bytes allowed per injected file content."""
return FLAGS.quota_max_injected_file_content_bytes return FLAGS.quota_max_injected_file_content_bytes
def allowed_injected_file_path_bytes(context): def allowed_injected_file_path_bytes(context):
"""Return the number of bytes allowed in an injected file path""" """Return the number of bytes allowed in an injected file path."""
return FLAGS.quota_max_injected_file_path_bytes return FLAGS.quota_max_injected_file_path_bytes
class QuotaError(exception.ApiError): class QuotaError(exception.ApiError):
"""Quota Exceeeded""" """Quota Exceeeded."""
pass pass

View File

@ -16,9 +16,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """AMQP-based RPC.
AMQP-based RPC. Queues have consumers and publishers.
Queues have consumers and publishers.
No fan-out support yet. No fan-out support yet.
""" """
import json import json
@ -40,17 +43,19 @@ from nova import log as logging
from nova import utils from nova import utils
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.rpc') LOG = logging.getLogger('nova.rpc')
FLAGS = flags.FLAGS
flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool') flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool')
class Connection(carrot_connection.BrokerConnection): class Connection(carrot_connection.BrokerConnection):
"""Connection instance object""" """Connection instance object."""
@classmethod @classmethod
def instance(cls, new=True): def instance(cls, new=True):
"""Returns the instance""" """Returns the instance."""
if new or not hasattr(cls, '_instance'): if new or not hasattr(cls, '_instance'):
params = dict(hostname=FLAGS.rabbit_host, params = dict(hostname=FLAGS.rabbit_host,
port=FLAGS.rabbit_port, port=FLAGS.rabbit_port,
@ -71,9 +76,11 @@ class Connection(carrot_connection.BrokerConnection):
@classmethod @classmethod
def recreate(cls): def recreate(cls):
"""Recreates the connection instance """Recreates the connection instance.
This is necessary to recover from some network errors/disconnects""" This is necessary to recover from some network errors/disconnects.
"""
try: try:
del cls._instance del cls._instance
except AttributeError, e: except AttributeError, e:
@ -84,10 +91,12 @@ class Connection(carrot_connection.BrokerConnection):
class Consumer(messaging.Consumer): class Consumer(messaging.Consumer):
"""Consumer base class """Consumer base class.
Contains methods for connecting the fetch method to async loops.
Contains methods for connecting the fetch method to async loops
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
for i in xrange(FLAGS.rabbit_max_retries): for i in xrange(FLAGS.rabbit_max_retries):
if i > 0: if i > 0:
@ -100,19 +109,18 @@ class Consumer(messaging.Consumer):
fl_host = FLAGS.rabbit_host fl_host = FLAGS.rabbit_host
fl_port = FLAGS.rabbit_port fl_port = FLAGS.rabbit_port
fl_intv = FLAGS.rabbit_retry_interval fl_intv = FLAGS.rabbit_retry_interval
LOG.error(_("AMQP server on %(fl_host)s:%(fl_port)d is" LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is'
" unreachable: %(e)s. Trying again in %(fl_intv)d" ' unreachable: %(e)s. Trying again in %(fl_intv)d'
" seconds.") ' seconds.') % locals())
% locals())
self.failed_connection = True self.failed_connection = True
if self.failed_connection: if self.failed_connection:
LOG.error(_("Unable to connect to AMQP server " LOG.error(_('Unable to connect to AMQP server '
"after %d tries. Shutting down."), 'after %d tries. Shutting down.'),
FLAGS.rabbit_max_retries) FLAGS.rabbit_max_retries)
sys.exit(1) sys.exit(1)
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
"""Wraps the parent fetch with some logic for failed connections""" """Wraps the parent fetch with some logic for failed connection."""
# TODO(vish): the logic for failed connections and logging should be # TODO(vish): the logic for failed connections and logging should be
# refactored into some sort of connection manager object # refactored into some sort of connection manager object
try: try:
@ -125,14 +133,14 @@ class Consumer(messaging.Consumer):
self.declare() self.declare()
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
if self.failed_connection: if self.failed_connection:
LOG.error(_("Reconnected to queue")) LOG.error(_('Reconnected to queue'))
self.failed_connection = False self.failed_connection = False
# NOTE(vish): This is catching all errors because we really don't # NOTE(vish): This is catching all errors because we really don't
# want exceptions to be logged 10 times a second if some # want exceptions to be logged 10 times a second if some
# persistent failure occurs. # persistent failure occurs.
except Exception, e: # pylint: disable=W0703 except Exception, e: # pylint: disable=W0703
if not self.failed_connection: if not self.failed_connection:
LOG.exception(_("Failed to fetch message from queue: %s" % e)) LOG.exception(_('Failed to fetch message from queue: %s' % e))
self.failed_connection = True self.failed_connection = True
def attach_to_eventlet(self): def attach_to_eventlet(self):
@ -143,8 +151,9 @@ class Consumer(messaging.Consumer):
class AdapterConsumer(Consumer): class AdapterConsumer(Consumer):
"""Calls methods on a proxy object based on method and args""" """Calls methods on a proxy object based on method and args."""
def __init__(self, connection=None, topic="broadcast", proxy=None):
def __init__(self, connection=None, topic='broadcast', proxy=None):
LOG.debug(_('Initing the Adapter Consumer for %s') % topic) LOG.debug(_('Initing the Adapter Consumer for %s') % topic)
self.proxy = proxy self.proxy = proxy
self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size)
@ -156,13 +165,14 @@ class AdapterConsumer(Consumer):
@exception.wrap_exception @exception.wrap_exception
def _receive(self, message_data, message): def _receive(self, message_data, message):
"""Magically looks for a method on the proxy object and calls it """Magically looks for a method on the proxy object and calls it.
Message data should be a dictionary with two keys: Message data should be a dictionary with two keys:
method: string representing the method to call method: string representing the method to call
args: dictionary of arg: value args: dictionary of arg: value
Example: {'method': 'echo', 'args': {'value': 42}} Example: {'method': 'echo', 'args': {'value': 42}}
""" """
LOG.debug(_('received %s') % message_data) LOG.debug(_('received %s') % message_data)
msg_id = message_data.pop('_msg_id', None) msg_id = message_data.pop('_msg_id', None)
@ -189,22 +199,23 @@ class AdapterConsumer(Consumer):
if msg_id: if msg_id:
msg_reply(msg_id, rval, None) msg_reply(msg_id, rval, None)
except Exception as e: except Exception as e:
logging.exception("Exception during message handling") logging.exception('Exception during message handling')
if msg_id: if msg_id:
msg_reply(msg_id, None, sys.exc_info()) msg_reply(msg_id, None, sys.exc_info())
return return
class Publisher(messaging.Publisher): class Publisher(messaging.Publisher):
"""Publisher base class""" """Publisher base class."""
pass pass
class TopicAdapterConsumer(AdapterConsumer): class TopicAdapterConsumer(AdapterConsumer):
"""Consumes messages on a specific topic""" """Consumes messages on a specific topic."""
exchange_type = "topic"
def __init__(self, connection=None, topic="broadcast", proxy=None): exchange_type = 'topic'
def __init__(self, connection=None, topic='broadcast', proxy=None):
self.queue = topic self.queue = topic
self.routing_key = topic self.routing_key = topic
self.exchange = FLAGS.control_exchange self.exchange = FLAGS.control_exchange
@ -214,27 +225,29 @@ class TopicAdapterConsumer(AdapterConsumer):
class FanoutAdapterConsumer(AdapterConsumer): class FanoutAdapterConsumer(AdapterConsumer):
"""Consumes messages from a fanout exchange""" """Consumes messages from a fanout exchange."""
exchange_type = "fanout"
def __init__(self, connection=None, topic="broadcast", proxy=None): exchange_type = 'fanout'
self.exchange = "%s_fanout" % topic
def __init__(self, connection=None, topic='broadcast', proxy=None):
self.exchange = '%s_fanout' % topic
self.routing_key = topic self.routing_key = topic
unique = uuid.uuid4().hex unique = uuid.uuid4().hex
self.queue = "%s_fanout_%s" % (topic, unique) self.queue = '%s_fanout_%s' % (topic, unique)
self.durable = False self.durable = False
LOG.info(_("Created '%(exchange)s' fanout exchange " LOG.info(_('Created "%(exchange)s" fanout exchange '
"with '%(key)s' routing key"), 'with "%(key)s" routing key'),
dict(exchange=self.exchange, key=self.routing_key)) dict(exchange=self.exchange, key=self.routing_key))
super(FanoutAdapterConsumer, self).__init__(connection=connection, super(FanoutAdapterConsumer, self).__init__(connection=connection,
topic=topic, proxy=proxy) topic=topic, proxy=proxy)
class TopicPublisher(Publisher): class TopicPublisher(Publisher):
"""Publishes messages on a specific topic""" """Publishes messages on a specific topic."""
exchange_type = "topic"
def __init__(self, connection=None, topic="broadcast"): exchange_type = 'topic'
def __init__(self, connection=None, topic='broadcast'):
self.routing_key = topic self.routing_key = topic
self.exchange = FLAGS.control_exchange self.exchange = FLAGS.control_exchange
self.durable = False self.durable = False
@ -243,20 +256,22 @@ class TopicPublisher(Publisher):
class FanoutPublisher(Publisher): class FanoutPublisher(Publisher):
"""Publishes messages to a fanout exchange.""" """Publishes messages to a fanout exchange."""
exchange_type = "fanout"
exchange_type = 'fanout'
def __init__(self, topic, connection=None): def __init__(self, topic, connection=None):
self.exchange = "%s_fanout" % topic self.exchange = '%s_fanout' % topic
self.queue = "%s_fanout" % topic self.queue = '%s_fanout' % topic
self.durable = False self.durable = False
LOG.info(_("Creating '%(exchange)s' fanout exchange"), LOG.info(_('Creating "%(exchange)s" fanout exchange'),
dict(exchange=self.exchange)) dict(exchange=self.exchange))
super(FanoutPublisher, self).__init__(connection=connection) super(FanoutPublisher, self).__init__(connection=connection)
class DirectConsumer(Consumer): class DirectConsumer(Consumer):
"""Consumes messages directly on a channel specified by msg_id""" """Consumes messages directly on a channel specified by msg_id."""
exchange_type = "direct"
exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None): def __init__(self, connection=None, msg_id=None):
self.queue = msg_id self.queue = msg_id
@ -268,8 +283,9 @@ class DirectConsumer(Consumer):
class DirectPublisher(Publisher): class DirectPublisher(Publisher):
"""Publishes messages directly on a channel specified by msg_id""" """Publishes messages directly on a channel specified by msg_id."""
exchange_type = "direct"
exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None): def __init__(self, connection=None, msg_id=None):
self.routing_key = msg_id self.routing_key = msg_id
@ -279,9 +295,9 @@ class DirectPublisher(Publisher):
def msg_reply(msg_id, reply=None, failure=None): def msg_reply(msg_id, reply=None, failure=None):
"""Sends a reply or an error on the channel signified by msg_id """Sends a reply or an error on the channel signified by msg_id.
failure should be a sys.exc_info() tuple. Failure should be a sys.exc_info() tuple.
""" """
if failure: if failure:
@ -303,17 +319,20 @@ def msg_reply(msg_id, reply=None, failure=None):
class RemoteError(exception.Error): class RemoteError(exception.Error):
"""Signifies that a remote class has raised an exception """Signifies that a remote class has raised an exception.
Containes a string representation of the type of the original exception, Containes a string representation of the type of the original exception,
the value of the original exception, and the traceback. These are the value of the original exception, and the traceback. These are
sent to the parent as a joined string so printing the exception sent to the parent as a joined string so printing the exception
contains all of the relevent info.""" contains all of the relevent info.
"""
def __init__(self, exc_type, value, traceback): def __init__(self, exc_type, value, traceback):
self.exc_type = exc_type self.exc_type = exc_type
self.value = value self.value = value
self.traceback = traceback self.traceback = traceback
super(RemoteError, self).__init__("%s %s\n%s" % (exc_type, super(RemoteError, self).__init__('%s %s\n%s' % (exc_type,
value, value,
traceback)) traceback))
@ -339,6 +358,7 @@ def _pack_context(msg, context):
context out into a bunch of separate keys. If we want to support context out into a bunch of separate keys. If we want to support
more arguments in rabbit messages, we may want to do the same more arguments in rabbit messages, we may want to do the same
for args at some point. for args at some point.
""" """
context = dict([('_context_%s' % key, value) context = dict([('_context_%s' % key, value)
for (key, value) in context.to_dict().iteritems()]) for (key, value) in context.to_dict().iteritems()])
@ -346,11 +366,11 @@ def _pack_context(msg, context):
def call(context, topic, msg): def call(context, topic, msg):
"""Sends a message on a topic and wait for a response""" """Sends a message on a topic and wait for a response."""
LOG.debug(_("Making asynchronous call on %s ..."), topic) LOG.debug(_('Making asynchronous call on %s ...'), topic)
msg_id = uuid.uuid4().hex msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id}) msg.update({'_msg_id': msg_id})
LOG.debug(_("MSG_ID is %s") % (msg_id)) LOG.debug(_('MSG_ID is %s') % (msg_id))
_pack_context(msg, context) _pack_context(msg, context)
class WaitMessage(object): class WaitMessage(object):
@ -387,8 +407,8 @@ def call(context, topic, msg):
def cast(context, topic, msg): def cast(context, topic, msg):
"""Sends a message on a topic without waiting for a response""" """Sends a message on a topic without waiting for a response."""
LOG.debug(_("Making asynchronous cast on %s..."), topic) LOG.debug(_('Making asynchronous cast on %s...'), topic)
_pack_context(msg, context) _pack_context(msg, context)
conn = Connection.instance() conn = Connection.instance()
publisher = TopicPublisher(connection=conn, topic=topic) publisher = TopicPublisher(connection=conn, topic=topic)
@ -397,8 +417,8 @@ def cast(context, topic, msg):
def fanout_cast(context, topic, msg): def fanout_cast(context, topic, msg):
"""Sends a message on a fanout exchange without waiting for a response""" """Sends a message on a fanout exchange without waiting for a response."""
LOG.debug(_("Making asynchronous fanout cast...")) LOG.debug(_('Making asynchronous fanout cast...'))
_pack_context(msg, context) _pack_context(msg, context)
conn = Connection.instance() conn = Connection.instance()
publisher = FanoutPublisher(topic, connection=conn) publisher = FanoutPublisher(topic, connection=conn)
@ -407,14 +427,14 @@ def fanout_cast(context, topic, msg):
def generic_response(message_data, message): def generic_response(message_data, message):
"""Logs a result and exits""" """Logs a result and exits."""
LOG.debug(_('response %s'), message_data) LOG.debug(_('response %s'), message_data)
message.ack() message.ack()
sys.exit(0) sys.exit(0)
def send_message(topic, message, wait=True): def send_message(topic, message, wait=True):
"""Sends a message for testing""" """Sends a message for testing."""
msg_id = uuid.uuid4().hex msg_id = uuid.uuid4().hex
message.update({'_msg_id': msg_id}) message.update({'_msg_id': msg_id})
LOG.debug(_('topic is %s'), topic) LOG.debug(_('topic is %s'), topic)
@ -425,14 +445,14 @@ def send_message(topic, message, wait=True):
queue=msg_id, queue=msg_id,
exchange=msg_id, exchange=msg_id,
auto_delete=True, auto_delete=True,
exchange_type="direct", exchange_type='direct',
routing_key=msg_id) routing_key=msg_id)
consumer.register_callback(generic_response) consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(), publisher = messaging.Publisher(connection=Connection.instance(),
exchange=FLAGS.control_exchange, exchange=FLAGS.control_exchange,
durable=False, durable=False,
exchange_type="topic", exchange_type='topic',
routing_key=topic) routing_key=topic)
publisher.send(message) publisher.send(message)
publisher.close() publisher.close()
@ -441,8 +461,8 @@ def send_message(topic, message, wait=True):
consumer.wait() consumer.wait()
if __name__ == "__main__": if __name__ == '__main__':
# NOTE(vish): you can send messages from the command line using # You can send messages from the command line using
# topic and a json sting representing a dictionary # topic and a json string representing a dictionary
# for the method # for the method
send_message(sys.argv[1], json.loads(sys.argv[2])) send_message(sys.argv[1], json.loads(sys.argv[2]))

View File

@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Generic Node baseclass for all workers that run on hosts."""
Generic Node baseclass for all workers that run on hosts
"""
import inspect import inspect
import os import os
@ -30,13 +28,11 @@ from eventlet import event
from eventlet import greenthread from eventlet import greenthread
from eventlet import greenpool from eventlet import greenpool
from sqlalchemy.exc import OperationalError
from nova import context from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
from nova import log as logging
from nova import flags from nova import flags
from nova import log as logging
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova import version from nova import version
@ -79,7 +75,7 @@ class Service(object):
def start(self): def start(self):
vcs_string = version.version_string_with_vcs() vcs_string = version.version_string_with_vcs()
logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)"), logging.audit(_('Starting %(topic)s node (version %(vcs_string)s)'),
{'topic': self.topic, 'vcs_string': vcs_string}) {'topic': self.topic, 'vcs_string': vcs_string})
self.manager.init_host() self.manager.init_host()
self.model_disconnected = False self.model_disconnected = False
@ -140,29 +136,24 @@ class Service(object):
return getattr(manager, key) return getattr(manager, key)
@classmethod @classmethod
def create(cls, def create(cls, host=None, binary=None, topic=None, manager=None,
host=None, report_interval=None, periodic_interval=None):
binary=None,
topic=None,
manager=None,
report_interval=None,
periodic_interval=None):
"""Instantiates class and passes back application object. """Instantiates class and passes back application object.
Args: :param host: defaults to FLAGS.host
host, defaults to FLAGS.host :param binary: defaults to basename of executable
binary, defaults to basename of executable :param topic: defaults to bin_name - 'nova-' part
topic, defaults to bin_name - "nova-" part :param manager: defaults to FLAGS.<topic>_manager
manager, defaults to FLAGS.<topic>_manager :param report_interval: defaults to FLAGS.report_interval
report_interval, defaults to FLAGS.report_interval :param periodic_interval: defaults to FLAGS.periodic_interval
periodic_interval, defaults to FLAGS.periodic_interval
""" """
if not host: if not host:
host = FLAGS.host host = FLAGS.host
if not binary: if not binary:
binary = os.path.basename(inspect.stack()[-1][1]) binary = os.path.basename(inspect.stack()[-1][1])
if not topic: if not topic:
topic = binary.rpartition("nova-")[2] topic = binary.rpartition('nova-')[2]
if not manager: if not manager:
manager = FLAGS.get('%s_manager' % topic, None) manager = FLAGS.get('%s_manager' % topic, None)
if not report_interval: if not report_interval:
@ -175,12 +166,12 @@ class Service(object):
return service_obj return service_obj
def kill(self): def kill(self):
"""Destroy the service object in the datastore""" """Destroy the service object in the datastore."""
self.stop() self.stop()
try: try:
db.service_destroy(context.get_admin_context(), self.service_id) db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound: except exception.NotFound:
logging.warn(_("Service killed that has no database entry")) logging.warn(_('Service killed that has no database entry'))
def stop(self): def stop(self):
for x in self.timers: for x in self.timers:
@ -198,7 +189,7 @@ class Service(object):
pass pass
def periodic_tasks(self): def periodic_tasks(self):
"""Tasks to be run at a periodic interval""" """Tasks to be run at a periodic interval."""
self.manager.periodic_tasks(context.get_admin_context()) self.manager.periodic_tasks(context.get_admin_context())
def report_state(self): def report_state(self):
@ -208,8 +199,8 @@ class Service(object):
try: try:
service_ref = db.service_get(ctxt, self.service_id) service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound: except exception.NotFound:
logging.debug(_("The service database object disappeared, " logging.debug(_('The service database object disappeared, '
"Recreating it.")) 'Recreating it.'))
self._create_service_ref(ctxt) self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id) service_ref = db.service_get(ctxt, self.service_id)
@ -218,23 +209,24 @@ class Service(object):
{'report_count': service_ref['report_count'] + 1}) {'report_count': service_ref['report_count'] + 1})
# TODO(termie): make this pattern be more elegant. # TODO(termie): make this pattern be more elegant.
if getattr(self, "model_disconnected", False): if getattr(self, 'model_disconnected', False):
self.model_disconnected = False self.model_disconnected = False
logging.error(_("Recovered model server connection!")) logging.error(_('Recovered model server connection!'))
# TODO(vish): this should probably only catch connection errors # TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable=W0702 except Exception: # pylint: disable=W0702
if not getattr(self, "model_disconnected", False): if not getattr(self, 'model_disconnected', False):
self.model_disconnected = True self.model_disconnected = True
logging.exception(_("model server went away")) logging.exception(_('model server went away'))
class WsgiService(object): class WsgiService(object):
"""Base class for WSGI based services. """Base class for WSGI based services.
For each api you define, you must also define these flags: For each api you define, you must also define these flags:
:<api>_listen: The address on which to listen :<api>_listen: The address on which to listen
:<api>_listen_port: The port on which to listen :<api>_listen_port: The port on which to listen
""" """
def __init__(self, conf, apis): def __init__(self, conf, apis):
@ -250,13 +242,14 @@ class WsgiService(object):
class ApiService(WsgiService): class ApiService(WsgiService):
"""Class for our nova-api service""" """Class for our nova-api service."""
@classmethod @classmethod
def create(cls, conf=None): def create(cls, conf=None):
if not conf: if not conf:
conf = wsgi.paste_config_file(FLAGS.api_paste_config) conf = wsgi.paste_config_file(FLAGS.api_paste_config)
if not conf: if not conf:
message = (_("No paste configuration found for: %s"), message = (_('No paste configuration found for: %s'),
FLAGS.api_paste_config) FLAGS.api_paste_config)
raise exception.Error(message) raise exception.Error(message)
api_endpoints = ['ec2', 'osapi'] api_endpoints = ['ec2', 'osapi']
@ -280,11 +273,11 @@ def serve(*services):
FLAGS.ParseNewFlags() FLAGS.ParseNewFlags()
name = '_'.join(x.binary for x in services) name = '_'.join(x.binary for x in services)
logging.debug(_("Serving %s"), name) logging.debug(_('Serving %s'), name)
logging.debug(_("Full set of FLAGS:")) logging.debug(_('Full set of FLAGS:'))
for flag in FLAGS: for flag in FLAGS:
flag_get = FLAGS.get(flag, None) flag_get = FLAGS.get(flag, None)
logging.debug("%(flag)s : %(flag_get)s" % locals()) logging.debug('%(flag)s : %(flag_get)s' % locals())
for x in services: for x in services:
x.start() x.start()
@ -315,20 +308,20 @@ def serve_wsgi(cls, conf=None):
def _run_wsgi(paste_config_file, apis): def _run_wsgi(paste_config_file, apis):
logging.debug(_("Using paste.deploy config at: %s"), paste_config_file) logging.debug(_('Using paste.deploy config at: %s'), paste_config_file)
apps = [] apps = []
for api in apis: for api in apis:
config = wsgi.load_paste_configuration(paste_config_file, api) config = wsgi.load_paste_configuration(paste_config_file, api)
if config is None: if config is None:
logging.debug(_("No paste configuration for app: %s"), api) logging.debug(_('No paste configuration for app: %s'), api)
continue continue
logging.debug(_("App Config: %(api)s\n%(config)r") % locals()) logging.debug(_('App Config: %(api)s\n%(config)r') % locals())
logging.info(_("Running %s API"), api) logging.info(_('Running %s API'), api)
app = wsgi.load_paste_app(paste_config_file, api) app = wsgi.load_paste_app(paste_config_file, api)
apps.append((app, getattr(FLAGS, "%s_listen_port" % api), apps.append((app, getattr(FLAGS, '%s_listen_port' % api),
getattr(FLAGS, "%s_listen" % api))) getattr(FLAGS, '%s_listen' % api)))
if len(apps) == 0: if len(apps) == 0:
logging.error(_("No known API applications configured in %s."), logging.error(_('No known API applications configured in %s.'),
paste_config_file) paste_config_file)
return return

View File

@ -16,12 +16,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Base classes for our unit tests.
Base classes for our unit tests.
Allows overriding of flags for use of fakes,
and some black magic for inline callbacks.
"""
Allows overriding of flags for use of fakes, and some black magic for
inline callbacks.
"""
import datetime import datetime
import functools import functools
@ -52,9 +52,9 @@ flags.DEFINE_bool('fake_tests', True,
def skip_if_fake(func): def skip_if_fake(func):
"""Decorator that skips a test if running in fake mode""" """Decorator that skips a test if running in fake mode."""
def _skipper(*args, **kw): def _skipper(*args, **kw):
"""Wrapped skipper function""" """Wrapped skipper function."""
if FLAGS.fake_tests: if FLAGS.fake_tests:
raise unittest.SkipTest('Test cannot be run in fake mode') raise unittest.SkipTest('Test cannot be run in fake mode')
else: else:
@ -63,9 +63,10 @@ def skip_if_fake(func):
class TestCase(unittest.TestCase): class TestCase(unittest.TestCase):
"""Test case base class for all unit tests""" """Test case base class for all unit tests."""
def setUp(self): def setUp(self):
"""Run before each test method to initialize test environment""" """Run before each test method to initialize test environment."""
super(TestCase, self).setUp() super(TestCase, self).setUp()
# NOTE(vish): We need a better method for creating fixtures for tests # NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system # now that we have some required db setup for the system
@ -86,8 +87,7 @@ class TestCase(unittest.TestCase):
self._original_flags = FLAGS.FlagValuesDict() self._original_flags = FLAGS.FlagValuesDict()
def tearDown(self): def tearDown(self):
"""Runs after each test method to finalize/tear down test """Runs after each test method to tear down test environment."""
environment."""
try: try:
self.mox.UnsetStubs() self.mox.UnsetStubs()
self.stubs.UnsetAll() self.stubs.UnsetAll()
@ -121,7 +121,7 @@ class TestCase(unittest.TestCase):
pass pass
def flags(self, **kw): def flags(self, **kw):
"""Override flag variables for a test""" """Override flag variables for a test."""
for k, v in kw.iteritems(): for k, v in kw.iteritems():
if k in self.flag_overrides: if k in self.flag_overrides:
self.reset_flags() self.reset_flags()
@ -131,7 +131,11 @@ class TestCase(unittest.TestCase):
setattr(FLAGS, k, v) setattr(FLAGS, k, v)
def reset_flags(self): def reset_flags(self):
"""Resets all flag variables for the test. Runs after each test""" """Resets all flag variables for the test.
Runs after each test.
"""
FLAGS.Reset() FLAGS.Reset()
for k, v in self._original_flags.iteritems(): for k, v in self._original_flags.iteritems():
setattr(FLAGS, k, v) setattr(FLAGS, k, v)
@ -158,7 +162,6 @@ class TestCase(unittest.TestCase):
def _monkey_patch_wsgi(self): def _monkey_patch_wsgi(self):
"""Allow us to kill servers spawned by wsgi.Server.""" """Allow us to kill servers spawned by wsgi.Server."""
# TODO(termie): change these patterns to use functools
self.original_start = wsgi.Server.start self.original_start = wsgi.Server.start
@functools.wraps(self.original_start) @functools.wraps(self.original_start)
@ -189,12 +192,13 @@ class TestCase(unittest.TestCase):
If you don't care (or don't know) a given value, you can specify If you don't care (or don't know) a given value, you can specify
the string DONTCARE as the value. This will cause that dict-item the string DONTCARE as the value. This will cause that dict-item
to be skipped. to be skipped.
""" """
def raise_assertion(msg): def raise_assertion(msg):
d1str = str(d1) d1str = str(d1)
d2str = str(d2) d2str = str(d2)
base_msg = ("Dictionaries do not match. %(msg)s d1: %(d1str)s " base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
"d2: %(d2str)s" % locals()) 'd2: %(d2str)s' % locals())
raise AssertionError(base_msg) raise AssertionError(base_msg)
d1keys = set(d1.keys()) d1keys = set(d1.keys())
@ -202,8 +206,8 @@ class TestCase(unittest.TestCase):
if d1keys != d2keys: if d1keys != d2keys:
d1only = d1keys - d2keys d1only = d1keys - d2keys
d2only = d2keys - d1keys d2only = d2keys - d1keys
raise_assertion("Keys in d1 and not d2: %(d1only)s. " raise_assertion('Keys in d1 and not d2: %(d1only)s. '
"Keys in d2 and not d1: %(d2only)s" % locals()) 'Keys in d2 and not d1: %(d2only)s' % locals())
for key in d1keys: for key in d1keys:
d1value = d1[key] d1value = d1[key]
@ -217,19 +221,19 @@ class TestCase(unittest.TestCase):
"d2['%(key)s']=%(d2value)s" % locals()) "d2['%(key)s']=%(d2value)s" % locals())
def assertDictListMatch(self, L1, L2): def assertDictListMatch(self, L1, L2):
"""Assert a list of dicts are equivalent""" """Assert a list of dicts are equivalent."""
def raise_assertion(msg): def raise_assertion(msg):
L1str = str(L1) L1str = str(L1)
L2str = str(L2) L2str = str(L2)
base_msg = ("List of dictionaries do not match: %(msg)s " base_msg = ('List of dictionaries do not match: %(msg)s '
"L1: %(L1str)s L2: %(L2str)s" % locals()) 'L1: %(L1str)s L2: %(L2str)s' % locals())
raise AssertionError(base_msg) raise AssertionError(base_msg)
L1count = len(L1) L1count = len(L1)
L2count = len(L2) L2count = len(L2)
if L1count != L2count: if L1count != L2count:
raise_assertion("Length mismatch: len(L1)=%(L1count)d != " raise_assertion('Length mismatch: len(L1)=%(L1count)d != '
"len(L2)=%(L2count)d" % locals()) 'len(L2)=%(L2count)d' % locals())
for d1, d2 in zip(L1, L2): for d1, d2 in zip(L1, L2):
self.assertDictMatch(d1, d2) self.assertDictMatch(d1, d2)

View File

@ -136,6 +136,12 @@ class RequestTest(test.TestCase):
request.body = "asdf<br />" request.body = "asdf<br />"
self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type)
def test_request_content_type_with_charset(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Content-Type"] = "application/json; charset=UTF-8"
result = request.get_content_type()
self.assertEqual(result, "application/json")
def test_content_type_from_accept_xml(self): def test_content_type_from_accept_xml(self):
request = wsgi.Request.blank('/tests/123') request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/xml" request.headers["Accept"] = "application/xml"

View File

@ -618,7 +618,8 @@ class IptablesFirewallTestCase(test.TestCase):
instance_ref = db.instance_create(self.context, instance_ref = db.instance_create(self.context,
{'user_id': 'fake', {'user_id': 'fake',
'project_id': 'fake', 'project_id': 'fake',
'mac_address': '56:12:12:12:12:12'}) 'mac_address': '56:12:12:12:12:12',
'instance_type_id': 1})
ip = '10.11.12.13' ip = '10.11.12.13'
network_ref = db.project_get_network(self.context, network_ref = db.project_get_network(self.context,
@ -841,7 +842,8 @@ class NWFilterTestCase(test.TestCase):
instance_ref = db.instance_create(self.context, instance_ref = db.instance_create(self.context,
{'user_id': 'fake', {'user_id': 'fake',
'project_id': 'fake', 'project_id': 'fake',
'mac_address': '00:A0:C9:14:C8:29'}) 'mac_address': '00:A0:C9:14:C8:29',
'instance_type_id': 1})
inst_id = instance_ref['id'] inst_id = instance_ref['id']
ip = '10.11.12.13' ip = '10.11.12.13'

View File

@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Utilities and helper functions."""
System-level utilities and helper functions.
"""
import base64 import base64
import datetime import datetime
@ -43,9 +41,8 @@ from eventlet import event
from eventlet import greenthread from eventlet import greenthread
from eventlet import semaphore from eventlet import semaphore
from eventlet.green import subprocess from eventlet.green import subprocess
None
from nova import exception from nova import exception
from nova.exception import ProcessExecutionError
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@ -56,7 +53,7 @@ FLAGS = flags.FLAGS
def import_class(import_str): def import_class(import_str):
"""Returns a class from a string including module and class""" """Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.') mod_str, _sep, class_str = import_str.rpartition('.')
try: try:
__import__(mod_str) __import__(mod_str)
@ -67,7 +64,7 @@ def import_class(import_str):
def import_object(import_str): def import_object(import_str):
"""Returns an object including a module or module and class""" """Returns an object including a module or module and class."""
try: try:
__import__(import_str) __import__(import_str)
return sys.modules[import_str] return sys.modules[import_str]
@ -99,11 +96,12 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
cli_id = 64 bit identifier cli_id = 64 bit identifier
? = unknown, probably flags/padding ? = unknown, probably flags/padding
bit 9 was 1 and the rest were 0 in testing bit 9 was 1 and the rest were 0 in testing
""" """
if session_id is None: if session_id is None:
session_id = random.randint(0, 0xffffffffffffffff) session_id = random.randint(0, 0xffffffffffffffff)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = struct.pack("!BQxxxxxx", 0x38, session_id) data = struct.pack('!BQxxxxxx', 0x38, session_id)
sock.sendto(data, (address, port)) sock.sendto(data, (address, port))
sock.settimeout(timeout) sock.settimeout(timeout)
try: try:
@ -112,7 +110,7 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
return False return False
finally: finally:
sock.close() sock.close()
fmt = "!BQxxxxxQxxxx" fmt = '!BQxxxxxQxxxx'
if len(received) != struct.calcsize(fmt): if len(received) != struct.calcsize(fmt):
print struct.calcsize(fmt) print struct.calcsize(fmt)
return False return False
@ -122,15 +120,8 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
def fetchfile(url, target): def fetchfile(url, target):
LOG.debug(_("Fetching %s") % url) LOG.debug(_('Fetching %s') % url)
# c = pycurl.Curl() execute('curl', '--fail', url, '-o', target)
# fp = open(target, "wb")
# c.setopt(c.URL, url)
# c.setopt(c.WRITEDATA, fp)
# c.perform()
# c.close()
# fp.close()
execute("curl", "--fail", url, "-o", target)
def execute(*cmd, **kwargs): def execute(*cmd, **kwargs):
@ -147,7 +138,7 @@ def execute(*cmd, **kwargs):
while attempts > 0: while attempts > 0:
attempts -= 1 attempts -= 1
try: try:
LOG.debug(_("Running cmd (subprocess): %s"), ' '.join(cmd)) LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
env = os.environ.copy() env = os.environ.copy()
if addl_env: if addl_env:
env.update(addl_env) env.update(addl_env)
@ -163,20 +154,21 @@ def execute(*cmd, **kwargs):
result = obj.communicate() result = obj.communicate()
obj.stdin.close() obj.stdin.close()
if obj.returncode: if obj.returncode:
LOG.debug(_("Result was %s") % obj.returncode) LOG.debug(_('Result was %s') % obj.returncode)
if type(check_exit_code) == types.IntType \ if type(check_exit_code) == types.IntType \
and obj.returncode != check_exit_code: and obj.returncode != check_exit_code:
(stdout, stderr) = result (stdout, stderr) = result
raise ProcessExecutionError(exit_code=obj.returncode, raise exception.ProcessExecutionError(
stdout=stdout, exit_code=obj.returncode,
stderr=stderr, stdout=stdout,
cmd=' '.join(cmd)) stderr=stderr,
cmd=' '.join(cmd))
return result return result
except ProcessExecutionError: except exception.ProcessExecutionError:
if not attempts: if not attempts:
raise raise
else: else:
LOG.debug(_("%r failed. Retrying."), cmd) LOG.debug(_('%r failed. Retrying.'), cmd)
if delay_on_retry: if delay_on_retry:
greenthread.sleep(random.randint(20, 200) / 100.0) greenthread.sleep(random.randint(20, 200) / 100.0)
finally: finally:
@ -188,13 +180,13 @@ def execute(*cmd, **kwargs):
def ssh_execute(ssh, cmd, process_input=None, def ssh_execute(ssh, cmd, process_input=None,
addl_env=None, check_exit_code=True): addl_env=None, check_exit_code=True):
LOG.debug(_("Running cmd (SSH): %s"), ' '.join(cmd)) LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
if addl_env: if addl_env:
raise exception.Error("Environment not supported over SSH") raise exception.Error(_('Environment not supported over SSH'))
if process_input: if process_input:
# This is (probably) fixable if we need it... # This is (probably) fixable if we need it...
raise exception.Error("process_input not supported over SSH") raise exception.Error(_('process_input not supported over SSH'))
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
channel = stdout_stream.channel channel = stdout_stream.channel
@ -212,7 +204,7 @@ def ssh_execute(ssh, cmd, process_input=None,
# exit_status == -1 if no exit code was returned # exit_status == -1 if no exit code was returned
if exit_status != -1: if exit_status != -1:
LOG.debug(_("Result was %s") % exit_status) LOG.debug(_('Result was %s') % exit_status)
if check_exit_code and exit_status != 0: if check_exit_code and exit_status != 0:
raise exception.ProcessExecutionError(exit_code=exit_status, raise exception.ProcessExecutionError(exit_code=exit_status,
stdout=stdout, stdout=stdout,
@ -251,7 +243,7 @@ def debug(arg):
def runthis(prompt, *cmd, **kwargs): def runthis(prompt, *cmd, **kwargs):
LOG.debug(_("Running %s"), (" ".join(cmd))) LOG.debug(_('Running %s'), (' '.join(cmd)))
rv, err = execute(*cmd, **kwargs) rv, err = execute(*cmd, **kwargs)
@ -266,48 +258,49 @@ def generate_mac():
random.randint(0x00, 0x7f), random.randint(0x00, 0x7f),
random.randint(0x00, 0xff), random.randint(0x00, 0xff),
random.randint(0x00, 0xff)] random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac)) return ':'.join(map(lambda x: '%02x' % x, mac))
# Default symbols to use for passwords. Avoids visually confusing characters. # Default symbols to use for passwords. Avoids visually confusing characters.
# ~6 bits per symbol # ~6 bits per symbol
DEFAULT_PASSWORD_SYMBOLS = ("23456789" # Removed: 0,1 DEFAULT_PASSWORD_SYMBOLS = ('23456789' # Removed: 0,1
"ABCDEFGHJKLMNPQRSTUVWXYZ" # Removed: I, O 'ABCDEFGHJKLMNPQRSTUVWXYZ' # Removed: I, O
"abcdefghijkmnopqrstuvwxyz") # Removed: l 'abcdefghijkmnopqrstuvwxyz') # Removed: l
# ~5 bits per symbol # ~5 bits per symbol
EASIER_PASSWORD_SYMBOLS = ("23456789" # Removed: 0, 1 EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
"ABCDEFGHJKLMNPQRSTUVWXYZ") # Removed: I, O 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS): def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
"""Generate a random password from the supplied symbols. """Generate a random password from the supplied symbols.
Believed to be reasonably secure (with a reasonable password length!) Believed to be reasonably secure (with a reasonable password length!)
""" """
r = random.SystemRandom() r = random.SystemRandom()
return "".join([r.choice(symbols) for _i in xrange(length)]) return ''.join([r.choice(symbols) for _i in xrange(length)])
def last_octet(address): def last_octet(address):
return int(address.split(".")[-1]) return int(address.split('.')[-1])
def get_my_linklocal(interface): def get_my_linklocal(interface):
try: try:
if_str = execute("ip", "-f", "inet6", "-o", "addr", "show", interface) if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link" 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')] 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] address = [w.group(1) for w in links if w is not None]
if address[0] is not None: if address[0] is not None:
return address[0] return address[0]
else: else:
raise exception.Error(_("Link Local address is not found.:%s") raise exception.Error(_('Link Local address is not found.:%s')
% if_str) % if_str)
except Exception as ex: except Exception as ex:
raise exception.Error(_("Couldn't get Link Local IP of %(interface)s" raise exception.Error(_("Couldn't get Link Local IP of %(interface)s"
" :%(ex)s") % locals()) " :%(ex)s") % locals())
def to_global_ipv6(prefix, mac): def to_global_ipv6(prefix, mac):
@ -319,15 +312,15 @@ def to_global_ipv6(prefix, mac):
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\ return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
format() format()
except TypeError: except TypeError:
raise TypeError(_("Bad mac for to_global_ipv6: %s") % mac) raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
def to_mac(ipv6_address): def to_mac(ipv6_address):
address = netaddr.IPAddress(ipv6_address) address = netaddr.IPAddress(ipv6_address)
mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff") mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff')
mask2 = netaddr.IPAddress("::0200:0:0:0") mask2 = netaddr.IPAddress('::0200:0:0:0')
mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]]) return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]])
def utcnow(): def utcnow():
@ -341,7 +334,7 @@ utcnow.override_time = None
def is_older_than(before, seconds): def is_older_than(before, seconds):
"""Return True if before is older than seconds""" """Return True if before is older than seconds."""
return utcnow() - before > datetime.timedelta(seconds=seconds) return utcnow() - before > datetime.timedelta(seconds=seconds)
@ -379,7 +372,7 @@ def isotime(at=None):
def parse_isotime(timestr): def parse_isotime(timestr):
"""Turn an iso formatted time back into a datetime""" """Turn an iso formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, TIME_FORMAT) return datetime.datetime.strptime(timestr, TIME_FORMAT)
@ -433,16 +426,19 @@ class LazyPluggable(object):
class LoopingCallDone(Exception): class LoopingCallDone(Exception):
"""The poll-function passed to LoopingCall can raise this exception to """Exception to break out and stop a LoopingCall.
The poll-function passed to LoopingCall can raise this exception to
break out of the loop normally. This is somewhat analogous to break out of the loop normally. This is somewhat analogous to
StopIteration. StopIteration.
An optional return-value can be included as the argument to the exception; An optional return-value can be included as the argument to the exception;
this return-value will be returned by LoopingCall.wait() this return-value will be returned by LoopingCall.wait()
""" """
def __init__(self, retvalue=True): def __init__(self, retvalue=True):
""":param retvalue: Value that LoopingCall.wait() should return""" """:param retvalue: Value that LoopingCall.wait() should return."""
self.retvalue = retvalue self.retvalue = retvalue
@ -493,7 +489,7 @@ def xhtml_escape(value):
http://github.com/facebook/tornado/blob/master/tornado/escape.py http://github.com/facebook/tornado/blob/master/tornado/escape.py
""" """
return saxutils.escape(value, {'"': "&quot;"}) return saxutils.escape(value, {'"': '&quot;'})
def utf8(value): def utf8(value):
@ -504,7 +500,7 @@ def utf8(value):
""" """
if isinstance(value, unicode): if isinstance(value, unicode):
return value.encode("utf-8") return value.encode('utf-8')
assert isinstance(value, str) assert isinstance(value, str)
return value return value
@ -554,7 +550,7 @@ class _NoopContextManager(object):
def synchronized(name, external=False): def synchronized(name, external=False):
"""Synchronization decorator """Synchronization decorator.
Decorating a method like so: Decorating a method like so:
@synchronized('mylock') @synchronized('mylock')
@ -578,6 +574,7 @@ def synchronized(name, external=False):
multiple processes. This means that if two different workers both run a multiple processes. This means that if two different workers both run a
a method decorated with @synchronized('mylock', external=True), only one a method decorated with @synchronized('mylock', external=True), only one
of them will execute at a time. of them will execute at a time.
""" """
def wrap(f): def wrap(f):
@ -590,13 +587,13 @@ def synchronized(name, external=False):
_semaphores[name] = semaphore.Semaphore() _semaphores[name] = semaphore.Semaphore()
sem = _semaphores[name] sem = _semaphores[name]
LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method ' LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method '
'"%(method)s"...' % {"lock": name, '"%(method)s"...' % {'lock': name,
"method": f.__name__})) 'method': f.__name__}))
with sem: with sem:
if external: if external:
LOG.debug(_('Attempting to grab file lock "%(lock)s" for ' LOG.debug(_('Attempting to grab file lock "%(lock)s" for '
'method "%(method)s"...' % 'method "%(method)s"...' %
{"lock": name, "method": f.__name__})) {'lock': name, 'method': f.__name__}))
lock_file_path = os.path.join(FLAGS.lock_path, lock_file_path = os.path.join(FLAGS.lock_path,
'nova-%s.lock' % name) 'nova-%s.lock' % name)
lock = lockfile.FileLock(lock_file_path) lock = lockfile.FileLock(lock_file_path)
@ -617,21 +614,23 @@ def synchronized(name, external=False):
def get_from_path(items, path): def get_from_path(items, path):
""" Returns a list of items matching the specified path. Takes an """Returns a list of items matching the specified path.
XPath-like expression e.g. prop1/prop2/prop3, and for each item in items,
looks up items[prop1][prop2][prop3]. Like XPath, if any of the Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item
in items, looks up items[prop1][prop2][prop3]. Like XPath, if any of the
intermediate results are lists it will treat each list item individually. intermediate results are lists it will treat each list item individually.
A 'None' in items or any child expressions will be ignored, this function A 'None' in items or any child expressions will be ignored, this function
will not throw because of None (anywhere) in items. The returned list will not throw because of None (anywhere) in items. The returned list
will contain no None values.""" will contain no None values.
"""
if path is None: if path is None:
raise exception.Error("Invalid mini_xpath") raise exception.Error('Invalid mini_xpath')
(first_token, sep, remainder) = path.partition("/") (first_token, sep, remainder) = path.partition('/')
if first_token == "": if first_token == '':
raise exception.Error("Invalid mini_xpath") raise exception.Error('Invalid mini_xpath')
results = [] results = []
@ -645,7 +644,7 @@ def get_from_path(items, path):
for item in items: for item in items:
if item is None: if item is None:
continue continue
get_method = getattr(item, "get", None) get_method = getattr(item, 'get', None)
if get_method is None: if get_method is None:
continue continue
child = get_method(first_token) child = get_method(first_token)
@ -666,7 +665,7 @@ def get_from_path(items, path):
def flatten_dict(dict_, flattened=None): def flatten_dict(dict_, flattened=None):
"""Recursively flatten a nested dictionary""" """Recursively flatten a nested dictionary."""
flattened = flattened or {} flattened = flattened or {}
for key, value in dict_.iteritems(): for key, value in dict_.iteritems():
if hasattr(value, 'iteritems'): if hasattr(value, 'iteritems'):
@ -677,9 +676,7 @@ def flatten_dict(dict_, flattened=None):
def partition_dict(dict_, keys): def partition_dict(dict_, keys):
"""Return two dicts, one containing only `keys` the other containing """Return two dicts, one with `keys` the other with everything else."""
everything but `keys`
"""
intersection = {} intersection = {}
difference = {} difference = {}
for key, value in dict_.iteritems(): for key, value in dict_.iteritems():
@ -691,9 +688,7 @@ def partition_dict(dict_, keys):
def map_dict_keys(dict_, key_map): def map_dict_keys(dict_, key_map):
"""Return a dictionary in which the dictionaries keys are mapped to """Return a dict in which the dictionaries keys are mapped to new keys."""
new keys.
"""
mapped = {} mapped = {}
for key, value in dict_.iteritems(): for key, value in dict_.iteritems():
mapped_key = key_map[key] if key in key_map else key mapped_key = key_map[key] if key in key_map else key
@ -702,15 +697,15 @@ def map_dict_keys(dict_, key_map):
def subset_dict(dict_, keys): def subset_dict(dict_, keys):
"""Return a dict that only contains a subset of keys""" """Return a dict that only contains a subset of keys."""
subset = partition_dict(dict_, keys)[0] subset = partition_dict(dict_, keys)[0]
return subset return subset
def check_isinstance(obj, cls): def check_isinstance(obj, cls):
"""Checks that obj is of type cls, and lets PyLint infer types""" """Checks that obj is of type cls, and lets PyLint infer types."""
if isinstance(obj, cls): if isinstance(obj, cls):
return obj return obj
raise Exception(_("Expected object of type: %s") % (str(cls))) raise Exception(_('Expected object of type: %s') % (str(cls)))
# TODO(justinsb): Can we make this better?? # TODO(justinsb): Can we make this better??
return cls() # Ugly PyLint hack return cls() # Ugly PyLint hack

View File

@ -21,9 +21,9 @@ except ImportError:
'revision_id': 'LOCALREVISION', 'revision_id': 'LOCALREVISION',
'revno': 0} 'revno': 0}
NOVA_VERSION = ['2011', '3'] NOVA_VERSION = ['2011', '3']
YEAR, COUNT = NOVA_VERSION YEAR, COUNT = NOVA_VERSION
FINAL = False # This becomes true at Release Candidate time FINAL = False # This becomes true at Release Candidate time
@ -39,8 +39,8 @@ def version_string():
def vcs_version_string(): def vcs_version_string():
return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) return '%s:%s' % (version_info['branch_nick'], version_info['revision_id'])
def version_string_with_vcs(): def version_string_with_vcs():
return "%s-%s" % (canonical_version_string(), vcs_version_string()) return '%s-%s' % (canonical_version_string(), vcs_version_string())

View File

@ -154,8 +154,8 @@ def _get_net_and_prefixlen(cidr):
def _get_ip_version(cidr): def _get_ip_version(cidr):
net = IPy.IP(cidr) net = IPy.IP(cidr)
return int(net.version()) return int(net.version())
def _get_network_info(instance): def _get_network_info(instance):
@ -165,9 +165,10 @@ def _get_network_info(instance):
ip_addresses = db.fixed_ip_get_all_by_instance(admin_context, ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
instance['id']) instance['id'])
networks = db.network_get_all_by_instance(admin_context, networks = db.network_get_all_by_instance(admin_context,
instance['id']) instance['id'])
flavor = db.instance_type_get_by_id(admin_context,
instance['instance_type_id'])
network_info = [] network_info = []
for network in networks: for network in networks:
@ -191,7 +192,9 @@ def _get_network_info(instance):
mapping = { mapping = {
'label': network['label'], 'label': network['label'],
'gateway': network['gateway'], 'gateway': network['gateway'],
'broadcast': network['broadcast'],
'mac': instance['mac_address'], 'mac': instance['mac_address'],
'rxtx_cap': flavor['rxtx_cap'],
'dns': [network['dns']], 'dns': [network['dns']],
'ips': [ip_dict(ip) for ip in network_ips]} 'ips': [ip_dict(ip) for ip in network_ips]}
@ -309,19 +312,10 @@ class LibvirtConnection(driver.ComputeDriver):
def destroy(self, instance, cleanup=True): def destroy(self, instance, cleanup=True):
instance_name = instance['name'] instance_name = instance['name']
# TODO(justinsb): Refactor all lookupByName calls for error-handling
try: try:
virt_dom = self._conn.lookupByName(instance_name) virt_dom = self._lookup_by_name(instance_name)
except libvirt.libvirtError as e: except exception.NotFound:
errcode = e.get_error_code() virt_dom = None
if errcode == libvirt.VIR_ERR_NO_DOMAIN:
virt_dom = None
else:
LOG.warning(_("Error from libvirt during lookup of "
"%(instance_name)s. Code=%(errcode)s "
"Error=%(e)s") %
locals())
raise
# If the instance is already terminated, we're still happy # If the instance is already terminated, we're still happy
# Otherwise, destroy it # Otherwise, destroy it
@ -359,28 +353,19 @@ class LibvirtConnection(driver.ComputeDriver):
locals()) locals())
raise raise
# We'll save this for when we do shutdown, def _wait_for_destroy():
# instead of destroy - but destroy returns immediately """Called at an interval until the VM is gone."""
timer = utils.LoopingCall(f=None) instance_name = instance['name']
while True:
try: try:
state = self.get_info(instance['name'])['state'] state = self.get_info(instance_name)['state']
db.instance_set_state(context.get_admin_context(), except exception.NotFound:
instance['id'], state) msg = _("Instance %s destroyed successfully.") % instance_name
if state == power_state.SHUTOFF: LOG.info(msg)
break raise utils.LoopingCallDone
# Let's not hammer on the DB timer = utils.LoopingCall(_wait_for_destroy)
time.sleep(1) timer.start(interval=0.5, now=True)
except Exception as ex:
msg = _("Error encountered when destroying instance '%(id)s': "
"%(ex)s") % {"id": instance["id"], "ex": ex}
LOG.debug(msg)
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.SHUTOFF)
break
self.firewall_driver.unfilter_instance(instance) self.firewall_driver.unfilter_instance(instance)
@ -401,7 +386,7 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception @exception.wrap_exception
def attach_volume(self, instance_name, device_path, mountpoint): def attach_volume(self, instance_name, device_path, mountpoint):
virt_dom = self._conn.lookupByName(instance_name) virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2] mount_device = mountpoint.rpartition("/")[2]
if device_path.startswith('/dev/'): if device_path.startswith('/dev/'):
xml = """<disk type='block'> xml = """<disk type='block'>
@ -445,7 +430,7 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception @exception.wrap_exception
def detach_volume(self, instance_name, mountpoint): def detach_volume(self, instance_name, mountpoint):
virt_dom = self._conn.lookupByName(instance_name) virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2] mount_device = mountpoint.rpartition("/")[2]
xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device)
if not xml: if not xml:
@ -462,7 +447,7 @@ class LibvirtConnection(driver.ComputeDriver):
""" """
image_service = utils.import_object(FLAGS.image_service) image_service = utils.import_object(FLAGS.image_service)
virt_dom = self._conn.lookupByName(instance['name']) virt_dom = self._lookup_by_name(instance['name'])
elevated = context.get_admin_context() elevated = context.get_admin_context()
base = image_service.show(elevated, instance['image_id']) base = image_service.show(elevated, instance['image_id'])
@ -522,6 +507,12 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception @exception.wrap_exception
def reboot(self, instance): def reboot(self, instance):
"""Reboot a virtual machine, given an instance reference.
This method actually destroys and re-creates the domain to ensure the
reboot happens, as the guest OS cannot ignore this action.
"""
self.destroy(instance, False) self.destroy(instance, False)
xml = self.to_xml(instance) xml = self.to_xml(instance)
self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.setup_basic_filtering(instance)
@ -529,24 +520,23 @@ class LibvirtConnection(driver.ComputeDriver):
self._create_new_domain(xml) self._create_new_domain(xml)
self.firewall_driver.apply_instance_filter(instance) self.firewall_driver.apply_instance_filter(instance)
timer = utils.LoopingCall(f=None)
def _wait_for_reboot(): def _wait_for_reboot():
try: """Called at an interval until the VM is running again."""
state = self.get_info(instance['name'])['state'] instance_name = instance['name']
db.instance_set_state(context.get_admin_context(),
instance['id'], state)
if state == power_state.RUNNING:
LOG.debug(_('instance %s: rebooted'), instance['name'])
timer.stop()
except Exception, exn:
LOG.exception(_('_wait_for_reboot failed: %s'), exn)
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.SHUTDOWN)
timer.stop()
timer.f = _wait_for_reboot try:
state = self.get_info(instance_name)['state']
except exception.NotFound:
msg = _("During reboot, %s disappeared.") % instance_name
LOG.error(msg)
raise utils.LoopingCallDone
if state == power_state.RUNNING:
msg = _("Instance %s rebooted successfully.") % instance_name
LOG.info(msg)
raise utils.LoopingCallDone
timer = utils.LoopingCall(_wait_for_reboot)
return timer.start(interval=0.5, now=True) return timer.start(interval=0.5, now=True)
@exception.wrap_exception @exception.wrap_exception
@ -566,7 +556,15 @@ class LibvirtConnection(driver.ComputeDriver):
raise exception.ApiError("resume not supported for libvirt") raise exception.ApiError("resume not supported for libvirt")
@exception.wrap_exception @exception.wrap_exception
def rescue(self, instance, callback=None): def rescue(self, instance):
"""Loads a VM using rescue images.
A rescue is normally performed when something goes wrong with the
primary images and data needs to be corrected/recovered. Rescuing
should not edit or over-ride the original image, only allow for
data recovery.
"""
self.destroy(instance, False) self.destroy(instance, False)
xml = self.to_xml(instance, rescue=True) xml = self.to_xml(instance, rescue=True)
@ -576,29 +574,33 @@ class LibvirtConnection(driver.ComputeDriver):
self._create_image(instance, xml, '.rescue', rescue_images) self._create_image(instance, xml, '.rescue', rescue_images)
self._create_new_domain(xml) self._create_new_domain(xml)
timer = utils.LoopingCall(f=None)
def _wait_for_rescue(): def _wait_for_rescue():
try: """Called at an interval until the VM is running again."""
state = self.get_info(instance['name'])['state'] instance_name = instance['name']
db.instance_set_state(None, instance['id'], state)
if state == power_state.RUNNING:
LOG.debug(_('instance %s: rescued'), instance['name'])
timer.stop()
except Exception, exn:
LOG.exception(_('_wait_for_rescue failed: %s'), exn)
db.instance_set_state(None,
instance['id'],
power_state.SHUTDOWN)
timer.stop()
timer.f = _wait_for_rescue try:
state = self.get_info(instance_name)['state']
except exception.NotFound:
msg = _("During reboot, %s disappeared.") % instance_name
LOG.error(msg)
raise utils.LoopingCallDone
if state == power_state.RUNNING:
msg = _("Instance %s rescued successfully.") % instance_name
LOG.info(msg)
raise utils.LoopingCallDone
timer = utils.LoopingCall(_wait_for_rescue)
return timer.start(interval=0.5, now=True) return timer.start(interval=0.5, now=True)
@exception.wrap_exception @exception.wrap_exception
def unrescue(self, instance, callback=None): def unrescue(self, instance):
# NOTE(vish): Because reboot destroys and recreates an instance using """Reboot the VM which is being rescued back into primary images.
# the normal xml file, we can just call reboot here
Because reboot destroys and re-creates instances, unresue should
simply call reboot.
"""
self.reboot(instance) self.reboot(instance)
@exception.wrap_exception @exception.wrap_exception
@ -610,10 +612,6 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception @exception.wrap_exception
def spawn(self, instance, network_info=None): def spawn(self, instance, network_info=None):
xml = self.to_xml(instance, False, network_info) xml = self.to_xml(instance, False, network_info)
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.NOSTATE,
'launching')
self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.setup_basic_filtering(instance, network_info)
self.firewall_driver.prepare_instance_filter(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info)
self._create_image(instance, xml, network_info) self._create_image(instance, xml, network_info)
@ -626,25 +624,23 @@ class LibvirtConnection(driver.ComputeDriver):
instance['name']) instance['name'])
domain.setAutostart(1) domain.setAutostart(1)
timer = utils.LoopingCall(f=None)
def _wait_for_boot(): def _wait_for_boot():
try: """Called at an interval until the VM is running."""
state = self.get_info(instance['name'])['state'] instance_name = instance['name']
db.instance_set_state(context.get_admin_context(),
instance['id'], state)
if state == power_state.RUNNING:
LOG.debug(_('instance %s: booted'), instance['name'])
timer.stop()
except:
LOG.exception(_('instance %s: failed to boot'),
instance['name'])
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.SHUTDOWN)
timer.stop()
timer.f = _wait_for_boot try:
state = self.get_info(instance_name)['state']
except exception.NotFound:
msg = _("During reboot, %s disappeared.") % instance_name
LOG.error(msg)
raise utils.LoopingCallDone
if state == power_state.RUNNING:
msg = _("Instance %s spawned successfully.") % instance_name
LOG.info(msg)
raise utils.LoopingCallDone
timer = utils.LoopingCall(_wait_for_boot)
return timer.start(interval=0.5, now=True) return timer.start(interval=0.5, now=True)
def _flush_xen_console(self, virsh_output): def _flush_xen_console(self, virsh_output):
@ -710,7 +706,7 @@ class LibvirtConnection(driver.ComputeDriver):
raise Exception(_('Unable to find an open port')) raise Exception(_('Unable to find an open port'))
def get_pty_for_instance(instance_name): def get_pty_for_instance(instance_name):
virt_dom = self._conn.lookupByName(instance_name) virt_dom = self._lookup_by_name(instance_name)
xml = virt_dom.XMLDesc(0) xml = virt_dom.XMLDesc(0)
dom = minidom.parseString(xml) dom = minidom.parseString(xml)
@ -735,7 +731,7 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception @exception.wrap_exception
def get_vnc_console(self, instance): def get_vnc_console(self, instance):
def get_vnc_port_for_instance(instance_name): def get_vnc_port_for_instance(instance_name):
virt_dom = self._conn.lookupByName(instance_name) virt_dom = self._lookup_by_name(instance_name)
xml = virt_dom.XMLDesc(0) xml = virt_dom.XMLDesc(0)
# TODO: use etree instead of minidom # TODO: use etree instead of minidom
dom = minidom.parseString(xml) dom = minidom.parseString(xml)
@ -1044,23 +1040,34 @@ class LibvirtConnection(driver.ComputeDriver):
instance['name']) instance['name'])
return xml return xml
def get_info(self, instance_name): def _lookup_by_name(self, instance_name):
# NOTE(justinsb): When libvirt isn't running / can't connect, we get: """Retrieve libvirt domain object given an instance name.
# libvir: Remote error : unable to connect to
# '/var/run/libvirt/libvirt-sock', libvirtd may need to be started:
# No such file or directory
try:
virt_dom = self._conn.lookupByName(instance_name)
except libvirt.libvirtError as e:
errcode = e.get_error_code()
if errcode == libvirt.VIR_ERR_NO_DOMAIN:
raise exception.NotFound(_("Instance %s not found")
% instance_name)
LOG.warning(_("Error from libvirt during lookup. "
"Code=%(errcode)s Error=%(e)s") %
locals())
raise
All libvirt error handling should be handled in this method and
relevant nova exceptions should be raised in response.
"""
try:
return self._conn.lookupByName(instance_name)
except libvirt.libvirtError as ex:
error_code = ex.get_error_code()
if error_code == libvirt.VIR_ERR_NO_DOMAIN:
msg = _("Instance %s not found") % instance_name
raise exception.NotFound(msg)
msg = _("Error from libvirt while looking up %(instance_name)s: "
"[Error Code %(error_code)s] %(ex)s") % locals()
raise exception.Error(msg)
def get_info(self, instance_name):
"""Retrieve information from libvirt for a specific instance name.
If a libvirt error is encountered during lookup, we might raise a
NotFound exception or Error exception depending on how severe the
libvirt error is.
"""
virt_dom = self._lookup_by_name(instance_name)
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
return {'state': state, return {'state': state,
'max_mem': max_mem, 'max_mem': max_mem,
@ -1097,7 +1104,7 @@ class LibvirtConnection(driver.ComputeDriver):
Returns a list of all block devices for this domain. Returns a list of all block devices for this domain.
""" """
domain = self._conn.lookupByName(instance_name) domain = self._lookup_by_name(instance_name)
# TODO(devcamcar): Replace libxml2 with etree. # TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0) xml = domain.XMLDesc(0)
doc = None doc = None
@ -1139,7 +1146,7 @@ class LibvirtConnection(driver.ComputeDriver):
Returns a list of all network interfaces for this instance. Returns a list of all network interfaces for this instance.
""" """
domain = self._conn.lookupByName(instance_name) domain = self._lookup_by_name(instance_name)
# TODO(devcamcar): Replace libxml2 with etree. # TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0) xml = domain.XMLDesc(0)
doc = None doc = None
@ -1354,7 +1361,7 @@ class LibvirtConnection(driver.ComputeDriver):
Note that this function takes an instance name, not an Instance, so Note that this function takes an instance name, not an Instance, so
that it can be called by monitor. that it can be called by monitor.
""" """
domain = self._conn.lookupByName(instance_name) domain = self._lookup_by_name(instance_name)
return domain.blockStats(disk) return domain.blockStats(disk)
def interface_stats(self, instance_name, interface): def interface_stats(self, instance_name, interface):
@ -1362,7 +1369,7 @@ class LibvirtConnection(driver.ComputeDriver):
Note that this function takes an instance name, not an Instance, so Note that this function takes an instance name, not an Instance, so
that it can be called by monitor. that it can be called by monitor.
""" """
domain = self._conn.lookupByName(instance_name) domain = self._lookup_by_name(instance_name)
return domain.interfaceStats(interface) return domain.interfaceStats(interface)
def get_console_pool_info(self, console_type): def get_console_pool_info(self, console_type):
@ -1558,7 +1565,7 @@ class LibvirtConnection(driver.ComputeDriver):
FLAGS.live_migration_bandwidth) FLAGS.live_migration_bandwidth)
except Exception: except Exception:
recover_method(ctxt, instance_ref) recover_method(ctxt, instance_ref, dest=dest)
raise raise
# Waiting for completion of live_migration. # Waiting for completion of live_migration.
@ -1734,11 +1741,16 @@ class NWFilterFirewall(FirewallDriver):
logging.info('ensuring static filters') logging.info('ensuring static filters')
self._ensure_static_filters() self._ensure_static_filters()
if instance['image_id'] == str(FLAGS.vpn_image_id):
base_filter = 'nova-vpn'
else:
base_filter = 'nova-base'
for (network, mapping) in network_info: for (network, mapping) in network_info:
nic_id = mapping['mac'].replace(':', '') nic_id = mapping['mac'].replace(':', '')
instance_filter_name = self._instance_filter_name(instance, nic_id) instance_filter_name = self._instance_filter_name(instance, nic_id)
self._define_filter(self._filter_container(instance_filter_name, self._define_filter(self._filter_container(instance_filter_name,
['nova-base'])) [base_filter]))
def _ensure_static_filters(self): def _ensure_static_filters(self):
if self.static_filters_configured: if self.static_filters_configured:
@ -1749,11 +1761,12 @@ class NWFilterFirewall(FirewallDriver):
'no-ip-spoofing', 'no-ip-spoofing',
'no-arp-spoofing', 'no-arp-spoofing',
'allow-dhcp-server'])) 'allow-dhcp-server']))
self._define_filter(self._filter_container('nova-vpn',
['allow-dhcp-server']))
self._define_filter(self.nova_base_ipv4_filter) self._define_filter(self.nova_base_ipv4_filter)
self._define_filter(self.nova_base_ipv6_filter) self._define_filter(self.nova_base_ipv6_filter)
self._define_filter(self.nova_dhcp_filter) self._define_filter(self.nova_dhcp_filter)
self._define_filter(self.nova_ra_filter) self._define_filter(self.nova_ra_filter)
self._define_filter(self.nova_vpn_filter)
if FLAGS.allow_project_net_traffic: if FLAGS.allow_project_net_traffic:
self._define_filter(self.nova_project_filter) self._define_filter(self.nova_project_filter)
if FLAGS.use_ipv6: if FLAGS.use_ipv6:
@ -1767,14 +1780,6 @@ class NWFilterFirewall(FirewallDriver):
''.join(["<filterref filter='%s'/>" % (f,) for f in filters])) ''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
return xml return xml
nova_vpn_filter = '''<filter name='nova-vpn' chain='root'>
<uuid>2086015e-cf03-11df-8c5d-080027c27973</uuid>
<filterref filter='allow-dhcp-server'/>
<filterref filter='nova-allow-dhcp-server'/>
<filterref filter='nova-base-ipv4'/>
<filterref filter='nova-base-ipv6'/>
</filter>'''
def nova_base_ipv4_filter(self): def nova_base_ipv4_filter(self):
retval = "<filter name='nova-base-ipv4' chain='ipv4'>" retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
for protocol in ['tcp', 'udp', 'icmp']: for protocol in ['tcp', 'udp', 'icmp']:
@ -1837,7 +1842,7 @@ class NWFilterFirewall(FirewallDriver):
""" """
if not network_info: if not network_info:
network_info = _get_network_info(instance) network_info = _get_network_info(instance)
if instance['image_id'] == FLAGS.vpn_image_id: if instance['image_id'] == str(FLAGS.vpn_image_id):
base_filter = 'nova-vpn' base_filter = 'nova-vpn'
else: else:
base_filter = 'nova-base' base_filter = 'nova-base'

View File

@ -103,3 +103,10 @@ class API(base.Base):
# TODO(vish): abstract status checking? # TODO(vish): abstract status checking?
if volume['status'] == "available": if volume['status'] == "available":
raise exception.ApiError(_("Volume is already detached")) raise exception.ApiError(_("Volume is already detached"))
def remove_from_compute(self, context, volume_id, host):
"""Remove volume from specified compute host."""
rpc.call(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "remove_volume",
"args": {'volume_id': volume_id}})

View File

@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Utility methods for working with WSGI servers."""
Utility methods for working with WSGI servers
"""
import os import os
import sys import sys
@ -33,7 +31,6 @@ import routes.middleware
import webob import webob
import webob.dec import webob.dec
import webob.exc import webob.exc
from paste import deploy from paste import deploy
from nova import exception from nova import exception
@ -66,7 +63,7 @@ class Server(object):
def start(self, application, port, host='0.0.0.0', backlog=128): def start(self, application, port, host='0.0.0.0', backlog=128):
"""Run a WSGI server with the given application.""" """Run a WSGI server with the given application."""
arg0 = sys.argv[0] arg0 = sys.argv[0]
logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals()) logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals())
socket = eventlet.listen((host, port), backlog=backlog) socket = eventlet.listen((host, port), backlog=backlog)
self.pool.spawn_n(self._run, application, socket) self.pool.spawn_n(self._run, application, socket)
@ -87,30 +84,34 @@ class Server(object):
class Request(webob.Request): class Request(webob.Request):
def best_match_content_type(self): def best_match_content_type(self):
""" """Determine the most acceptable content-type.
Determine the most acceptable content-type based on the
query extension then the Accept header
"""
parts = self.path.rsplit(".", 1) Based on the query extension then the Accept header.
"""
parts = self.path.rsplit('.', 1)
if len(parts) > 1: if len(parts) > 1:
format = parts[1] format = parts[1]
if format in ["json", "xml"]: if format in ['json', 'xml']:
return "application/{0}".format(parts[1]) return 'application/{0}'.format(parts[1])
ctypes = ["application/json", "application/xml"] ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes) bm = self.accept.best_match(ctypes)
return bm or "application/json" return bm or 'application/json'
def get_content_type(self): def get_content_type(self):
try: allowed_types = ("application/xml", "application/json")
ct = self.headers["Content-Type"] if not "Content-Type" in self.headers:
assert ct in ("application/xml", "application/json") msg = _("Missing Content-Type")
return ct LOG.debug(msg)
except Exception: raise webob.exc.HTTPBadRequest(msg)
raise webob.exc.HTTPBadRequest("Invalid content type") type = self.content_type
if type in allowed_types:
return type
LOG.debug(_("Wrong Content-Type: %s") % type)
raise webob.exc.HTTPBadRequest("Invalid content type")
class Application(object): class Application(object):
@ -118,7 +119,7 @@ class Application(object):
@classmethod @classmethod
def factory(cls, global_config, **local_config): def factory(cls, global_config, **local_config):
"""Used for paste app factories in paste.deploy config fles. """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [app:APPNAME] Any local configuration (that is, values under the [app:APPNAME]
section of the paste config) will be passed into the `__init__` method section of the paste config) will be passed into the `__init__` method
@ -173,8 +174,9 @@ class Application(object):
See the end of http://pythonpaste.org/webob/modules/dec.html See the end of http://pythonpaste.org/webob/modules/dec.html
for more info. for more info.
""" """
raise NotImplementedError(_("You must implement __call__")) raise NotImplementedError(_('You must implement __call__'))
class Middleware(Application): class Middleware(Application):
@ -184,11 +186,12 @@ class Middleware(Application):
initialized that will be called next. By default the middleware will initialized that will be called next. By default the middleware will
simply call its wrapped app, or you can override __call__ to customize its simply call its wrapped app, or you can override __call__ to customize its
behavior. behavior.
""" """
@classmethod @classmethod
def factory(cls, global_config, **local_config): def factory(cls, global_config, **local_config):
"""Used for paste app factories in paste.deploy config fles. """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [filter:APPNAME] Any local configuration (that is, values under the [filter:APPNAME]
section of the paste config) will be passed into the `__init__` method section of the paste config) will be passed into the `__init__` method
@ -240,20 +243,24 @@ class Middleware(Application):
class Debug(Middleware): class Debug(Middleware):
"""Helper class that can be inserted into any WSGI application chain """Helper class for debugging a WSGI application.
to get information about the request and response."""
Can be inserted into any WSGI application chain to get information
about the request and response.
"""
@webob.dec.wsgify(RequestClass=Request) @webob.dec.wsgify(RequestClass=Request)
def __call__(self, req): def __call__(self, req):
print ("*" * 40) + " REQUEST ENVIRON" print ('*' * 40) + ' REQUEST ENVIRON'
for key, value in req.environ.items(): for key, value in req.environ.items():
print key, "=", value print key, '=', value
print print
resp = req.get_response(self.application) resp = req.get_response(self.application)
print ("*" * 40) + " RESPONSE HEADERS" print ('*' * 40) + ' RESPONSE HEADERS'
for (key, value) in resp.headers.iteritems(): for (key, value) in resp.headers.iteritems():
print key, "=", value print key, '=', value
print print
resp.app_iter = self.print_generator(resp.app_iter) resp.app_iter = self.print_generator(resp.app_iter)
@ -262,11 +269,8 @@ class Debug(Middleware):
@staticmethod @staticmethod
def print_generator(app_iter): def print_generator(app_iter):
""" """Iterator that prints the contents of a wrapper string."""
Iterator that prints the contents of a wrapper string iterator print ('*' * 40) + ' BODY'
when iterated.
"""
print ("*" * 40) + " BODY"
for part in app_iter: for part in app_iter:
sys.stdout.write(part) sys.stdout.write(part)
sys.stdout.flush() sys.stdout.flush()
@ -275,13 +279,10 @@ class Debug(Middleware):
class Router(object): class Router(object):
""" """WSGI middleware that maps incoming requests to WSGI apps."""
WSGI middleware that maps incoming requests to WSGI apps.
"""
def __init__(self, mapper): def __init__(self, mapper):
""" """Create a router for the given routes.Mapper.
Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as WSGI app to call. You'll probably want to specify an 'action' as
@ -293,15 +294,16 @@ class Router(object):
sc = ServerController() sc = ServerController()
# Explicit mapping of one route to a controller+action # Explicit mapping of one route to a controller+action
mapper.connect(None, "/svrlist", controller=sc, action="list") mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined # Actions are all implicitly defined
mapper.resource("server", "servers", controller=sc) mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the # Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that # {path_info:.*} parameter so the target app can be handed just that
# section of the URL. # section of the URL.
mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp()) mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
""" """
self.map = mapper self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch, self._router = routes.middleware.RoutesMiddleware(self._dispatch,
@ -309,19 +311,22 @@ class Router(object):
@webob.dec.wsgify(RequestClass=Request) @webob.dec.wsgify(RequestClass=Request)
def __call__(self, req): def __call__(self, req):
""" """Route the incoming request to a controller based on self.map.
Route the incoming request to a controller based on self.map.
If no match, return a 404. If no match, return a 404.
""" """
return self._router return self._router
@staticmethod @staticmethod
@webob.dec.wsgify(RequestClass=Request) @webob.dec.wsgify(RequestClass=Request)
def _dispatch(req): def _dispatch(req):
""" """Dispatch the request to the appropriate controller.
Called by self._router after matching the incoming request to a route Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404 and putting the information into req.environ. Either returns 404
or the routed WSGI app's response. or the routed WSGI app's response.
""" """
match = req.environ['wsgiorg.routing_args'][1] match = req.environ['wsgiorg.routing_args'][1]
if not match: if not match:
@ -331,19 +336,19 @@ class Router(object):
class Controller(object): class Controller(object):
""" """WSGI app that dispatched to methods.
WSGI app that reads routing information supplied by RoutesMiddleware WSGI app that reads routing information supplied by RoutesMiddleware
and calls the requested action method upon itself. All action methods and calls the requested action method upon itself. All action methods
must, in addition to their normal parameters, accept a 'req' argument must, in addition to their normal parameters, accept a 'req' argument
which is the incoming wsgi.Request. They raise a webob.exc exception, which is the incoming wsgi.Request. They raise a webob.exc exception,
or return a dict which will be serialized by requested content type. or return a dict which will be serialized by requested content type.
""" """
@webob.dec.wsgify(RequestClass=Request) @webob.dec.wsgify(RequestClass=Request)
def __call__(self, req): def __call__(self, req):
""" """Call the method specified in req.environ by RoutesMiddleware."""
Call the method specified in req.environ by RoutesMiddleware.
"""
arg_dict = req.environ['wsgiorg.routing_args'][1] arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action'] action = arg_dict['action']
method = getattr(self, action) method = getattr(self, action)
@ -361,7 +366,7 @@ class Controller(object):
body = self._serialize(result, content_type, default_xmlns) body = self._serialize(result, content_type, default_xmlns)
response = webob.Response() response = webob.Response()
response.headers["Content-Type"] = content_type response.headers['Content-Type'] = content_type
response.body = body response.body = body
msg_dict = dict(url=req.url, status=response.status_int) msg_dict = dict(url=req.url, status=response.status_int)
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
@ -371,12 +376,13 @@ class Controller(object):
return result return result
def _serialize(self, data, content_type, default_xmlns): def _serialize(self, data, content_type, default_xmlns):
""" """Serialize the given dict to the provided content_type.
Serialize the given dict to the provided content_type.
Uses self._serialization_metadata if it exists, which is a dict mapping Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type. MIME types to information needed to serialize to that type.
""" """
_metadata = getattr(type(self), "_serialization_metadata", {}) _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata, default_xmlns) serializer = Serializer(_metadata, default_xmlns)
try: try:
@ -385,12 +391,13 @@ class Controller(object):
raise webob.exc.HTTPNotAcceptable() raise webob.exc.HTTPNotAcceptable()
def _deserialize(self, data, content_type): def _deserialize(self, data, content_type):
""" """Deserialize the request body to the specefied content type.
Deserialize the request body to the specefied content type.
Uses self._serialization_metadata if it exists, which is a dict mapping Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type. MIME types to information needed to serialize to that type.
""" """
_metadata = getattr(type(self), "_serialization_metadata", {}) _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata) serializer = Serializer(_metadata)
return serializer.deserialize(data, content_type) return serializer.deserialize(data, content_type)
@ -400,23 +407,22 @@ class Controller(object):
class Serializer(object): class Serializer(object):
""" """Serializes and deserializes dictionaries to certain MIME types."""
Serializes and deserializes dictionaries to certain MIME types.
"""
def __init__(self, metadata=None, default_xmlns=None): def __init__(self, metadata=None, default_xmlns=None):
""" """Create a serializer based on the given WSGI environment.
Create a serializer based on the given WSGI environment.
'metadata' is an optional dict mapping MIME types to information 'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type. needed to serialize a dictionary to that type.
""" """
self.metadata = metadata or {} self.metadata = metadata or {}
self.default_xmlns = default_xmlns self.default_xmlns = default_xmlns
def _get_serialize_handler(self, content_type): def _get_serialize_handler(self, content_type):
handlers = { handlers = {
"application/json": self._to_json, 'application/json': self._to_json,
"application/xml": self._to_xml, 'application/xml': self._to_xml,
} }
try: try:
@ -425,29 +431,27 @@ class Serializer(object):
raise exception.InvalidContentType() raise exception.InvalidContentType()
def serialize(self, data, content_type): def serialize(self, data, content_type):
""" """Serialize a dictionary into the specified content type."""
Serialize a dictionary into a string of the specified content type.
"""
return self._get_serialize_handler(content_type)(data) return self._get_serialize_handler(content_type)(data)
def deserialize(self, datastring, content_type): def deserialize(self, datastring, content_type):
""" """Deserialize a string to a dictionary.
Deserialize a string to a dictionary.
The string must be in the format of a supported MIME type. The string must be in the format of a supported MIME type.
""" """
return self.get_deserialize_handler(content_type)(datastring) return self.get_deserialize_handler(content_type)(datastring)
def get_deserialize_handler(self, content_type): def get_deserialize_handler(self, content_type):
handlers = { handlers = {
"application/json": self._from_json, 'application/json': self._from_json,
"application/xml": self._from_xml, 'application/xml': self._from_xml,
} }
try: try:
return handlers[content_type] return handlers[content_type]
except Exception: except Exception:
raise exception.InvalidContentType(_("Invalid content type %s" raise exception.InvalidContentType(_('Invalid content type %s'
% content_type)) % content_type))
def _from_json(self, datastring): def _from_json(self, datastring):
@ -460,11 +464,11 @@ class Serializer(object):
return {node.nodeName: self._from_xml_node(node, plurals)} return {node.nodeName: self._from_xml_node(node, plurals)}
def _from_xml_node(self, node, listnames): def _from_xml_node(self, node, listnames):
""" """Convert a minidom node to a simple Python type.
Convert a minidom node to a simple Python type.
listnames is a collection of names of XML nodes whose subnodes should listnames is a collection of names of XML nodes whose subnodes should
be considered list items. be considered list items.
""" """
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue return node.childNodes[0].nodeValue
@ -571,7 +575,6 @@ def paste_config_file(basename):
* /etc/nova, which may not be diffrerent from state_path on your distro * /etc/nova, which may not be diffrerent from state_path on your distro
""" """
configfiles = [basename, configfiles = [basename,
os.path.join(FLAGS.state_path, 'etc', 'nova', basename), os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
os.path.join(FLAGS.state_path, 'etc', basename), os.path.join(FLAGS.state_path, 'etc', basename),
@ -587,7 +590,7 @@ def load_paste_configuration(filename, appname):
filename = os.path.abspath(filename) filename = os.path.abspath(filename)
config = None config = None
try: try:
config = deploy.appconfig("config:%s" % filename, name=appname) config = deploy.appconfig('config:%s' % filename, name=appname)
except LookupError: except LookupError:
pass pass
return config return config
@ -598,7 +601,7 @@ def load_paste_app(filename, appname):
filename = os.path.abspath(filename) filename = os.path.abspath(filename)
app = None app = None
try: try:
app = deploy.loadapp("config:%s" % filename, name=appname) app = deploy.loadapp('config:%s' % filename, name=appname)
except LookupError: except LookupError:
pass pass
return app return app

View File

@ -7,6 +7,7 @@ function usage {
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message" echo " -h, --help Print this usage message"
echo "" echo ""
echo "Note: with no options specified, the script will try to run the tests in a virtual environment," echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
@ -21,6 +22,7 @@ function process_option {
-V|--virtual-env) let always_venv=1; let never_venv=0;; -V|--virtual-env) let always_venv=1; let never_venv=0;;
-N|--no-virtual-env) let always_venv=0; let never_venv=1;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
-f|--force) let force=1;; -f|--force) let force=1;;
-p|--pep8) let just_pep8=1;;
*) noseargs="$noseargs $1" *) noseargs="$noseargs $1"
esac esac
} }
@ -32,6 +34,7 @@ never_venv=0
force=0 force=0
noseargs= noseargs=
wrapper="" wrapper=""
just_pep8=0
for arg in "$@"; do for arg in "$@"; do
process_option $arg process_option $arg
@ -53,6 +56,18 @@ function run_tests {
return $RESULT return $RESULT
} }
function run_pep8 {
echo "Running pep8 ..."
srcfiles=`find bin -type f ! -name "nova.conf*"`
srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance"
pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles}
}
if [ $just_pep8 -eq 1 ]; then
run_pep8
exit
fi
NOSETESTS="python run_tests.py $noseargs" NOSETESTS="python run_tests.py $noseargs"
if [ $never_venv -eq 0 ] if [ $never_venv -eq 0 ]
@ -81,11 +96,9 @@ then
fi fi
fi fi
if [ -z "$noseargs" ]; run_tests || exit
then
srcfiles=`find bin -type f ! -name "nova.conf*"` # Also run pep8 if no options were provided.
srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance" if [ -z "$noseargs" ]; then
run_tests && pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles} || exit 1 run_pep8
else
run_tests
fi fi