]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
NetApp fix create vol different size than snapshot
authorNavneet Singh <singn@netapp.com>
Tue, 30 Jul 2013 05:34:20 +0000 (11:04 +0530)
committerNavneet Singh <singn@netapp.com>
Wed, 31 Jul 2013 18:20:44 +0000 (23:50 +0530)
This fixes the issue of creating vol of different
size than snapshot and creating cloned vol of
different size than original.

This change adds a lot of additional LUN geometry
and sub-LUN cloning code which is neccesary to
support extreme resizes, since normal resize
operations are limitted to be within a certain factor
of the original LUN size.

Closes-Bug:#1098581

Change-Id: I8652bfaa67c12c790fa80650a051497f70279b9c

cinder/tests/test_netapp.py
cinder/tests/test_netapp_nfs.py
cinder/volume/drivers/netapp/api.py
cinder/volume/drivers/netapp/iscsi.py
cinder/volume/drivers/netapp/nfs.py

index 252d749b63fafd9310be294bddd960af7ca4e5b1..d56cf690108c44e8d0c4489cf76191981121846b 100644 (file)
@@ -136,7 +136,7 @@ class FakeDirectCMODEServerHandler(FakeHTTPRequestHandler):
                 </is-space-reservation-enabled>
                 <mapped>false</mapped><multiprotocol-type>linux
                 </multiprotocol-type>
-                <online>true</online><path>/vol/navneet/lun2</path>
+                <online>true</online><path>/vol/navneet/lun1</path>
                 <prefix-size>0</prefix-size><qtree></qtree><read-only>
                 false</read-only><serial-number>2FfGI$APyN68</serial-number>
                 <share-state>none</share-state><size>20971520</size>
