NetApp eseries implementation for manage/unmanage
This patch enables manage and unmanage support for the eseries iscsi driver. Implements: Blueprint eseries-manage-unmanage Change-Id: I5a7f090300065d829bc94c81d8b976dcb541b2a0
This commit is contained in:
parent
002182ece8
commit
919708fb79
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2014 NetApp, Inc.
|
||||
# Copyright (c) 2015 Alex Meade. All Rights Reserved.
|
||||
# Copyright (c) 2015 Rushil Chugh. All Rights Reserved.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2015 Navneet Singh. 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
|
||||
|
@ -634,6 +634,9 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
|
|||
connector = {'initiator': 'iqn.1998-01.com.vmware:localhost-28a58148'}
|
||||
fake_size_gb = volume['size']
|
||||
fake_eseries_pool_label = 'DDP'
|
||||
fake_ref = {'source-name': 'CFDGJSLS'}
|
||||
fake_ret_vol = {'id': 'vol_id', 'label': 'label',
|
||||
'worldWideName': 'wwn', 'capacity': '2147583648'}
|
||||
|
||||
def setUp(self):
|
||||
super(NetAppEseriesISCSIDriverTestCase, self).setUp()
|
||||
|
@ -724,7 +727,6 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
|
|||
self.assertEqual(info['driver_volume_type'], 'iscsi')
|
||||
properties = info.get('data')
|
||||
self.assertIsNotNone(properties, 'Target portal is none')
|
||||
self.driver.terminate_connection(self.volume, self.connector)
|
||||
self.driver.delete_volume(self.volume)
|
||||
|
||||
def test_map_already_mapped_diff_host(self):
|
||||
|
@ -1057,3 +1059,113 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
|
|||
driver = common.NetAppDriver(configuration=configuration)
|
||||
self.assertRaises(exception.NoValidHost,
|
||||
driver._check_mode_get_or_register_storage_system)
|
||||
|
||||
def test_get_vol_with_label_wwn_missing(self):
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.driver._get_volume_with_label_wwn,
|
||||
None, None)
|
||||
|
||||
def test_get_vol_with_label_wwn_found(self):
|
||||
fake_vl_list = [{'volumeRef': '1', 'volumeUse': 'standardVolume',
|
||||
'label': 'l1', 'volumeGroupRef': 'g1',
|
||||
'worlWideName': 'w1ghyu'},
|
||||
{'volumeRef': '2', 'volumeUse': 'standardVolume',
|
||||
'label': 'l2', 'volumeGroupRef': 'g2',
|
||||
'worldWideName': 'w2ghyu'}]
|
||||
self.driver._objects['disk_pool_refs'] = ['g2', 'g3']
|
||||
self.driver._client.list_volumes = mock.Mock(return_value=fake_vl_list)
|
||||
vol = self.driver._get_volume_with_label_wwn('l2', 'w2:gh:yu')
|
||||
self.assertEqual(1, self.driver._client.list_volumes.call_count)
|
||||
self.assertEqual('2', vol['volumeRef'])
|
||||
|
||||
def test_get_vol_with_label_wwn_unmatched(self):
|
||||
fake_vl_list = [{'volumeRef': '1', 'volumeUse': 'standardVolume',
|
||||
'label': 'l1', 'volumeGroupRef': 'g1',
|
||||
'worlWideName': 'w1ghyu'},
|
||||
{'volumeRef': '2', 'volumeUse': 'standardVolume',
|
||||
'label': 'l2', 'volumeGroupRef': 'g2',
|
||||
'worldWideName': 'w2ghyu'}]
|
||||
self.driver._objects['disk_pool_refs'] = ['g2', 'g3']
|
||||
self.driver._client.list_volumes = mock.Mock(return_value=fake_vl_list)
|
||||
self.assertRaises(KeyError, self.driver._get_volume_with_label_wwn,
|
||||
'l2', 'abcdef')
|
||||
self.assertEqual(1, self.driver._client.list_volumes.call_count)
|
||||
|
||||
def test_manage_existing_get_size(self):
|
||||
self.driver._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=self.fake_ret_vol)
|
||||
size = self.driver.manage_existing_get_size(self.volume, self.fake_ref)
|
||||
self.assertEqual(3, size)
|
||||
self.driver._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
self.volume, self.fake_ref)
|
||||
|
||||
def test_get_exist_vol_source_name_missing(self):
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver._get_existing_vol_with_manage_ref,
|
||||
self.volume, {'id': '1234'})
|
||||
|
||||
def test_get_exist_vol_source_not_found(self):
|
||||
def _get_volume(v_id, v_name):
|
||||
d = {'id': '1'}
|
||||
return d[v_id]
|
||||
|
||||
self.driver._get_volume_with_label_wwn = mock.Mock(wraps=_get_volume)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver._get_existing_vol_with_manage_ref,
|
||||
{'id': 'id2'}, {'source-name': 'name2'})
|
||||
self.driver._get_volume_with_label_wwn.assert_called_once_with(
|
||||
'name2', None)
|
||||
|
||||
def test_get_exist_vol_with_manage_ref(self):
|
||||
fake_ret_vol = {'id': 'right'}
|
||||
self.driver._get_volume_with_label_wwn = mock.Mock(
|
||||
return_value=fake_ret_vol)
|
||||
actual_vol = self.driver._get_existing_vol_with_manage_ref(
|
||||
{'id': 'id2'}, {'source-name': 'name2'})
|
||||
self.driver._get_volume_with_label_wwn.assert_called_once_with(
|
||||
'name2', None)
|
||||
self.assertEqual(fake_ret_vol, actual_vol)
|
||||
|
||||
@mock.patch.object(utils, 'convert_uuid_to_es_fmt')
|
||||
def test_manage_existing_same_label(self, mock_convert_es_fmt):
|
||||
self.driver._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=self.fake_ret_vol)
|
||||
mock_convert_es_fmt.return_value = 'label'
|
||||
self.driver._del_volume_frm_cache = mock.Mock()
|
||||
self.driver._cache_volume = mock.Mock()
|
||||
self.driver.manage_existing(self.volume, self.fake_ref)
|
||||
self.driver._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
self.volume, self.fake_ref)
|
||||
mock_convert_es_fmt.assert_called_once_with(
|
||||
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
|
||||
self.assertEqual(0, self.driver._del_volume_frm_cache.call_count)
|
||||
self.driver._cache_volume.assert_called_once_with(self.fake_ret_vol)
|
||||
|
||||
@mock.patch.object(utils, 'convert_uuid_to_es_fmt')
|
||||
def test_manage_existing_new(self, mock_convert_es_fmt):
|
||||
self.driver._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=self.fake_ret_vol)
|
||||
mock_convert_es_fmt.return_value = 'vol_label'
|
||||
self.driver._del_volume_frm_cache = mock.Mock()
|
||||
self.driver._client.update_volume = mock.Mock(
|
||||
return_value={'id': 'update', 'worldWideName': 'wwn'})
|
||||
self.driver._cache_volume = mock.Mock()
|
||||
self.driver.manage_existing(self.volume, self.fake_ref)
|
||||
self.driver._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
self.volume, self.fake_ref)
|
||||
mock_convert_es_fmt.assert_called_once_with(
|
||||
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
|
||||
self.driver._client.update_volume.assert_called_once_with(
|
||||
'vol_id', 'vol_label')
|
||||
self.driver._del_volume_frm_cache.assert_called_once_with(
|
||||
'label')
|
||||
self.driver._cache_volume.assert_called_once_with(
|
||||
{'id': 'update', 'worldWideName': 'wwn'})
|
||||
|
||||
@mock.patch.object(iscsi.LOG, 'info')
|
||||
def test_unmanage(self, log_info):
|
||||
self.driver._get_volume = mock.Mock(return_value=self.fake_ret_vol)
|
||||
self.driver.unmanage(self.volume)
|
||||
self.driver._get_volume.assert_called_once_with(
|
||||
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
|
||||
self.assertEqual(1, log_info.call_count)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2014 NetApp, Inc. All Rights Reserved.
|
||||
# Copyright (c) 2015 Alex Meade. All Rights Reserved.
|
||||
# Copyright (c) 2015 Rushil Chugh. All Rights Reserved.
|
||||
# Copyright (c) 2015 Navneet Singh. 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
|
||||
|
@ -18,6 +19,7 @@ iSCSI driver for NetApp E-series storage systems.
|
|||
"""
|
||||
|
||||
import copy
|
||||
import math
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
|
@ -89,6 +91,7 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
'sata': 'SATA',
|
||||
}
|
||||
SSC_UPDATE_INTERVAL = 60 # seconds
|
||||
WORLDWIDENAME = 'worldWideName'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NetAppEseriesISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -98,8 +101,8 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
na_opts.netapp_connection_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_eseries_opts)
|
||||
self._backend_name = self.configuration.safe_get("volume_backend_name")\
|
||||
or "NetApp_ESeries"
|
||||
self._backend_name = self.configuration.safe_get(
|
||||
"volume_backend_name") or "NetApp_ESeries"
|
||||
self._objects = {'disk_pool_refs': [], 'pools': [],
|
||||
'volumes': {'label_ref': {}, 'ref_vol': {}},
|
||||
'snapshots': {'label_ref': {}, 'ref_snap': {}}}
|
||||
|
@ -250,7 +253,9 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
def _cache_volume(self, obj):
|
||||
"""Caches volumes for further reference."""
|
||||
if (obj.get('volumeUse') == 'standardVolume' and obj.get('label')
|
||||
and obj.get('volumeRef')):
|
||||
and obj.get('volumeRef')
|
||||
and obj.get('volumeGroupRef') in
|
||||
self._objects['disk_pool_refs']):
|
||||
self._objects['volumes']['label_ref'][obj['label']]\
|
||||
= obj['volumeRef']
|
||||
self._objects['volumes']['ref_vol'][obj['volumeRef']] = obj
|
||||
|
@ -312,19 +317,26 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
|
||||
def _get_volume(self, uid):
|
||||
label = utils.convert_uuid_to_es_fmt(uid)
|
||||
return self._get_volume_with_label_wwn(label)
|
||||
|
||||
def _get_volume_with_label_wwn(self, label=None, wwn=None):
|
||||
"""Searches volume with label or wwn or both."""
|
||||
if not (label or wwn):
|
||||
raise exception.InvalidInput(_('Either volume label or wwn'
|
||||
' is required as input.'))
|
||||
try:
|
||||
return self._get_cached_volume(label)
|
||||
except KeyError:
|
||||
return self._get_latest_volume(uid)
|
||||
|
||||
def _get_latest_volume(self, uid):
|
||||
label = utils.convert_uuid_to_es_fmt(uid)
|
||||
for vol in self._client.list_volumes():
|
||||
if vol.get('label') == label:
|
||||
wwn = wwn.replace(':', '').upper() if wwn else None
|
||||
for vol in self._client.list_volumes():
|
||||
if label and vol.get('label') != label:
|
||||
continue
|
||||
if wwn and vol.get(self.WORLDWIDENAME).upper() != wwn:
|
||||
continue
|
||||
self._cache_volume(vol)
|
||||
return self._get_cached_volume(label)
|
||||
raise exception.NetAppDriverException(_("Volume %(uid)s not found.")
|
||||
% {'uid': uid})
|
||||
label = vol.get('label')
|
||||
break
|
||||
return self._get_cached_volume(label)
|
||||
|
||||
def _get_cached_volume(self, label):
|
||||
vol_id = self._objects['volumes']['label_ref'][label]
|
||||
|
@ -573,7 +585,7 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info."""
|
||||
initiator_name = connector['initiator']
|
||||
vol = self._get_latest_volume(volume['name_id'])
|
||||
vol = self._get_volume(volume['name_id'])
|
||||
iscsi_details = self._get_iscsi_service_details()
|
||||
iscsi_portal = self._get_iscsi_portal_for_vol(vol, iscsi_details)
|
||||
mapping = self._map_volume_to_host(vol, initiator_name)
|
||||
|
@ -895,3 +907,53 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
|
|||
label)
|
||||
finally:
|
||||
na_utils.set_safe_attr(self, 'clean_job_running', False)
|
||||
|
||||
@cinder_utils.synchronized('manage_existing')
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
"""Brings an existing storage object under Cinder management."""
|
||||
vol = self._get_existing_vol_with_manage_ref(volume, existing_ref)
|
||||
label = utils.convert_uuid_to_es_fmt(volume['id'])
|
||||
if label == vol['label']:
|
||||
LOG.info(_LI("Volume with given ref %s need not be renamed during"
|
||||
" manage operation."), existing_ref)
|
||||
managed_vol = vol
|
||||
else:
|
||||
managed_vol = self._client.update_volume(vol['id'], label)
|
||||
self._del_volume_frm_cache(vol['label'])
|
||||
self._cache_volume(managed_vol)
|
||||
LOG.info(_LI("Manage operation completed for volume with new label"
|
||||
" %(label)s and wwn %(wwn)s."),
|
||||
{'label': label, 'wwn': managed_vol[self.WORLDWIDENAME]})
|
||||
|
||||
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.
|
||||
"""
|
||||
vol = self._get_existing_vol_with_manage_ref(volume, existing_ref)
|
||||
return int(math.ceil(float(vol['capacity']) / units.Gi))
|
||||
|
||||
def _get_existing_vol_with_manage_ref(self, volume, existing_ref):
|
||||
try:
|
||||
return self._get_volume_with_label_wwn(
|
||||
existing_ref.get('source-name'), existing_ref.get('source-id'))
|
||||
except exception.InvalidInput:
|
||||
reason = _('Reference must contain either source-name'
|
||||
' or source-id element.')
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref, reason=reason)
|
||||
except KeyError:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref,
|
||||
reason=_('Volume not found on configured storage pools.'))
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Removes the specified volume from Cinder management.
|
||||
|
||||
Does not delete the underlying backend storage object. Logs a
|
||||
message to indicate the volume is no longer under Cinder's control.
|
||||
"""
|
||||
managed_vol = self._get_volume(volume['id'])
|
||||
LOG.info(_LI("Unmanaged volume with current label %(label)s and wwn "
|
||||
"%(wwn)s."), {'label': managed_vol['label'],
|
||||
'wwn': managed_vol[self.WORLDWIDENAME]})
|
||||
|
|
Loading…
Reference in New Issue