From 1f94d9e84df9d064d9c739aa450ba545d841bd36 Mon Sep 17 00:00:00 2001 From: "Glenn M. Gobeli" Date: Mon, 23 Feb 2015 04:35:34 -0800 Subject: [PATCH] Add Manage/Unmanage support to NetApp NFS drivers This change adds support for the Cinder Manage and Unmanage API calls to the NetApp NFS drivers. This does not add the support for the generic NFS driver. Partially-Implements: Blueprint nfs-manage-unmanage Change-Id: I26eceae948bc151e28bf0c7af5de58cf86bcb605 --- cinder/tests/test_netapp_nfs.py | 241 ++++++++++++++++++ cinder/tests/test_utils.py | 13 + cinder/utils.py | 5 + cinder/volume/driver.py | 10 + .../drivers/netapp/dataontap/nfs_7mode.py | 10 + .../drivers/netapp/dataontap/nfs_base.py | 165 ++++++++++++ .../drivers/netapp/dataontap/nfs_cmode.py | 25 +- 7 files changed, 468 insertions(+), 1 deletion(-) diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py index dfd376d8a..8f082d7fd 100644 --- a/cinder/tests/test_netapp_nfs.py +++ b/cinder/tests/test_netapp_nfs.py @@ -16,6 +16,8 @@ import itertools import os +import shutil +import unittest from lxml import etree import mock @@ -27,6 +29,7 @@ from cinder.i18n import _LW from cinder.image import image_utils from cinder.openstack.common import log as logging from cinder import test +from cinder import utils as cinder_utils from cinder.volume import configuration as conf from cinder.volume.drivers.netapp import common from cinder.volume.drivers.netapp.dataontap.client import api @@ -115,6 +118,13 @@ class FakeResponse(object): class NetAppCmodeNfsDriverTestCase(test.TestCase): """Test direct NetApp C Mode driver.""" + + TEST_NFS_HOST = 'nfs-host1' + TEST_NFS_SHARE_PATH = '/export' + TEST_NFS_EXPORT1 = '%s:%s' % (TEST_NFS_HOST, TEST_NFS_SHARE_PATH) + TEST_NFS_EXPORT2 = 'nfs-host2:/export' + TEST_MNT_POINT = '/mnt/nfs' + def setUp(self): super(NetAppCmodeNfsDriverTestCase, self).setUp() self._custom_setup() @@ -859,6 +869,19 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): configuration.nfs_shares_config = '/nfs' return configuration + @mock.patch.object(utils, 'get_volume_extra_specs') + def test_check_volume_type_mismatch(self, get_specs): + if not hasattr(self._driver, 'vserver'): + return unittest.skip("Test only applies to cmode driver") + get_specs.return_value = {'thin_volume': 'true'} + self._driver._is_share_vol_type_match = mock.Mock(return_value=False) + self.assertRaises(exception.ManageExistingVolumeTypeMismatch, + self._driver._check_volume_type, 'vol', + 'share', 'file') + get_specs.assert_called_once_with('vol') + self._driver._is_share_vol_type_match.assert_called_once_with( + 'vol', 'share', 'file') + @mock.patch.object(client_base.Client, 'get_ontapi_version', mock.Mock(return_value=(1, 20))) @mock.patch.object(nfs_base.NetAppNfsDriver, 'do_setup', mock.Mock()) @@ -919,6 +942,216 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): self.assertEqual('446', na_server.get_port()) self.assertEqual('https', na_server.get_transport_type()) + @mock.patch.object(utils, 'get_volume_extra_specs') + def test_check_volume_type_qos(self, get_specs): + get_specs.return_value = {'netapp:qos_policy_group': 'qos'} + self._driver._get_vserver_and_exp_vol = mock.Mock( + return_value=('vs', 'vol')) + self._driver.zapi_client.file_assign_qos = mock.Mock( + side_effect=api.NaApiError) + self._driver._is_share_vol_type_match = mock.Mock(return_value=True) + self.assertRaises(exception.NetAppDriverException, + self._driver._check_volume_type, 'vol', + 'share', 'file') + get_specs.assert_called_once_with('vol') + self.assertEqual(1, + self._driver.zapi_client.file_assign_qos.call_count) + self.assertEqual(1, self._driver._get_vserver_and_exp_vol.call_count) + self._driver._is_share_vol_type_match.assert_called_once_with( + 'vol', 'share') + + @mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11') + def test_convert_vol_ref_share_name_to_share_ip(self, mock_hostname): + drv = self._driver + share = "%s/%s" % (self.TEST_NFS_EXPORT1, 'test_file_name') + modified_share = '10.12.142.11:/export/test_file_name' + + modified_vol_ref = drv._convert_vol_ref_share_name_to_share_ip(share) + + self.assertEqual(modified_share, modified_vol_ref) + + @mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11') + @mock.patch.object(os.path, 'isfile', return_value=True) + def test_get_share_mount_and_vol_from_vol_ref(self, mock_isfile, + mock_hostname): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, 'test_file_name') + vol_ref = {'source-name': vol_path} + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + + (share, mount, file_path) = \ + drv._get_share_mount_and_vol_from_vol_ref(vol_ref) + + self.assertEqual(self.TEST_NFS_EXPORT1, share) + self.assertEqual(self.TEST_MNT_POINT, mount) + self.assertEqual('test_file_name', file_path) + + @mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11') + def test_get_share_mount_and_vol_from_vol_ref_with_bad_ref(self, + mock_hostname): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + vol_ref = {'source-id': '1234546'} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + + self.assertRaises(exception.ManageExistingInvalidReference, + drv._get_share_mount_and_vol_from_vol_ref, vol_ref) + + @mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11') + def test_get_share_mount_and_vol_from_vol_ref_where_not_found(self, + mock_host): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT2, 'test_file_name') + vol_ref = {'source-name': vol_path} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + + self.assertRaises(exception.ManageExistingInvalidReference, + drv._get_share_mount_and_vol_from_vol_ref, vol_ref) + + @mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11') + def test_get_share_mount_and_vol_from_vol_ref_where_is_dir(self, + mock_host): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + vol_ref = {'source-name': self.TEST_NFS_EXPORT2} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + + self.assertRaises(exception.ManageExistingInvalidReference, + drv._get_share_mount_and_vol_from_vol_ref, vol_ref) + + @mock.patch.object(cinder_utils, 'get_file_size', return_value=1073741824) + def test_manage_existing_get_size(self, get_file_size): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + test_file = 'test_file_name' + volume = FakeVolume() + volume['name'] = 'file-new-managed-123' + volume['id'] = 'volume-new-managed-123' + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file) + vol_ref = {'source-name': vol_path} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + drv._get_share_mount_and_vol_from_vol_ref = mock.Mock( + return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, + test_file)) + + vol_size = drv.manage_existing_get_size(volume, vol_ref) + self.assertEqual(1, vol_size) + + @mock.patch.object(cinder_utils, 'get_file_size', return_value=1074253824) + def test_manage_existing_get_size_round_up(self, get_file_size): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + test_file = 'test_file_name' + volume = FakeVolume() + volume['name'] = 'file-new-managed-123' + volume['id'] = 'volume-new-managed-123' + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file) + vol_ref = {'source-name': vol_path} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + drv._get_share_mount_and_vol_from_vol_ref = mock.Mock( + return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, + test_file)) + + vol_size = drv.manage_existing_get_size(volume, vol_ref) + self.assertEqual(2, vol_size) + + @mock.patch.object(cinder_utils, 'get_file_size', return_value='badfloat') + def test_manage_existing_get_size_error(self, get_size): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + test_file = 'test_file_name' + volume = FakeVolume() + volume['name'] = 'file-new-managed-123' + volume['id'] = 'volume-new-managed-123' + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file) + vol_ref = {'source-name': vol_path} + + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + drv._get_share_mount_and_vol_from_vol_ref = mock.Mock( + return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, + test_file)) + + self.assertRaises(exception.VolumeBackendAPIException, + drv.manage_existing_get_size, volume, vol_ref) + + @mock.patch.object(cinder_utils, 'get_file_size', return_value=1074253824) + def test_manage_existing(self, get_file_size): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + test_file = 'test_file_name' + volume = FakeVolume() + volume['name'] = 'file-new-managed-123' + volume['id'] = 'volume-new-managed-123' + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file) + vol_ref = {'source-name': vol_path} + drv._check_volume_type = mock.Mock() + self.stubs.Set(drv, '_execute', mock.Mock()) + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + drv._get_share_mount_and_vol_from_vol_ref = mock.Mock( + return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, + test_file)) + shutil.move = mock.Mock() + + location = drv.manage_existing(volume, vol_ref) + self.assertEqual(self.TEST_NFS_EXPORT1, location['provider_location']) + drv._check_volume_type.assert_called_once_with( + volume, self.TEST_NFS_EXPORT1, test_file) + + @mock.patch.object(cinder_utils, 'get_file_size', return_value=1074253824) + def test_manage_existing_move_fails(self, get_file_size): + drv = self._driver + drv._mounted_shares = [self.TEST_NFS_EXPORT1] + test_file = 'test_file_name' + volume = FakeVolume() + volume['name'] = 'volume-new-managed-123' + volume['id'] = 'volume-new-managed-123' + vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file) + vol_ref = {'source-name': vol_path} + drv._check_volume_type = mock.Mock() + drv._ensure_shares_mounted = mock.Mock() + drv._get_mount_point_for_share = mock.Mock( + return_value=self.TEST_MNT_POINT) + drv._get_share_mount_and_vol_from_vol_ref = mock.Mock( + return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, + test_file)) + drv._execute = mock.Mock(side_effect=OSError) + self.assertRaises(exception.VolumeBackendAPIException, + drv.manage_existing, volume, vol_ref) + drv._check_volume_type.assert_called_once_with( + volume, self.TEST_NFS_EXPORT1, test_file) + + @mock.patch.object(nfs_base, 'LOG') + def test_unmanage(self, mock_log): + drv = self._driver + volume = FakeVolume() + volume['id'] = '123' + volume['provider_location'] = '/share' + drv.unmanage(volume) + self.assertEqual(1, mock_log.info.call_count) + class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase): """Test direct NetApp C Mode driver only and not inherit.""" @@ -1333,6 +1566,14 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase): pool = self._driver.get_pool({'provider_location': 'fake-share'}) self.assertEqual(pool, 'fake-share') + @mock.patch.object(utils, 'get_volume_extra_specs') + def test_check_volume_type_qos(self, get_specs): + get_specs.return_value = {'netapp:qos_policy_group': 'qos'} + self.assertRaises(exception.ManageExistingVolumeTypeMismatch, + self._driver._check_volume_type, + 'vol', 'share', 'file') + get_specs.assert_called_once_with('vol') + def _set_config(self, configuration): super(NetApp7modeNfsDriverTestCase, self)._set_config( configuration) diff --git a/cinder/tests/test_utils.py b/cinder/tests/test_utils.py index d733a57fa..21ea0c9c3 100644 --- a/cinder/tests/test_utils.py +++ b/cinder/tests/test_utils.py @@ -651,6 +651,19 @@ class GetDiskOfPartitionTestCase(test.TestCase): class GetBlkdevMajorMinorTestCase(test.TestCase): + @mock.patch('os.stat') + def test_get_file_size(self, mock_stat): + + class stat_result: + st_mode = 0o777 + st_size = 1074253824 + + test_file = '/var/tmp/made_up_file' + mock_stat.return_value = stat_result + size = utils.get_file_size(test_file) + self.assertEqual(size, stat_result.st_size) + mock_stat.assert_called_once_with(test_file) + @mock.patch('os.stat') def test_get_blkdev_major_minor(self, mock_stat): diff --git a/cinder/utils.py b/cinder/utils.py index c7b17eb26..378f238e2 100644 --- a/cinder/utils.py +++ b/cinder/utils.py @@ -607,6 +607,11 @@ def get_file_gid(path): return os.stat(path).st_gid +def get_file_size(path): + """Returns the file size.""" + return os.stat(path).st_size + + def _get_disk_of_partition(devpath, st=None): """Returns a disk device path from a partition device path, and stat for the device. If devpath is not a partition, devpath is returned as it is. diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index 16fd79caf..78ba9c1c1 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -937,6 +937,10 @@ class ManageableVD(object): compare against the properties of the referenced backend storage object. If they are incompatible, raise a ManageExistingVolumeTypeMismatch, specifying a reason for the failure. + + :param volume: Cinder volume to manage + :param existing_ref: Driver-specific information used to identify a + volume """ return @@ -945,6 +949,10 @@ class ManageableVD(object): """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 """ return @@ -958,6 +966,8 @@ class ManageableVD(object): drivers might use this call as an opportunity to clean up any Cinder-specific configuration that they have associated with the backend storage object. + + :param volume: Cinder volume to unmanage """ pass diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py b/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py index 34329f753..47a8581db 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py @@ -216,3 +216,13 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver): def _is_share_vol_compatible(self, volume, share): """Checks if share is compatible with volume to host it.""" return self._is_share_eligible(share, volume['size']) + + def _check_volume_type(self, volume, share, file_name): + """Matches a volume type for share file.""" + extra_specs = na_utils.get_volume_extra_specs(volume) + qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \ + if extra_specs else None + if qos_policy_group: + raise exception.ManageExistingVolumeTypeMismatch( + reason=(_("Setting file qos policy group is not supported" + " on this storage family and ontap version."))) diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py index 8afa7319d..24d112ba5 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py @@ -20,12 +20,15 @@ Volume driver for NetApp NFS storage. """ +import math import os import re +import shutil import threading import time from oslo_concurrency import processutils +from oslo_config import cfg from oslo_utils import excutils from oslo_utils import units import six.moves.urllib.parse as urlparse @@ -679,3 +682,165 @@ class NetAppNfsDriver(nfs.NfsDriver): 'subscribed_ratio': subscribed_ratio, 'apparent_size': apparent_size, 'apparent_available': apparent_available} + + def _check_volume_type(self, volume, share, file_name): + """Match volume type for share file.""" + raise NotImplementedError() + + def _convert_vol_ref_share_name_to_share_ip(self, vol_ref): + """Converts the share point name to an IP address + + The volume reference may have a DNS name portion in the share name. + Convert that to an IP address and then restore the entire path. + + :param vol_ref: Driver-specific information used to identify a volume + :return: A volume reference where share is in IP format. + """ + # First strip out share and convert to IP format. + share_split = vol_ref.rsplit(':', 1) + + vol_ref_share_ip = na_utils.resolve_hostname(share_split[0]) + + # Now place back into volume reference. + vol_ref_share = vol_ref_share_ip + ':' + share_split[1] + + return vol_ref_share + + def _get_share_mount_and_vol_from_vol_ref(self, vol_ref): + """Get the NFS share, the NFS mount, and the volume from reference + + Determine the NFS share point, the NFS mount point, and the volume + (with possible path) from the given volume reference. Raise exception + if unsuccessful. + + :param vol_ref: Driver-specific information used to identify a volume + :return: NFS Share, NFS mount, volume path or raise error + """ + # Check that the reference is valid. + if 'source-name' not in vol_ref: + reason = _('Reference must contain source-name element.') + raise exception.ManageExistingInvalidReference( + existing_ref=vol_ref, reason=reason) + vol_ref_name = vol_ref['source-name'] + + self._ensure_shares_mounted() + + # If a share was declared as '1.2.3.4:/a/b/c' in the nfs_shares_config + # file, but the admin tries to manage the file located at + # 'my.hostname.com:/a/b/c/d.vol', this might cause a lookup miss below + # when searching self._mounted_shares to see if we have an existing + # mount that would work to access the volume-to-be-managed (a string + # comparison is done instead of IP comparison). + vol_ref_share = self._convert_vol_ref_share_name_to_share_ip( + vol_ref_name) + for nfs_share in self._mounted_shares: + cfg_share = self._convert_vol_ref_share_name_to_share_ip(nfs_share) + (orig_share, work_share, file_path) = \ + vol_ref_share.partition(cfg_share) + if work_share == cfg_share: + file_path = file_path[1:] # strip off leading path divider + LOG.debug("Found possible share %s; checking mount.", + work_share) + nfs_mount = self._get_mount_point_for_share(nfs_share) + vol_full_path = os.path.join(nfs_mount, file_path) + if os.path.isfile(vol_full_path): + LOG.debug("Found share %(share)s and vol %(path)s on " + "mount %(mnt)s", + {'share': nfs_share, 'path': file_path, + 'mnt': nfs_mount}) + return nfs_share, nfs_mount, file_path + else: + LOG.debug("vol_ref %(ref)s not on share %(share)s.", + {'ref': vol_ref_share, 'share': nfs_share}) + + raise exception.ManageExistingInvalidReference( + existing_ref=vol_ref, + reason=_('Volume not found on configured storage backend.')) + + def manage_existing(self, volume, existing_vol_ref): + """Manages an existing volume. + + The specified Cinder volume is to be taken into Cinder management. + The driver will verify its existence and then rename it to the + new Cinder volume name. It is expected that the existing volume + reference is an NFS share point and some [/path]/volume; + e.g., 10.10.32.1:/openstack/vol_to_manage + or 10.10.32.1:/openstack/some_directory/vol_to_manage + + :param volume: Cinder volume to manage + :param existing_vol_ref: Driver-specific information used to identify a + volume + """ + # Attempt to find NFS share, NFS mount, and volume path from vol_ref. + (nfs_share, nfs_mount, vol_path) = \ + self._get_share_mount_and_vol_from_vol_ref(existing_vol_ref) + + LOG.debug("Asked to manage NFS volume %(vol)s, with vol ref %(ref)s", + {'vol': volume['id'], + 'ref': existing_vol_ref['source-name']}) + self._check_volume_type(volume, nfs_share, vol_path) + if vol_path == volume['name']: + LOG.debug("New Cinder volume %s name matches reference name: " + "no need to rename.", volume['name']) + else: + src_vol = os.path.join(nfs_mount, vol_path) + dst_vol = os.path.join(nfs_mount, volume['name']) + try: + shutil.move(src_vol, dst_vol) + LOG.debug("Setting newly managed Cinder volume name to %s", + volume['name']) + self._set_rw_permissions_for_all(dst_vol) + except (OSError, IOError) as err: + exception_msg = (_("Failed to manage existing volume %(name)s," + " because rename operation failed:" + " Error msg: %(msg)s."), + {'name': existing_vol_ref['source-name'], + 'msg': err}) + raise exception.VolumeBackendAPIException(data=exception_msg) + return {'provider_location': nfs_share} + + def manage_existing_get_size(self, volume, existing_vol_ref): + """Returns the 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_vol_ref: Existing volume to take under management + """ + # Attempt to find NFS share, NFS mount, and volume path from vol_ref. + (nfs_share, nfs_mount, vol_path) = \ + self._get_share_mount_and_vol_from_vol_ref(existing_vol_ref) + + try: + LOG.debug("Asked to get size of NFS vol_ref %s.", + existing_vol_ref['source-name']) + + file_path = os.path.join(nfs_mount, vol_path) + file_size = float(utils.get_file_size(file_path)) / units.Gi + vol_size = int(math.ceil(file_size)) + except (OSError, ValueError): + exception_message = (_("Failed to manage existing volume " + "%(name)s, because of error in getting " + "volume size."), + {'name': existing_vol_ref['source-name']}) + raise exception.VolumeBackendAPIException(data=exception_message) + + LOG.debug("Reporting size of NFS volume ref %(ref)s as %(size)d GB.", + {'ref': existing_vol_ref['source-name'], 'size': vol_size}) + + return vol_size + + def unmanage(self, volume): + """Removes the specified volume from Cinder management. + + Does not delete the underlying backend storage object. A log entry + will be made to notify the Admin that the volume is no longer being + managed. + + :param volume: Cinder volume to unmanage + """ + CONF = cfg.CONF + vol_str = CONF.volume_name_template % volume['id'] + vol_path = os.path.join(volume['provider_location'], vol_str) + LOG.info(_LI("Cinder NFS volume with current path \"%(cr)s\" is " + "no longer being managed."), {'cr': vol_path}) diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py index a9e5bbf31..21c8aca6a 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py @@ -31,6 +31,7 @@ from cinder.i18n import _, _LE, _LI, _LW from cinder.image import image_utils from cinder.openstack.common import log as logging from cinder import utils +from cinder.volume.drivers.netapp.dataontap.client import api as na_api from cinder.volume.drivers.netapp.dataontap.client import client_cmode from cinder.volume.drivers.netapp.dataontap import nfs_base from cinder.volume.drivers.netapp.dataontap import ssc_cmode @@ -106,7 +107,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver): qos_policy_group) return {'provider_location': volume['provider_location']} except Exception as ex: - LOG.error(_LW("Exception creattest_nfs.pying vol %(name)s on " + LOG.error(_LW("Exception creating vol %(name)s on " "share %(share)s. Details: %(ex)s") % {'name': volume['name'], 'share': volume['provider_location'], @@ -128,6 +129,28 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver): qos_policy_group, target_path) + def _check_volume_type(self, volume, share, file_name): + """Match volume type for share file.""" + extra_specs = na_utils.get_volume_extra_specs(volume) + qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \ + if extra_specs else None + if not self._is_share_vol_type_match(volume, share): + raise exception.ManageExistingVolumeTypeMismatch( + reason=(_("Volume type does not match for share %s."), + share)) + if qos_policy_group: + try: + vserver, flex_vol_name = self._get_vserver_and_exp_vol( + share=share) + self.zapi_client.file_assign_qos(flex_vol_name, + qos_policy_group, + file_name) + except na_api.NaApiError as ex: + LOG.exception(_LE('Setting file QoS policy group failed. %s'), + ex) + raise exception.NetAppDriverException( + reason=(_('Setting file QoS policy group failed. %s'), ex)) + def _clone_volume(self, volume_name, clone_name, volume_id, share=None): """Clones mounted volume on NetApp Cluster.""" -- 2.45.2