@@ -389,6 +389,28 @@ class FakeDirectCMODEServerHandler(FakeHTTPRequestHandler):
                           <num-records>1</num-records></results>"""
         elif 'ems-autosupport-log' == api:
                 body = """<results status="passed"/>"""
+        elif 'lun-resize' == api:
+                body = """<results status="passed"/>"""
+        elif 'lun-get-geometry' == api:
+                body = """<results status="passed">
+                          <size>1</size>
+                          <bytes-per-sector>2</bytes-per-sector>
+                          <sectors-per-track>8</sectors-per-track>
+                          <tracks-per-cylinder>2</tracks-per-cylinder>
+                          <cylinders>4</cylinders>
+                          <max-resize-size>5</max-resize-size>
+                          </results>"""
+        elif 'volume-options-list-info' == api:
+                body = """<results status="passed">
+                          <options>
+                          <option>
+                          <name>compression</name>
+                          <value>off</value>
+                          </option>
+                          </options>
+                          </results>"""
+        elif 'lun-move' == api:
+                body = """<results status="passed"/>"""
         else:
                 # Unknown API
                 s.send_response(500)
@@ -477,12 +499,18 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
                   'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
                   'display_name': None, 'display_description': 'lun1',
                   'volume_type_id': None}
-    volume_clone_fail = {'name': 'cl_fail', 'size': 1, 'volume_name': 'fail',
-                         'os_type': 'linux', 'provider_location': 'cl_fail',
-                         'id': 'lun1', 'provider_auth': None,
-                         'project_id': 'project', 'display_name': None,
-                         'display_description': 'lun1',
-                         'volume_type_id': None}
+    volume_clone = {'name': 'cl_sm', 'size': 3, 'volume_name': 'lun1',
+                    'os_type': 'linux', 'provider_location': 'cl_sm',
+                    'id': 'lun1', 'provider_auth': None,
+                    'project_id': 'project', 'display_name': None,
+                    'display_description': 'lun1',
+                    'volume_type_id': None}
+    volume_clone_large = {'name': 'cl_lg', 'size': 6, 'volume_name': 'lun1',
+                          'os_type': 'linux', 'provider_location': 'cl_lg',
+                          'id': 'lun1', 'provider_auth': None,
+                          'project_id': 'project', 'display_name': None,
+                          'display_description': 'lun1',
+                          'volume_type_id': None}
     connector = {'initiator': 'iqn.1993-08.org.debian:01:10'}
     vol_fail = {'name': 'lun_fail', 'size': 10000, 'volume_name': 'lun1',
                 'os_type': 'linux', 'provider_location': 'lun1',
@@ -567,34 +595,12 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
         self.driver.terminate_connection(self.volume, self.connector)
         self.driver.delete_volume(self.volume)
 
-    def test_fail_vol_from_snapshot_creation(self):
-        self.driver.create_volume(self.volume)
-        try:
-            self.driver.create_volume_from_snapshot(self.volume,
-                                                    self.snapshot_fail)
-            raise AssertionError()
-        except exception.VolumeBackendAPIException:
-            pass
-        finally:
-            self.driver.delete_volume(self.volume)
-
     def test_cloned_volume_destroy(self):
         self.driver.create_volume(self.volume)
         self.driver.create_cloned_volume(self.snapshot, self.volume)
         self.driver.delete_volume(self.snapshot)
         self.driver.delete_volume(self.volume)
 
-    def test_fail_cloned_volume_creation(self):
-        self.driver.create_volume(self.volume)
-        try:
-            self.driver.create_cloned_volume(self.volume_clone_fail,
-                                             self.volume)
-            raise AssertionError()
-        except exception.VolumeBackendAPIException:
-            pass
-        finally:
-            self.driver.delete_volume(self.volume)
-
     def test_map_by_creating_igroup(self):
         self.driver.create_volume(self.volume)
         updates = self.driver.create_export(None, self.volume)
@@ -615,6 +621,22 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
     def test_vol_stats(self):
         self.driver.get_volume_stats(refresh=True)
 
+    def test_create_vol_snapshot_diff_size_resize(self):
+        self.driver.create_volume(self.volume)
+        self.driver.create_snapshot(self.snapshot)
+        self.driver.create_volume_from_snapshot(
+            self.volume_clone, self.snapshot)
+        self.driver.delete_snapshot(self.snapshot)
+        self.driver.delete_volume(self.volume)
+
+    def test_create_vol_snapshot_diff_size_subclone(self):
+        self.driver.create_volume(self.volume)
+        self.driver.create_snapshot(self.snapshot)
+        self.driver.create_volume_from_snapshot(
+            self.volume_clone_large, self.snapshot)
+        self.driver.delete_snapshot(self.snapshot)
+        self.driver.delete_volume(self.volume)
+
 
 class NetAppDriverNegativeTestCase(test.TestCase):
     """Test case for NetAppDriver"""
@@ -691,7 +713,7 @@ class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
     <are-vols-busy>false</are-vols-busy>
     <luns>
       <lun-info>
-        <path>/vol/vol1/clone1</path>
+        <path>/vol/vol1/lun1</path>
         <size>20971520</size>
         <online>true</online>
         <mapped>false</mapped>
@@ -997,6 +1019,28 @@ class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
                 body = """<results status="passed"/>"""
         elif 'ems-autosupport-log' == api:
                 body = """<results status="passed"/>"""
+        elif 'lun-resize' == api:
+                body = """<results status="passed"/>"""
+        elif 'lun-get-geometry' == api:
+                body = """<results status="passed">
+                          <size>1</size>
+                          <bytes-per-sector>2</bytes-per-sector>
+                          <sectors-per-track>8</sectors-per-track>
+                          <tracks-per-cylinder>2</tracks-per-cylinder>
+                          <cylinders>4</cylinders>
+                          <max-resize-size>5</max-resize-size>
+                          </results>"""
+        elif 'volume-options-list-info' == api:
+                body = """<results status="passed">
+                          <options>
+                          <option>
+                          <name>compression</name>
+                          <value>off</value>
+                          </option>
+                          </options>
+                          </results>"""
+        elif 'lun-move' == api:
+                body = """<results status="passed"/>"""
         else:
                 # Unknown API
                 s.send_response(500)
index 4bdf855d2fb6724a88a1f0a9673afd0657f4862f..d56aee78d8957ea3a16fb909401330070b749a2c 100644 (file)
@@ -51,6 +51,9 @@ class FakeVolume(object):
     def __getitem__(self, key):
         return self.__dict__[key]
 
+    def __setitem__(self, key, val):
+        self.__dict__[key] = val
+
 
 class FakeSnapshot(object):
     def __init__(self, volume_size=0):
@@ -100,21 +103,20 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
         drv = self._driver
         mox = self.mox
         volume = FakeVolume(1)
-        snapshot = FakeSnapshot(2)
-
-        self.assertRaises(exception.CinderException,
-                          drv.create_volume_from_snapshot,
-                          volume,
-                          snapshot)
-
         snapshot = FakeSnapshot(1)
 
         location = '127.0.0.1:/nfs'
         expected_result = {'provider_location': location}
         mox.StubOutWithMock(drv, '_clone_volume')
         mox.StubOutWithMock(drv, '_get_volume_location')
+        mox.StubOutWithMock(drv, 'local_path')
+        mox.StubOutWithMock(drv, '_discover_file_till_timeout')
+        mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
         drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg())
         drv._get_volume_location(IgnoreArg()).AndReturn(location)
+        drv.local_path(IgnoreArg()).AndReturn('/mnt')
+        drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True)
+        drv._set_rw_permissions_for_all(IgnoreArg())
 
         mox.ReplayAll()
 
@@ -163,16 +165,6 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
 
         mox.VerifyAll()
 
-    def test_cloned_volume_size_fail(self):
-        volume_clone_fail = FakeVolume(1)
-        volume_src = FakeVolume(2)
-        try:
-            self._driver.create_cloned_volume(volume_clone_fail,
-                                              volume_src)
-            raise AssertionError()
-        except exception.CinderException:
-            pass
-
     def _custom_setup(self):
         kwargs = {}
         kwargs['netapp_mode'] = 'proxy'
index 4212601d52d57408a1a62c77a5f0ef7718dcf03c..5d0716092a67e6a7d90ea1bfe8198bc697106ca7 100644 (file)
@@ -125,10 +125,10 @@ class NaServer(object):
         self._refresh_conn = True
 
     def get_api_version(self):
-        """Gets the api version."""
+        """Gets the api version tuple."""
         if hasattr(self, '_api_version'):
-            return self._api_version
-        return self._api_version
+            return (self._api_major_version, self._api_minor_version)
+        return None
 
     def set_port(self, port):
         """Set the server communication port."""
index da9859092aecf6dc05b1a3e2d11517a8f6777df1..193d00ea7a9c3cf5b99aa010053928f151dd1a8a 100644 (file)
@@ -28,6 +28,7 @@ import time
 import uuid
 
 from cinder import exception
+from cinder.openstack.common import excutils
 from cinder.openstack.common import log as logging
 from cinder import units
 from cinder import utils
@@ -185,12 +186,19 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
             msg_fmt = {'name': name}
             LOG.warn(msg % msg_fmt)
             return
+        self._destroy_lun(metadata['Path'])
+        self.lun_table.pop(name)
+
+    def _destroy_lun(self, path, force=True):
+        """Destroys the lun at the path."""
         lun_destroy = NaElement.create_node_with_children(
             'lun-destroy',
-            **{'path': metadata['Path'], 'force': 'true'})
+            **{'path': path})
+        if force:
+            lun_destroy.add_new_child('force', 'true')
         self.client.invoke_successfully(lun_destroy, True)
-        LOG.debug(_("Destroyed LUN %s") % name)
-        self.lun_table.pop(name)
+        seg = path.split("/")
+        LOG.debug(_("Destroyed LUN %s") % seg[-1])
 
     def ensure_export(self, context, volume):
         """Driver entry point to get the export info for an existing volume."""
@@ -300,14 +308,17 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
 
         vol_size = volume['size']
         snap_size = snapshot['volume_size']
-        if vol_size != snap_size:
-            msg = _('Cannot create volume of size %(vol_size)s from '
-                    'snapshot of size %(snap_size)s')
-            msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
-            raise exception.VolumeBackendAPIException(data=msg % msg_fmt)
         snapshot_name = snapshot['name']
         new_name = volume['name']
         self._clone_lun(snapshot_name, new_name, 'true')
+        if vol_size != snap_size:
+            try:
+                self.extend_volume(volume, volume['size'])
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    LOG.error(
+                        _("Resizing %s failed. Cleaning volume."), new_name)
+                    self.delete_volume(volume)
 
     def terminate_connection(self, volume, connector, **kwargs):
         """Driver entry point to unattach a volume from an instance.
