from cinder.volume.drivers.netapp.dataontap import block_7mode
from cinder.volume.drivers.netapp.dataontap.block_7mode import \
NetAppBlockStorage7modeLibrary as block_lib_7mode
+from cinder.volume.drivers.netapp.dataontap import block_base
from cinder.volume.drivers.netapp.dataontap.block_base import \
NetAppBlockStorageLibrary as block_lib
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client.api import NaApiError
from cinder.volume.drivers.netapp.dataontap.client import client_base
+from cinder.volume.drivers.netapp import utils as na_utils
class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
self.library.zapi_client.create_lun.assert_called_once_with(
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
self.assertTrue(self.library.vol_refresh_voluntary)
+
+ @mock.patch.object(na_utils, 'get_volume_extra_specs')
+ def test_check_volume_type_for_lun_qos_not_supported(self, get_specs):
+ get_specs.return_value = {'specs': 's',
+ 'netapp:qos_policy_group': 'qos'}
+ mock_lun = block_base.NetAppLun('handle', 'name', '1',
+ {'Volume': 'name', 'Path': '/vol/lun'})
+ self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
+ self.library._check_volume_type_for_lun,
+ {'vol': 'vol'}, mock_lun, {'ref': 'ref'})
+ get_specs.assert_called_once_with({'vol': 'vol'})
mock.Mock(return_value=None))
@mock.patch.object(block_base, 'LOG', mock.Mock())
def test_create_volume(self):
+ self.library.zapi_client.get_lun_by_args.return_value = ['lun']
self.library.create_volume({'name': 'lun1', 'size': 100,
'id': uuid.uuid4(),
'host': 'hostname@backend#vol1'})
@mock.patch.object(na_utils, 'get_volume_extra_specs',
mock.Mock(return_value={'netapp:raid_type': 'raid4'}))
def test_create_volume_obsolete_extra_spec(self):
+ self.library.zapi_client.get_lun_by_args.return_value = ['lun']
self.library.create_volume({'name': 'lun1', 'size': 100,
'id': uuid.uuid4(),
mock.Mock(return_value={'netapp_thick_provisioned':
'true'}))
def test_create_volume_deprecated_extra_spec(self):
+ self.library.zapi_client.get_lun_by_args.return_value = ['lun']
self.library.create_volume({'name': 'lun1', 'size': 100,
'id': uuid.uuid4(),
self.library.do_setup(mock.Mock())
self.assertTrue(mock_check_flags.called)
+
+ def test_get_existing_vol_manage_missing_id_path(self):
+ self.assertRaises(exception.ManageExistingInvalidReference,
+ self.library._get_existing_vol_with_manage_ref,
+ {})
+
+ def test_get_existing_vol_manage_not_found(self):
+ self.zapi_client.get_lun_by_args.return_value = []
+ self.assertRaises(exception.ManageExistingInvalidReference,
+ self.library._get_existing_vol_with_manage_ref,
+ {'source-id': 'src_id',
+ 'source-name': 'lun_path'})
+ self.assertEqual(1, self.zapi_client.get_lun_by_args.call_count)
+
+ @mock.patch.object(block_lib, '_extract_lun_info',
+ mock.Mock(return_value=block_base.NetAppLun(
+ 'lun0', 'lun0', '3', {'UUID': 'src_id'})))
+ def test_get_existing_vol_manage_lun(self):
+ self.zapi_client.get_lun_by_args.return_value = ['lun0', 'lun1']
+ lun = self.library._get_existing_vol_with_manage_ref(
+ {'source-id': 'src_id', 'path': 'lun_path'})
+ self.assertEqual(1, self.zapi_client.get_lun_by_args.call_count)
+ self.library._extract_lun_info.assert_called_once_with('lun0')
+ self.assertEqual('lun0', lun.name)
+
+ @mock.patch.object(block_lib, '_get_existing_vol_with_manage_ref',
+ mock.Mock(return_value=block_base.NetAppLun(
+ 'handle', 'name', '1073742824', {})))
+ def test_manage_existing_get_size(self):
+ size = self.library.manage_existing_get_size(
+ {'id': 'vol_id'}, {'ref': 'ref'})
+ self.assertEqual(2, size)
+ self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
+ {'ref': 'ref'})
+
+ @mock.patch.object(block_base.LOG, 'info')
+ def test_unmanage(self, log):
+ mock_lun = block_base.NetAppLun('handle', 'name', '1',
+ {'Path': 'p', 'UUID': 'uuid'})
+ self.library._get_lun_from_table = mock.Mock(return_value=mock_lun)
+ self.library.unmanage({'name': 'vol'})
+ self.library._get_lun_from_table.assert_called_once_with('vol')
+ self.assertEqual(1, log.call_count)
+
+ def test_manage_existing_lun_same_name(self):
+ mock_lun = block_base.NetAppLun('handle', 'name', '1',
+ {'Path': '/vol/vol1/name'})
+ self.library._get_existing_vol_with_manage_ref = mock.Mock(
+ return_value=mock_lun)
+ self.library._check_volume_type_for_lun = mock.Mock()
+ self.library._add_lun_to_table = mock.Mock()
+ self.zapi_client.move_lun = mock.Mock()
+ self.library.manage_existing({'name': 'name'}, {'ref': 'ref'})
+ self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
+ {'ref': 'ref'})
+ self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
+ self.assertEqual(1, self.library._add_lun_to_table.call_count)
+ self.assertEqual(0, self.zapi_client.move_lun.call_count)
+
+ def test_manage_existing_lun_new_path(self):
+ mock_lun = block_base.NetAppLun(
+ 'handle', 'name', '1', {'Path': '/vol/vol1/name'})
+ self.library._get_existing_vol_with_manage_ref = mock.Mock(
+ return_value=mock_lun)
+ self.library._check_volume_type_for_lun = mock.Mock()
+ self.library._add_lun_to_table = mock.Mock()
+ self.zapi_client.move_lun = mock.Mock()
+ self.library.manage_existing({'name': 'volume'}, {'ref': 'ref'})
+ self.assertEqual(
+ 2, self.library._get_existing_vol_with_manage_ref.call_count)
+ self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
+ self.assertEqual(1, self.library._add_lun_to_table.call_count)
+ self.zapi_client.move_lun.assert_called_once_with(
+ '/vol/vol1/name', '/vol/vol1/volume')
+
+ def test_check_vol_type_for_lun(self):
+ self.assertRaises(NotImplementedError,
+ self.library._check_volume_type_for_lun,
+ 'vol', 'lun', 'existing_ref')
+
+ def test_is_lun_valid_on_storage(self):
+ self.assertTrue(self.library._is_lun_valid_on_storage('lun'))
import mock
+from cinder import exception
from cinder import test
import cinder.tests.volume.drivers.netapp.dataontap.fakes as fake
import cinder.tests.volume.drivers.netapp.fakes as na_fakes
+from cinder.volume.drivers.netapp.dataontap import block_base
from cinder.volume.drivers.netapp.dataontap.block_base import \
NetAppBlockStorageLibrary as block_lib
from cinder.volume.drivers.netapp.dataontap import block_cmode
self.library.zapi_client.create_lun.assert_called_once_with(
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
self.assertEqual(1, self.library._update_stale_vols.call_count)
+
+ @mock.patch.object(ssc_cmode, 'get_volumes_for_specs')
+ @mock.patch.object(ssc_cmode, 'get_cluster_latest_ssc')
+ @mock.patch.object(na_utils, 'get_volume_extra_specs')
+ def test_check_volume_type_for_lun_fail(
+ self, get_specs, get_ssc, get_vols):
+ self.library.ssc_vols = ['vol']
+ get_specs.return_value = {'specs': 's'}
+ get_vols.return_value = [ssc_cmode.NetAppVolume(name='name',
+ vserver='vs')]
+ mock_lun = block_base.NetAppLun('handle', 'name', '1',
+ {'Volume': 'fake', 'Path': '/vol/lun'})
+ self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
+ self.library._check_volume_type_for_lun,
+ {'vol': 'vol'}, mock_lun, {'ref': 'ref'})
+ get_specs.assert_called_once_with({'vol': 'vol'})
+ get_vols.assert_called_with(['vol'], {'specs': 's'})
+ self.assertEqual(1, get_ssc.call_count)
+
+ @mock.patch.object(block_cmode.LOG, 'error')
+ @mock.patch.object(ssc_cmode, 'get_volumes_for_specs')
+ @mock.patch.object(ssc_cmode, 'get_cluster_latest_ssc')
+ @mock.patch.object(na_utils, 'get_volume_extra_specs')
+ def test_check_volume_type_for_lun_qos_fail(
+ self, get_specs, get_ssc, get_vols, driver_log):
+ self.zapi_client.connection.set_api_version(1, 20)
+ self.library.ssc_vols = ['vol']
+ get_specs.return_value = {'specs': 's',
+ 'netapp:qos_policy_group': 'qos'}
+ get_vols.return_value = [ssc_cmode.NetAppVolume(name='name',
+ vserver='vs')]
+ mock_lun = block_base.NetAppLun('handle', 'name', '1',
+ {'Volume': 'name', 'Path': '/vol/lun'})
+ self.zapi_client.set_lun_qos_policy_group = mock.Mock(
+ side_effect=netapp_api.NaApiError)
+ self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
+ self.library._check_volume_type_for_lun,
+ {'vol': 'vol'}, mock_lun, {'ref': 'ref'})
+ get_specs.assert_called_once_with({'vol': 'vol'})
+ get_vols.assert_called_with(['vol'], {'specs': 's'})
+ self.assertEqual(0, get_ssc.call_count)
+ self.zapi_client.set_lun_qos_policy_group.assert_called_once_with(
+ '/vol/lun', 'qos')
+ self.assertEqual(1, driver_log.call_count)
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = lun.get_child_content(
'is-space-reservation-enabled')
+ meta_dict['UUID'] = lun.get_child_content('uuid')
return meta_dict
def _get_fc_target_wwpns(self, include_partner=True):
"""Driver entry point for destroying existing volumes."""
super(NetAppBlockStorage7modeLibrary, self).delete_volume(volume)
self.vol_refresh_voluntary = True
+
+ def _is_lun_valid_on_storage(self, lun):
+ """Validate LUN specific to storage system."""
+ if self.volume_list:
+ lun_vol = lun.get_metadata_property('Volume')
+ if lun_vol not in self.volume_list:
+ return False
+ return True
+
+ def _check_volume_type_for_lun(self, volume, lun, existing_ref):
+ """Check if lun satisfies volume type."""
+ extra_specs = na_utils.get_volume_extra_specs(volume)
+ if extra_specs and extra_specs.pop('netapp:qos_policy_group', None):
+ raise exception.ManageExistingVolumeTypeMismatch(
+ reason=_("Setting LUN QoS policy group is not supported"
+ " on this storage family and ONTAP version."))
Volume driver library for NetApp 7/C-mode block storage systems.
"""
+import math
import sys
import uuid
self.lun_table = {}
self.lookup_service = fczm_utils.create_lookup_service()
self.app_version = kwargs.get("app_version", "unknown")
+ self.db = kwargs.get('db')
self.configuration = kwargs['configuration']
self.configuration.append_config_values(na_opts.netapp_connection_opts)
size = default_size if not int(volume['size'])\
else int(volume['size']) * units.Gi
- metadata = {'OsType': 'linux', 'SpaceReserved': 'true'}
+ metadata = {'OsType': 'linux',
+ 'SpaceReserved': 'true',
+ 'Path': '/vol/%s/%s' % (ontap_volume_name, lun_name)}
extra_specs = na_utils.get_volume_extra_specs(volume)
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
"""Returns LUN handle based on filer type."""
raise NotImplementedError()
+ def _extract_lun_info(self, lun):
+ """Extracts the LUNs from API and populates the LUN table."""
+
+ meta_dict = self._create_lun_meta(lun)
+ path = lun.get_child_content('path')
+ (_rest, _splitter, name) = path.rpartition('/')
+ handle = self._create_lun_handle(meta_dict)
+ size = lun.get_child_content('size')
+ return NetAppLun(handle, name, size, meta_dict)
+
def _extract_and_populate_luns(self, api_luns):
"""Extracts the LUNs from API and populates the LUN table."""
for lun in api_luns:
- meta_dict = self._create_lun_meta(lun)
- path = lun.get_child_content('path')
- (_rest, _splitter, name) = path.rpartition('/')
- handle = self._create_lun_handle(meta_dict)
- size = lun.get_child_content('size')
- discovered_lun = NetAppLun(handle, name, size, meta_dict)
+ discovered_lun = self._extract_lun_info(lun)
self._add_lun_to_table(discovered_lun)
def _map_lun(self, name, initiator_list, initiator_type, lun_id=None):
block_count = ls / bs
return block_count
+ def _check_volume_type_for_lun(self, volume, lun, existing_ref):
+ """Checks if lun satifies the volume type."""
+ raise NotImplementedError()
+
+ def manage_existing(self, volume, existing_ref):
+ """Brings an existing storage object under Cinder management.
+
+ existing_ref can contain source-id or source-name or both.
+ source-id: lun uuid.
+ source-name: complete lun path eg. /vol/vol0/lun.
+ """
+ lun = self._get_existing_vol_with_manage_ref(existing_ref)
+ self._check_volume_type_for_lun(volume, lun, existing_ref)
+ path = lun.get_metadata_property('Path')
+ if lun.name == volume['name']:
+ LOG.info(_LI("LUN with given ref %s need not be renamed "
+ "during manage operation."), existing_ref)
+ else:
+ (rest, splitter, name) = path.rpartition('/')
+ new_path = '%s/%s' % (rest, volume['name'])
+ self.zapi_client.move_lun(path, new_path)
+ lun = self._get_existing_vol_with_manage_ref(
+ {'source-name': new_path})
+ self._add_lun_to_table(lun)
+ LOG.info(_LI("Manage operation completed for LUN with new path"
+ " %(path)s and uuid %(uuid)s."),
+ {'path': lun.get_metadata_property('Path'),
+ 'uuid': lun.get_metadata_property('UUID')})
+
+ 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.
+ """
+ lun = self._get_existing_vol_with_manage_ref(existing_ref)
+ return int(math.ceil(float(lun.size) / units.Gi))
+
+ def _get_existing_vol_with_manage_ref(self, existing_ref):
+ """Get the corresponding LUN from the storage server."""
+ uuid = existing_ref.get('source-id')
+ path = existing_ref.get('source-name')
+ if not (uuid or path):
+ reason = _('Reference must contain either source-id'
+ ' or source-name element.')
+ raise exception.ManageExistingInvalidReference(
+ existing_ref=existing_ref, reason=reason)
+ lun_info = {}
+ lun_info.setdefault('path', path) if path else None
+ if hasattr(self, 'vserver') and uuid:
+ lun_info['uuid'] = uuid
+ luns = self.zapi_client.get_lun_by_args(**lun_info)
+ if luns:
+ for lun in luns:
+ netapp_lun = self._extract_lun_info(lun)
+ storage_valid = self._is_lun_valid_on_storage(netapp_lun)
+ uuid_valid = True
+ if uuid:
+ if netapp_lun.get_metadata_property('UUID') == uuid:
+ uuid_valid = True
+ else:
+ uuid_valid = False
+ if storage_valid and uuid_valid:
+ return netapp_lun
+ raise exception.ManageExistingInvalidReference(
+ existing_ref=existing_ref,
+ reason=(_('LUN not found with given ref %s.') % existing_ref))
+
+ def _is_lun_valid_on_storage(self, lun):
+ """Validate lun specific to storage system."""
+ return True
+
+ def unmanage(self, volume):
+ """Removes the specified volume from Cinder management.
+
+ Does not delete the underlying backend storage object.
+ """
+ managed_lun = self._get_lun_from_table(volume['name'])
+ LOG.info(_LI("Unmanaged LUN with current path %(path)s and uuid "
+ "%(uuid)s."),
+ {'path': managed_lun.get_metadata_property('Path'),
+ 'uuid': managed_lun.get_metadata_property('UUID')
+ or 'unknown'})
+
def initialize_connection_iscsi(self, volume, connector):
"""Driver entry point to attach a volume to an instance.
import six
from cinder import exception
-from cinder.i18n import _
+from cinder.i18n import _, _LE
from cinder.openstack.common import log as logging
from cinder import utils
from cinder.volume.drivers.netapp.dataontap import block_base
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
from cinder.volume.drivers.netapp import options as na_opts
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = \
lun.get_child_content('is-space-reservation-enabled')
+ meta_dict['UUID'] = lun.get_child_content('uuid')
return meta_dict
def _get_fc_target_wwpns(self, include_partner=True):
if netapp_vol:
self._update_stale_vols(
volume=ssc_cmode.NetAppVolume(netapp_vol, self.vserver))
+
+ def _check_volume_type_for_lun(self, volume, lun, existing_ref):
+ """Check if LUN satisfies volume type."""
+ extra_specs = na_utils.get_volume_extra_specs(volume)
+ match_write = False
+
+ def scan_ssc_data():
+ volumes = ssc_cmode.get_volumes_for_specs(self.ssc_vols,
+ extra_specs)
+ for vol in volumes:
+ if lun.get_metadata_property('Volume') == vol.id['name']:
+ return True
+ return False
+
+ match_read = scan_ssc_data()
+ if not match_read:
+ ssc_cmode.get_cluster_latest_ssc(
+ self, self.zapi_client.get_connection(), self.vserver)
+ match_read = scan_ssc_data()
+
+ qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
+ if extra_specs else None
+ if qos_policy_group:
+ if match_read:
+ try:
+ path = lun.get_metadata_property('Path')
+ self.zapi_client.set_lun_qos_policy_group(path,
+ qos_policy_group)
+ match_write = True
+ except netapp_api.NaApiError as nae:
+ LOG.error(_LE("Failure setting QoS policy group. %s"), nae)
+ else:
+ match_write = True
+ if not (match_read and match_write):
+ raise exception.ManageExistingVolumeTypeMismatch(
+ reason=(_("LUN with given ref %(ref)s does not satisfy volume"
+ " type. Ensure LUN volume with ssc features is"
+ " present on vserver %(vs)s.")
+ % {'ref': existing_ref, 'vs': self.vserver}))
query.add_node_with_children('lun-info', **args)
luns = self.connection.invoke_successfully(lun_iter, True)
attr_list = luns.get_child_by_name('attributes-list')
+ if not attr_list:
+ return []
return attr_list.get_children()
def file_assign_qos(self, flex_vol, qos_policy_group, file_path):
'vserver': self.vserver})
self.connection.invoke_successfully(file_assign_qos, True)
+ def set_lun_qos_policy_group(self, path, qos_policy_group):
+ """Sets qos_policy_group on a LUN."""
+ set_qos_group = netapp_api.NaElement.create_node_with_children(
+ 'lun-set-qos-policy-group',
+ **{'path': path, 'qos-policy-group': qos_policy_group})
+ self.connection.invoke_successfully(set_qos_group, True)
+
def get_if_info_by_ip(self, ip):
"""Gets the network interface info by ip."""
net_if_iter = netapp_api.NaElement('net-interface-get-iter')
def remove_export(self, context, volume):
self.library.remove_export(context, volume)
+ def manage_existing(self, volume, existing_ref):
+ return self.library.manage_existing(volume, existing_ref)
+
+ def manage_existing_get_size(self, volume, existing_ref):
+ return self.library.manage_existing_get_size(volume, existing_ref)
+
+ def unmanage(self, volume):
+ return self.library.unmanage(volume)
+
def initialize_connection(self, volume, connector):
return self.library.initialize_connection_iscsi(volume, connector)
def remove_export(self, context, volume):
self.library.remove_export(context, volume)
+ def manage_existing(self, volume, existing_ref):
+ return self.library.manage_existing(volume, existing_ref)
+
+ def manage_existing_get_size(self, volume, existing_ref):
+ return self.library.manage_existing_get_size(volume, existing_ref)
+
+ def unmanage(self, volume):
+ return self.library.unmanage(volume)
+
def initialize_connection(self, volume, connector):
return self.library.initialize_connection_iscsi(volume, connector)