diff --git a/Authors b/Authors index 56344957e38e..0c49f76a200d 100644 --- a/Authors +++ b/Authors @@ -21,6 +21,7 @@ Jesse Andrews Joe Heck Joel Moore Jonathan Bryce +Josh Durgin Josh Kearney Joshua McKenty Justin Santa Barbara diff --git a/bin/nova-api b/bin/nova-api index 419f0bbdc29c..b30c5ee71a22 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -40,59 +40,48 @@ from nova import flags from nova import log as logging from nova import wsgi +logging.basicConfig() LOG = logging.getLogger('nova.api') -LOG.setLevel(logging.DEBUG) -LOG.addHandler(logging.StreamHandler()) FLAGS = flags.FLAGS -API_ENDPOINTS = ['ec2', 'openstack'] +API_ENDPOINTS = ['ec2', 'osapi'] -def load_configuration(paste_config): +def load_configuration(paste_config, name): """Load the paste configuration from the config file and return it.""" config = None - # Try each known name to get the global DEFAULTS, which will give ports - for name in API_ENDPOINTS: - try: - config = deploy.appconfig("config:%s" % paste_config, name=name) - except LookupError: - pass - if config: - verbose = config.get('verbose', None) - if verbose: - FLAGS.verbose = int(verbose) == 1 - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - return config - LOG.debug(_("Paste config at %s has no secion for known apis"), - paste_config) - print _("Paste config at %s has no secion for any known apis") % \ - paste_config - os.exit(1) - - -def launch_api(paste_config_file, section, server, port, host): - """Launch an api server from the specified port and IP.""" - LOG.debug(_("Launching %s api on %s:%s"), section, host, port) - app = deploy.loadapp('config:%s' % paste_config_file, name=section) - server.start(app, int(port), host) + try: + config = deploy.appconfig("config:%s" % paste_config, name=name) + return config + except LookupError: + return None def run_app(paste_config_file): LOG.debug(_("Using paste.deploy config at: %s"), configfile) - config = load_configuration(paste_config_file) - LOG.debug(_("Configuration: %r"), config) server = wsgi.Server() - ip = config.get('host', '0.0.0.0') + apps = [] for api in API_ENDPOINTS: - port = config.get("%s_port" % api, None) - if not port: + config = load_configuration(paste_config_file, api) + if config is None: continue - host = config.get("%s_host" % api, ip) - launch_api(configfile, api, server, port, host) - LOG.debug(_("All api servers launched, now waiting")) - server.wait() + if int(config.get('verbose', 0)) == 1: + FLAGS.verbose = True + host = config.get("%s_host" % api, config.get('host', '0.0.0.0')) + port = config.get("%s_port" % api, getattr(FLAGS, "%s_port" % api)) + setattr(FLAGS, "%s_host" % api, host) + setattr(FLAGS, "%s_port" % api, port) + LOG.info(_("Running %s API"), api) + app = deploy.loadapp('config:%s' % paste_config_file, name=api) + apps.append((app, int(port), host)) + if len(apps) == 0: + LOG.error(_("No known API applications configured in %s."), + paste_config_file) + else: + for app in apps: + server.start(*app) + server.wait() if __name__ == '__main__': diff --git a/bin/nova-combined b/bin/nova-combined index 53322f1a0828..f932fdfd5718 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -44,7 +44,6 @@ from nova import wsgi FLAGS = flags.FLAGS -flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') diff --git a/bin/nova-manage b/bin/nova-manage index 3f595719054d..3e290567c8c0 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -77,12 +77,14 @@ from nova import crypto from nova import db from nova import exception from nova import flags +from nova import log as logging from nova import quota from nova import utils from nova.auth import manager from nova.cloudpipe import pipelib +logging.basicConfig() FLAGS = flags.FLAGS flags.DECLARE('fixed_range', 'nova.network.manager') flags.DECLARE('num_networks', 'nova.network.manager') diff --git a/etc/nova-api.conf b/etc/nova-api.conf index f4ae7bcce4d5..4873e465d180 100644 --- a/etc/nova-api.conf +++ b/etc/nova-api.conf @@ -1,9 +1,5 @@ [DEFAULT] verbose = 1 -ec2_port = 8773 -ec2_address = 0.0.0.0 -openstack_port = 8774 -openstack_address = 0.0.0.0 ####### # EC2 # @@ -64,13 +60,13 @@ paste.app_factory = nova.api.ec2.metadatarequesthandler:MetadataRequestHandler.f # Openstack # ############# -[composite:openstack] +[composite:osapi] use = egg:Paste#urlmap /: osversions /v1.0: openstackapi [pipeline:openstackapi] -pipeline = faultwrap auth ratelimit osapi +pipeline = faultwrap auth ratelimit osapiapp [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -81,7 +77,7 @@ paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory [filter:ratelimit] paste.filter_factory = nova.api.openstack.ratelimiting:RateLimitingMiddleware.factory -[app:osapi] +[app:osapiapp] paste.app_factory = nova.api.openstack:APIRouter.factory [pipeline:osversions] diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 093515cb4fa2..238cb0f38376 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -278,8 +278,7 @@ class Authorizer(wsgi.Middleware): return self.application else: LOG.audit(_("Unauthorized request for controller=%s " - "and action=%s"), controller_name, action, - context=context) + "and action=%s"), controller, action, context=context) raise webob.exc.HTTPUnauthorized() def _matches_any_role(self, context, roles): diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 832426b94aec..e5308ca879a6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -263,15 +263,15 @@ class CloudController(object): name, _sep, host = region.partition('=') endpoint = '%s://%s:%s%s' % (FLAGS.ec2_prefix, host, - FLAGS.cc_port, + FLAGS.ec2_port, FLAGS.ec2_suffix) regions.append({'regionName': name, 'regionEndpoint': endpoint}) else: regions = [{'regionName': 'nova', 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_prefix, - FLAGS.cc_host, - FLAGS.cc_port, + FLAGS.ec2_host, + FLAGS.ec2_port, FLAGS.ec2_suffix)}] return {'regionInfo': regions} diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 89f02998de1c..ea7d07b1eee5 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -682,7 +682,7 @@ class AuthManager(object): region, _sep, region_host = item.partition("=") regions[region] = region_host else: - regions = {'nova': FLAGS.cc_host} + regions = {'nova': FLAGS.ec2_host} for region, host in regions.iteritems(): rc = self.__generate_rc(user, pid, @@ -727,28 +727,28 @@ class AuthManager(object): def __generate_rc(user, pid, use_dmz=True, host=None): """Generate rc file for user""" if use_dmz: - cc_host = FLAGS.cc_dmz + ec2_host = FLAGS.ec2_dmz_host else: - cc_host = FLAGS.cc_host + ec2_host = FLAGS.ec2_host # NOTE(vish): Always use the dmz since it is used from inside the # instance s3_host = FLAGS.s3_dmz if host: s3_host = host - cc_host = host + ec2_host = host rc = open(FLAGS.credentials_template).read() rc = rc % {'access': user.access, 'project': pid, 'secret': user.secret, - 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix, - cc_host, - FLAGS.cc_port, - FLAGS.ec2_suffix), + 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme, + ec2_host, + FLAGS.ec2_port, + FLAGS.ec2_path), 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), - 'os': '%s://%s:%s%s' % (FLAGS.os_prefix, - cc_host, - FLAGS.cc_port, - FLAGS.os_suffix), + 'os': '%s://%s:%s%s' % (FLAGS.osapi_scheme, + ec2_host, + FLAGS.osapi_port, + FLAGS.osapi_path), 'user': user.name, 'nova': FLAGS.ca_file, 'cert': FLAGS.credential_cert_file, diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 8aefd341f87a..dc6f55af290c 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -68,8 +68,8 @@ class CloudPipe(object): shellfile = open(FLAGS.boot_script_template, "r") s = string.Template(shellfile.read()) shellfile.close() - boot_script = s.substitute(cc_dmz=FLAGS.cc_dmz, - cc_port=FLAGS.cc_port, + boot_script = s.substitute(cc_dmz=FLAGS.ec2_dmz_host, + cc_port=FLAGS.ec2_port, dmz_net=FLAGS.dmz_net, dmz_mask=FLAGS.dmz_mask, num_vpn=FLAGS.cnt_vpn_clients) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2e4f8fc397d7..39df21e305d7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -765,12 +765,14 @@ def instance_get_by_id(context, instance_id): if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ip.floating_ips')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ip.floating_ips')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ diff --git a/nova/flags.py b/nova/flags.py index fdcba6c72b70..81e2e36f99ae 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -254,13 +254,15 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('os_prefix', 'http', 'prefix for openstack') -DEFINE_string('cc_host', '$my_ip', 'ip of api server') -DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') -DEFINE_integer('cc_port', 8773, 'cloud controller port') -DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') -DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack') +DEFINE_string('ec2_host', '$my_ip', 'ip of api server') +DEFINE_string('ec2_dmz_host', '$my_ip', 'internal ip of api server') +DEFINE_integer('ec2_port', 8773, 'cloud controller port') +DEFINE_string('ec2_scheme', 'http', 'prefix for ec2') +DEFINE_string('ec2_path', '/services/Cloud', 'suffix for ec2') +DEFINE_string('osapi_host', '$my_ip', 'ip of api server') +DEFINE_string('osapi_scheme', 'http', 'prefix for openstack') +DEFINE_integer('osapi_port', 8774, 'OpenStack API port') +DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack') DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 3743fc7e84ea..69bb37b490b6 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -60,7 +60,7 @@ def metadata_forward(): """Create forwarding rule for metadata""" _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " - "--to-destination %s:%s" % (FLAGS.cc_dmz, FLAGS.cc_port)) + "--to-destination %s:%s" % (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) def init_host(): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 655c55fa1b34..bd863b3a2746 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -243,11 +243,24 @@ class LibvirtConnection(object): def attach_volume(self, instance_name, device_path, mountpoint): virt_dom = self._conn.lookupByName(instance_name) mount_device = mountpoint.rpartition("/")[2] - xml = """ - - - - """ % (device_path, mount_device) + if device_path.startswith('/dev/'): + xml = """ + + + + """ % (device_path, mount_device) + elif ':' in device_path: + (protocol, name) = device_path.split(':') + xml = """ + + + + """ % (protocol, + name, + mount_device) + else: + raise exception.Invalid(_("Invalid device path %s") % device_path) + virt_dom.attachDevice(xml) def _get_disk_xml(self, xml, device): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7e3585991a63..7aebb502fbba 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -252,7 +252,7 @@ class VMOps(object): raise Exception(_("suspend: instance not present %s") % instance_name) task = self._session.call_xenapi('Async.VM.suspend', vm) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def resume(self, instance, callback): """resume the specified instance""" @@ -262,7 +262,7 @@ class VMOps(object): raise Exception(_("resume: instance not present %s") % instance_name) task = self._session.call_xenapi('Async.VM.resume', vm, False, True) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def get_info(self, instance_id): """Return data about VM instance""" diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 6bc925f3eedf..44bfeaf0cd06 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -49,6 +49,8 @@ flags.DEFINE_string('iscsi_target_prefix', 'iqn.2010-10.org.openstack:', 'prefix for iscsi volumes') flags.DEFINE_string('iscsi_ip_prefix', '127.0', 'discover volumes on the ip that starts with this prefix') +flags.DEFINE_string('rbd_pool', 'rbd', + 'the rbd pool in which volumes are stored') class VolumeDriver(object): @@ -314,3 +316,58 @@ class FakeISCSIDriver(ISCSIDriver): """Execute that simply logs the command.""" LOG.debug(_("FAKE ISCSI: %s"), cmd) return (None, None) + + +class RBDDriver(VolumeDriver): + """Implements RADOS block device (RBD) volume commands""" + + def check_for_setup_error(self): + """Returns an error if prerequisites aren't met""" + (stdout, stderr) = self._execute("rados lspools") + pools = stdout.split("\n") + if not FLAGS.rbd_pool in pools: + raise exception.Error(_("rbd has no pool %s") % + FLAGS.rbd_pool) + + def create_volume(self, volume): + """Creates a logical volume.""" + if int(volume['size']) == 0: + size = 100 + else: + size = int(volume['size']) * 1024 + self._try_execute("rbd --pool %s --size %d create %s" % + (FLAGS.rbd_pool, + size, + volume['name'])) + + def delete_volume(self, volume): + """Deletes a logical volume.""" + self._try_execute("rbd --pool %s rm %s" % + (FLAGS.rbd_pool, + volume['name'])) + + def local_path(self, volume): + """Returns the path of the rbd volume.""" + # This is the same as the remote path + # since qemu accesses it directly. + return self.discover_volume(volume) + + def ensure_export(self, context, volume): + """Synchronously recreates an export for a logical volume.""" + pass + + def create_export(self, context, volume): + """Exports the volume""" + pass + + def remove_export(self, context, volume): + """Removes an export for a logical volume""" + pass + + def discover_volume(self, volume): + """Discover volume on a remote host""" + return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) + + def undiscover_volume(self, volume): + """Undiscover volume on a remote host""" + pass