@@ -339,6 +350,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
         """Creates an actual lun on filer."""
         raise NotImplementedError()
 
+    def _create_lun(self, volume, lun, size, metadata):
+        """Issues api request for creating lun on volume."""
+        path = '/vol/%s/%s' % (volume, lun)
+        lun_create = NaElement.create_node_with_children(
+            'lun-create-by-size',
+            **{'path': path, 'size': size,
+                'ostype': metadata['OsType'],
+                'space-reservation-enabled': metadata['SpaceReserved']})
+        self.client.invoke_successfully(lun_create, True)
+
     def _get_iscsi_service_details(self):
         """Returns iscsi iqn."""
         raise NotImplementedError()
@@ -501,12 +522,13 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
             raise exception.VolumeBackendAPIException(data=msg)
         self.lun_table[lun.name] = lun
 
-    def _clone_lun(self, name, new_name, space_reserved):
+    def _clone_lun(self, name, new_name, space_reserved='true',
+                   start_block=0, end_block=0, block_count=0):
         """Clone LUN with the given name to the new name."""
         raise NotImplementedError()
 
     def _get_lun_by_args(self, **args):
-        """Retrives lun with specified args."""
+        """Retrives luns with specified args."""
         raise NotImplementedError()
 
     def _get_lun_attr(self, name, attr):
