</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>
<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)
'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',
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)
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"""
<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>
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)
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
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."""
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.
"""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()
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):
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.
"""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."""
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
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']
'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'
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,
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
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')
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')
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)
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."""
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