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:
parent
27eadbc499
commit
b100eb05ab
174
nova/tests/unit/virt/libvirt/volume/test_storpool.py
Normal file
174
nova/tests/unit/virt/libvirt/volume/test_storpool.py
Normal 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)
|
@ -171,6 +171,7 @@ libvirt_volume_drivers = [
|
||||
'veritas_hyperscale='
|
||||
'nova.virt.libvirt.volume.vrtshyperscale.'
|
||||
'LibvirtHyperScaleVolumeDriver',
|
||||
'storpool=nova.virt.libvirt.volume.storpool.LibvirtStorPoolVolumeDriver',
|
||||
]
|
||||
|
||||
|
||||
|
57
nova/virt/libvirt/volume/storpool.py
Normal file
57
nova/virt/libvirt/volume/storpool.py
Normal 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
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added the StorPool libvirt volume attachment driver.
|
Loading…
Reference in New Issue
Block a user