@@ -525,13 +547,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
         vol_size = volume['size']
         src_vol = self.lun_table[src_vref['name']]
         src_vol_size = src_vref['size']
-        if vol_size != src_vol_size:
-            msg = _('Cannot clone volume of size %(vol_size)s from '
-                    'src volume of size %(src_vol_size)s')
-            msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
-            raise exception.VolumeBackendAPIException(data=msg % msg_fmt)
         new_name = volume['name']
         self._clone_lun(src_vol.name, new_name, 'true')
+        if vol_size != src_vol_size:
+            try:
+                self.extend_volume(volume, volume['size'])
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    LOG.error(
+                        _("Resizing %s failed. Cleaning volume."), new_name)
+                    self.delete_volume(volume)
 
     def get_volume_stats(self, refresh=False):
         """Get volume stats.
@@ -548,6 +573,172 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
         """Retrieve stats info from volume group."""
         raise NotImplementedError()
 
+    def extend_volume(self, volume, new_size):
+        """Extend an existing volume to the new size."""
+        name = volume['name']
+        path = self.lun_table[name].metadata['Path']
+        curr_size_bytes = str(self.lun_table[name].size)
+        new_size_bytes = str(int(new_size) * units.GiB)
+        # Reused by clone scenarios.
+        # Hence comparing the stored size.
+        if curr_size_bytes != new_size_bytes:
+            lun_geometry = self._get_lun_geometry(path)
+            if (lun_geometry and lun_geometry.get("max_resize")
+                    and lun_geometry.get("max_resize") >= new_size_bytes):
+                self._do_direct_resize(path, new_size_bytes)
+            else:
+                self._do_sub_clone_resize(path, new_size_bytes)
+            self.lun_table[name].size = new_size_bytes
+        else:
+            LOG.info(_("No need to extend volume %s"
+                       " as it is already the requested new size."), name)
+
+    def _do_direct_resize(self, path, new_size_bytes, force=True):
+        """Uses the resize api to resize the lun."""
+        seg = path.split("/")
+        LOG.info(_("Resizing lun %s directly to new size."), seg[-1])
+        lun_resize = NaElement("lun-resize")
+        lun_resize.add_new_child('path', path)
+        lun_resize.add_new_child('size', new_size_bytes)
+        if force:
+            lun_resize.add_new_child('force', 'true')
+        self.client.invoke_successfully(lun_resize, True)
+
+    def _get_lun_geometry(self, path):
+        """Gets the lun geometry."""
+        geometry = {}
+        lun_geo = NaElement("lun-get-geometry")
+        lun_geo.add_new_child('path', path)
+        try:
+            result = self.client.invoke_successfully(lun_geo, True)
+            geometry['size'] = result.get_child_content("size")
+            geometry['bytes_per_sector'] =\
+                result.get_child_content("bytes-per-sector")
+            geometry['sectors_per_track'] =\
+                result.get_child_content("sectors-per-track")
+            geometry['tracks_per_cylinder'] =\
+                result.get_child_content("tracks-per-cylinder")
+            geometry['cylinders'] =\
+                result.get_child_content("cylinders")
+            geometry['max_resize'] =\
+                result.get_child_content("max-resize-size")
+        except Exception as e:
+            LOG.error(_("Lun %(path)s geometry failed. Message - %(msg)s")
+                      % {'path': path, 'msg': e.message})
+        return geometry
+
+    def _get_volume_options(self, volume_name):
+        """Get the value for the volume option."""
+        opts = []
+        vol_option_list = NaElement("volume-options-list-info")
+        vol_option_list.add_new_child('volume', volume_name)
+        result = self.client.invoke_successfully(vol_option_list, True)
+        options = result.get_child_by_name("options")
+        if options:
+            opts = options.get_children()
+        return opts
+
+    def _get_vol_option(self, volume_name, option_name):
+        """Get the value for the volume option."""
+        value = None
+        options = self._get_volume_options(volume_name)
+        for opt in options:
+            if opt.get_child_content('name') == option_name:
+                value = opt.get_child_content('value')
+                break
+        return value
+
+    def _move_lun(self, path, new_path):
+        """Moves the lun at path to new path."""
+        seg = path.split("/")
+        new_seg = new_path.split("/")
+        LOG.debug(_("Moving lun %(name)s to %(new_name)s.")
+                  % {'name': seg[-1], 'new_name': new_seg[-1]})
+        lun_move = NaElement("lun-move")
+        lun_move.add_new_child("path", path)
+        lun_move.add_new_child("new-path", new_path)
+        self.client.invoke_successfully(lun_move, True)
+
+    def _do_sub_clone_resize(self, path, new_size_bytes):
+        """Does sub lun clone after verification.
+
+            Clones the block ranges and swaps
+            the luns also deletes older lun
+            after a successful clone.
+        """
+        seg = path.split("/")
+        LOG.info(_("Resizing lun %s using sub clone to new size."), seg[-1])
+        name = seg[-1]
+        vol_name = seg[2]
+        lun = self.lun_table[name]
+        metadata = lun.metadata
+        compression = self._get_vol_option(vol_name, 'compression')
+        if compression == "on":
+            msg = _('%s cannot be sub clone resized'
+                    ' as it is hosted on compressed volume')
+            raise exception.VolumeBackendAPIException(data=msg % name)
+        else:
+            block_count = self._get_lun_block_count(path)
+            if block_count == 0:
+                msg = _('%s cannot be sub clone resized'
+                        ' as it contains no blocks.')
+                raise exception.VolumeBackendAPIException(data=msg % name)
+            new_lun = 'new-%s' % (name)
+            self._create_lun(vol_name, new_lun, new_size_bytes, metadata)
+            try:
+                self._clone_lun(name, new_lun, block_count=block_count)
+                self._post_sub_clone_resize(path)
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    new_path = '/vol/%s/%s' % (vol_name, new_lun)
+                    self._destroy_lun(new_path)
+
+    def _post_sub_clone_resize(self, path):
+        """Try post sub clone resize in a transactional manner."""
+        st_tm_mv, st_nw_mv, st_del_old = None, None, None
+        seg = path.split("/")
+        LOG.info(_("Post clone resize lun %s"), seg[-1])
+        new_lun = 'new-%s' % (seg[-1])
+        tmp_lun = 'tmp-%s' % (seg[-1])
+        tmp_path = "/vol/%s/%s" % (seg[2], tmp_lun)
+        new_path = "/vol/%s/%s" % (seg[2], new_lun)
+        try:
+            st_tm_mv = self._move_lun(path, tmp_path)
+            st_nw_mv = self._move_lun(new_path, path)
+            st_del_old = self._destroy_lun(tmp_path)
+        except Exception as e:
+            if st_tm_mv is None:
+                msg = _("Failure staging lun %s to tmp.")
+                raise exception.VolumeBackendAPIException(data=msg % (seg[-1]))
+            else:
+                if st_nw_mv is None:
+                    self._move_lun(tmp_path, path)
+                    msg = _("Failure moving new cloned lun to %s.")
+                    raise exception.VolumeBackendAPIException(
+                        data=msg % (seg[-1]))
+                elif st_del_old is None:
+                    LOG.error(_("Failure deleting staged tmp lun %s."),
+                              tmp_lun)
+                else:
+                    LOG.error(_("Unknown exception in"
+                                " post clone resize lun %s."), seg[-1])
+                    LOG.error(_("Exception details: %s") % (e.__str__()))
+
+    def _get_lun_block_count(self, path):
+        """Gets block counts for the lun."""
+        LOG.debug(_("Getting lun block count."))
+        block_count = 0
+        lun_infos = self._get_lun_by_args(path=path)
+        if not lun_infos:
+            seg = path.split('/')
+            msg = _('Failure getting lun info for %s.')
+            raise exception.VolumeBackendAPIException(data=msg % seg[-1])
+        lun_info = lun_infos[-1]
+        bs = int(lun_info.get_child_content('block-size'))
+        ls = int(lun_info.get_child_content('size'))
+        block_count = ls / bs
+        return block_count
+
 
 class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
     """NetApp C-mode iSCSI volume driver."""
@@ -585,12 +776,7 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
             raise exception.VolumeBackendAPIException(data=msg % name)
         for volume in volumes:
             try:
-                path = '/vol/%s/%s' % (volume.id['name'], name)
-                lun_create = NaElement.create_node_with_children(
-                    'lun-create-by-size',
-                    **{'path': path, 'size': size,
-                        'ostype': metadata['OsType']})
-                self.client.invoke_successfully(lun_create, True)
+                self._create_lun(volume.id['name'], name, size, metadata)
                 metadata['Path'] = '/vol/%s/%s' % (volume.id['name'], name)
                 metadata['Volume'] = volume.id['name']
                 metadata['Qtree'] = None
@@ -763,7 +949,8 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
                 break
         return igroup_list
 
-    def _clone_lun(self, name, new_name, space_reserved):
+    def _clone_lun(self, name, new_name, space_reserved='true',
+                   start_block=0, end_block=0, block_count=0):
         """Clone LUN with the given handle to the new name."""
         metadata = self._get_lun_attr(name, 'metadata')
         volume = metadata['Volume']
@@ -771,6 +958,15 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
             'clone-create',
             **{'volume': volume, 'source-path': name,
                 'destination-path': new_name, 'space-reserve': space_reserved})
+        if block_count > 0:
+            block_ranges = NaElement("block-ranges")
+            block_range = NaElement.create_node_with_children(
+                'block-range',
+                **{'source-block-number': str(start_block),
+                   'destination-block-number': str(end_block),
+                   'block-count': str(block_count)})
+            block_ranges.add_child_elem(block_range)
+            clone_create.add_child_elem(block_ranges)
         self.client.invoke_successfully(clone_create, True)
         LOG.debug(_("Cloned LUN with new name %s") % new_name)
         lun = self._get_lun_by_args(vserver=self.vserver, path='/vol/%s/%s'
@@ -903,9 +1099,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
         if self.volume_list:
             self.volume_list = self.volume_list.split(',')
             self.volume_list = [el.strip() for el in self.volume_list]
+        (major, minor) = self._get_ontapi_version()
+        self.client.set_api_version(major, minor)
         if self.vfiler:
-            (major, minor) = self._get_ontapi_version()
-            self.client.set_api_version(major, minor)
             self.client.set_vfiler(self.vfiler)
 
     def _create_lun_on_eligible_vol(self, name, size, metadata,
@@ -917,12 +1113,7 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
         if not volume:
             msg = _('Failed to get vol with required size for volume: %s')
             raise exception.VolumeBackendAPIException(data=msg % name)
-        path = '/vol/%s/%s' % (volume['name'], name)
-        lun_create = NaElement.create_node_with_children(
-            'lun-create-by-size',
-            **{'path': path, 'size': size, 'ostype': metadata['OsType'],
-                'space-reservation-enabled': metadata['SpaceReserved']})
-        self.client.invoke_successfully(lun_create, True)
+        self._create_lun(volume['name'], name, size, metadata)
         metadata['Path'] = '/vol/%s/%s' % (volume['name'], name)
         metadata['Volume'] = volume['name']
         metadata['Qtree'] = None
@@ -945,24 +1136,10 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
                 if self.volume_list:
                     if avl_vol['name'] in self.volume_list:
                         return avl_vol
-                else:
-                    if self._check_vol_not_root(avl_vol):
+                elif self._get_vol_option(avl_vol['name'], 'root') != 'true':
                         return avl_vol
         return None
 
-    def _check_vol_not_root(self, vol):
-        """Checks if a volume is not root."""
-        vol_options = NaElement.create_node_with_children(
-            'volume-options-list-info', **{'volume': vol['name']})
-        result = self.client.invoke_successfully(vol_options, True)
-        options = result.get_child_by_name('options')
-        ops = options.get_children()
-        for op in ops:
-            if op.get_child_content('name') == 'root' and\
-                    op.get_child_content('value') == 'true':
-                return False
-        return True
-
     def _get_igroup_by_initiator(self, initiator):
         """Get igroups by initiator."""
         igroup_list = NaElement('igroup-list-info')
@@ -1075,16 +1252,26 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
                     break
         return (igroup, lun_id)
 
-    def _clone_lun(self, name, new_name, space_reserved):
+    def _clone_lun(self, name, new_name, space_reserved='true',
+                   start_block=0, end_block=0, block_count=0):
         """Clone LUN with the given handle to the new name."""
         metadata = self._get_lun_attr(name, 'metadata')
         path = metadata['Path']
         (parent, splitter, name) = path.rpartition('/')
         clone_path = '%s/%s' % (parent, new_name)
         clone_start = NaElement.create_node_with_children(
-            'clone-start',
-            **{'source-path': path, 'destination-path': clone_path,
-                'no-snap': 'true'})
+            'clone-start', **{'source-path': path,
+                              'destination-path': clone_path,
+                              'no-snap': 'true'})
+        if block_count > 0:
+            block_ranges = NaElement("block-ranges")
+            block_range = NaElement.create_node_with_children(
+                'block-range',
+                **{'source-block-number': str(start_block),
+                    'destination-block-number': str(end_block),
+                    'block-count': str(block_count)})
+            block_ranges.add_child_elem(block_range)
+            clone_start.add_child_elem(block_ranges)
         result = self.client.invoke_successfully(clone_start, True)
         clone_id_el = result.get_child_by_name('clone-id')
         cl_id_info = clone_id_el.get_child_by_name('clone-id-info')
@@ -1092,8 +1279,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
         clone_id = cl_id_info.get_child_content('clone-op-id')
         if vol_uuid:
             self._check_clone_status(clone_id, vol_uuid, name, new_name)
-        cloned_lun = self._get_lun_by_args(path=clone_path)
-        if cloned_lun:
+        luns = self._get_lun_by_args(path=clone_path)
+        if luns:
+            cloned_lun = luns[0]
             self._set_space_reserve(clone_path, space_reserved)
             clone_meta = self._create_lun_meta(cloned_lun)
             handle = self._create_lun_handle(clone_meta)
@@ -1149,15 +1337,11 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
                         clone_ops_info.get_child_content('reason'))
 
     def _get_lun_by_args(self, **args):
-        """Retrives lun with specified args."""
+        """Retrives luns with specified args."""
         lun_info = NaElement.create_node_with_children('lun-list-info', **args)
         result = self.client.invoke_successfully(lun_info, True)
         luns = result.get_child_by_name('luns')
-        if luns:
-            infos = luns.get_children()
-            if infos:
-                return infos[0]
-        return None
+        return luns.get_children()
 
     def _create_lun_meta(self, lun):
         """Creates lun metadata dictionary."""
@@ -1189,3 +1373,15 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
         provide_ems(self, self.client, data, netapp_backend,
                     server_type="7mode")
         self._stats = data
+
+    def _get_lun_block_count(self, path):
+        """Gets block counts for the lun."""
+        bs = super(
+            NetAppDirect7modeISCSIDriver, self)._get_lun_block_count(path)
+        api_version = self.client.get_api_version()
+        if api_version:
+            major = api_version[0]
+            minor = api_version[1]
+            if major == 1 and minor < 15:
+                bs = bs - 1
+        return bs
index e5599a3fea36933c5c39008bad55704f17e60ec7..dad6a2fe7802601924c5cf8dcca87d2223fb705b 100644 (file)
@@ -30,6 +30,7 @@ from oslo.config import cfg
 
 from cinder import exception
 from cinder.image import image_utils
+from cinder.openstack.common import excutils
 from cinder.openstack.common import log as logging
 from cinder.openstack.common import processutils
 from cinder import units
@@ -93,16 +94,27 @@ class NetAppNFSDriver(nfs.NfsDriver):
         vol_size = volume.size
         snap_size = snapshot.volume_size
 
-        if vol_size != snap_size:
-            msg = _('Cannot create volume of size %(vol_size)s from '
-                    'snapshot of size %(snap_size)s')
-            msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
-            raise exception.CinderException(msg % msg_fmt)
-
         self._clone_volume(snapshot.name, volume.name, snapshot.volume_id)
         share = self._get_volume_location(snapshot.volume_id)
+        volume['provider_location'] = share
+        path = self.local_path(volume)
+
+        if self._discover_file_till_timeout(path):
+            self._set_rw_permissions_for_all(path)
+            if vol_size != snap_size:
+                try:
+                    self.extend_volume(volume, vol_size)
+                except Exception as e:
+                    with excutils.save_and_reraise_exception():
+                        LOG.error(
+                            _("Resizing %s failed. Cleaning volume."),
+                            volume.name)
+                        self._execute('rm', path, run_as_root=True)
+        else:
+            raise exception.CinderException(
+                _("NFS file %s not discovered.") % volume['name'])
 
-        return {'provider_location': share}
+        return {'provider_location': volume['provider_location']}
 
     def create_snapshot(self, snapshot):
         """Creates a snapshot."""
@@ -190,17 +202,26 @@ class NetAppNFSDriver(nfs.NfsDriver):
         """Creates a clone of the specified volume."""
         vol_size = volume.size
         src_vol_size = src_vref.size
-
-        if vol_size != src_vol_size:
-            msg = _('Cannot create clone of size %(vol_size)s from '
-                    'volume of size %(src_vol_size)s')
-            msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
-            raise exception.CinderException(msg % msg_fmt)
-
         self._clone_volume(src_vref.name, volume.name, src_vref.id)
         share = self._get_volume_location(src_vref.id)
+        volume['provider_location'] = share
+        path = self.local_path(volume)
+
+        if self._discover_file_till_timeout(path):
+            self._set_rw_permissions_for_all(path)
+            if vol_size != src_vol_size:
+                try:
+                    self.extend_volume(volume, vol_size)
+                except Exception as e:
+                    LOG.error(
+                        _("Resizing %s failed. Cleaning volume."), volume.name)
+                    self._execute('rm', path, run_as_root=True)
+                    raise e
+        else:
+            raise exception.CinderException(
+                _("NFS file %s not discovered.") % volume['name'])
 
-        return {'provider_location': share}
+        return {'provider_location': volume['provider_location']}
 
     def _update_volume_stats(self):
         """Retrieve stats info from volume group."""
@@ -584,6 +605,12 @@ class NetAppNFSDriver(nfs.NfsDriver):
         direct_url = "%s/%s" % (share_location, rel_path)
         return direct_url
 
+    def extend_volume(self, volume, new_size):
+        """Extend an existing volume to the new size."""
+        LOG.info(_('Extending volume %s.'), volume['name'])
+        path = self.local_path(volume)
+        self._resize_image_file(path, new_size)
+
 
 class NetAppDirectNfsDriver (NetAppNFSDriver):
     """Executes commands related to volumes on NetApp filer."""