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:
rajinir 2016-05-19 10:37:26 -05:00
parent e43aa0061b
commit 62b0acb503
3 changed files with 171 additions and 2 deletions

View File

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

View File

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

View File

@ -0,0 +1,3 @@
---
features:
- Added manage/unmanage volume support for Dell Equallogic driver.