diff options
-rwxr-xr-x | bin/nova-manage | 55 | ||||
-rw-r--r-- | nova/volume/driver.py | 33 | ||||
-rw-r--r-- | nova/volume/manager.py | 37 |
3 files changed, 112 insertions, 13 deletions
diff --git a/bin/nova-manage b/bin/nova-manage index d0901ddfc..a347e86ce 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -79,7 +79,9 @@ from nova import exception from nova import flags from nova import log as logging from nova import quota +from nova import rpc from nova import utils +from nova.api.ec2.cloud import ec2_id_to_id from nova.auth import manager from nova.cloudpipe import pipelib from nova.db import migration @@ -95,6 +97,16 @@ flags.DECLARE('vpn_start', 'nova.network.manager') flags.DECLARE('fixed_range_v6', 'nova.network.manager') +def param2id(object_id): + """Helper function to convert various id types to internal id. + args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10' + """ + if '-' in object_id: + return ec2_id_to_id(object_id) + else: + return int(object_id) + + class VpnCommands(object): """Class for managing VPNs.""" @@ -535,6 +547,46 @@ class DbCommands(object): print migration.db_version() +class VolumeCommands(object): + """Methods for dealing with a cloud in an odd state""" + + def delete(self, volume_id): + """Delete a volume, bypassing the check that it + must be available. + args: volume_id_id""" + ctxt = context.get_admin_context() + volume = db.volume_get(ctxt, param2id(volume_id)) + host = volume['host'] + if volume['status'] == 'in-use': + print "Volume is in-use." + print "Detach volume from instance and then try again." + return + + rpc.cast(ctxt, + db.queue_get_for(ctxt, FLAGS.volume_topic, host), + {"method": "delete_volume", + "args": {"volume_id": volume['id']}}) + + def reattach(self, volume_id): + """Re-attach a volume that has previously been attached + to an instance. Typically called after a compute host + has been rebooted. + args: volume_id_id""" + ctxt = context.get_admin_context() + volume = db.volume_get(ctxt, param2id(volume_id)) + if not volume['instance_id']: + print "volume is not attached to an instance" + return + instance = db.instance_get(ctxt, volume['instance_id']) + host = instance['host'] + rpc.cast(ctxt, + db.queue_get_for(ctxt, FLAGS.compute_topic, host), + {"method": "attach_volume", + "args": {"instance_id": instance['id'], + "volume_id": volume['id'], + "mountpoint": volume['mountpoint']}}) + + CATEGORIES = [ ('user', UserCommands), ('project', ProjectCommands), @@ -545,7 +597,8 @@ CATEGORIES = [ ('network', NetworkCommands), ('service', ServiceCommands), ('log', LogCommands), - ('db', DbCommands)] + ('db', DbCommands), + ('volume', VolumeCommands)] def lazy_match(name, key_value_tuples): diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 5fefa10cf..da7307733 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -100,6 +100,14 @@ class VolumeDriver(object): def delete_volume(self, volume): """Deletes a logical volume.""" + try: + self._try_execute("sudo lvdisplay %s/%s" % + (FLAGS.volume_group, + volume['name'])) + except Exception as e: + # If the volume isn't present, then don't attempt to delete + return True + self._try_execute("sudo lvremove -f %s/%s" % (FLAGS.volume_group, volume['name'])) @@ -218,8 +226,14 @@ class ISCSIDriver(VolumeDriver): def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" - iscsi_target = self.db.volume_get_iscsi_target_num(context, + try: + iscsi_target = self.db.volume_get_iscsi_target_num(context, volume['id']) + except exception.NotFound: + LOG.info(_("Skipping ensure_export. No iscsi_target " + + "provisioned for volume: %d"), volume['id']) + return + iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name']) volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name']) self._sync_exec("sudo ietadm --op new " @@ -258,8 +272,23 @@ class ISCSIDriver(VolumeDriver): def remove_export(self, context, volume): """Removes an export for a logical volume.""" - iscsi_target = self.db.volume_get_iscsi_target_num(context, + try: + iscsi_target = self.db.volume_get_iscsi_target_num(context, volume['id']) + except exception.NotFound: + LOG.info(_("Skipping remove_export. No iscsi_target " + + "provisioned for volume: %d"), volume['id']) + return + + try: + # ietadm show will exit with an error + # this export has already been removed + self._execute("sudo ietadm --op show --tid=%s " % iscsi_target) + except Exception as e: + LOG.info(_("Skipping remove_export. No iscsi_target " + + "is presently exported for volume: %d"), volume['id']) + return + self._execute("sudo ietadm --op delete --tid=%s " "--lun=0" % iscsi_target) self._execute("sudo ietadm --op delete --tid=%s" % diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 6348539c5..82e3521a8 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -84,7 +84,10 @@ class VolumeManager(manager.Manager): volumes = self.db.volume_get_all_by_host(ctxt, self.host) LOG.debug(_("Re-exporting %s volumes"), len(volumes)) for volume in volumes: - self.driver.ensure_export(ctxt, volume) + if volume['status'] in ['available', 'in-use']: + self.driver.ensure_export(ctxt, volume) + else: + LOG.info(_("volume %s: skipping export"), volume_ref['name']) def create_volume(self, context, volume_id): """Creates and exports the volume.""" @@ -99,12 +102,18 @@ class VolumeManager(manager.Manager): # before passing it to the driver. volume_ref['host'] = self.host - LOG.debug(_("volume %s: creating lv of size %sG"), volume_ref['name'], - volume_ref['size']) - self.driver.create_volume(volume_ref) + try: + LOG.debug(_("volume %s: creating lv of size %sG"), + volume_ref['name'], + volume_ref['size']) + self.driver.create_volume(volume_ref) - LOG.debug(_("volume %s: creating export"), volume_ref['name']) - self.driver.create_export(context, volume_ref) + LOG.debug(_("volume %s: creating export"), volume_ref['name']) + self.driver.create_export(context, volume_ref) + except Exception as e: + self.db.volume_update(context, + volume_ref['id'], {'status': 'error'}) + raise e now = datetime.datetime.utcnow() self.db.volume_update(context, @@ -121,10 +130,18 @@ class VolumeManager(manager.Manager): raise exception.Error(_("Volume is still attached")) if volume_ref['host'] != self.host: raise exception.Error(_("Volume is not local to this node")) - LOG.debug(_("volume %s: removing export"), volume_ref['name']) - self.driver.remove_export(context, volume_ref) - LOG.debug(_("volume %s: deleting"), volume_ref['name']) - self.driver.delete_volume(volume_ref) + + try: + LOG.debug(_("volume %s: removing export"), volume_ref['name']) + self.driver.remove_export(context, volume_ref) + LOG.debug(_("volume %s: deleting"), volume_ref['name']) + self.driver.delete_volume(volume_ref) + except Exception as e: + self.db.volume_update(context, + volume_ref['id'], + {'status': 'error_deleting'}) + raise e + self.db.volume_destroy(context, volume_id) LOG.debug(_("volume %s: deleted successfully"), volume_ref['name']) return True |