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
This commit is contained in:
Peter Penchev 2018-01-12 11:34:17 +02:00
parent 27eadbc499
commit b100eb05ab
4 changed files with 235 additions and 0 deletions

View File

@ -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)

View File

@ -171,6 +171,7 @@ libvirt_volume_drivers = [
'veritas_hyperscale='
'nova.virt.libvirt.volume.vrtshyperscale.'
'LibvirtHyperScaleVolumeDriver',
'storpool=nova.virt.libvirt.volume.storpool.LibvirtStorPoolVolumeDriver',
]

View File

@ -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

View File

@ -0,0 +1,3 @@
---
features:
- Added the StorPool libvirt volume attachment driver.