Skip to content

Instantly share code, notes, and snippets.

@mgagne
Created April 1, 2017 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgagne/9402089c11f8c80f6d6cd49f3db76512 to your computer and use it in GitHub Desktop.
Save mgagne/9402089c11f8c80f6d6cd49f3db76512 to your computer and use it in GitHub Desktop.
OpenStack - Extend attached volume
From 0832a277eb0a1eadaed2ec2e36e90d0d78ce30d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mathieu=20Gagn=C3=A9?= <mgagne@iweb.com>
Date: Fri, 19 Jul 2013 20:12:48 +0000
Subject: [PATCH] Add ability to extend volume when volume is 'in-use'
Change-Id: If3de78f34c3d34ea9b9d04e3af7d1f42e0d5626b
---
cinder/tests/unit/test_volume.py | 2 +-
cinder/volume/api.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cinder/tests/unit/test_volume.py b/cinder/tests/unit/test_volume.py
index 589310d..933368c 100644
--- a/cinder/tests/unit/test_volume.py
+++ b/cinder/tests/unit/test_volume.py
@@ -3937,7 +3937,7 @@ class VolumeTestCase(BaseVolumeTestCase):
volume = tests_utils.create_volume(self.context, size=2,
status='creating', host=CONF.host)
self.volume.create_volume(self.context, volume['id'])
- volume['status'] = 'in-use'
+ volume['status'] = 'error'
volume_api = cinder.volume.api.API()
diff --git a/cinder/volume/api.py b/cinder/volume/api.py
index 3b0615c..306ea19 100644
--- a/cinder/volume/api.py
+++ b/cinder/volume/api.py
@@ -1203,7 +1203,7 @@ class API(base.Base):
@wrap_check_policy
def extend(self, context, volume, new_size):
- if volume.status != 'available':
+ if volume.status not in ['available', 'in-use']:
msg = _('Volume %(vol_id)s status must be available '
'to extend, but current status is: '
'%(vol_status)s.') % {'vol_id': volume.id,
From 5bba8f4d2cefa75d28e185d046048f35446cf801 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mathieu=20Gagn=C3=A9?= <mgagne@iweb.com>
Date: Mon, 22 Jul 2013 20:33:22 +0000
Subject: [PATCH] Restore volume status after successful extend
Restore volume status to 'in-use' if the volume was attached to
an instance before extending volume.
Change-Id: Ib22ac2ffb19bab270b6c4b34395667239eee737c
---
cinder/volume/manager.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py
index dcc10ab..dfb3121 100644
--- a/cinder/volume/manager.py
+++ b/cinder/volume/manager.py
@@ -2139,8 +2139,16 @@ class VolumeManager(manager.SchedulerDependentManager):
return
QUOTAS.commit(context, reservations, project_id=project_id)
- volume.update({'size': int(new_size), 'status': 'available'})
+
+ attachments = volume.volume_attachment
+ if not attachments:
+ orig_volume_status = 'available'
+ else:
+ orig_volume_status = 'in-use'
+
+ volume.update({'size': int(new_size), 'status': orig_volume_status})
volume.save()
+
pool = vol_utils.extract_host(volume.host, 'pool')
if pool is None:
# Legacy volume, put them into default pool
From cb49926d24281d9c1c40e81a7a803b842094257f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mathieu=20Gagne=CC=81?= <mgagne@iweb.com>
Date: Fri, 11 Oct 2013 12:42:11 -0400
Subject: [PATCH] Notify Nova when attached volume is extended
Use external-server-events to signal Nova about volume extension.
Change-Id: I197bf300836539d182901cfb1cdab75202d1ee9b
---
cinder/compute/nova.py | 44 +++++++++++++++++++++++++++++++++++++++++++-
cinder/volume/manager.py | 6 ++++++
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/cinder/compute/nova.py b/cinder/compute/nova.py
index 1c8aebd..e7738c7 100644
--- a/cinder/compute/nova.py
+++ b/cinder/compute/nova.py
@@ -27,6 +27,7 @@ from requests import exceptions as request_exceptions
from cinder import context as ctx
from cinder.db import base
from cinder import exception
+from cinder.i18n import _LE, _LI, _LW
nova_opts = [
cfg.StrOpt('nova_catalog_info',
@@ -63,7 +64,8 @@ NOVA_API_VERSION = 2
nova_extensions = [ext for ext in nova_client.discover_extensions(2)
if ext.name in ("assisted_volume_snapshots",
- "list_extensions")]
+ "list_extensions",
+ "server_external_events")]
def novaclient(context, admin_endpoint=False, privileged_user=False,
@@ -157,6 +159,42 @@ def novaclient(context, admin_endpoint=False, privileged_user=False,
class API(base.Base):
"""API for interacting with novaclient."""
+ def _get_volume_extended_event(self, server_id, volume_id):
+ return {'name': 'volume-extended',
+ 'server_uuid': server_id,
+ 'tag': volume_id}
+
+ def _send_events(self, context, events):
+ nova = novaclient(context, privileged_user=True)
+ try:
+ response = nova.server_external_events.create(events)
+ except nova_exceptions.NotFound:
+ LOG.debug('Nova returned NotFound for events: %s',
+ events)
+ except Exception:
+ LOG.exception(_LE('Failed to notify nova on events: %s'),
+ events)
+ else:
+ if not isinstance(response, list):
+ LOG.error(_LE('Error response returned from nova: %s'),
+ response)
+ return
+ response_error = False
+ for event in response:
+ try:
+ code = event['code']
+ except KeyError:
+ response_error = True
+ continue
+ if code != 200:
+ LOG.warning(_LW('Nova event: %s returned with failed '
+ 'status'), event)
+ else:
+ LOG.info(_LI('Nova event response: %s'), event)
+ if response_error:
+ LOG.error(_LE('Error response returned from nova: %s'),
+ response)
+
def has_extension(self, context, extension, timeout=None):
try:
nova_exts = novaclient(context).list_extensions.show_all()
@@ -195,3 +233,7 @@ class API(base.Base):
raise exception.ServerNotFound(uuid=server_id)
except request_exceptions.Timeout:
raise exception.APITimeout(service='Nova')
+
+ def extend_volume(self, context, server_id, volume_id):
+ event = self._get_volume_extended_event(server_id, volume_id)
+ self._send_events(context, [event])
diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py
index dfb3121..88ad262 100644
--- a/cinder/volume/manager.py
+++ b/cinder/volume/manager.py
@@ -2149,6 +2149,12 @@ class VolumeManager(manager.SchedulerDependentManager):
volume.update({'size': int(new_size), 'status': orig_volume_status})
volume.save()
+ if orig_volume_status == 'in-use':
+ nova_api = compute.API()
+ for attachment in volume.volume_attachment:
+ instance_uuid = attachment.instance_uuid
+ nova_api.extend_volume(context, instance_uuid, volume_id)
+
pool = vol_utils.extract_host(volume.host, 'pool')
if pool is None:
# Legacy volume, put them into default pool
From ec8de314fcbf503d6580ebcbfa4938e218bac14b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mathieu=20Gagne=CC=81?= <mgagne@iweb.com>
Date: Mon, 13 Feb 2017 19:32:44 -0500
Subject: [PATCH] Add ability to signal and perform online volume size change
Allow Cinder to use external events to signal a volume extension.
1) Nova will then call os-brick to perform the volume extension
so the host can detect its new size.
2) Compute driver will resize the device in QEMU so instance can detect
the new disk size without rebooting.
This change adds the 'volume-extended' external event.
The event tag needs to be the extended volume id.
Change-Id: Ic019d19ca11360632020e34ad66cd50df4d94fa0
---
nova/compute/manager.py | 44 +++++++++++++++++++++++++++++++++
nova/objects/external_event.py | 3 +++
nova/tests/unit/objects/test_objects.py | 2 +-
nova/virt/driver.py | 15 +++++++++++
nova/virt/fake.py | 4 +++
nova/virt/libvirt/driver.py | 26 +++++++++++++++++++
nova/virt/libvirt/volume/iscsi.py | 7 ++++++
nova/virt/libvirt/volume/volume.py | 4 +++
8 files changed, 104 insertions(+), 1 deletion(-)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 73da38b..aedbbf4 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -6717,6 +6717,46 @@ class ComputeManager(manager.Manager):
instance=instance)
break
+ def _process_instance_volume_extended_event(self, context, instance,
+ extended_volume_id):
+ # If an attached volume is extended by cinder, it needs to
+ # be extended by virt driver so host can detect its new size.
+ # And bdm needs to be updated.
+ LOG.debug('Handling volume-extended event for volume %(vol)s',
+ {'vol': extended_volume_id}, instance=instance)
+
+ if extended_volume_id is None:
+ LOG.warning(_LW('Unexpected attempt to extend volume '
+ 'without providing the volume_id.'))
+ return
+
+ bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
+ context, instance.uuid)
+
+ for bdm in bdms:
+ if bdm.volume_id == extended_volume_id:
+ LOG.info(_LI('Cinder extended volume %(vol)s; '
+ 'extending it to detect new size'),
+ {'vol': bdm.volume_id},
+ instance=instance)
+ volume = self.volume_api.get(context, bdm.volume_id)
+ bdm.update({'volume_size': volume['size']})
+
+ if bdm.connection_info is None:
+ continue
+ connection_info = jsonutils.loads(bdm.connection_info)
+ try:
+ self.driver.extend_volume(connection_info,
+ instance,
+ bdm.device_name)
+ except exception.NovaException as ex:
+ LOG.warning(
+ _LW('Extend volume failed, '
+ 'volume_id=%(volume_id)s, reason: %(msg)s'),
+ {'volume_id': extended_volume_id, 'msg': ex},
+ instance=instance)
+ break
+
@wrap_exception()
def external_instance_event(self, context, instances, events):
# NOTE(danms): Some event types are handled by the manager, such
@@ -6742,6 +6782,10 @@ class ComputeManager(manager.Manager):
self._process_instance_vif_deleted_event(context,
instance,
event.tag)
+ elif event.name == 'volume-extended':
+ self._process_instance_volume_extended_event(context,
+ instance,
+ event.tag)
else:
self._process_instance_event(instance, event)
diff --git a/nova/objects/external_event.py b/nova/objects/external_event.py
index f31c4da..d2600e6 100644
--- a/nova/objects/external_event.py
+++ b/nova/objects/external_event.py
@@ -24,6 +24,8 @@ EVENT_NAMES = [
'network-vif-unplugged',
'network-vif-deleted',
+ # Volume was extended for this instance, tag is volume_id
+ 'volume-extended',
]
EVENT_STATUSES = ['failed', 'completed', 'in-progress']
@@ -34,6 +36,7 @@ class InstanceExternalEvent(obj_base.NovaObject):
# Version 1.0: Initial version
# Supports network-changed and vif-plugged
# Version 1.1: adds network-vif-deleted event
+ # Version 1.1: adds volume-extended event
VERSION = '1.1'
fields = {
diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py
index 199e351..80ccb97 100644
--- a/nova/tests/unit/objects/test_objects.py
+++ b/nova/tests/unit/objects/test_objects.py
@@ -1132,7 +1132,7 @@ object_data = {
'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33',
'InstanceActionEventList': '1.1-13d92fb953030cdbfee56481756e02be',
'InstanceActionList': '1.0-4a53826625cc280e15fae64a575e0879',
- 'InstanceExternalEvent': '1.1-6e446ceaae5f475ead255946dd443417',
+ 'InstanceExternalEvent': '1.1-23eb6ba79cde5cd06d3445f845ba4589',
'InstanceFault': '1.2-7ef01f16f1084ad1304a513d6d410a38',
'InstanceFaultList': '1.1-f8ec07cbe3b60f5f07a8b7a06311ac0d',
'InstanceGroup': '1.10-1a0c8c7447dc7ecb9da53849430c4a5f',
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index fd0be49..11f7c49 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -491,6 +491,21 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
+ def extend_volume(self, connection_info, instance, mountpoint):
+ """Extend the disk attached to the instance.
+
+ :param dict connection_info:
+ The connection for the extended volume.
+ :param nova.objects.instance.Instance instance:
+ The instance whose volume gets extended.
+ :param str mountpoint:
+ The mountpoint in the instance where the volume for
+ `connection_info` is attached to.
+
+ :return: None
+ """
+ raise NotImplementedError()
+
def attach_interface(self, instance, image_meta, vif):
"""Use hotplug to add a network interface to a running instance.
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 4435590..7f86f41 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -304,6 +304,10 @@ class FakeDriver(driver.ComputeDriver):
self._mounts[instance_name] = {}
self._mounts[instance_name][mountpoint] = new_connection_info
+ def extend_volume(self, connection_info, instance, mountpoint):
+ """Extend the disk attached to the instance."""
+ pass
+
def attach_interface(self, instance, image_meta, vif):
if vif['id'] in self._interfaces:
raise exception.InterfaceAttachFailed(
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 78f9c7c..5bf2135 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -1489,6 +1489,32 @@ class LibvirtDriver(driver.ComputeDriver):
self._disconnect_volume(connection_info, disk_dev)
+ def extend_volume(self, connection_info, instance, mountpoint):
+ driver = self._get_volume_driver(connection_info)
+ new_size = driver.extend_volume(connection_info, mountpoint)
+
+ # Resize the device in QEMU so its size is updated and
+ # detected by the instance without rebooting.
+ try:
+ guest = self._host.get_guest(instance)
+ state = guest.get_power_state(self._host)
+ active_state = state in (power_state.RUNNING, power_state.PAUSED)
+ if active_state:
+ disk_path = connection_info['data']['device_path']
+ LOG.debug('resizing block device %(dev)s to %(size)u kb',
+ {'dev': disk_path, 'size': new_size})
+ dev = guest.get_block_device(disk_path)
+ dev.resize(new_size / units.Ki)
+ else:
+ LOG.debug('Skipping block device resize, guest is not running',
+ instance=instance)
+ except exception.InstanceNotFound:
+ LOG.warn(_LW('During extend_volume, instance disappeared.'),
+ instance=instance)
+ except libvirt.libvirtError:
+ LOG.error(_LE('resizing block device failed.'),
+ instance=instance, exc_info=True)
+
def attach_interface(self, instance, image_meta, vif):
guest = self._host.get_guest(instance)
diff --git a/nova/virt/libvirt/volume/iscsi.py b/nova/virt/libvirt/volume/iscsi.py
index 96bc8f7..762affd 100644
--- a/nova/virt/libvirt/volume/iscsi.py
+++ b/nova/virt/libvirt/volume/iscsi.py
@@ -102,3 +102,10 @@ class LibvirtISCSIVolumeDriver(libvirt_volume.LibvirtBaseVolumeDriver):
super(LibvirtISCSIVolumeDriver,
self).disconnect_volume(connection_info, disk_dev)
+
+ def extend_volume(self, connection_info, disk_info):
+ """Extend the volume."""
+ LOG.debug("calling os-brick to extend iSCSI Volume")
+ new_size = self.connector.extend_volume(connection_info['data'])
+ LOG.debug("Extend iSCSI Volume %s; new_size=%s", disk_info, new_size)
+ return new_size
diff --git a/nova/virt/libvirt/volume/volume.py b/nova/virt/libvirt/volume/volume.py
index 6ce01fb..3143a90 100644
--- a/nova/virt/libvirt/volume/volume.py
+++ b/nova/virt/libvirt/volume/volume.py
@@ -133,6 +133,10 @@ class LibvirtBaseVolumeDriver(object):
"""Disconnect the volume."""
pass
+ def extend_volume(self, connection_info, disk_info):
+ """Extend the volume."""
+ pass
+
class LibvirtVolumeDriver(LibvirtBaseVolumeDriver):
"""Class for volumes backed by local file."""
@hemna
Copy link

hemna commented Apr 5, 2017

Need to add some try blocks around the call into os-brick's extend_volume to detect failures and to try and clean up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment