From e601ab4a1068029b2f0b79789ed506fda1332404 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 16 Dec 2010 20:59:27 -0600 Subject: XenAPI Snapshots first cut --- nova/compute/manager.py | 30 ++++++++++++++- nova/virt/libvirt_conn.py | 7 ++++ nova/virt/xenapi/vm_utils.py | 91 ++++++++++++++++++++++++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 43 +++++++++++++++++---- nova/virt/xenapi_conn.py | 7 ++++ 5 files changed, 170 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7eb60e262..8fc8d5e1a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -134,8 +134,10 @@ class ComputeManager(manager.Manager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def reboot_instance(self, context, instance_id): + def XXreboot_instance(self, context, instance_id): + #def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -156,6 +158,32 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) + #FIXME(sirp): Hacking reboot to snapshot + @exception.wrap_exception + def reboot_instance(self, context, instance_id): + #def snapshot_instance(self, context, instance_id): + """Snapshot an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + #FIXME(sirp): update_state currently only refreshes the state field + # if we add is_snapshotting, we will need this refreshed too, + # potentially? + self._update_state(context, instance_id) + + #TODO(sirp): check for is_snapshotting=True here? + if instance_ref['state'] != power_state.RUNNING: + logging.warn('trying to snapshot a non-running ' + 'instance: %s (state: %s excepted: %s)', + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING) + + logging.debug('instance %s: snapshotting', instance_ref['name']) + #TODO(sirp): set is_snapshotting=True here? + self.driver.snapshot(instance_ref) + #self._update_state(context, instance_id) + @exception.wrap_exception def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5a8c71850..112ac7d37 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -265,6 +265,13 @@ class LibvirtConnection(object): raise exception.NotFound("No disk at %s" % mount_device) virt_dom.detachDevice(xml) + @exception.wrap_exception + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): only exists for XenAPI driver for now + raise NotImplementedError( + "Instance snapshotting is not supported for libvirt at this time") + @exception.wrap_exception def reboot(self, instance): self.destroy(instance, False) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2f5d78e75..c3e8b30b7 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -19,6 +19,7 @@ Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ +import time #FIXME(sirp): take this out, replace with greenthread.sleep import logging import urllib from xml.dom import minidom @@ -148,6 +149,63 @@ class VMHelper(): vm_ref, network_ref) return vif_ref + + @classmethod + def create_snapshot(cls, session, vm_ref, label): + logging.debug("Snapshotting VM %s with label '%s'...", vm_ref, label) + + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + + #TODO(sirp): Make safe_lookup_vdi for assert? + vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) + if vdi_refs is None or len(vdi_refs) != 1: + raise Exception("Unexpected number of VDIs (%s) found for VM %s" + % (len(vdi_refs), vm_ref)) + vdi_ref = vdi_refs[0] + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec["uuid"] + + #NOTE(sirp): We may need to wait for our parent to coalese with his + # parent + original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + + task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) + template_vm_ref = session.wait_for_task(task) + logging.debug('Created snapshot %s from VM %s.', template_vm_ref, + vm_ref) + + #NOTE(sirp): wait for any coalescing + #NOTE(sirp): for some reason re-scan wasn't occuring automatically on + # XS5.6 + #TODO(sirp): clean this up, perhaps use LoopingCall + sr_ref = vdi_rec["SR"] + scan_sr(session, sr_ref) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + time.sleep(5) + while original_parent_uuid and (parent_uuid != original_parent_uuid): + scan_sr(session, sr_ref) + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + #TODO(sirp): make this non-blocking + time.sleep(5) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + + vdi_uuids = [vdi_uuid, parent_uuid] + return template_vm_ref, vdi_uuids + + + @classmethod + def upload_image(cls, session, vdi_uuids, glance_label): + logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, + glance_label) + kwargs = {'vdi_uuids': ','.join(vdi_uuids), + 'glance_label': glance_label} + task = session.async_call_plugin('glance', 'put_vdis', kwargs) + session.wait_for_task(task) + + @classmethod def fetch_image(cls, session, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place @@ -258,3 +316,36 @@ def get_rrd(host, uuid): return xml.read() except IOError: return None + + +#TODO(sirp): This code comes from XS5.6 pluginlib.py, we should refactor to +# use that implmenetation +def get_vhd_parent(session, vdi_rec): + """ + Returns the VHD parent of the given VDI record, as a (ref, rec) pair. + Returns None if we're at the root of the tree. + """ + if 'vhd-parent' in vdi_rec['sm_config']: + parent_uuid = vdi_rec['sm_config']['vhd-parent'] + #NOTE(sirp): changed xenapi -> get_xenapi() + parent_ref = session.get_xenapi().VDI.get_by_uuid(parent_uuid) + parent_rec = session.get_xenapi().VDI.get_record(parent_ref) + #NOTE(sirp): changed log -> logging + logging.debug("VHD %s has parent %s", vdi_rec['uuid'], parent_ref) + return parent_ref, parent_rec + else: + return None + +def get_vhd_parent_uuid(session, vdi_ref): + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + ret = get_vhd_parent(session, vdi_rec) + if ret: + parent_ref, parent_rec = ret + return parent_rec["uuid"] + else: + return None + +def scan_sr(session, sr_ref): + logging.debug("Re-scanning SR %s", sr_ref) + task = session.call_xenapi('Async.SR.scan', sr_ref) + session.wait_for_task(task) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3034df9e1..50b5ec3d6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -78,6 +78,31 @@ class VMOps(object): self._session.call_xenapi('VM.start', vm_ref, False, False) logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + vm_ref = VMHelper.lookup(self._session, instance.name) + + #TODO(sirp): this is the label in Xen, we need to add a human friendly + # label that we store in paralalx + label = "%s-snapshot" % instance.name + glance_name = "MySnapshot" + + try: + template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( + self._session, vm_ref, label) + except XenAPI.Failure, exc: + logging.error("Unable to Snapshot %s: %s", vm_ref, exc) + return + + try: + # call plugin to ship snapshot off to glance + VMHelper.upload_image( + self._session, template_vdi_uuids, glance_name) + finally: + self._destroy(template_vm_ref, shutdown=False) def reboot(self, instance): """ Reboot VM instance """ @@ -89,20 +114,24 @@ class VMOps(object): self._session.wait_for_task(task) def destroy(self, instance): - """ Destroy VM instance """ vm = VMHelper.lookup(self._session, instance.name) + return self._destroy(vm, shutdown=True) + + def _destroy(self, vm, shutdown=True): + """ Destroy VM instance """ if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. return # Get the VDIs related to the VM vdis = VMHelper.lookup_vm_vdis(self._session, vm) - try: - task = self._session.call_xenapi('Async.VM.hard_shutdown', - vm) - self._session.wait_for_task(task) - except XenAPI.Failure, exc: - logging.warn(exc) + if shutdown: + try: + task = self._session.call_xenapi('Async.VM.hard_shutdown', + vm) + self._session.wait_for_task(task) + except XenAPI.Failure, exc: + logging.warn(exc) # Disk clean-up if vdis: for vdi in vdis: diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6beb08f5e..8aacec507 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -115,6 +115,13 @@ class XenAPIConnection(object): """ Create VM instance """ self._vmops.spawn(instance) + + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + self._vmops.snapshot(instance) + def reboot(self, instance): """ Reboot VM instance """ self._vmops.reboot(instance) -- cgit From ae54d5bdf3e0615c5be9ebe4f03f7256f22484ee Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 17 Dec 2010 14:21:03 -0600 Subject: Add wait_for_vhd_coalesce --- nova/virt/xenapi/vm_utils.py | 52 +++++++++++++++++++++++++++++--------------- nova/virt/xenapi/vmops.py | 3 +++ 2 files changed, 37 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index c3e8b30b7..1b18b0a01 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -175,26 +175,12 @@ class VMHelper(): logging.debug('Created snapshot %s from VM %s.', template_vm_ref, vm_ref) - #NOTE(sirp): wait for any coalescing - #NOTE(sirp): for some reason re-scan wasn't occuring automatically on - # XS5.6 - #TODO(sirp): clean this up, perhaps use LoopingCall sr_ref = vdi_rec["SR"] - scan_sr(session, sr_ref) - parent_uuid = get_vhd_parent_uuid(session, vdi_ref) - time.sleep(5) - while original_parent_uuid and (parent_uuid != original_parent_uuid): - scan_sr(session, sr_ref) - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - #TODO(sirp): make this non-blocking - time.sleep(5) - parent_uuid = get_vhd_parent_uuid(session, vdi_ref) - - vdi_uuids = [vdi_uuid, parent_uuid] - return template_vm_ref, vdi_uuids + parent_uuid = wait_for_vhd_coalesce( + session, sr_ref, vdi_ref, original_parent_uuid) + #TODO(sirp): we need to assert only one parent, not parents two deep + return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod def upload_image(cls, session, vdi_uuids, glance_label): @@ -336,6 +322,7 @@ def get_vhd_parent(session, vdi_rec): else: return None + def get_vhd_parent_uuid(session, vdi_ref): vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) ret = get_vhd_parent(session, vdi_rec) @@ -345,7 +332,36 @@ def get_vhd_parent_uuid(session, vdi_ref): else: return None + def scan_sr(session, sr_ref): logging.debug("Re-scanning SR %s", sr_ref) task = session.call_xenapi('Async.SR.scan', sr_ref) session.wait_for_task(task) + + +def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): + """ TODO Explain why coalescing has to occur here """ + #TODO(sirp): we need to timeout this req after a while + #NOTE(sirp): for some reason re-scan wasn't occuring automatically on + # XS5.6 + #TODO(sirp): clean this up, perhaps use LoopingCall + def _get_vhd_parent_uuid_with_refresh(first_time): + if not first_time: + #TODO(sirp): should this interval be a gflag? + #TODO(sirp): make this non-blocking + time.sleep(5) + scan_sr(session, sr_ref) + return get_vhd_parent_uuid(session, vdi_ref) + + parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=True) + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + while original_parent_uuid and (parent_uuid != original_parent_uuid): + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=False) + + return parent_uuid + diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 50b5ec3d6..988c54d6d 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -81,6 +81,7 @@ class VMOps(object): def snapshot(self, instance): """ Create snapshot from a running VM instance """ + logging.debug("Starting snapshot for VM %s", instance) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added vm_ref = VMHelper.lookup(self._session, instance.name) @@ -104,6 +105,8 @@ class VMOps(object): finally: self._destroy(template_vm_ref, shutdown=False) + logging.debug("Finished snapshot and upload for VM %s", instance) + def reboot(self, instance): """ Reboot VM instance """ instance_name = instance.name -- cgit From 650a0cdbc854d37fd62348ce34a14ef91ccbabad Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 17 Dec 2010 19:17:39 -0600 Subject: Cleaned up TODOs, using flags now --- nova/compute/manager.py | 3 ++- nova/flags.py | 3 +++ nova/virt/xenapi/vm_utils.py | 50 +++++++++++++++++++++++--------------------- nova/virt/xenapi/vmops.py | 15 ++++++------- nova/virt/xenapi_conn.py | 9 +++++--- 5 files changed, 43 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8fc8d5e1a..b2584773a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -181,7 +181,8 @@ class ComputeManager(manager.Manager): logging.debug('instance %s: snapshotting', instance_ref['name']) #TODO(sirp): set is_snapshotting=True here? - self.driver.snapshot(instance_ref) + glance_name = "MySnapshot3" + self.driver.snapshot(instance_ref, glance_name) #self._update_state(context, instance_id) @exception.wrap_exception diff --git a/nova/flags.py b/nova/flags.py index 87444565a..3318af058 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -212,6 +212,9 @@ DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') +DEFINE_integer('glance_port', 9292, 'glance port') +DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 1b18b0a01..729f0daaf 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -19,11 +19,12 @@ Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ -import time #FIXME(sirp): take this out, replace with greenthread.sleep import logging +import pickle import urllib from xml.dom import minidom +from eventlet import event from nova import flags from nova import utils from nova.auth.manager import AuthManager @@ -166,8 +167,6 @@ class VMHelper(): vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) vdi_uuid = vdi_rec["uuid"] - #NOTE(sirp): We may need to wait for our parent to coalese with his - # parent original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) @@ -186,8 +185,14 @@ class VMHelper(): def upload_image(cls, session, vdi_uuids, glance_label): logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, glance_label) - kwargs = {'vdi_uuids': ','.join(vdi_uuids), - 'glance_label': glance_label} + + params = {'vdi_uuids': vdi_uuids, + 'glance_label': glance_label, + 'glance_storage_location': FLAGS.glance_storage_location, + 'glance_host': FLAGS.glance_host, + 'glance_port': FLAGS.glance_port} + + kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'put_vdis', kwargs) session.wait_for_task(task) @@ -341,27 +346,24 @@ def scan_sr(session, sr_ref): def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): """ TODO Explain why coalescing has to occur here """ - #TODO(sirp): we need to timeout this req after a while #NOTE(sirp): for some reason re-scan wasn't occuring automatically on # XS5.6 - #TODO(sirp): clean this up, perhaps use LoopingCall - def _get_vhd_parent_uuid_with_refresh(first_time): - if not first_time: - #TODO(sirp): should this interval be a gflag? - #TODO(sirp): make this non-blocking - time.sleep(5) - scan_sr(session, sr_ref) - return get_vhd_parent_uuid(session, vdi_ref) - - parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=True) - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - while original_parent_uuid and (parent_uuid != original_parent_uuid): - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=False) + #TODO(sirp): we need to timeout this req after a while + def _poll_vhds(): + scan_sr(session, sr_ref) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + if original_parent_uuid and (parent_uuid != original_parent_uuid): + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + else: + done.send(parent_uuid) + + done = event.Event() + loop = utils.LoopingCall(_poll_vhds) + loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True) + parent_uuid = done.wait() + loop.stop() return parent_uuid diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 988c54d6d..a44492d57 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -79,29 +79,26 @@ class VMOps(object): logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ - logging.debug("Starting snapshot for VM %s", instance) + #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added + + logging.debug("Starting snapshot for VM %s", instance) vm_ref = VMHelper.lookup(self._session, instance.name) - #TODO(sirp): this is the label in Xen, we need to add a human friendly - # label that we store in paralalx label = "%s-snapshot" % instance.name - glance_name = "MySnapshot" - try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( self._session, vm_ref, label) except XenAPI.Failure, exc: logging.error("Unable to Snapshot %s: %s", vm_ref, exc) return - + try: # call plugin to ship snapshot off to glance - VMHelper.upload_image( - self._session, template_vdi_uuids, glance_name) + VMHelper.upload_image(self._session, template_vdi_uuids, name) finally: self._destroy(template_vm_ref, shutdown=False) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 8aacec507..92e66d32d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -77,7 +77,10 @@ flags.DEFINE_float('xenapi_task_poll_interval', 'The interval used for polling of remote tasks ' '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') - +flags.DEFINE_float('xenapi_vhd_coalesce_poll_interval', + 5.0, + 'The interval used for polling of coalescing vhds.' + ' Used only if connection_type=xenapi.') XenAPI = None @@ -116,11 +119,11 @@ class XenAPIConnection(object): self._vmops.spawn(instance) - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - self._vmops.snapshot(instance) + self._vmops.snapshot(instance, name) def reboot(self, instance): """ Reboot VM instance """ -- cgit From 40c8a8a1a1e834c4e5bb61c853397a90475d83ff Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 20 Dec 2010 17:36:10 -0600 Subject: Typo fix, stubbing out to use admin project for now --- nova/api/openstack/__init__.py | 4 +++- nova/api/openstack/images.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8..178838dfd 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -89,7 +89,9 @@ class AuthMiddleware(wsgi.Middleware): if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) - req.environ['nova.context'] = context.RequestContext(user, user) + #FIXME(sirp): stubbing project to admin for now + project = manager.AuthManager().get_project('admin') + req.environ['nova.context'] = context.RequestContext(user, project) return self.application diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4a0a8e6f1..78849223d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -46,8 +46,9 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" + ctxt = req.environ['nova.context'] try: - images = self._service.detail(req.environ['nova.context']) + images = self._service.detail(ctxt) images = nova.api.openstack.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() -- cgit From c364724a0dc7a658058fcb167af66ee7eb5bcd2a Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Dec 2010 01:41:28 -0500 Subject: Use paste.deploy for running the api server. --- nova/api/__init__.py | 1 - nova/api/cloudpipe/__init__.py | 3 ++ nova/api/ec2/__init__.py | 50 ++++++++++++++++++++++++++++++++-- nova/api/ec2/metadatarequesthandler.py | 3 ++ nova/api/openstack/__init__.py | 13 +++++++++ 5 files changed, 67 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 80f9f2109..92b495e8c 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -29,7 +29,6 @@ import routes import webob.dec from nova import flags -from nova import utils from nova import wsgi from nova.api import cloudpipe from nova.api import ec2 diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py index 6d40990a8..47349d9f9 100644 --- a/nova/api/cloudpipe/__init__.py +++ b/nova/api/cloudpipe/__init__.py @@ -67,3 +67,6 @@ class API(wsgi.Application): project_id = self.get_project_id_from_ip(req.remote_addr) cert = self.str_params['cert'] return crypto.sign_csr(urllib.unquote(cert), project_id) + +def cloudpipe_factory(global_opts, **local_opts): + return API() diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a6ee16c33..50cb18078 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -225,10 +225,9 @@ class Executor(wsgi.Application): args = req.environ['ec2.action_args'] api_request = apirequest.APIRequest(controller, action) + result = None try: result = api_request.send(context, **args) - req.headers['Content-Type'] = 'text/xml' - return result except exception.ApiError as ex: if ex.code: @@ -238,6 +237,13 @@ class Executor(wsgi.Application): # TODO(vish): do something more useful with unknown exceptions except Exception as ex: return self._error(req, type(ex).__name__, str(ex)) + else: + resp = webob.Response() + resp.status = 200 + resp.headers['Content-Type'] = 'text/xml' + resp.body = str(result) + return resp + def _error(self, req, code, message): logging.error("%s: %s", code, message) @@ -249,3 +255,43 @@ class Executor(wsgi.Application): '%s' '?' % (code, message)) return resp + +class Versions(wsgi.Application): + + @webob.dec.wsgify + def __call__(self, req): + """Respond to a request for all EC2 versions.""" + # available api versions + versions = [ + '1.0', + '2007-01-19', + '2007-03-01', + '2007-08-29', + '2007-10-10', + '2007-12-15', + '2008-02-01', + '2008-09-01', + '2009-04-04', + ] + return ''.join('%s\n' % v for v in versions) + +def authenticate_factory(global_args, **local_args): + def authenticator(app): + return Authenticate(app) + return authenticator + +def router_factory(global_args, **local_args): + def router(app): + return Router(app) + return router + +def authorizer_factory(global_args, **local_args): + def authorizer(app): + return Authorizer(app) + return authorizer + +def executor_factory(global_args, **local_args): + return Executor() + +def versions_factory(global_args, **local_args): + return Versions() diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 2f4f414cc..fffefb97b 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -72,3 +72,6 @@ class MetadataRequestHandler(object): if data is None: raise webob.exc.HTTPNotFound() return self.print_data(data) + +def metadata_factory(global_args, **local_args): + return MetadataRequestHandler() diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8..cb825cf41 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -210,3 +210,16 @@ def limited(items, req): limit = min(1000, limit) range_end = offset + limit return items[offset:range_end] + +def auth_factory(global_conf, **local_conf): + def auth(app): + return AuthMiddleware(app) + return auth + +def ratelimit_factory(global_conf, **local_conf): + def rl(app): + return RateLimitingMiddleware(app) + return rl + +def router_factory(global_cof, **local_conf): + return APIRouter() -- cgit From 8c8b289f2626b1d9bad76bc5d4819904ace5800d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Dec 2010 02:21:01 -0500 Subject: Remove ec2 config chain and move openstack versions to top-level application. --- nova/api/openstack/__init__.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index cb825cf41..f3686997f 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -20,7 +20,6 @@ WSGI middleware for OpenStack API controllers. """ -import json import time import logging @@ -41,7 +40,6 @@ from nova.api.openstack import images from nova.api.openstack import ratelimiting from nova.api.openstack import servers from nova.api.openstack import sharedipgroups -from nova.auth import manager FLAGS = flags.FLAGS @@ -193,6 +191,19 @@ class APIRouter(wsgi.Router): super(APIRouter, self).__init__(mapper) +class Versions(wsgi.Application): + @webob.dec.wsgify + def __call__(self, req): + """Respond to a request for all OpenStack API versions.""" + response = { + "versions": [ + dict(status="CURRENT", id="v1.0")]} + metadata = { + "application/xml": { + "attributes": dict(version=["status", "id"])}} + return wsgi.Serializer(req.environ, metadata).to_content_type(response) + + def limited(items, req): """Return a slice of items according to requested offset and limit. @@ -223,3 +234,6 @@ def ratelimit_factory(global_conf, **local_conf): def router_factory(global_cof, **local_conf): return APIRouter() + +def versions_factory(global_conf, **local_conf): + return Versions() -- cgit From 729468d0be1bf97c869b1169414154a76d9b96b2 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Dec 2010 19:20:28 -0500 Subject: Burnin support by specifying a specific host via availability_zone for running instances and volumes on. --- nova/api/ec2/cloud.py | 38 ++++++++++++++ nova/compute/api.py | 4 +- nova/db/sqlalchemy/models.py | 4 +- nova/scheduler/driver.py | 5 ++ nova/scheduler/simple.py | 26 +++++++++ nova/tests/scheduler_unittest.py | 110 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 182 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8375c4399..b181a947f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -189,9 +189,45 @@ class CloudController(object): return data def describe_availability_zones(self, context, **kwargs): + if ('zone_name' in kwargs and + 'verbose' in kwargs['zone_name'] and + context.is_admin): + return self._describe_availability_zones_verbose(context, + **kwargs) + else: + return self._describe_availability_zones(context, **kwargs) + + def _describe_availability_zones(self, context, **kwargs): return {'availabilityZoneInfo': [{'zoneName': 'nova', 'zoneState': 'available'}]} + def _describe_availability_zones_verbose(self, context, **kwargs): + rv = {'availabilityZoneInfo': [{'zoneName': 'nova', + 'zoneState': 'available'}]} + + services = db.service_get_all(context) + now = db.get_time() + hosts = [] + for host in [service['host'] for service in services]: + if not host in hosts: + hosts.append(host) + for host in hosts: + rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host, + 'zoneState': ''}) + hsvcs = [service for service in services if service['host'] == host] + for svc in hsvcs: + delta = now - (svc['updated_at'] or svc['created_at']) + alive = (delta.seconds <= FLAGS.service_down_time) + art = (alive and ":-)") or "XXX" + active = 'enabled' + if svc['disabled']: + active = 'disabled' + rv['availabilityZoneInfo'].append({ + 'zoneName': '| |- %s' % svc['binary'], + 'zoneState': '%s %s %s' % (active, art, + svc['updated_at'])}) + return rv + def describe_regions(self, context, region_name=None, **kwargs): if FLAGS.region_list: regions = [] @@ -757,6 +793,8 @@ class CloudController(object): description=kwargs.get('display_description'), key_name=kwargs.get('key_name'), security_group=kwargs.get('security_group'), + availability_zone=kwargs.get('placement', {}).get( + 'AvailabilityZone'), generate_hostname=internal_id_to_ec2_id) return self._format_run_instances(context, instances[0]['reservation_id']) diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da..299083bf4 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -57,6 +57,7 @@ class ComputeAPI(base.Base): max_count=1, kernel_id=None, ramdisk_id=None, display_name='', description='', key_name=None, key_data=None, security_group='default', + availability_zone=None, generate_hostname=generate_default_hostname): """Create the number of instances requested if quote and other arguments check out ok.""" @@ -121,7 +122,8 @@ class ComputeAPI(base.Base): 'display_name': display_name, 'display_description': description, 'key_name': key_name, - 'key_data': key_data} + 'key_data': key_data, + 'availability_zone': availability_zone} elevated = context.elevated() instances = [] diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 96d981571..41e8cfefa 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -148,7 +148,7 @@ class Service(BASE, NovaBase): binary = Column(String(255)) topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) - disabled = Column(Boolean, default=False) + disabled = Column(Boolean, default=True) class Instance(BASE, NovaBase): @@ -210,6 +210,8 @@ class Instance(BASE, NovaBase): launched_at = Column(DateTime) terminated_at = Column(DateTime) + availability_zone = Column(String(255)) + # User editable field for display in user-facing UIs display_name = Column(String(255)) display_description = Column(String(255)) diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index f271d573f..340f40310 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -37,6 +37,11 @@ class NoValidHost(exception.Error): pass +class WillNotSchedule(exception.Error): + """The specified host is not up or doesn't exist.""" + pass + + class Scheduler(object): """The base class that all Scheduler clases should inherit from.""" diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 7f5093656..9e85ba952 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -43,6 +43,19 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_run_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" instance_ref = db.instance_get(context, instance_id) + if instance_ref['availability_zone'] and context.is_admin: + zone, _x, host = instance_ref['availability_zone'].partition(':') + service = db.service_get_by_args(context.elevated(), host, + 'nova-compute') + if not self.service_is_up(service): + raise driver.WillNotSchedule("Host %s is not alive" % host) + + # TODO(vish): this probably belongs in the manager, if we + # can generalize this somehow + now = datetime.datetime.utcnow() + db.instance_update(context, instance_id, {'host': host, + 'scheduled_at': now}) + return host results = db.service_get_all_compute_sorted(context) for result in results: (service, instance_cores) = result @@ -62,6 +75,19 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" volume_ref = db.volume_get(context, volume_id) + if (':' in volume_ref['availability_zone']) and context.is_admin: + zone, _x, host = volume_ref['availability_zone'].partition(':') + service = db.service_get_by_args(context.elevated(), host, + 'nova-volume') + if not self.service_is_up(service): + raise driver.WillNotSchedule("Host %s not available" % host) + + # TODO(vish): this probably belongs in the manager, if we + # can generalize this somehow + now = datetime.datetime.utcnow() + db.volume_update(context, volume_id, {'host': host, + 'scheduled_at': now}) + return host results = db.service_get_all_volume_sorted(context) for result in results: (service, volume_gigabytes) = result diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index d1756b8fb..92262cc7d 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -19,6 +19,8 @@ Tests For Scheduler """ +import datetime + from nova import context from nova import db from nova import flags @@ -93,7 +95,7 @@ class SimpleDriverTestCase(test.TestCase): self.manager.delete_user(self.user) self.manager.delete_project(self.project) - def _create_instance(self): + def _create_instance(self, **kwargs): """Create a test instance""" inst = {} inst['image_id'] = 'ami-test' @@ -104,6 +106,7 @@ class SimpleDriverTestCase(test.TestCase): inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 inst['vcpus'] = 1 + inst['availability_zone'] = kwargs.get('availability_zone', None) return db.instance_create(self.context, inst)['id'] def _create_volume(self): @@ -112,9 +115,29 @@ class SimpleDriverTestCase(test.TestCase): vol['image_id'] = 'ami-test' vol['reservation_id'] = 'r-fakeres' vol['size'] = 1 + vol['availability_zone'] = 'test' return db.volume_create(self.context, vol)['id'] - def test_hosts_are_up(self): + def test_doesnt_report_disabled_hosts_as_up(self): + """Ensures driver doesn't find hosts before they are enabled""" + # NOTE(vish): constructing service without create method + # because we are going to use it without queue + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2.start() + hosts = self.scheduler.driver.hosts_up(self.context, 'compute') + self.assertEqual(0, len(hosts)) + compute1.kill() + compute2.kill() + + def test_reports_enabled_hosts_as_up(self): """Ensures driver can find the hosts that are up""" # NOTE(vish): constructing service without create method # because we are going to use it without queue @@ -128,8 +151,12 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') - self.assertEqual(len(hosts), 2) + self.assertEqual(2, len(hosts)) compute1.kill() compute2.kill() @@ -145,6 +172,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance() @@ -156,6 +187,67 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() + def test_specific_host_gets_instance(self): + """Ensures if you set availability_zone it launches on that zone""" + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) + instance_id1 = self._create_instance() + compute1.run_instance(self.context, instance_id1) + instance_id2 = self._create_instance(availability_zone='nova:host1') + host = self.scheduler.driver.schedule_run_instance(self.context, + instance_id2) + self.assertEqual('host1', host) + compute1.terminate_instance(self.context, instance_id1) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + compute2.kill() + + def test_wont_sechedule_if_specified_host_is_down(self): + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + now = datetime.datetime.utcnow() + delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2) + past = now - delta + db.service_update(self.context, s1['id'], {'disabled': False, + 'updated_at': past}) + instance_id2 = self._create_instance(availability_zone='nova:host1') + self.assertRaises(driver.WillNotSchedule, + self.scheduler.driver.schedule_run_instance, + self.context, + instance_id2) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + + def test_will_schedule_on_disabled_host_if_specified(self): + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + db.service_get_by_args(self.context, 'host1', 'nova-compute') + instance_id2 = self._create_instance(availability_zone='nova:host1') + host = self.scheduler.driver.schedule_run_instance(self.context, + instance_id2) + self.assertEqual('host1', host) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + def test_too_many_cores(self): """Ensures we don't go over max cores""" compute1 = service.Service('host1', @@ -168,6 +260,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): @@ -201,6 +297,10 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) volume_id1 = self._create_volume() volume1.create_volume(self.context, volume_id1) volume_id2 = self._create_volume() @@ -224,6 +324,10 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) volume_ids1 = [] volume_ids2 = [] for index in xrange(FLAGS.max_gigabytes): -- cgit From af4d6e84c67b8f59f63ef0275778fa897dac9e95 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 13:01:33 -0600 Subject: Getting Snapshots to work with cloudservers command-line tool --- nova/api/openstack/__init__.py | 3 +-- nova/api/openstack/images.py | 8 +++++--- nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 11 +++-------- nova/flags.py | 2 +- nova/image/glance.py | 5 ++++- nova/virt/xenapi/vm_utils.py | 8 +++----- 7 files changed, 26 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 44f3aafec..dba008111 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -89,8 +89,7 @@ class AuthMiddleware(wsgi.Middleware): if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) - #FIXME(sirp): stubbing project to admin for now - project = manager.AuthManager().get_project('admin') + project = manager.AuthManager().get_project(FLAGS.default_project) req.environ['nova.context'] = context.RequestContext(user, project) return self.application diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 78849223d..de072b28f 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -66,9 +66,11 @@ class Controller(wsgi.Controller): raise faults.Fault(exc.HTTPNotFound()) def create(self, req): - # Only public images are supported for now, so a request to - # make a backup of a server cannot be supproted. - raise faults.Fault(exc.HTTPNotFound()) + ctxt = req.environ['nova.context'] + env = self._deserialize(req.body, req) + data = {'instance_id': env["image"]["serverId"], + 'name': env["image"]["name"] } + return dict(image=self._service.create(ctxt, data)) def update(self, req, id): # Users may not modify public images, and that's all that diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da..6864e694e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -271,6 +271,15 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) + def snapshot(self, context, instance_id, name): + """Snapshot the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "snapshot_instance", + "args": {"instance_id": instance['id'], "name": name}}) + def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 56b11c4ed..f74eacaf5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -134,10 +134,8 @@ class ComputeManager(manager.Manager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) - #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def XXreboot_instance(self, context, instance_id): - #def reboot_instance(self, context, instance_id): + def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -158,10 +156,8 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) - #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def reboot_instance(self, context, instance_id): - #def snapshot_instance(self, context, instance_id): + def snapshot_instance(self, context, instance_id, name): """Snapshot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -181,8 +177,7 @@ class ComputeManager(manager.Manager): logging.debug('instance %s: snapshotting', instance_ref['name']) #TODO(sirp): set is_snapshotting=True here? - glance_name = "MySnapshot3" - self.driver.snapshot(instance_ref, glance_name) + self.driver.snapshot(instance_ref, name) #self._update_state(context, instance_id) @exception.wrap_exception diff --git a/nova/flags.py b/nova/flags.py index 51956d5cf..cf4614b8d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -214,7 +214,6 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') DEFINE_integer('glance_port', 9292, 'glance port') DEFINE_string('glance_host', '127.0.0.1', 'glance host') -DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -236,6 +235,7 @@ DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') +DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') DEFINE_string('default_instance_type', 'm1.small', diff --git a/nova/image/glance.py b/nova/image/glance.py index 1ca6cf2eb..84e4eaa7d 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -24,6 +24,7 @@ import urlparse import webob.exc +from nova.compute import api as compute_api from nova import utils from nova import flags from nova import exception @@ -202,7 +203,9 @@ class GlanceImageService(nova.image.service.BaseImageService): :raises AlreadyExists if the image already exist. """ - return self.parallax.add_image_metadata(data) + instance_id = data["instance_id"] + name = data["name"] + compute_api.ComputeAPI().snapshot(context, instance_id, name) def update(self, context, image_id, data): """Replace the contents of the given image with the new data. diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index a4501cdbb..7a93c44c7 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -183,13 +183,11 @@ class VMHelper(): return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod - def upload_image(cls, session, vdi_uuids, glance_label): - logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, - glance_label) + def upload_image(cls, session, vdi_uuids, image_name): + logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, image_name) params = {'vdi_uuids': vdi_uuids, - 'glance_label': glance_label, - 'glance_storage_location': FLAGS.glance_storage_location, + 'image_name': image_name, 'glance_host': FLAGS.glance_host, 'glance_port': FLAGS.glance_port} -- cgit From 7c03b9aa49b390e13cfbe8315a62c660778ef854 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:00:31 -0600 Subject: i18n support for xs-snaps --- nova/compute/manager.py | 11 ++++------ nova/flags.py | 2 +- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vm_utils.py | 50 +++++++++++++++++++++++++------------------- nova/virt/xenapi/vmops.py | 26 +++++++++++++---------- nova/virt/xenapi_conn.py | 2 -- 6 files changed, 51 insertions(+), 44 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index f9124aa6c..cf459d96a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -162,23 +162,20 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - #FIXME(sirp): update_state currently only refreshes the state field + #NOTE(sirp): update_state currently only refreshes the state field # if we add is_snapshotting, we will need this refreshed too, # potentially? self._update_state(context, instance_id) - #TODO(sirp): check for is_snapshotting=True here? + logging.debug(_('instance %s: snapshotting'), instance_ref['name']) if instance_ref['state'] != power_state.RUNNING: - logging.warn('trying to snapshot a non-running ' - 'instance: %s (state: %s excepted: %s)', + logging.warn(_('trying to snapshot a non-running ' + 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING) - logging.debug('instance %s: snapshotting', instance_ref['name']) - #TODO(sirp): set is_snapshotting=True here? self.driver.snapshot(instance_ref, name) - #self._update_state(context, instance_id) @exception.wrap_exception def rescue_instance(self, context, instance_id): diff --git a/nova/flags.py b/nova/flags.py index b61447c76..d773a7e4c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -213,7 +213,7 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', utils.get_my_ip(),, 'glance host') +DEFINE_string('glance_host', utils.get_my_ip(), 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 3b5954b3b..c8d0aa306 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -239,9 +239,9 @@ class LibvirtConnection(object): @exception.wrap_exception def snapshot(self, instance): """ Create snapshot from a running VM instance """ - #TODO(sirp): only exists for XenAPI driver for now raise NotImplementedError( - "Instance snapshotting is not supported for libvirt at this time") + _("Instance snapshotting is not supported for libvirt" + "at this time")) @exception.wrap_exception def reboot(self, instance): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f4e608e4d..5b1c36418 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -153,17 +153,22 @@ class VMHelper(): @classmethod - def create_snapshot(cls, session, vm_ref, label): - logging.debug("Snapshotting VM %s with label '%s'...", vm_ref, label) + def create_snapshot(cls, session, instance_id, vm_ref, label): + logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added #TODO(sirp): Make safe_lookup_vdi for assert? vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) - if vdi_refs is None or len(vdi_refs) != 1: - raise Exception("Unexpected number of VDIs (%s) found for VM %s" - % (len(vdi_refs), vm_ref)) + if vdi_refs is None: + raise Exception(_("No VDIs found for VM %s") % vm_ref) + else: + num_vdis = len(vdi_refs) + if num_vdis != 1: + raise Exception(_("Unexpected number of VDIs (%s) found for " + "VM %s") % (num_vdis, vm_ref)) + vdi_ref = vdi_refs[0] vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) vdi_uuid = vdi_rec["uuid"] @@ -171,20 +176,21 @@ class VMHelper(): original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) - template_vm_ref = session.wait_for_task(task) - logging.debug('Created snapshot %s from VM %s.', template_vm_ref, + template_vm_ref = session.wait_for_task(instance_id, task) + logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) sr_ref = vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( - session, sr_ref, vdi_ref, original_parent_uuid) + session, instance_id, sr_ref, vdi_ref, original_parent_uuid) #TODO(sirp): we need to assert only one parent, not parents two deep return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod - def upload_image(cls, session, vdi_uuids, image_name): - logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, image_name) + def upload_image(cls, session, instance_id, vdi_uuids, image_name): + logging.debug(_("Asking xapi to upload %s as '%s'"), + vdi_uuids, image_name) params = {'vdi_uuids': vdi_uuids, 'image_name': image_name, @@ -193,11 +199,11 @@ class VMHelper(): kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'put_vdis', kwargs) - session.wait_for_task(task) + session.wait_for_task(instance_id, task) @classmethod - def fetch_image(cls, session, image, user, project, use_sr): + def fetch_image(cls, session, instance_id, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place it on dom0's filesystem. The former is for VM disks, the latter for its kernel and ramdisk (if external kernels are being used). @@ -214,7 +220,7 @@ class VMHelper(): if use_sr: args['add_partition'] = 'true' task = session.async_call_plugin('objectstore', fn, args) - uuid = session.wait_for_task(task) + uuid = session.wait_for_task(instance_id, task) return uuid @classmethod @@ -317,7 +323,7 @@ def get_vhd_parent(session, vdi_rec): parent_ref = session.get_xenapi().VDI.get_by_uuid(parent_uuid) parent_rec = session.get_xenapi().VDI.get_record(parent_ref) #NOTE(sirp): changed log -> logging - logging.debug("VHD %s has parent %s", vdi_rec['uuid'], parent_ref) + logging.debug(_("VHD %s has parent %s"), vdi_rec['uuid'], parent_ref) return parent_ref, parent_rec else: return None @@ -333,25 +339,27 @@ def get_vhd_parent_uuid(session, vdi_ref): return None -def scan_sr(session, sr_ref): - logging.debug("Re-scanning SR %s", sr_ref) +def scan_sr(session, instance_id, sr_ref): + logging.debug(_("Re-scanning SR %s"), sr_ref) task = session.call_xenapi('Async.SR.scan', sr_ref) - session.wait_for_task(task) + session.wait_for_task(instance_id, task) -def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): +def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): """ TODO Explain why coalescing has to occur here """ #NOTE(sirp): for some reason re-scan wasn't occuring automatically on # XS5.6 #TODO(sirp): we need to timeout this req after a while def _poll_vhds(): - scan_sr(session, sr_ref) + scan_sr(session, instance_id, sr_ref) parent_uuid = get_vhd_parent_uuid(session, vdi_ref) if original_parent_uuid and (parent_uuid != original_parent_uuid): logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) + _("Parent %s doesn't match original parent %s, " + "waiting for coalesce..."), + parent_uuid, original_parent_uuid) else: done.send(parent_uuid) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d011f4489..787f959a5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -67,11 +67,14 @@ class VMOps(object): user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) vdi_uuid = VMHelper.fetch_image( - self._session, instance.image_id, user, project, True) + self._session, instance.id, instance.image_id, user, project, + True) kernel = VMHelper.fetch_image( - self._session, instance.kernel_id, user, project, False) + self._session, instance.id, instance.kernel_id, user, project, + False) ramdisk = VMHelper.fetch_image( - self._session, instance.ramdisk_id, user, project, False) + self._session, instance.id, instance.ramdisk_id, user, project, + False) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) vm_ref = VMHelper.create_vm( self._session, instance, kernel, ramdisk) @@ -90,24 +93,25 @@ class VMOps(object): #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - logging.debug("Starting snapshot for VM %s", instance) + logging.debug(_("Starting snapshot for VM %s"), instance) vm_ref = VMHelper.lookup(self._session, instance.name) label = "%s-snapshot" % instance.name try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( - self._session, vm_ref, label) + self._session, instance.id, vm_ref, label) except XenAPI.Failure, exc: - logging.error("Unable to Snapshot %s: %s", vm_ref, exc) + logging.error(_("Unable to Snapshot %s: %s"), vm_ref, exc) return try: # call plugin to ship snapshot off to glance - VMHelper.upload_image(self._session, template_vdi_uuids, name) + VMHelper.upload_image( + self._session, instance.id, template_vdi_uuids, name) finally: - self._destroy(template_vm_ref, shutdown=False) + self._destroy(instance, template_vm_ref, shutdown=False) - logging.debug("Finished snapshot and upload for VM %s", instance) + logging.debug(_("Finished snapshot and upload for VM %s"), instance) def reboot(self, instance): """Reboot VM instance""" @@ -121,9 +125,9 @@ class VMOps(object): def destroy(self, instance): """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) - return self._destroy(vm, shutdown=True) + return self._destroy(instance, vm, shutdown=True) - def _destroy(self, vm, shutdown=True): + def _destroy(self, instance, vm, shutdown=True): """ Destroy VM instance """ if vm is None: # Don't complain, just return. This lets us clean up instances diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index e6bc80d6e..58ad6ca6a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -124,8 +124,6 @@ class XenAPIConnection(object): def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ - #TODO(sirp): Add quiesce and VSS locking support when Windows support - # is added self._vmops.snapshot(instance, name) def reboot(self, instance): -- cgit From f31395c30c835201372802e9cdf9293dcbabdb5c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:54:02 -0600 Subject: Adding more comments regarding XS snapshots --- nova/tests/compute_unittest.py | 8 ++++++++ nova/virt/fake.py | 16 ++++++++++++++++ nova/virt/libvirt_conn.py | 2 +- nova/virt/xenapi/vm_utils.py | 20 +++++++++++++++++--- nova/virt/xenapi/vmops.py | 22 +++++++++++++++++++++- nova/virt/xenapi_conn.py | 1 - 6 files changed, 63 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 187ca31de..025291a92 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -142,6 +142,14 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_snapshot(self): + """Ensure instance can be snapshotted""" + instance_id = self._create_instance() + name = "myfakesnapshot" + self.compute.run_instance(self.context, instance_id) + self.compute.snapshot_instance(self.context, instance_id, name) + self.compute.terminate_instance(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 73e273edd..c6db66aa6 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -106,6 +106,22 @@ class FakeConnection(object): self.instances[instance.name] = fake_instance fake_instance._state = power_state.RUNNING + + def snapshot(self, instance, name): + """ + Snapshots the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + + The second parameter is the name of the snapshot. + + The work will be done asynchronously. This function returns a + Deferred that allows the caller to detect when it is complete. + """ + pass + + def reboot(self, instance): """ Reboot the specified instance. diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c8d0aa306..ad1a35643 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -237,7 +237,7 @@ class LibvirtConnection(object): virt_dom.detachDevice(xml) @exception.wrap_exception - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ raise NotImplementedError( _("Instance snapshotting is not supported for libvirt" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 5b1c36418..b7e20121c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -154,6 +154,9 @@ class VMHelper(): @classmethod def create_snapshot(cls, session, instance_id, vm_ref, label): + """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, + Snapshot VHD + """ logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support @@ -189,6 +192,9 @@ class VMHelper(): @classmethod def upload_image(cls, session, instance_id, vdi_uuids, image_name): + """ Requests that the Glance plugin bundle the specified VDIs and + push them into Glance using the specified human-friendly name. + """ logging.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) @@ -347,9 +353,17 @@ def scan_sr(session, instance_id, sr_ref): def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): - """ TODO Explain why coalescing has to occur here """ - #NOTE(sirp): for some reason re-scan wasn't occuring automatically on - # XS5.6 + """ Spin until the parent VHD is coalesced into its parent VHD + + Before coalesce: + * original_parent_vhd + * parent_vhd + snapshot + + Atter coalesce: + * parent_vhd + snapshot + """ #TODO(sirp): we need to timeout this req after a while def _poll_vhds(): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 787f959a5..dd6243dd5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -88,7 +88,27 @@ class VMOps(object): vm_ref) def snapshot(self, instance, name): - """ Create snapshot from a running VM instance """ + """ Create snapshot from a running VM instance + + :param instance: instance to be snapshotted + :param name: name/label to be given to the snapshot + + Steps involved in a XenServer snapshot: + + 1. XAPI-Snapshot: Snapshotting the instance using XenAPI. This + creates: Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, + Snapshot VHD + + 2. Wait-for-coalesce: The Snapshot VDI and Instance VDI both point to + a 'base-copy' VDI. The base_copy is immutable and may be chained + with other base_copies. If chained, the base_copies + coalesce together, so, we must wait for this coalescing to occur to + get a stable representation of the data on disk. + + 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer + that will bundle the VHDs together and then push the bundle into + Glance. + """ #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 58ad6ca6a..816b92a0f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -121,7 +121,6 @@ class XenAPIConnection(object): """Create VM instance""" self._vmops.spawn(instance) - def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ self._vmops.snapshot(instance, name) -- cgit From 749af384c0b7ca36bdd8c511f02b819a65e5dae0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 22 Dec 2010 17:09:46 -0600 Subject: Modified InstanceDiagnostics and truncate action --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 4 ++++ nova/db/sqlalchemy/models.py | 10 ++-------- nova/virt/xenapi_conn.py | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index de95ee548..d8cd86116 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -176,6 +176,7 @@ class APIRouter(wsgi.Router): logging.debug("Including admin operations in API.") server_members['pause'] = 'POST' server_members['unpause'] = 'POST' + server_members["diagnostics"] = "GET" mapper.resource("server", "servers", controller=servers.Controller(), collection={'detail': 'GET'}, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c3322f7c..65e371a90 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -195,3 +195,7 @@ class Controller(wsgi.Controller): logging.error("Compute.api::unpause %s", readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + + def diagnostics(self, req, id): + """Permit Admins to retrieve server diagnostics.""" + return {} diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index eac6a304e..600943005 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -22,7 +22,7 @@ SQLAlchemy models for nova data. import datetime from sqlalchemy.orm import relationship, backref, object_mapper -from sqlalchemy import Column, Integer, Float, String, schema +from sqlalchemy import Column, Integer, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base @@ -232,13 +232,7 @@ class InstanceDiagnostics(BASE, NovaBase): id = Column(Integer, primary_key=True) instance_id = Column(Integer, ForeignKey('instances.id')) - memory_available = Column(Float) - memory_free = Column(Float) - cpu_load = Column(Float) - disk_read = Column(Float) - disk_write = Column(Float) - net_tx = Column(Float) - net_rx = Column(Float) + diagnostics = Column(Text) class InstanceActions(BASE, NovaBase): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 146e2f153..7870a21ac 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -204,7 +204,7 @@ class XenAPISession(object): status = self._session.xenapi.task.get_status(task) action = dict( instance_id=int(instance_id), - action=name, + action=name[0:255], # Ensure action is never > 255 error=None) if status == "pending": return -- cgit From 358961f3cf259487a2ff9bbb225defdc7cd9e7a7 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 11:22:51 -0600 Subject: Fixed trunk and PEP8 cleanup --- nova/virt/libvirt_conn.py | 16 +++++++++------- nova/virt/xenapi_conn.py | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 651b2af93..809a484dc 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -512,9 +512,11 @@ class LibvirtConnection(object): if FLAGS.allow_project_net_traffic: net, mask = _get_net_and_mask(network['cidr']) - extra_params = ("\n" - "\n" - ) % (net, mask) + extra_params = ( + "\n" + "\n") % ( + net, + mask) else: extra_params = "\n" @@ -800,8 +802,8 @@ class NWFilterFirewall(object): the base filter are all in place. """ - nwfilter_xml = ("\n" - ) % instance['name'] + nwfilter_xml = ("\n") % ( + instance['name']) if instance['image_id'] == FLAGS.vpn_image_id: nwfilter_xml += " \n" @@ -814,8 +816,8 @@ class NWFilterFirewall(object): for security_group in instance.security_groups: self.ensure_security_group_filter(security_group['id']) - nwfilter_xml += (" \n" - ) % security_group['id'] + nwfilter_xml += (" \n") % ( + security_group['id']) nwfilter_xml += "" self._define_filter(nwfilter_xml) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7870a21ac..af16fcdf3 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -110,6 +110,10 @@ class XenAPIConnection(object): self._vmops = VMOps(session) self._volumeops = VolumeOps(session) + def init_host(self): + """Initialize anything that is necessary for the driver to function""" + return + def list_instances(self): """List VM instances""" return self._vmops.list_instances() -- cgit From 55a80811a5982cb9af5b80e7ac3e925334a1b22d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 12:23:28 -0600 Subject: Working diagnostics API; removed diagnostics DB model - not needed --- nova/api/openstack/servers.py | 3 ++- nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 11 +++++++++++ nova/db/sqlalchemy/models.py | 11 +---------- nova/virt/xenapi/vmops.py | 6 +++--- nova/virt/xenapi_conn.py | 4 ++-- 6 files changed, 28 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 65e371a90..f758b252a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -198,4 +198,5 @@ class Controller(wsgi.Controller): def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" - return {} + ctxt = req.environ["nova.context"] + return self.compute_api.diagnostics(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 4953fe559..1c24c6634 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -283,6 +283,15 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) + def diagnostics(self, context, instance_id): + """Retrieve diagnostics for the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance["host"] + return rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "get_diagnostics", + "args": {"instance_id": instance["id"]}}) + def rescue(self, context, instance_id): """Rescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ff8202cca..3a5677bc5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -296,6 +296,17 @@ class ComputeManager(manager.Manager): instance_id, result)) + @exception.wrap_exception + def get_diagnostics(self, context, instance_id): + """Retrieve diagnostics for an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + if instance_ref["state"] == power_state.RUNNING: + logging.debug(_("instance %s: retrieving diagnostics"), + instance_ref["internal_id"]) + return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 163d2470b..d93dd64c5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -236,15 +236,6 @@ class Instance(BASE, NovaBase): # 'shutdown', 'shutoff', 'crashed']) -class InstanceDiagnostics(BASE, NovaBase): - """Represents a guest VM's diagnostics""" - __tablename__ = "instance_diagnostics" - id = Column(Integer, primary_key=True) - instance_id = Column(Integer, ForeignKey('instances.id')) - - diagnostics = Column(Text) - - class InstanceActions(BASE, NovaBase): """Represents a guest VM's actions and results""" __tablename__ = "instance_actions" @@ -555,7 +546,7 @@ def register_models(): it will never need to be called explicitly elsewhere. """ from sqlalchemy import create_engine - models = (Service, Instance, InstanceDiagnostics, InstanceActions, + models = (Service, Instance, InstanceActions, Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4d897af35..50e20bff4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -156,11 +156,11 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) - def get_diagnostics(self, instance_id): + def get_diagnostics(self, instance): """Return data about VM diagnostics""" - vm = VMHelper.lookup(self._session, instance_id) + vm = VMHelper.lookup(self._session, instance.name) if vm is None: - raise Exception("instance not present %s" % instance_id) + raise Exception("instance not present %s" % instance.name) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index af16fcdf3..5c746a08f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -142,9 +142,9 @@ class XenAPIConnection(object): """Return data about VM instance""" return self._vmops.get_info(instance_id) - def get_diagnostics(self, instance_id): + def get_diagnostics(self, instance): """Return data about VM diagnostics""" - return self._vmops.get_diagnostics(instance_id) + return self._vmops.get_diagnostics(instance) def get_console_output(self, instance): """Return snapshot of console""" -- cgit From 13e8c8d83b8fc44cff343ea751a98f66857d1865 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 13:14:56 -0600 Subject: Resolved merge conflict --- nova/virt/xenapi_conn.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4ea8a6737..2bf29b295 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -225,13 +225,8 @@ class XenAPISession(object): name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) action = dict( -<<<<<<< TREE - instance_id=int(instance_id), + instance_id=int(id), action=name[0:255], # Ensure action is never > 255 -======= - id=int(id), - action=name, ->>>>>>> MERGE-SOURCE error=None) if status == "pending": return -- cgit From bd46ab4a721da856da5743c9f55ab5e50ec9b60f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 13:36:01 -0600 Subject: PEP8 fix --- nova/virt/xenapi/vmops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5039913ba..7571fe2e4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -186,7 +186,8 @@ class VMOps(object): """Return data about VM diagnostics""" vm = VMHelper.lookup(self._session, instance.name) if vm is None: - raise exception.NotFound(_("Instance not found %s") % instance.name) + raise exception.NotFound(_("Instance not found %s") % + instance.name) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) -- cgit From 59c3e5bf0dda0c0c1b77307a339f3102c7179885 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Dec 2010 18:09:52 -0600 Subject: Faked out handling for shared ip groups so they return something --- nova/api/openstack/ratelimiting/__init__.py | 4 ++-- nova/api/openstack/sharedipgroups.py | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 91a8b2e55..cbb4b897e 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -64,9 +64,9 @@ class RateLimitingMiddleware(wsgi.Middleware): If the request should be rate limited, return a 413 status with a Retry-After header giving the time when the request would succeed. """ - return self.limited_request(req, self.application) + return self.rate_limited_request(req, self.application) - def limited_request(self, req, application): + def rate_limited_request(self, req, application): """Rate limit the request. If the request should be rate limited, return a 413 status with a diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 75d02905c..4f4bb1016 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +from webob import exc + from nova import wsgi @@ -22,19 +24,25 @@ class Controller(wsgi.Controller): """ The Shared IP Groups Controller for the Openstack API """ def index(self, req): - raise NotImplementedError + """ Returns a list of Shared IP Groups for the user """ + return dict(sharedipgroups=[]) def show(self, req, id): - raise NotImplementedError + """ Shows in-depth information on a specific Shared IP Group """ + return dict(sharedipgroup={}) def update(self, req, id): - raise NotImplementedError + """ You can't update a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, id): - raise NotImplementedError + """ Deletes a Shared IP Group """ + raise faults.Fault(exc.HTTPNotFound()) - def detail(self, req): - raise NotImplementedError + def detail(self, req, id): + """ Returns a complete list of Shared IP Groups """ + return dict(sharedipgroups=[]) def create(self, req): - raise NotImplementedError + """ Creates a new Shared IP group """ + raise faults.Fault(exc.HTTPNotFound()) -- cgit From 3490fde00fd8bfb00834b1085de62d86c9c9d061 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 12:08:22 -0600 Subject: A few fixes --- nova/api/openstack/backup_schedules.py | 5 ++++- nova/api/openstack/servers.py | 3 ++- nova/api/openstack/sharedipgroups.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index fc70b5c6c..c484023e0 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -24,12 +24,14 @@ import nova.image.service class Controller(wsgi.Controller): + """ The backup schedule API controller for the Openstack API """ def __init__(self): pass def index(self, req, server_id): - return faults.Fault(exc.HTTPNotFound()) + """ Returns the list of backup schedules for a given instance """ + return dict(backup_schedules=[]) def create(self, req, server_id): """ No actual update method required, since the existing API allows @@ -37,4 +39,5 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) def delete(self, req, server_id, id): + """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d60e2cab..c8851387d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -43,6 +43,7 @@ def _entity_list(entities): def _entity_detail(inst): """ Maps everything to Rackspace-like attributes for return""" power_mapping = { + None: 'build', power_state.NOSTATE: 'build', power_state.RUNNING: 'active', power_state.BLOCKED: 'active', @@ -153,7 +154,7 @@ class Controller(wsgi.Controller): try: self.compute_api.update_instance(req.environ['nova.context'], - instance['id'], + inst_dict['id'], **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 4f4bb1016..7e98b0712 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -18,6 +18,7 @@ from webob import exc from nova import wsgi +from nova.api.openstack import faults class Controller(wsgi.Controller): -- cgit From b879c746049241837af3785adc3068fbe35f199d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 13:20:37 -0600 Subject: backup schedule changes --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/backup_schedules.py | 10 +++++++++- nova/api/openstack/servers.py | 6 ++++-- nova/api/openstack/sharedipgroups.py | 27 ++++++++++++++++++++++++--- 4 files changed, 38 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c49399f28..1f78820c8 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -98,7 +98,7 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, member=server_members) - mapper.resource("backup_schedule", "backup_schedules", + mapper.resource("backup_schedule", "backup_schedule", controller=backup_schedules.Controller(), parent_resource=dict(member_name='server', collection_name='servers')) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index c484023e0..a8b0fbbb9 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -22,16 +22,24 @@ from nova import wsgi from nova.api.openstack import faults import nova.image.service +def _entity_inst(inst): + """ Coerces the backup schedule into proper dictionary format """ + return dict(backupSchedule=inst) class Controller(wsgi.Controller): """ The backup schedule API controller for the Openstack API """ + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'backupSchedule': []}}} + def __init__(self): pass def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ - return dict(backup_schedules=[]) + return _entity_inst({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c8851387d..7abf5ec53 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -153,8 +153,10 @@ class Controller(wsgi.Controller): update_dict['display_name'] = inst_dict['server']['name'] try: - self.compute_api.update_instance(req.environ['nova.context'], - inst_dict['id'], + ctxt = req.environ['nova.context'] + inst_ref = self.compute_api.get_instance(ctxt, id) + self.compute_api.update_instance(ctxt, + id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 7e98b0712..32ebfa45a 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -21,16 +21,37 @@ from nova import wsgi from nova.api.openstack import faults +def _entity_list(entities): + """ Coerces a list of shared IP groups into proper dictionary format """ + return dict(sharedIpGroups=entities) + + +def _entity_inst(inst): + """ Coerces a shared IP group instance into proper dictionary format """ + return dict(sharedIpGroup=inst) + + +def _entity_detail(inst): + """ Coerces a shared IP group instance into proper dictionary format with + correctly mapped attributes """ + return dict(sharedIpGroup=inst) + + class Controller(wsgi.Controller): """ The Shared IP Groups Controller for the Openstack API """ + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'sharedIpGroup': []}}} + def index(self, req): """ Returns a list of Shared IP Groups for the user """ - return dict(sharedipgroups=[]) + return _entity_list([]) def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - return dict(sharedipgroup={}) + return _entity_inst({}) def update(self, req, id): """ You can't update a Shared IP Group """ @@ -42,7 +63,7 @@ class Controller(wsgi.Controller): def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ - return dict(sharedipgroups=[]) + return _entity_detail({}) def create(self, req): """ Creates a new Shared IP group """ -- cgit From 748aa8089eabfd15425199c2318079e9bf84578f Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 27 Dec 2010 14:23:53 -0600 Subject: Fixing bad merge --- nova/virt/xenapi/vmops.py | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a0497b3e2..23531348e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -96,7 +96,30 @@ class VMOps(object): self._session.call_xenapi('VM.start', vm_ref, False, False) logging.info(_('Spawning VM %s created %s.'), instance.name, vm_ref) - + + # NOTE(armando): Do we really need to do this in virt? + timer = utils.LoopingCall(f=None) + + def _wait_for_boot(): + try: + state = self.get_info(instance['name'])['state'] + db.instance_set_state(context.get_admin_context(), + instance['id'], state) + if state == power_state.RUNNING: + logging.debug(_('Instance %s: booted'), instance['name']) + timer.stop() + except Exception, exc: + logging.warn(exc) + logging.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 + return timer.start(interval=0.5, now=True) + def snapshot(self, instance, name): """ Create snapshot from a running VM instance @@ -143,29 +166,6 @@ class VMOps(object): logging.debug(_("Finished snapshot and upload for VM %s"), instance) - # NOTE(armando): Do we really need to do this in virt? - timer = utils.LoopingCall(f=None) - - def _wait_for_boot(): - try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - logging.debug(_('Instance %s: booted'), instance['name']) - timer.stop() - except Exception, exc: - logging.warn(exc) - logging.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 - return timer.start(interval=0.5, now=True) - def reboot(self, instance): """Reboot VM instance""" instance_name = instance.name -- cgit From 8d522838ace090a7325d930df08c37f1e9d9803e Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 27 Dec 2010 15:42:30 -0600 Subject: Fixing snapshots, pep8 fixes --- nova/api/openstack/images.py | 2 +- nova/tests/api/openstack/fakes.py | 1 + nova/virt/fake.py | 2 -- nova/virt/xenapi/vm_utils.py | 58 +++++++++++++++++++++------------------ nova/virt/xenapi/vmops.py | 16 +++++------ nova/virt/xenapi_conn.py | 2 +- 6 files changed, 43 insertions(+), 38 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 8a6772fa5..3a95e3961 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -71,7 +71,7 @@ class Controller(wsgi.Controller): ctxt = req.environ['nova.context'] env = self._deserialize(req.body, req) data = {'instance_id': env["image"]["serverId"], - 'name': env["image"]["name"] } + 'name': env["image"]["name"]} return dict(image=self._service.create(ctxt, data)) def update(self, req, id): diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index e1ed6a6a8..961431154 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -115,6 +115,7 @@ def stub_out_compute_api_snapshot(stubs): return 123 stubs.Set(nova.compute.api.ComputeAPI, 'snapshot', snapshot) + def stub_out_glance(stubs, initial_fixtures=[]): class FakeParallaxClient: diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 6d3cef268..0f456aef6 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -112,7 +112,6 @@ class FakeConnection(object): self.instances[instance.name] = fake_instance fake_instance._state = power_state.RUNNING - def snapshot(self, instance, name): """ Snapshots the specified instance. @@ -127,7 +126,6 @@ class FakeConnection(object): """ pass - def reboot(self, instance): """ Reboot the specified instance. diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2b76afc62..f4195cb1b 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -27,6 +27,7 @@ from xml.dom import minidom from eventlet import event from nova import exception from nova import flags +from nova import utils from nova.auth.manager import AuthManager from nova.compute import instance_types from nova.compute import power_state @@ -205,44 +206,34 @@ class VMHelper(HelperBase): vm_ref, network_ref) return vif_ref - @classmethod def create_snapshot(cls, session, instance_id, vm_ref, label): """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD """ - logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) - + logging.debug(_("Snapshotting VM %s with label '%s'..."), + vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - - #TODO(sirp): Make safe_lookup_vdi for assert? - vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) - if vdi_refs is None: - raise Exception(_("No VDIs found for VM %s") % vm_ref) - else: - num_vdis = len(vdi_refs) - if num_vdis != 1: - raise Exception(_("Unexpected number of VDIs (%s) found for " - "VM %s") % (num_vdis, vm_ref)) - - vdi_ref = vdi_refs[0] - vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) - vdi_uuid = vdi_rec["uuid"] - - original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + vm_vdi_ref, vm_vdi_rec = get_vdi_for_vm_safely(session, vm_ref) + vm_vdi_uuid = vm_vdi_rec["uuid"] + original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) template_vm_ref = session.wait_for_task(instance_id, task) + template_vdi_ref, template_vdi_rec = get_vdi_for_vm_safely( + session, template_vm_ref) + + template_vdi_uuid = template_vdi_rec["uuid"] logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) - sr_ref = vdi_rec["SR"] + sr_ref = vm_vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( - session, instance_id, sr_ref, vdi_ref, original_parent_uuid) + session, instance_id, sr_ref, vm_vdi_ref, original_parent_uuid) #TODO(sirp): we need to assert only one parent, not parents two deep - return template_vm_ref, [vdi_uuid, parent_uuid] + return template_vm_ref, [template_vdi_uuid, parent_uuid] @classmethod def upload_image(cls, session, instance_id, vdi_uuids, image_name): @@ -252,7 +243,7 @@ class VMHelper(HelperBase): logging.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) - params = {'vdi_uuids': vdi_uuids, + params = {'vdi_uuids': vdi_uuids, 'image_name': image_name, 'glance_host': FLAGS.glance_host, 'glance_port': FLAGS.glance_port} @@ -261,7 +252,6 @@ class VMHelper(HelperBase): task = session.async_call_plugin('glance', 'put_vdis', kwargs) session.wait_for_task(instance_id, task) - @classmethod def fetch_image(cls, session, instance_id, image, user, project, type): """ @@ -418,7 +408,7 @@ def scan_sr(session, instance_id, sr_ref): def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): """ Spin until the parent VHD is coalesced into its parent VHD - + Before coalesce: * original_parent_vhd * parent_vhd @@ -440,7 +430,7 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, parent_uuid, original_parent_uuid) else: done.send(parent_uuid) - + done = event.Event() loop = utils.LoopingCall(_poll_vhds) loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True) @@ -448,3 +438,19 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, loop.stop() return parent_uuid + +def get_vdi_for_vm_safely(session, vm_ref): + """Returns (vdi_ref, vdi_uuid)""" + #TODO(sirp): Make safe_lookup_vdi for assert? + vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) + if vdi_refs is None: + raise Exception(_("No VDIs found for VM %s") % vm_ref) + else: + num_vdis = len(vdi_refs) + if num_vdis != 1: + raise Exception(_("Unexpected number of VDIs (%s) found for " + "VM %s") % (num_vdis, vm_ref)) + + vdi_ref = vdi_refs[0] + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + return vdi_ref, vdi_rec diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 23531348e..2cc36ad54 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -122,7 +122,7 @@ class VMOps(object): def snapshot(self, instance, name): """ Create snapshot from a running VM instance - + :param instance: instance to be snapshotted :param name: name/label to be given to the snapshot @@ -131,7 +131,7 @@ class VMOps(object): 1. XAPI-Snapshot: Snapshotting the instance using XenAPI. This creates: Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD - + 2. Wait-for-coalesce: The Snapshot VDI and Instance VDI both point to a 'base-copy' VDI. The base_copy is immutable and may be chained with other base_copies. If chained, the base_copies @@ -153,14 +153,14 @@ class VMOps(object): try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( self._session, instance.id, vm_ref, label) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.error(_("Unable to Snapshot %s: %s"), vm_ref, exc) return - + try: # call plugin to ship snapshot off to glance VMHelper.upload_image( - self._session, instance.id, template_vdi_uuids, name) + self._session, instance.id, template_vdi_uuids, name) finally: self._destroy(instance, template_vm_ref, shutdown=False) @@ -193,7 +193,7 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up @@ -202,13 +202,13 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VDI.destroy', vdi) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # VM Destroy try: task = self._session.call_xenapi('Async.VM.destroy', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) def _wait_with_callback(self, instance_id, task, callback): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 1a3dc9f16..abd2a02ee 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -233,7 +233,7 @@ class XenAPISession(object): name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) action = dict( - id=int(id), + instance_id=int(id), action=name, error=None) if status == "pending": -- cgit From 431c54ba76a2a85ff55658c571f68378b47ce39d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 17:29:45 -0600 Subject: Renamed based on feedback from another branch --- nova/api/openstack/backup_schedules.py | 2 ++ nova/api/openstack/servers.py | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index a8b0fbbb9..9b8e605f6 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -22,10 +22,12 @@ from nova import wsgi from nova.api.openstack import faults import nova.image.service + def _entity_inst(inst): """ Coerces the backup schedule into proper dictionary format """ return dict(backupSchedule=inst) + class Controller(wsgi.Controller): """ The backup schedule API controller for the Openstack API """ diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7abf5ec53..9369f6516 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -35,13 +35,9 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) -def _entity_list(entities): - """ Coerces a list of servers into proper dictionary format """ - return dict(servers=entities) - - -def _entity_detail(inst): - """ Maps everything to Rackspace-like attributes for return""" +def _translate_detail_keys(inst): + """ Coerces into dictionary format, mapping everything to Rackspace-like + attributes for return""" power_mapping = { None: 'build', power_state.NOSTATE: 'build', @@ -67,8 +63,9 @@ def _entity_detail(inst): return dict(server=inst_dict) -def _entity_inst(inst): - """ Filters all model attributes save for id and name """ +def _translate_keys(inst): + """ Coerces into dictionary format, excluding all model attributes + save for id and name """ return dict(server=dict(id=inst['internal_id'], name=inst['display_name'])) @@ -87,29 +84,29 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - return self._items(req, entity_maker=_entity_inst) + return self._items(req, entity_maker=_translte_keys) def detail(self, req): """ Returns a list of server details for a given user """ - return self._items(req, entity_maker=_entity_detail) + return self._items(req, entity_maker=_translate_detail_keys) def _items(self, req, entity_maker): """Returns a list of servers for a given user. - entity_maker - either _entity_detail or _entity_inst + entity_maker - either _translate_detail_keys or _translate_keys """ instance_list = self.compute_api.get_instances( req.environ['nova.context']) limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] - return _entity_list(res) + return dict(servers=res) def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.get_instance( req.environ['nova.context'], int(id)) - return _entity_detail(instance) + return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -138,7 +135,7 @@ class Controller(wsgi.Controller): description=env['server']['name'], key_name=key_pair['name'], key_data=key_pair['public_key']) - return _entity_inst(instances[0]) + return _translate_keys(instances[0]) def update(self, req, id): """ Updates the server name or password """ -- cgit From 31d3aed581302e73b3f155b1dd72586324433e91 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 17:35:59 -0600 Subject: Typo fix --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9369f6516..d18889563 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -84,7 +84,7 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - return self._items(req, entity_maker=_translte_keys) + return self._items(req, entity_maker=_translate_keys) def detail(self, req): """ Returns a list of server details for a given user """ -- cgit From eab0ce934e6296910b26c087e4268a65dc233a55 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 28 Dec 2010 01:37:04 +0000 Subject: Bug #694880: nova-compute now depends upon Cheetah even when not using libvirt Only import Cheetah when needed, as we do already with libvirt and libxml2. This ensures that users of other virt backends don't need Cheetah to run nova-compute. --- nova/tests/test_virt.py | 1 + nova/virt/libvirt_conn.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1c155abe4..4aa489d08 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -33,6 +33,7 @@ flags.DECLARE('instances_path', 'nova.compute.manager') class LibvirtConnTestCase(test.TestCase): def setUp(self): super(LibvirtConnTestCase, self).setUp() + libvirt_conn._late_load_cheetah() self.flags(fake_call=True) self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 67b9fc47e..8b67b937f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -58,10 +58,9 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import images -from Cheetah.Template import Template - libvirt = None libxml2 = None +Template = None FLAGS = flags.FLAGS @@ -88,14 +87,23 @@ flags.DEFINE_bool('allow_project_net_traffic', def get_connection(read_only): # These are loaded late so that there's no need to install these # libraries when not using libvirt. + # Cheetah is separate because the unit tests want to load Cheetah, + # but not libvirt. global libvirt global libxml2 if libvirt is None: libvirt = __import__('libvirt') if libxml2 is None: libxml2 = __import__('libxml2') + _late_load_cheetah() return LibvirtConnection(read_only) +def _late_load_cheetah(): + global Template + if Template is None: + t = __import__('Cheetah.Template', globals(), locals(), ['Template'], + -1) + Template = t.Template def _get_net_and_mask(cidr): net = IPy.IP(cidr) -- cgit From 0fb37c5a08db4b2631ff687cb0fc6af43ba20190 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 11:49:07 -0600 Subject: Added InstanceAction DB functions --- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 9 +++++++++ 2 files changed, 14 insertions(+) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..127f15478 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -383,6 +383,11 @@ def instance_action_create(context, values): return IMPL.instance_action_create(context, values) +def instance_get_actions(context, instance_id): + """Get instance actions by instance id.""" + return IMPL.instance_get_actions(context, instance_id) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..d2127989c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,6 +856,15 @@ def instance_action_create(context, values): return action_ref +@require_context +def instance_get_actions(context, instance_id): + """Return the actions associated to the given instance id""" + session = get_session() + return session.query(models.InstanceActions).\ + filter_by(instance_id=instance_id).\ + all() + + ################### -- cgit From 7ddd833bc61252061b3dfd2449765a93f750bffa Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 12:19:25 -0600 Subject: Added get_diagnostics placeholders to libvirt and fake --- nova/virt/fake.py | 3 +++ nova/virt/libvirt_conn.py | 3 +++ 2 files changed, 6 insertions(+) (limited to 'nova') diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..880772b22 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -202,6 +202,9 @@ class FakeConnection(object): 'num_cpu': 2, 'cpu_time': 0} + def get_diagnostics(self, instance_name): + pass + def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..06ace7457 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -567,6 +567,9 @@ class LibvirtConnection(object): 'num_cpu': num_cpu, 'cpu_time': cpu_time} + def get_diagnostics(self, instance_name): + raise exception.APIError("diagnostics not supported for libvirt") + def get_disks(self, instance_name): """ Note that this function takes an instance name, not an Instance, so -- cgit From 96384b689953e381f2210d4a78f1b5239a78e507 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 28 Dec 2010 12:53:32 -0600 Subject: Add some basic snapshot tests --- nova/tests/test_xenapi.py | 106 +++++++++++++++++++++++++++++-------------- nova/tests/xenapi/stubs.py | 66 +++++++++++++++++++++++++++ nova/virt/xenapi/fake.py | 64 ++++++++++++++++++++++++-- nova/virt/xenapi/vm_utils.py | 15 +++--- 4 files changed, 206 insertions(+), 45 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..f0d84e9aa 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -29,9 +29,9 @@ from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state from nova.virt import xenapi_conn -from nova.virt.xenapi import fake +from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import volume_utils -from nova.tests.db import fakes +from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs FLAGS = flags.FLAGS @@ -47,9 +47,9 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fakes.stub_out_db_instance_api(self.stubs) + db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) - fake.reset() + xenapi_fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', 'user_id': 'fake', @@ -83,7 +83,7 @@ class XenAPIVolumeTestCase(test.TestCase): label = 'SR-%s' % vol['ec2_id'] description = 'Test-SR' sr_ref = helper.create_iscsi_storage(session, info, label, description) - srs = fake.get_all('SR') + srs = xenapi_fake.get_all('SR') self.assertEqual(sr_ref, srs[0]) db.volume_destroy(context.get_admin_context(), vol['id']) @@ -107,17 +107,17 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') def check(): # check that the VM has a VBD attached to it # Get XenAPI reference for the VM - vms = fake.get_all('VM') + vms = xenapi_fake.get_all('VM') # Get XenAPI record for VBD - vbds = fake.get_all('VBD') - vbd = fake.get_record('VBD', vbds[0]) + vbds = xenapi_fake.get_all('VBD') + vbd = xenapi_fake.get_record('VBD', vbds[0]) vm_ref = vbd['VM'] self.assertEqual(vm_ref, vms[0]) @@ -130,7 +130,7 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') self.assertRaises(Exception, conn.attach_volume, instance.name, @@ -156,41 +156,66 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) def test_list_instances_0(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instances = conn.list_instances() + instances = self.conn.list_instances() self.assertEquals(instances, []) + def test_instance_snapshot(self): + stubs.stubout_instance_snapshot(self.stubs) + instance = self._create_instance() + + name = "MySnapshot" + template_vm_ref = self.conn.snapshot(instance, name) + + def ensure_vm_was_torn_down(): + vm_labels = [] + for vm_ref in xenapi_fake.get_all('VM'): + vm_rec = xenapi_fake.get_record('VM', vm_ref) + if not vm_rec["is_control_domain"]: + vm_labels.append(vm_rec["name_label"]) + + self.assertEquals(vm_labels, [1]) + + def ensure_vbd_was_torn_down(): + vbd_labels = [] + for vbd_ref in xenapi_fake.get_all('VBD'): + vbd_rec = xenapi_fake.get_record('VBD', vbd_ref) + vbd_labels.append(vbd_rec["vm_name_label"]) + + self.assertEquals(vbd_labels, [1]) + + def ensure_vdi_was_torn_down(): + for vdi_ref in xenapi_fake.get_all('VDI'): + vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) + name_label = vdi_rec["name_label"] + self.assert_(not name_label.endswith('snapshot')) + + def check(): + ensure_vm_was_torn_down() + ensure_vbd_was_torn_down() + ensure_vdi_was_torn_down() + + check() + def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) - conn.spawn(instance) + instance = self._create_instance() def check(): - instances = conn.list_instances() + instances = self.conn.list_instances() self.assertEquals(instances, [1]) # Get Nova record for VM - vm_info = conn.get_info(1) + vm_info = self.conn.get_info(1) # Get XenAPI record for VM - vms = fake.get_all('VM') - vm = fake.get_record('VM', vms[0]) + vms = xenapi_fake.get_all('VM') + vm = xenapi_fake.get_record('VM', vms[0]) # Check that m1.large above turned into the right thing. instance_type = instance_types.INSTANCE_TYPES['m1.large'] @@ -218,3 +243,18 @@ class XenAPIVMTestCase(test.TestCase): self.manager.delete_project(self.project) self.manager.delete_user(self.user) self.stubs.UnsetAll() + + def _create_instance(self): + """Creates and spawns a test instance""" + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } + instance = db.instance_create(values) + self.conn.spawn(instance) + return instance diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index a7e592fee..55f751f11 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -19,6 +19,54 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils +from nova.virt.xenapi import vm_utils + + +def stubout_instance_snapshot(stubs): + @classmethod + def fake_fetch_image(cls, session, instance_id, image, user, project, + type): + # Stubout wait_for_task + def fake_wait_for_task(self, id, task): + class FakeEvent: + + def send(self, value): + self.rv = value + + def wait(self): + return self.rv + + done = FakeEvent() + self._poll_task(id, task, done) + rv = done.wait() + return rv + + stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', + fake_wait_for_task) + + from nova.virt.xenapi.fake import create_vdi + name_label = "instance-%s" % instance_id + #TODO: create fake SR record + sr_ref = "fakesr" + vdi_ref = create_vdi(name_label=name_label, read_only=False, + sr_ref=sr_ref, sharable=False) + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec['uuid'] + return vdi_uuid + + stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) + + def fake_parse_xmlrpc_value(val): + return val + + stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) + + def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): + #TODO(sirp): Should we actually fake out the data here + return "fakeparent" + + stubs.Set(vm_utils, 'wait_for_vhd_coalesce', fake_wait_for_vhd_coalesce) def stubout_session(stubs, cls): @@ -63,6 +111,24 @@ class FakeSessionForVMTests(fake.SessionBase): vm['is_a_template'] = False vm['is_control_domain'] = False + def VM_snapshot(self, session_ref, vm_ref, label): + status = "Running" + template_vm_ref = fake.create_vm(label, status, is_a_template=True, + is_control_domain=False) + + sr_ref = "fakesr" + template_vdi_ref = fake.create_vdi(label, read_only=True, + sr_ref=sr_ref, sharable=False) + + template_vbd_ref = fake.create_vbd(template_vm_ref, template_vdi_ref) + return template_vm_ref + + def VDI_destroy(self, session_ref, vdi_ref): + fake.destroy_vdi(vdi_ref) + + def VM_destroy(self, session_ref, vm_ref): + fake.destroy_vm(vm_ref) + class FakeSessionForVolumeTests(fake.SessionBase): """ Stubs out a XenAPISession for Volume tests """ diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 1eaf31c25..aa4026f97 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -55,6 +55,8 @@ import datetime import logging import uuid +from pprint import pformat + from nova import exception @@ -64,6 +66,10 @@ _CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ _db_content = {} +def log_db_contents(msg=None): + logging.debug(_("%s: _db_content => %s"), msg or "", pformat(_db_content)) + + def reset(): for c in _CLASSES: _db_content[c] = {} @@ -93,6 +99,24 @@ def create_vm(name_label, status, }) +def destroy_vm(vm_ref): + vm_rec = _db_content['VM'][vm_ref] + + vbd_refs = vm_rec['VBDs'] + for vbd_ref in vbd_refs: + destroy_vbd(vbd_ref) + + del _db_content['VM'][vm_ref] + + +def destroy_vbd(vbd_ref): + del _db_content['VBD'][vbd_ref] + + +def destroy_vdi(vdi_ref): + del _db_content['VDI'][vdi_ref] + + def create_vdi(name_label, read_only, sr_ref, sharable): return _create_object('VDI', { 'name_label': name_label, @@ -109,6 +133,23 @@ def create_vdi(name_label, read_only, sr_ref, sharable): }) +def create_vbd(vm_ref, vdi_ref): + vbd_rec = {'VM': vm_ref, 'VDI': vdi_ref} + vbd_ref = _create_object('VBD', vbd_rec) + after_VBD_create(vbd_ref, vbd_rec) + return vbd_ref + + +def after_VBD_create(vbd_ref, vbd_rec): + """Create backref from VM to VBD when VBD is created""" + vm_ref = vbd_rec['VM'] + vm_rec = _db_content['VM'][vm_ref] + vm_rec['VBDs'] = [vbd_ref] + + vm_name_label = _db_content['VM'][vm_ref]['name_label'] + vbd_rec['vm_name_label'] = vm_name_label + + def create_pbd(config, sr_ref, attached): return _create_object('PBD', { 'device-config': config, @@ -277,11 +318,12 @@ class SessionBase(object): self._check_arg_count(params, 2) return get_record(cls, params[1]) - if (func == 'get_by_name_label' or - func == 'get_by_uuid'): + if func in ('get_by_name_label', 'get_by_uuid'): self._check_arg_count(params, 2) + return_singleton = (func == 'get_by_uuid') return self._get_by_field( - _db_content[cls], func[len('get_by_'):], params[1]) + _db_content[cls], func[len('get_by_'):], params[1], + return_singleton=return_singleton) if len(params) == 2: field = func[len('get_'):] @@ -324,6 +366,13 @@ class SessionBase(object): (cls, _) = name.split('.') ref = is_sr_create and \ _create_sr(cls, params) or _create_object(cls, params[1]) + + # Call hook to provide any fixups needed (ex. creating backrefs) + try: + globals()["after_%s_create" % cls](ref, params[1]) + except KeyError: + pass + obj = get_record(cls, ref) # Add RO fields @@ -359,11 +408,18 @@ class SessionBase(object): raise Failure(['MESSAGE_PARAMETER_COUNT_MISMATCH', expected, actual]) - def _get_by_field(self, recs, k, v): + def _get_by_field(self, recs, k, v, return_singleton): result = [] for ref, rec in recs.iteritems(): if rec.get(k) == v: result.append(ref) + + if return_singleton: + try: + return result[0] + except IndexError: + return None + return result diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f4195cb1b..68da807fc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -211,24 +211,25 @@ class VMHelper(HelperBase): """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD """ - logging.debug(_("Snapshotting VM %s with label '%s'..."), - vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added + logging.debug(_("Snapshotting VM %s with label '%s'..."), + vm_ref, label) + vm_vdi_ref, vm_vdi_rec = get_vdi_for_vm_safely(session, vm_ref) vm_vdi_uuid = vm_vdi_rec["uuid"] + sr_ref = vm_vdi_rec["SR"] + original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) template_vm_ref = session.wait_for_task(instance_id, task) - template_vdi_ref, template_vdi_rec = get_vdi_for_vm_safely( - session, template_vm_ref) - + template_vdi_rec = get_vdi_for_vm_safely(session, template_vm_ref)[1] template_vdi_uuid = template_vdi_rec["uuid"] + logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) - sr_ref = vm_vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( session, instance_id, sr_ref, vm_vdi_ref, original_parent_uuid) @@ -440,8 +441,6 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, def get_vdi_for_vm_safely(session, vm_ref): - """Returns (vdi_ref, vdi_uuid)""" - #TODO(sirp): Make safe_lookup_vdi for assert? vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) if vdi_refs is None: raise Exception(_("No VDIs found for VM %s") % vm_ref) -- cgit From 7811a77753943ee87f3c3b10f37d22e61c5119d0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 14:32:45 -0600 Subject: Make action log available through Admin API --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 5 +++++ nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 10 ++++++++++ nova/db/sqlalchemy/api.py | 9 ++++++--- nova/virt/fake.py | 3 +++ nova/virt/libvirt_conn.py | 5 ++++- nova/virt/xenapi/vmops.py | 8 ++++++++ nova/virt/xenapi_conn.py | 4 ++++ 9 files changed, 50 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index be342b670..412e16315 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -94,6 +94,7 @@ class APIRouter(wsgi.Router): server_members['pause'] = 'POST' server_members['unpause'] = 'POST' server_members["diagnostics"] = "GET" + server_members["actions"] = "GET" server_members['suspend'] = 'POST' server_members['resume'] = 'POST' diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c00aa26ce..0091a9bd2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -224,3 +224,8 @@ class Controller(wsgi.Controller): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] return self.compute_api.diagnostics(ctxt, id) + + def actions(self, req, id): + """Permit Admins to retrieve server actions.""" + ctxt = req.environ["nova.context"] + return self.compute_api.actions(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 68919442a..3d9728c77 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -293,6 +293,15 @@ class ComputeAPI(base.Base): {"method": "get_diagnostics", "args": {"instance_id": instance["id"]}}) + def actions(self, context, instance_id): + """Retrieve actions for the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance["host"] + return rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "get_actions", + "args": {"instance_id": instance["id"]}}) + def suspend(self, context, instance_id): """suspend the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7dea04132..924b72004 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -307,6 +307,16 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception + def get_actions(self, context, instance_id): + """Retrieve actions for an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_("instance %s: retrieving actions"), + instance_ref["internal_id"]) + return self.driver.get_actions(instance_ref) + def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d2127989c..5fd928a79 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,13 +856,16 @@ def instance_action_create(context, values): return action_ref -@require_context +@require_admin_context def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() - return session.query(models.InstanceActions).\ + actions = {} + for action in session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all() + all(): + actions[action.action] = action.error + return actions ################### diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 880772b22..e9d1e4bd7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -205,6 +205,9 @@ class FakeConnection(object): def get_diagnostics(self, instance_name): pass + def get_actions(self, instance_name): + pass + def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 06ace7457..9c8aa1fae 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -568,7 +568,10 @@ class LibvirtConnection(object): 'cpu_time': cpu_time} def get_diagnostics(self, instance_name): - raise exception.APIError("diagnostics not supported for libvirt") + raise exception.APIError("diagnostics are not supported for libvirt") + + def get_actions(self, instance_name): + raise exception.APIError("actions are not supported for libvirt") def get_disks(self, instance_name): """ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 332e603db..93f7f7695 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -226,6 +226,14 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) + def get_actions(self, instance): + """Return VM action history""" + vm = VMHelper.lookup(self._session, instance.name) + if vm is None: + raise exception.NotFound(_("Instance not found %s") % + instance.name) + return db.instance_get_actions(context.get_admin_context(), instance.id) + def get_console_output(self, instance): """Return snapshot of console""" # TODO: implement this to fix pylint! diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index a7ec22012..92274daf9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -163,6 +163,10 @@ class XenAPIConnection(object): """Return data about VM diagnostics""" return self._vmops.get_diagnostics(instance) + def get_actions(self, instance): + """Return VM action history""" + return self._vmops.get_actions(instance) + def get_console_output(self, instance): """Return snapshot of console""" return self._vmops.get_console_output(instance) -- cgit From 902df6eb4968743dd451e54cde27ce88fc83ddaa Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Dec 2010 15:48:48 -0600 Subject: Whoops --- nova/api/openstack/backup_schedules.py | 4 ++-- nova/api/openstack/sharedipgroups.py | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 9b8e605f6..fcc07bdd3 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -23,7 +23,7 @@ from nova.api.openstack import faults import nova.image.service -def _entity_inst(inst): +def _translate_keys(inst): """ Coerces the backup schedule into proper dictionary format """ return dict(backupSchedule=inst) @@ -41,7 +41,7 @@ class Controller(wsgi.Controller): def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ - return _entity_inst({}) + return _translate_keys({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 32ebfa45a..845f5bead 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -21,17 +21,12 @@ from nova import wsgi from nova.api.openstack import faults -def _entity_list(entities): - """ Coerces a list of shared IP groups into proper dictionary format """ - return dict(sharedIpGroups=entities) - - -def _entity_inst(inst): +def _translate_keys(inst): """ Coerces a shared IP group instance into proper dictionary format """ return dict(sharedIpGroup=inst) -def _entity_detail(inst): +def _translate_detail_keys(inst): """ Coerces a shared IP group instance into proper dictionary format with correctly mapped attributes """ return dict(sharedIpGroup=inst) @@ -47,11 +42,11 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of Shared IP Groups for the user """ - return _entity_list([]) + return dict(sharedIpGroups=[]) def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - return _entity_inst({}) + return _translate_keys({}) def update(self, req, id): """ You can't update a Shared IP Group """ @@ -63,7 +58,7 @@ class Controller(wsgi.Controller): def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ - return _entity_detail({}) + return _translate_detail_keys({}) def create(self, req): """ Creates a new Shared IP group """ -- cgit From 5a25de893f34cb9b05996406488188b6ed47fca1 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Dec 2010 17:14:01 -0500 Subject: Add flag --enable_new_services to toggle default state of service when created. --- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 2 ++ nova/tests/test_service.py | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..fcb1cc3f9 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -27,6 +27,9 @@ The underlying driver is loaded as a :class:`LazyPluggable`. :sql_connection: string specifying the sqlalchemy connection to use, like: `sqlite:///var/lib/nova/nova.sqlite`. + +:enable_new_services: when adding a new service to the database, is it in the + pool of available hardware (Default: False) """ from nova import exception @@ -37,6 +40,8 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_string('db_backend', 'sqlalchemy', 'The backend to use for db') +flags.DEFINE_boolean('enable_new_services', False, + 'Services to be added to the available pool on create') IMPL = utils.LazyPluggable(FLAGS['db_backend'], diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..bcc076c5a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -236,6 +236,8 @@ def service_get_by_args(context, host, binary): def service_create(context, values): service_ref = models.Service() service_ref.update(values) + if FLAGS.enable_new_services: + service_ref.disabled = False service_ref.save() return service_ref diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index b30838ad7..01f27e5b4 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -22,6 +22,8 @@ Unit Tests for remote procedure calls using queue import mox +from nova import context +from nova import db from nova import exception from nova import flags from nova import rpc @@ -72,6 +74,29 @@ class ServiceManagerTestCase(test.TestCase): self.assertEqual(serv.test_method(), 'service') +class ServiceFlagsTestCase(test.TestCase): + def test_service_enabled_on_create_based_on_flag(self): + self.flags(enable_new_services=True) + host = 'foo' + binary = 'nova-fake' + app = service.Service.create(host=host, binary=binary) + app.start() + app.stop() + ref = db.service_get(context.get_admin_context(), app.service_id) + db.service_destroy(context.get_admin_context(), app.service_id) + self.assert_(not ref['disabled']) + + def test_service_disabled_on_create_based_on_flag(self): + self.flags(enable_new_services=False) + host = 'foo' + binary = 'nova-fake' + app = service.Service.create(host=host, binary=binary) + app.start() + app.stop() + ref = db.service_get(context.get_admin_context(), app.service_id) + db.service_destroy(context.get_admin_context(), app.service_id) + self.assert_(ref['disabled']) + class ServiceTestCase(test.TestCase): """Test cases for Services""" -- cgit From 8ee15b6e93b5666b4645f2cefef357b0af3d26d0 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 28 Dec 2010 23:25:50 +0100 Subject: Address bug #695157 by using a blank request class and setting an empty request path. --- nova/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index c7ee9ed14..b5d6b96c1 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -270,7 +270,7 @@ class Serializer(object): needed to serialize a dictionary to that type. """ self.metadata = metadata or {} - req = webob.Request(environ) + req = webob.Request.blank('', environ) suffix = req.path_info.split('.')[-1].lower() if suffix == 'json': self.handler = self._to_json -- cgit From 9da1fcd6eca6f2f88e95242b8d046f4ee11f3761 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Dec 2010 17:54:31 -0500 Subject: Defualt services to enabled. --- nova/db/api.py | 4 ++-- nova/db/sqlalchemy/api.py | 4 ++-- nova/db/sqlalchemy/models.py | 2 +- nova/tests/test_scheduler.py | 34 +++++++--------------------------- 4 files changed, 12 insertions(+), 32 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index fcb1cc3f9..f32e1e3f5 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -29,7 +29,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`. `sqlite:///var/lib/nova/nova.sqlite`. :enable_new_services: when adding a new service to the database, is it in the - pool of available hardware (Default: False) + pool of available hardware (Default: True) """ from nova import exception @@ -40,7 +40,7 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_string('db_backend', 'sqlalchemy', 'The backend to use for db') -flags.DEFINE_boolean('enable_new_services', False, +flags.DEFINE_boolean('enable_new_services', True, 'Services to be added to the available pool on create') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index bcc076c5a..9f0597b54 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -236,8 +236,8 @@ def service_get_by_args(context, host, binary): def service_create(context, values): service_ref = models.Service() service_ref.update(values) - if FLAGS.enable_new_services: - service_ref.disabled = False + if not FLAGS.enable_new_services: + service_ref.disabled = True service_ref.save() return service_ref diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 86c24cb90..ca54d4466 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -148,7 +148,7 @@ class Service(BASE, NovaBase): binary = Column(String(255)) topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) - disabled = Column(Boolean, default=True) + disabled = Column(Boolean, default=False) class Certificate(BASE, NovaBase): diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 65430fb36..e8021ed5a 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -133,6 +133,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': True}) + db.service_update(self.context, s2['id'], {'disabled': True}) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(0, len(hosts)) compute1.kill() @@ -152,10 +156,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(2, len(hosts)) compute1.kill() @@ -173,10 +173,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance() @@ -200,10 +196,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance(availability_zone='nova:host1') @@ -225,8 +217,7 @@ class SimpleDriverTestCase(test.TestCase): now = datetime.datetime.utcnow() delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2) past = now - delta - db.service_update(self.context, s1['id'], {'disabled': False, - 'updated_at': past}) + db.service_update(self.context, s1['id'], {'updated_at': past}) instance_id2 = self._create_instance(availability_zone='nova:host1') self.assertRaises(driver.WillNotSchedule, self.scheduler.driver.schedule_run_instance, @@ -241,7 +232,8 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute1.start() - db.service_get_by_args(self.context, 'host1', 'nova-compute') + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': True}) instance_id2 = self._create_instance(availability_zone='nova:host1') host = self.scheduler.driver.schedule_run_instance(self.context, instance_id2) @@ -261,10 +253,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): @@ -298,10 +286,6 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) volume_id1 = self._create_volume() volume1.create_volume(self.context, volume_id1) volume_id2 = self._create_volume() @@ -325,10 +309,6 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) volume_ids1 = [] volume_ids2 = [] for index in xrange(FLAGS.max_gigabytes): -- cgit From 99a228a8ef3ee2760774fbafd136f137bd578dba Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Dec 2010 17:34:51 -0600 Subject: removed superfluous line --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2232d24b2..845183258 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -152,7 +152,6 @@ class Controller(wsgi.Controller): try: ctxt = req.environ['nova.context'] - inst_ref = self.compute_api.get_instance(ctxt, id) self.compute_api.update_instance(ctxt, id, **update_dict) -- cgit From 380b28f89481c52dbcda0b54fd7409b6bc72bb56 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Wed, 29 Dec 2010 01:58:04 +0000 Subject: Fix pep8 violations. --- nova/virt/libvirt_conn.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8b67b937f..e9c6c2713 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -98,6 +98,7 @@ def get_connection(read_only): _late_load_cheetah() return LibvirtConnection(read_only) + def _late_load_cheetah(): global Template if Template is None: @@ -105,6 +106,7 @@ def _late_load_cheetah(): -1) Template = t.Template + def _get_net_and_mask(cidr): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) -- cgit From 34f5bed4d9c99af58c83b82b499f898c270124a8 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Wed, 29 Dec 2010 11:24:42 -0800 Subject: Re-added flag definition for injected_network_template. Tested & verified fix in the same env as the original bug. --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f6a218fa4..39215c4e1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -68,6 +68,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') +flags.DEFINE_string('injected_network_template', + utils.abspath('virt/interfaces.template'), + 'Template file for injected network') flags.DEFINE_string('libvirt_xml_template', utils.abspath('virt/libvirt.xml.template'), 'Libvirt XML Template') -- cgit From 823c5fc1ff3c37acbfe9b30d7057f53b050b93c6 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 29 Dec 2010 14:02:57 -0600 Subject: Added tests --- nova/tests/api/openstack/test_servers.py | 16 ++++++++++++++++ nova/tests/test_compute.py | 16 ++++++++++++++-- nova/tests/test_xenapi.py | 30 ++++++++++++++++++++---------- nova/virt/xenapi/vm_utils.py | 4 ++++ 4 files changed, 54 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 5d23db588..321ddceee 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -95,6 +95,10 @@ class ServersTest(unittest.TestCase): fake_compute_api) self.stubs.Set(nova.compute.api.ComputeAPI, 'resume', fake_compute_api) + self.stubs.Set(nova.compute.api.ComputeAPI, "diagnostics", + fake_compute_api) + self.stubs.Set(nova.compute.api.ComputeAPI, "actions", + fake_compute_api) self.allow_admin = FLAGS.allow_admin_api def tearDown(self): @@ -274,6 +278,18 @@ class ServersTest(unittest.TestCase): res = req.get_response(nova.api.API('os')) self.assertEqual(res.status_int, 202) + def test_server_diagnostics(self): + req = webob.Request.blank("/v1.0/servers/1/diagnostics") + req.method = "GET" + res = req.get_response(nova.api.API("os")) + self.assertEqual(res.status_int, 404) + + def test_server_actions(self): + req = webob.Request.blank("/v1.0/servers/1/actions") + req.method = "GET" + res = req.get_response(nova.api.API("os")) + self.assertEqual(res.status_int, 404) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bcb8a1526..757b1f462 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,13 +102,15 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - self.assertEqual(len(instances), 1) + + instance_count = len(instances) + self.assertNotEqual(instance_count, 0) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(instance_count, len(instances) + 1) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -151,6 +153,16 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_diagnostics(self): + """Ensure instance diagnostics are available""" + instance_id = self._create_instance() + self.compute.get_diagnostics(self.context, instance_id) + + def test_actions(self): + """Ensure instance actions are available""" + instance_id = self._create_instance() + self.compute.get_actions(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..11baead8b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -159,6 +159,16 @@ class XenAPIVMTestCase(test.TestCase): fake.reset() fakes.stub_out_db_instance_api(self.stubs) fake.create_network('fake', FLAGS.flat_network_bridge) + self.values = { + "name": 1, + "id": 1, + "project_id": self.project.id, + "user_id": self.user.id, + "image_id": 1, + "kernel_id": 2, + "ramdisk_id": 3, + "instance_type": "m1.large", + "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) @@ -166,19 +176,19 @@ class XenAPIVMTestCase(test.TestCase): instances = conn.list_instances() self.assertEquals(instances, []) + def test_get_diagnostics(self): + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + conn = xenapi_conn.get_connection(False) + + instance = db.instance_create(self.values) + conn.spawn(instance) + + conn.get_diagnostics(instance) + def test_spawn(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) + instance = db.instance_create(self.values) conn.spawn(instance) def check(): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 47fb6db53..cca7c1476 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -299,6 +299,10 @@ class VMHelper(HelperBase): try: host = session.get_xenapi_host() host_ip = session.get_xenapi().host.get_record(host)["address"] + except (cls.XenAPI.Failure, KeyError) as e: + return {"Unable to retrieve diagnostics": e} + + try: diags = {} xml = get_rrd(host_ip, record["uuid"]) if xml: -- cgit From 5b8137b2f50a4ed3eb105e38cefa280927f1c2ea Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 29 Dec 2010 14:15:04 -0600 Subject: PEP8 cleanup --- nova/db/sqlalchemy/api.py | 2 +- nova/virt/xenapi/vmops.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5fd928a79..8e68d12a4 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -865,7 +865,7 @@ def instance_get_actions(context, instance_id): filter_by(instance_id=instance_id).\ all(): actions[action.action] = action.error - return actions + return actions ################### diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 93f7f7695..1dfb9005c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -232,7 +232,9 @@ class VMOps(object): if vm is None: raise exception.NotFound(_("Instance not found %s") % instance.name) - return db.instance_get_actions(context.get_admin_context(), instance.id) + return db.instance_get_actions( + context.get_admin_context(), + instance.id) def get_console_output(self, instance): """Return snapshot of console""" -- cgit From e7be0b485e2d6c7cd95d1f5b7e6a401032f437e6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:01:34 -0500 Subject: Fix scheduler testcase so it knows all flags and can run in isolation. --- nova/tests/test_scheduler.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 91517cc5d..78e4a1c77 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -33,6 +33,7 @@ from nova.scheduler import driver FLAGS = flags.FLAGS flags.DECLARE('max_cores', 'nova.scheduler.simple') +flags.DECLARE('stub_network', 'nova.compute.manager') class TestDriver(driver.Scheduler): -- cgit From c1acb68ef54309584816fbf5c93e38266accb2f0 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Wed, 29 Dec 2010 15:04:21 -0800 Subject: Add the pool_recycle setting to enable connection pooling features for the sql engine. The setting is hard-coded to 3600 seconds (one hour) per the recommendation provided on sqlalchemy's site --- nova/db/sqlalchemy/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index e0d84c107..5f31743cd 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -36,7 +36,7 @@ def get_session(autocommit=True, expire_on_commit=False): global _MAKER if not _MAKER: if not _ENGINE: - _ENGINE = create_engine(FLAGS.sql_connection, echo=False) + _ENGINE = create_engine(FLAGS.sql_connection, pool_recycle=3600, echo=False) _MAKER = (sessionmaker(bind=_ENGINE, autocommit=autocommit, expire_on_commit=expire_on_commit)) -- cgit From ba31a61ae6348bffbd70d5875f12a540d49e8885 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 30 Dec 2010 02:20:31 -0500 Subject: pep 8 --- nova/api/ec2/__init__.py | 7 ++++++- nova/api/ec2/metadatarequesthandler.py | 1 + nova/api/openstack/__init__.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 7bec7f81e..aa3bfaeb4 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -313,7 +313,6 @@ class Executor(wsgi.Application): resp.body = str(result) return resp - def _error(self, req, code, message): logging.error("%s: %s", code, message) resp = webob.Response() @@ -325,6 +324,7 @@ class Executor(wsgi.Application): '?' % (code, message)) return resp + class Versions(wsgi.Application): @webob.dec.wsgify @@ -344,23 +344,28 @@ class Versions(wsgi.Application): ] return ''.join('%s\n' % v for v in versions) + def authenticate_factory(global_args, **local_args): def authenticator(app): return Authenticate(app) return authenticator + def router_factory(global_args, **local_args): def router(app): return Router(app) return router + def authorizer_factory(global_args, **local_args): def authorizer(app): return Authorizer(app) return authorizer + def executor_factory(global_args, **local_args): return Executor() + def versions_factory(global_args, **local_args): return Versions() diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 9d1594ae7..a57a6698a 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -80,5 +80,6 @@ class MetadataRequestHandler(object): raise webob.exc.HTTPNotFound() return self.print_data(data) + def metadata_factory(global_args, **local_args): return MetadataRequestHandler() diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 5dd092a1f..dc7794876 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -129,5 +129,6 @@ class Versions(wsgi.Application): def router_factory(global_cof, **local_conf): return APIRouter() + def versions_factory(global_conf, **local_conf): return Versions() -- cgit From 601b19291a7cf1bcda7bcd4ebf27e4eefe3e28fd Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 30 Dec 2010 14:09:16 -0600 Subject: Calling compute api directly from OpenStack image create --- nova/api/openstack/images.py | 10 +++++----- nova/image/glance.py | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2ee73480f..867ee5a7e 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,7 @@ import nova.image.service from nova.api.openstack import common from nova.api.openstack import faults - +from nova.compute import api as compute_api FLAGS = flags.FLAGS @@ -127,11 +127,11 @@ class Controller(wsgi.Controller): raise faults.Fault(exc.HTTPNotFound()) def create(self, req): - ctxt = req.environ['nova.context'] + context = req.environ['nova.context'] env = self._deserialize(req.body, req) - data = {'instance_id': env["image"]["serverId"], - 'name': env["image"]["name"]} - return dict(image=self._service.create(ctxt, data)) + instance_id = env["image"]["serverId"] + name = env["image"]["name"] + return compute_api.ComputeAPI().snapshot(context, instance_id, name) def update(self, req, id): # Users may not modify public images, and that's all that diff --git a/nova/image/glance.py b/nova/image/glance.py index b61cba10e..cc3192e7c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -203,10 +203,6 @@ class GlanceImageService(nova.image.service.BaseImageService): :raises AlreadyExists if the image already exist. """ - instance_id = data["instance_id"] - name = data["name"] - compute_api.ComputeAPI().snapshot(context, instance_id, name) - # FIXME(sirp): This needs to be reworked for new-style glance return self.parallax.add_image_metadata(data) def update(self, context, image_id, data): -- cgit From 42f6a993bcc4d0bc8823e4d039b1f59a6d6758a8 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 15:13:32 -0600 Subject: Implemented review feedback --- nova/compute/manager.py | 6 +++--- nova/virt/fake.py | 3 --- nova/virt/libvirt_conn.py | 3 --- nova/virt/xenapi/vmops.py | 10 ---------- nova/virt/xenapi_conn.py | 4 ---- 5 files changed, 3 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 924b72004..d2ade8f52 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -299,7 +299,6 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this server.""" - context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) if instance_ref["state"] == power_state.RUNNING: @@ -310,12 +309,13 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_actions(self, context, instance_id): """Retrieve actions for an instance on this server.""" - context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) logging.debug(_("instance %s: retrieving actions"), instance_ref["internal_id"]) - return self.driver.get_actions(instance_ref) + return self.db.instance_get_actions( + context, + instance_id) def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" diff --git a/nova/virt/fake.py b/nova/virt/fake.py index e9d1e4bd7..880772b22 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -205,9 +205,6 @@ class FakeConnection(object): def get_diagnostics(self, instance_name): pass - def get_actions(self, instance_name): - pass - def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8407938c6..cf45ac13b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -583,9 +583,6 @@ class LibvirtConnection(object): def get_diagnostics(self, instance_name): raise exception.APIError("diagnostics are not supported for libvirt") - def get_actions(self, instance_name): - raise exception.APIError("actions are not supported for libvirt") - def get_disks(self, instance_name): """ Note that this function takes an instance name, not an Instance, so diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1dfb9005c..332e603db 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -226,16 +226,6 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) - def get_actions(self, instance): - """Return VM action history""" - vm = VMHelper.lookup(self._session, instance.name) - if vm is None: - raise exception.NotFound(_("Instance not found %s") % - instance.name) - return db.instance_get_actions( - context.get_admin_context(), - instance.id) - def get_console_output(self, instance): """Return snapshot of console""" # TODO: implement this to fix pylint! diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 92274daf9..a7ec22012 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -163,10 +163,6 @@ class XenAPIConnection(object): """Return data about VM diagnostics""" return self._vmops.get_diagnostics(instance) - def get_actions(self, instance): - """Return VM action history""" - return self._vmops.get_actions(instance) - def get_console_output(self, instance): """Return snapshot of console""" return self._vmops.get_console_output(instance) -- cgit From 00abbb4401c87cca9f1540d7be1c0119fc7aee44 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 16:06:48 -0600 Subject: Review feedback --- nova/compute/api.py | 6 +----- nova/compute/manager.py | 11 ----------- nova/tests/test_compute.py | 11 ++--------- nova/tests/test_xenapi.py | 31 ++++++------------------------- 4 files changed, 9 insertions(+), 50 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 4fcefb384..3f700349a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -305,11 +305,7 @@ class ComputeAPI(base.Base): def actions(self, context, instance_id): """Retrieve actions for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) - host = instance["host"] - return rpc.call(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "get_actions", - "args": {"instance_id": instance["id"]}}) + return self.db.instance_get_actions(context, instance["id"]) def suspend(self, context, instance_id): """suspend the instance with instance_id""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c4768d266..c9aff75ac 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -327,17 +327,6 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) - @exception.wrap_exception - def get_actions(self, context, instance_id): - """Retrieve actions for an instance on this server.""" - instance_ref = self.db.instance_get(context, instance_id) - - logging.debug(_("instance %s: retrieving actions"), - instance_ref["internal_id"]) - return self.db.instance_get_actions( - context, - instance_id) - def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e3679b6b6..2664adc0d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,15 +102,13 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - - instance_count = len(instances) - self.assertNotEqual(instance_count, 0) + self.assertEqual(len(instances), 1) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(instance_count, len(instances) + 1) + self.assertEqual(len(instances), 0) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -158,11 +156,6 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.get_diagnostics(self.context, instance_id) - def test_actions(self): - """Ensure instance actions are available""" - instance_id = self._create_instance() - self.compute.get_actions(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 3528d7bfe..a09750672 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -156,10 +156,11 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) self.values = { "name": 1, "id": 1, @@ -171,12 +172,6 @@ class XenAPIVMTestCase(test.TestCase): "instance_type": "m1.large", "mac_address": "aa:bb:cc:dd:ee:ff"} - xenapi_fake.reset() - db_fakes.stub_out_db_instance_api(self.stubs) - xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - self.conn = xenapi_conn.get_connection(False) - def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) @@ -228,11 +223,6 @@ class XenAPIVMTestCase(test.TestCase): check() def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - instance = self._create_instance() def check(): @@ -275,15 +265,6 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - instance = db.instance_create(values) + instance = db.instance_create(self.values) self.conn.spawn(instance) return instance -- cgit From 5c34edb3fc215a519f1a00e8c3ff6223ee050041 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 30 Dec 2010 16:13:42 -0600 Subject: Fail --- nova/db/sqlalchemy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..baa743765 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -452,7 +452,7 @@ class AuthToken(BASE, NovaBase): """ __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) - user_id = Column(Integer) + user_id = Column(String(255)) server_manageent_url = Column(String(255)) storage_url = Column(String(255)) cdn_management_url = Column(String(255)) -- cgit From d89b3a4b5c1f6bfe1f59da6c33cb469da589e866 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 16:27:31 -0600 Subject: Make compute.api methods verbs --- nova/api/openstack/servers.py | 4 ++-- nova/compute/api.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e1c2d36ea..c5cbe21ef 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -222,9 +222,9 @@ class Controller(wsgi.Controller): def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] - return self.compute_api.diagnostics(ctxt, id) + return self.compute_api.get_diagnostics(ctxt, id) def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.actions(ctxt, id) + return self.compute_api.get_actions(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 3f700349a..28189e4a9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -293,7 +293,7 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) - def diagnostics(self, context, instance_id): + def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) host = instance["host"] @@ -302,7 +302,7 @@ class ComputeAPI(base.Base): {"method": "get_diagnostics", "args": {"instance_id": instance["id"]}}) - def actions(self, context, instance_id): + def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) return self.db.instance_get_actions(context, instance["id"]) -- cgit From d1129fa4b14d9edba8d6c4c3cb2d13e7c66c1391 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 17:02:16 -0600 Subject: Oopsies --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 321ddceee..70ff714e6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -95,9 +95,9 @@ class ServersTest(unittest.TestCase): fake_compute_api) self.stubs.Set(nova.compute.api.ComputeAPI, 'resume', fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, "diagnostics", + self.stubs.Set(nova.compute.api.ComputeAPI, "get_diagnostics", fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, "actions", + self.stubs.Set(nova.compute.api.ComputeAPI, "get_actions", fake_compute_api) self.allow_admin = FLAGS.allow_admin_api -- cgit From e453e2761daee6e96da9575a860e694a065c68c0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:12:10 -0600 Subject: Removed problematic test --- nova/tests/test_compute.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 2664adc0d..1fb9143f1 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -151,11 +151,6 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) - def test_diagnostics(self): - """Ensure instance diagnostics are available""" - instance_id = self._create_instance() - self.compute.get_diagnostics(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() -- cgit From 7f27f62e41fd655049574975bd3bf6c5b00e9ccf Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Thu, 30 Dec 2010 16:21:11 -0800 Subject: Converted the pool_recycle setting to be a flag with a default of 3600 seconds --- nova/db/sqlalchemy/session.py | 4 +++- nova/flags.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index 5f31743cd..ca23e93d8 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -36,7 +36,9 @@ def get_session(autocommit=True, expire_on_commit=False): global _MAKER if not _MAKER: if not _ENGINE: - _ENGINE = create_engine(FLAGS.sql_connection, pool_recycle=3600, echo=False) + _ENGINE = create_engine(FLAGS.sql_connection, + pool_recycle=FLAGS.sql_idle_timeout, + echo=False) _MAKER = (sessionmaker(bind=_ENGINE, autocommit=autocommit, expire_on_commit=expire_on_commit)) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..338dcbf46 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -260,6 +260,9 @@ DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'), DEFINE_string('sql_connection', 'sqlite:///$state_path/nova.sqlite', 'connection string for sql database') +DEFINE_string('sql_idle_timeout', + '3600', + 'timeout for idle sql database connections') DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') -- cgit From f55991c01421350434893200db8f01ca3911d957 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:56:12 -0600 Subject: Improved test --- nova/tests/test_xenapi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a09750672..539d132bd 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -180,9 +180,7 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - + instance = self._create_instance() conn.get_diagnostics(instance) def test_instance_snapshot(self): -- cgit From ffaf32b9ac9a3e71ac25f68eb593bbf16a11946a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 19:07:20 -0600 Subject: Cleanup --- nova/tests/test_xenapi.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 539d132bd..c95a53af3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -161,27 +161,14 @@ class XenAPIVMTestCase(test.TestCase): xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.get_connection(False) - self.values = { - "name": 1, - "id": 1, - "project_id": self.project.id, - "user_id": self.user.id, - "image_id": 1, - "kernel_id": 2, - "ramdisk_id": 3, - "instance_type": "m1.large", - "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) def test_get_diagnostics(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = self._create_instance() - conn.get_diagnostics(instance) + self.conn.get_diagnostics(instance) def test_instance_snapshot(self): stubs.stubout_instance_snapshot(self.stubs) @@ -263,6 +250,15 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - instance = db.instance_create(self.values) + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff' + } + instance = db.instance_create(values) self.conn.spawn(instance) return instance -- cgit From 5fd9ff898bf372f26bac3c0530521ba7abb7f26c Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Mon, 3 Jan 2011 10:55:52 -0800 Subject: removed extra whitespace chars at the end of the changed lines --- nova/db/sqlalchemy/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index ca23e93d8..c3876c02a 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -36,8 +36,8 @@ def get_session(autocommit=True, expire_on_commit=False): global _MAKER if not _MAKER: if not _ENGINE: - _ENGINE = create_engine(FLAGS.sql_connection, - pool_recycle=FLAGS.sql_idle_timeout, + _ENGINE = create_engine(FLAGS.sql_connection, + pool_recycle=FLAGS.sql_idle_timeout, echo=False) _MAKER = (sessionmaker(bind=_ENGINE, autocommit=autocommit, -- cgit From 6a8f011789ddad57726ce55962b51a04a69fe527 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 3 Jan 2011 16:08:52 -0600 Subject: Fixes LP688545 --- nova/compute/manager.py | 1 + nova/db/sqlalchemy/__init__.py | 21 ++++++++++++++++++++- nova/flags.py | 2 ++ nova/tests/test_xenapi.py | 19 ++++++++++--------- 4 files changed, 33 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c9aff75ac..6e8f34347 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -327,6 +327,7 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py index 3288ebd20..22aa1cfe6 100644 --- a/nova/db/sqlalchemy/__init__.py +++ b/nova/db/sqlalchemy/__init__.py @@ -19,6 +19,25 @@ """ SQLAlchemy database backend """ +import logging +import time + +from sqlalchemy.exc import OperationalError + +from nova import flags from nova.db.sqlalchemy import models -models.register_models() + +FLAGS = flags.FLAGS + + +for i in xrange(FLAGS.sql_max_retries): + if i > 0: + time.sleep(FLAGS.sql_retry_interval) + + try: + models.register_models() + break + except OperationalError: + logging.exception(_("Data store is unreachable." + " Trying again in %d seconds.") % FLAGS.sql_retry_interval) diff --git a/nova/flags.py b/nova/flags.py index e872ba217..4b7334927 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -266,6 +266,8 @@ DEFINE_string('sql_connection', DEFINE_string('sql_idle_timeout', '3600', 'timeout for idle sql database connections') +DEFINE_integer('sql_max_retries', 12, 'sql connection attempts') +DEFINE_integer('sql_retry_interval', 10, 'sql connection retry interval') DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index c95a53af3..33571dad0 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -250,15 +250,16 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff' - } + values = { + 'name': 1, + 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff'} instance = db.instance_create(values) self.conn.spawn(instance) return instance -- cgit