From b100eb05ab5f69576f3588d646158cbf5a7dad19 Mon Sep 17 00:00:00 2001 From: Peter Penchev Date: Fri, 12 Jan 2018 11:34:17 +0200 Subject: [PATCH] Add the Nova libvirt StorPool attachment driver. StorPool is distributed data storage software running on standard x86 servers. StorPool aggregates the performance and capacity of all drives into a shared pool of storage distributed among the servers. Within this storage pool the user creates thin-provisioned volumes that are exposed to the clients as block devices. StorPool consists of two parts wrapped in one package - a server and a client. The StorPool server allows a hypervisor to act as a storage node, while the StorPool client allows a hypervisor node to access the storage pool and act as a compute node. In OpenStack terms the StorPool solution allows each hypervisor node to be both a storage and a compute node simultaneously. This driver allows StorPool volumes defined in Cinder to be attached as additional disks to a virtual machine. Change-Id: I3d40009eb17d054f33b3ed641643e285ba094ec2 Implements: blueprint libvirt-storpool-volume-attach --- .../unit/virt/libvirt/volume/test_storpool.py | 174 ++++++++++++++++++ nova/virt/libvirt/driver.py | 1 + nova/virt/libvirt/volume/storpool.py | 57 ++++++ ...rpool-libvirt-driver-8dfa78f46f58b034.yaml | 3 + 4 files changed, 235 insertions(+) create mode 100644 nova/tests/unit/virt/libvirt/volume/test_storpool.py create mode 100644 nova/virt/libvirt/volume/storpool.py create mode 100644 releasenotes/notes/add-storpool-libvirt-driver-8dfa78f46f58b034.yaml diff --git a/nova/tests/unit/virt/libvirt/volume/test_storpool.py b/nova/tests/unit/virt/libvirt/volume/test_storpool.py new file mode 100644 index 000000000000..96dd4894e3b2 --- /dev/null +++ b/nova/tests/unit/virt/libvirt/volume/test_storpool.py @@ -0,0 +1,174 @@ +# (c) Copyright 2015 - 2018 StorPool +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from os_brick import initiator + +from nova.tests.unit.virt.libvirt.volume import test_volume +from nova.virt.libvirt.volume import storpool as vol_sp + +test_attached = {} + + +class MockStorPoolExc(Exception): + def __init__(self, msg): + super(MockStorPoolExc, self).__init__(msg) + + +def storpoolVolumeName(vid): + return 'os--volume--{id}'.format(id=vid) + + +def storpoolVolumePath(vid): + return '/dev/storpool/' + storpoolVolumeName(vid) + + +class MockStorPoolConnector(object): + def __init__(self, inst): + self.inst = inst + + def connect_volume(self, connection_info): + self.inst.assertIn('client_id', connection_info) + self.inst.assertIn('volume', connection_info) + self.inst.assertIn('access_mode', connection_info) + + v = connection_info['volume'] + if v in test_attached: + raise MockStorPoolExc('Duplicate volume attachment') + test_attached[v] = { + 'info': connection_info, + 'path': storpoolVolumePath(v) + } + return {'type': 'block', 'path': test_attached[v]['path']} + + def disconnect_volume(self, connection_info, device_info): + self.inst.assertIn('client_id', connection_info) + self.inst.assertIn('volume', connection_info) + + v = connection_info['volume'] + if v not in test_attached: + raise MockStorPoolExc('Unknown volume to detach') + self.inst.assertIs(test_attached[v]['info'], connection_info) + del test_attached[v] + + def extend_volume(self, connection_info): + self.inst.assertIn('volume', connection_info) + self.inst.assertIn('real_size', connection_info) + + v = connection_info['volume'] + if v not in test_attached: + raise MockStorPoolExc('Extending a volume that is not attached') + return connection_info['real_size'] + + +class MockStorPoolInitiator(object): + def __init__(self, inst): + self.inst = inst + + def factory(self, proto, helper): + self.inst.assertEqual(proto, initiator.STORPOOL) + self.inst.assertIsNotNone(helper) + return MockStorPoolConnector(self.inst) + + +class LibvirtStorPoolVolumeDriverTestCase( + test_volume.LibvirtVolumeBaseTestCase): + + def mock_storpool(f): + def _config_inner_inner1(inst, *args, **kwargs): + @mock.patch( + 'os_brick.initiator.connector.InitiatorConnector', + new=MockStorPoolInitiator(inst)) + def _config_inner_inner2(): + return f(inst, *args, **kwargs) + + return _config_inner_inner2() + + return _config_inner_inner1 + + def assertStorpoolAttached(self, names): + self.assertListEqual(sorted(test_attached.keys()), sorted(names)) + + def conn_info(self, volume_id): + return { + 'data': { + 'access_mode': 'rw', + 'client_id': '1', + 'volume': volume_id, + 'real_size': 42 if volume_id == '1' else 616 + }, 'serial': volume_id + } + + @mock_storpool + def test_storpool_config(self): + libvirt_driver = vol_sp.LibvirtStorPoolVolumeDriver(self.fake_host) + ci = self.conn_info('1') + ci['data']['device_path'] = '/dev/storpool/something' + c = libvirt_driver.get_config(ci, self.disk_info) + self.assertEqual('block', c.source_type) + self.assertEqual('/dev/storpool/something', c.source_path) + + @mock_storpool + def test_storpool_attach_detach_extend(self): + libvirt_driver = vol_sp.LibvirtStorPoolVolumeDriver(self.fake_host) + self.assertDictEqual({}, test_attached) + + ci_1 = self.conn_info('1') + ci_2 = self.conn_info('2') + + self.assertRaises(MockStorPoolExc, + libvirt_driver.extend_volume, + ci_1, mock.sentinel.instance) + + self.assertRaises(MockStorPoolExc, + libvirt_driver.extend_volume, + ci_2, mock.sentinel.instance) + + libvirt_driver.connect_volume(ci_1, mock.sentinel.instance) + self.assertStorpoolAttached(('1',)) + + ns_1 = libvirt_driver.extend_volume(ci_1, mock.sentinel.instance) + self.assertEqual(ci_1['data']['real_size'], ns_1) + + self.assertRaises(MockStorPoolExc, + libvirt_driver.extend_volume, + ci_2, mock.sentinel.instance) + + libvirt_driver.connect_volume(ci_2, mock.sentinel.instance) + self.assertStorpoolAttached(('1', '2')) + + ns_1 = libvirt_driver.extend_volume(ci_1, mock.sentinel.instance) + self.assertEqual(ci_1['data']['real_size'], ns_1) + + ns_2 = libvirt_driver.extend_volume(ci_2, mock.sentinel.instance) + self.assertEqual(ci_2['data']['real_size'], ns_2) + + self.assertRaises(MockStorPoolExc, + libvirt_driver.connect_volume, + ci_2, mock.sentinel.instance) + + libvirt_driver.disconnect_volume(ci_1, mock.sentinel.instance) + self.assertStorpoolAttached(('2',)) + + self.assertRaises(MockStorPoolExc, + libvirt_driver.disconnect_volume, + ci_1, mock.sentinel.instance) + + self.assertRaises(MockStorPoolExc, + libvirt_driver.extend_volume, + ci_1, mock.sentinel.instance) + + libvirt_driver.disconnect_volume(ci_2, mock.sentinel.instance) + self.assertDictEqual({}, test_attached) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 9f054c8e34ba..add4808c70b1 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -171,6 +171,7 @@ libvirt_volume_drivers = [ 'veritas_hyperscale=' 'nova.virt.libvirt.volume.vrtshyperscale.' 'LibvirtHyperScaleVolumeDriver', + 'storpool=nova.virt.libvirt.volume.storpool.LibvirtStorPoolVolumeDriver', ] diff --git a/nova/virt/libvirt/volume/storpool.py b/nova/virt/libvirt/volume/storpool.py new file mode 100644 index 000000000000..a99589a6ca58 --- /dev/null +++ b/nova/virt/libvirt/volume/storpool.py @@ -0,0 +1,57 @@ +# (c) Copyright 2015 - 2018 StorPool +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from os_brick import initiator +from os_brick.initiator import connector +from oslo_log import log as logging + +from nova import utils +from nova.virt.libvirt.volume import volume as libvirt_volume + +LOG = logging.getLogger(__name__) + + +class LibvirtStorPoolVolumeDriver(libvirt_volume.LibvirtVolumeDriver): + """Driver to attach StorPool volumes to libvirt.""" + + def __init__(self, host): + super(LibvirtStorPoolVolumeDriver, self).__init__(host) + + self.connector = connector.InitiatorConnector.factory( + initiator.STORPOOL, utils.get_root_helper()) + + def connect_volume(self, connection_info, instance): + LOG.debug("Attaching StorPool volume %s", + connection_info['data']['volume'], instance=instance) + device_info = self.connector.connect_volume(connection_info['data']) + LOG.debug("Attached StorPool volume %s", + device_info, instance=instance) + connection_info['data']['device_path'] = device_info['path'] + + def disconnect_volume(self, connection_info, instance): + LOG.debug("Detaching StorPool volume %s", + connection_info['data']['volume'], instance=instance) + self.connector.disconnect_volume(connection_info['data'], None) + LOG.debug("Detached StorPool volume", instance=instance) + + def extend_volume(self, connection_info, instance): + """Extend the volume.""" + LOG.debug("Extending StorPool volume %s", + connection_info['data']['volume'], instance=instance) + new_size = self.connector.extend_volume(connection_info['data']) + LOG.debug("Extended StorPool Volume %s; new_size=%s", + connection_info['data']['device_path'], + new_size, instance=instance) + return new_size diff --git a/releasenotes/notes/add-storpool-libvirt-driver-8dfa78f46f58b034.yaml b/releasenotes/notes/add-storpool-libvirt-driver-8dfa78f46f58b034.yaml new file mode 100644 index 000000000000..6ccfe8a2fb7a --- /dev/null +++ b/releasenotes/notes/add-storpool-libvirt-driver-8dfa78f46f58b034.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added the StorPool libvirt volume attachment driver.