Volume manage/unmanage support for Eqlx driver
This change will allow cinder to manage existing volumes on Dell PS Series(Equallogic) backend and also unmanage them. DocImpact Implements: blueprint eqlx-volume-manage-unmanage Change-Id: I2a2f950e9ea3017d8c0bee8dbc025cf4f4a8eb99
This commit is contained in:
parent
e43aa0061b
commit
62b0acb503
@ -66,6 +66,8 @@ class DellEQLSanISCSIDriverTestCase(test.TestCase):
|
||||
configuration=self.configuration)
|
||||
self.volume_name = "fakevolume"
|
||||
self.volid = "fakeid"
|
||||
self.volume = {'name': self.volume_name,
|
||||
'display_name': 'fake_display_name'}
|
||||
self.connector = {
|
||||
'ip': '10.0.0.2',
|
||||
'initiator': 'iqn.1993-08.org.debian:01:2227dab76162',
|
||||
@ -78,6 +80,11 @@ class DellEQLSanISCSIDriverTestCase(test.TestCase):
|
||||
|
||||
self.fake_iqn = 'iqn.2003-10.com.equallogic:group01:25366:fakev'
|
||||
self.fake_iqn_return = ['iSCSI target name is %s.' % self.fake_iqn]
|
||||
self.fake_volume_output = ["Size: 5GB",
|
||||
"iSCSI Name: %s" % self.fake_iqn,
|
||||
"Description: "]
|
||||
self.fake_volume_info = {'size': 5.0,
|
||||
'iSCSI_Name': self.fake_iqn}
|
||||
self.driver._group_ip = '10.0.1.6'
|
||||
self.properties = {
|
||||
'target_discovered': True,
|
||||
@ -240,6 +247,63 @@ class DellEQLSanISCSIDriverTestCase(test.TestCase):
|
||||
mock_eql_execute.configure_mock(**mock_attrs)
|
||||
self.driver.extend_volume(volume, new_size)
|
||||
|
||||
def test_get_volume_info(self):
|
||||
attrs = ('volume', 'select', self.volume, 'show')
|
||||
with mock.patch.object(self.driver,
|
||||
'_eql_execute') as mock_eql_execute:
|
||||
mock_eql_execute.return_value = self.fake_volume_output
|
||||
data = self.driver._get_volume_info(self.volume)
|
||||
mock_eql_execute.assert_called_with(*attrs)
|
||||
self.assertEqual(self.fake_volume_info, data)
|
||||
|
||||
def test_get_volume_info_negative(self):
|
||||
with mock.patch.object(self.driver,
|
||||
'_eql_execute') as mock_eql_execute:
|
||||
mock_eql_execute.side_effect = processutils.ProcessExecutionError(
|
||||
stdout='% Error ..... does not exist.\n')
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver._get_volume_info, self.volume_name)
|
||||
|
||||
def test_manage_existing(self):
|
||||
ref = {'source-name': self.volume_name}
|
||||
attrs = ('volume', 'select', self.volume_name,
|
||||
'multihost-access', 'enable')
|
||||
with mock.patch.object(self.driver,
|
||||
'_eql_execute') as mock_eql_execute:
|
||||
with mock.patch.object(self.driver,
|
||||
'_get_volume_info') as mock_volume_info:
|
||||
mock_volume_info.return_value = self.fake_volume_info
|
||||
mock_eql_execute.return_value = self.fake_iqn_return
|
||||
model_update = self.driver.manage_existing(self.volume, ref)
|
||||
mock_eql_execute.assert_called_with(*attrs)
|
||||
self.assertEqual(self._model_update, model_update)
|
||||
|
||||
def test_manage_existing_invalid_ref(self):
|
||||
ref = {}
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.driver.manage_existing, self.volume, ref)
|
||||
|
||||
def test_manage_existing_get_size(self):
|
||||
ref = {'source-name': self.volume_name}
|
||||
with mock.patch.object(self.driver,
|
||||
'_eql_execute') as mock_eql_execute:
|
||||
mock_eql_execute.return_value = self.fake_volume_output
|
||||
size = self.driver.manage_existing_get_size(self.volume, ref)
|
||||
self.assertEqual(float('5.0'), size)
|
||||
|
||||
def test_manage_existing_get_size_invalid_ref(self):
|
||||
"""Error on manage with invalid reference."""
|
||||
ref = {}
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.driver.manage_existing_get_size,
|
||||
self.volume, ref)
|
||||
|
||||
def test_unmanage(self):
|
||||
with mock.patch.object(self.driver,
|
||||
'_eql_execute') as mock_eql_execute:
|
||||
mock_eql_execute.return_value = None
|
||||
self.driver.unmanage(self.volume)
|
||||
|
||||
def test_initialize_connection(self):
|
||||
volume = {'name': self.volume_name}
|
||||
mock_attrs = {'args': ['volume', 'select', volume['name'], 'access',
|
||||
@ -349,8 +413,10 @@ class DellEQLSanISCSIDriverTestCase(test.TestCase):
|
||||
|
||||
def test_get_space_in_gb(self):
|
||||
self.assertEqual(123.0, self.driver._get_space_in_gb('123.0GB'))
|
||||
self.assertEqual(124.0, self.driver._get_space_in_gb('123.5GB'))
|
||||
self.assertEqual(123.0 * 1024, self.driver._get_space_in_gb('123.0TB'))
|
||||
self.assertEqual(1.0, self.driver._get_space_in_gb('1024.0MB'))
|
||||
self.assertEqual(2.0, self.driver._get_space_in_gb('1536.0MB'))
|
||||
|
||||
def test_get_output(self):
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
"""Volume driver for Dell EqualLogic Storage."""
|
||||
|
||||
import functools
|
||||
import math
|
||||
import random
|
||||
|
||||
import eventlet
|
||||
@ -153,10 +154,11 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
1.0 - Initial driver
|
||||
1.1.0 - Misc fixes
|
||||
1.2.0 - Deprecated eqlx_cli_timeout infavor of ssh_conn_timeout
|
||||
1.3.0 - Added support for manage/unmanage volume
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.2.0"
|
||||
VERSION = "1.3.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DellEQLSanISCSIDriver, self).__init__(*args, **kwargs)
|
||||
@ -300,6 +302,9 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
def _get_volume_data(self, lines):
|
||||
prefix = 'iSCSI target name is '
|
||||
target_name = self._get_prefixed_value(lines, prefix)[:-1]
|
||||
return self._get_model_update(target_name)
|
||||
|
||||
def _get_model_update(self, target_name):
|
||||
lun_id = "%s:%s,1 %s 0" % (self._group_ip, '3260', target_name)
|
||||
model_update = {}
|
||||
model_update['provider_location'] = lun_id
|
||||
@ -318,7 +323,7 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
elif val.endswith('TB'):
|
||||
scale = 1.0 * 1024
|
||||
part = 'TB'
|
||||
return scale * float(val.partition(part)[0])
|
||||
return math.ceil(scale * float(val.partition(part)[0]))
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from eqlx group."""
|
||||
@ -369,6 +374,25 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
|
||||
self._stats = data
|
||||
|
||||
def _get_volume_info(self, volume_name):
|
||||
"""Get the volume details on the array"""
|
||||
command = ['volume', 'select', volume_name, 'show']
|
||||
try:
|
||||
data = {}
|
||||
for line in self._eql_execute(*command):
|
||||
if line.startswith('Size:'):
|
||||
out_tup = line.rstrip().partition(' ')
|
||||
data['size'] = self._get_space_in_gb(out_tup[-1])
|
||||
elif line.startswith('iSCSI Name:'):
|
||||
out_tup = line.rstrip().partition(': ')
|
||||
data['iSCSI_Name'] = out_tup[-1]
|
||||
return data
|
||||
except processutils.ProcessExecutionError:
|
||||
msg = (_("Volume does not exists %s.") % volume_name)
|
||||
LOG.error(msg)
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=volume_name, reason=msg)
|
||||
|
||||
def _check_volume(self, volume):
|
||||
"""Check if the volume exists on the Array."""
|
||||
command = ['volume', 'select', volume['name'], 'show']
|
||||
@ -452,6 +476,18 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
'for volume "%s".'),
|
||||
volume['name'])
|
||||
|
||||
def _set_volume_description(self, volume, description):
|
||||
"""Set the description of the volume"""
|
||||
try:
|
||||
cmd = ['volume', 'select',
|
||||
volume['name'], 'description', description]
|
||||
self._eql_execute(*cmd)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE('Failed to set description '
|
||||
'for volume "%s".'),
|
||||
volume['name'])
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete a volume."""
|
||||
try:
|
||||
@ -622,5 +658,69 @@ class DellEQLSanISCSIDriver(san.SanISCSIDriver):
|
||||
'current_size': volume['size'],
|
||||
'new_size': new_size})
|
||||
|
||||
def _get_existing_volume_ref_name(self, ref):
|
||||
existing_volume_name = None
|
||||
if 'source-name' in ref:
|
||||
existing_volume_name = ref['source-name']
|
||||
elif 'source-id' in ref:
|
||||
existing_volume_name = ref['source-id']
|
||||
else:
|
||||
msg = _('Reference must contain source-id or source-name.')
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
return existing_volume_name
|
||||
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
"""Manage an existing volume on the backend storage."""
|
||||
existing_volume_name = self._get_existing_volume_ref_name(existing_ref)
|
||||
try:
|
||||
cmd = ['volume', 'rename',
|
||||
existing_volume_name, volume['name']]
|
||||
self._eql_execute(*cmd)
|
||||
self._set_volume_description(volume, '"OpenStack Managed"')
|
||||
self.add_multihost_access(volume)
|
||||
data = self._get_volume_info(volume['name'])
|
||||
updates = self._get_model_update(data['iSCSI_Name'])
|
||||
LOG.info(_LI("Backend volume %(back_vol)s renamed to "
|
||||
"%(vol)s and is now managed by cinder."),
|
||||
{'back_vol': existing_volume_name,
|
||||
'vol': volume['name']})
|
||||
return updates
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE('Failed to manage volume "%s".'), volume['name'])
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_ref):
|
||||
"""Return size of volume to be managed by manage_existing.
|
||||
|
||||
When calculating the size, round up to the next GB.
|
||||
|
||||
:param volume: Cinder volume to manage
|
||||
:param existing_ref: Driver-specific information used to identify a
|
||||
volume
|
||||
"""
|
||||
existing_volume_name = self._get_existing_volume_ref_name(existing_ref)
|
||||
data = self._get_volume_info(existing_volume_name)
|
||||
return data['size']
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Removes the specified volume from Cinder management.
|
||||
|
||||
Does not delete the underlying backend storage object.
|
||||
|
||||
:param volume: Cinder volume to unmanage
|
||||
"""
|
||||
try:
|
||||
self._set_volume_description(volume, '"OpenStack UnManaged"')
|
||||
LOG.info(_LI("Virtual volume %(disp)s '%(vol)s' is no "
|
||||
"longer managed."),
|
||||
{'disp': volume['display_name'],
|
||||
'vol': volume['name']})
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE('Failed to unmanage volume "%s".'),
|
||||
volume['name'])
|
||||
|
||||
def local_path(self, volume):
|
||||
raise NotImplementedError()
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added manage/unmanage volume support for Dell Equallogic driver.
|
Loading…
Reference in New Issue
Block a user