|
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.""" |
This comment has been minimized.
Need to add some try blocks around the call into os-brick's extend_volume to detect failures and to try and clean up.