# 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
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()
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):
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)
# 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
"""
import copy
+import math
import socket
import time
import uuid
'sata': 'SATA',
}
SSC_UPDATE_INTERVAL = 60 # seconds
+ WORLDWIDENAME = 'worldWideName'
def __init__(self, *args, **kwargs):
super(NetAppEseriesISCSIDriver, self).__init__(*args, **kwargs)
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': {}}}
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
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]
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)
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]})