]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add Manage/Unmanage support to NetApp NFS drivers
authorGlenn M. Gobeli <Glenn.Gobeli@netapp.com>
Mon, 23 Feb 2015 12:35:34 +0000 (04:35 -0800)
committerTom Barron <tpb@dyncloud.net>
Fri, 6 Mar 2015 21:08:18 +0000 (21:08 +0000)
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
cinder/tests/test_utils.py
cinder/utils.py
cinder/volume/driver.py
cinder/volume/drivers/netapp/dataontap/nfs_7mode.py
cinder/volume/drivers/netapp/dataontap/nfs_base.py
cinder/volume/drivers/netapp/dataontap/nfs_cmode.py

index dfd376d8a7c01128023b48cba9b9a83cdd344032..8f082d7fd96f4fdc83c5848887f24d210c895ffc 100644 (file)
@@ -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)
index d733a57fae76eb4f730340ba12f82a40e427dba6..21ea0c9c3afa5b8dff936c5a82d2febb95c93eee 100644 (file)
@@ -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):
 
index c7b17eb26124513abb0a11e43f49d30303437e26..378f238e2ddae50d1ea5abb5b6e2c01cdefad7fd 100644 (file)
@@ -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.
index 16fd79caf351688e7fc9496fe50c90a2b549a5f5..78ba9c1c1379288b42bd9451c32dd76bc9bfa354 100644 (file)
@@ -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
 
index 34329f753f811b8b98f63283199f571bf34af73d..47a8581db87875dd8689768a82a4d1e68e05b93f 100644 (file)
@@ -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.")))
index 8afa7319d025937ad659d14cb41bda58ecb3115a..24d112ba546d6da3b43be17729bbbd61d9ab2538 100644 (file)
 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})
index a9e5bbf311ec36f060d539cab80550905855815c..21c8aca6ad07dbf85095da3f4d03bbf5de4f7058 100644 (file)
@@ -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."""