Merge "Add manage/unmanage support to NetApp cDOT driver"
This commit is contained in:
commit
4d8266ed1e
@ -916,6 +916,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
api_args = {'path': '/vol/%s' % volume_name}
|
api_args = {'path': '/vol/%s' % volume_name}
|
||||||
self.send_request('sis-enable', api_args)
|
self.send_request('sis-enable', api_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def disable_dedup(self, volume_name):
|
||||||
|
"""Disable deduplication on volume."""
|
||||||
|
api_args = {'path': '/vol/%s' % volume_name}
|
||||||
|
self.send_request('sis-disable', api_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def enable_compression(self, volume_name):
|
def enable_compression(self, volume_name):
|
||||||
"""Enable compression on volume."""
|
"""Enable compression on volume."""
|
||||||
@ -925,6 +931,45 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
}
|
}
|
||||||
self.send_request('sis-set-config', api_args)
|
self.send_request('sis-set-config', api_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def disable_compression(self, volume_name):
|
||||||
|
"""Disable compression on volume."""
|
||||||
|
api_args = {
|
||||||
|
'path': '/vol/%s' % volume_name,
|
||||||
|
'enable-compression': 'false'
|
||||||
|
}
|
||||||
|
self.send_request('sis-set-config', api_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def get_volume_efficiency_status(self, volume_name):
|
||||||
|
"""Get dedupe & compression status for a volume."""
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'sis-status-info': {
|
||||||
|
'path': '/vol/%s' % volume_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'sis-status-info': {
|
||||||
|
'state': None,
|
||||||
|
'is-compression-enabled': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = self.send_request('sis-get-iter', api_args)
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name(
|
||||||
|
'attributes-list') or netapp_api.NaElement('none')
|
||||||
|
sis_status_info = attributes_list.get_child_by_name(
|
||||||
|
'sis-status-info') or netapp_api.NaElement('none')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'dedupe': True if 'enabled' == sis_status_info.get_child_content(
|
||||||
|
'state') else False,
|
||||||
|
'compression': True if 'true' == sis_status_info.get_child_content(
|
||||||
|
'is-compression-enabled') else False,
|
||||||
|
}
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def set_volume_max_files(self, volume_name, max_files):
|
def set_volume_max_files(self, volume_name, max_files):
|
||||||
"""Set flexvol file limit."""
|
"""Set flexvol file limit."""
|
||||||
@ -976,6 +1021,78 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
errors[0].get_child_content('error-code'),
|
errors[0].get_child_content('error-code'),
|
||||||
errors[0].get_child_content('error-message'))
|
errors[0].get_child_content('error-message'))
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def set_volume_name(self, volume_name, new_volume_name):
|
||||||
|
"""Set flexvol name."""
|
||||||
|
api_args = {
|
||||||
|
'volume': volume_name,
|
||||||
|
'new-volume-name': new_volume_name,
|
||||||
|
}
|
||||||
|
self.send_request('volume-rename', api_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def manage_volume(self, aggregate_name, volume_name,
|
||||||
|
thin_provisioned=False, snapshot_policy=None,
|
||||||
|
language=None, dedup_enabled=False,
|
||||||
|
compression_enabled=False, max_files=None):
|
||||||
|
"""Update volume as needed to bring under management as a share."""
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': aggregate_name,
|
||||||
|
'name': volume_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-inode-attributes': {},
|
||||||
|
'volume-language-attributes': {},
|
||||||
|
'volume-snapshot-attributes': {},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'space-guarantee': ('none' if thin_provisioned else
|
||||||
|
'volume')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if language:
|
||||||
|
api_args['attributes']['volume-attributes'][
|
||||||
|
'volume-language-attributes']['language'] = language
|
||||||
|
if max_files:
|
||||||
|
api_args['attributes']['volume-attributes'][
|
||||||
|
'volume-inode-attributes']['files-total'] = max_files
|
||||||
|
if snapshot_policy:
|
||||||
|
api_args['attributes']['volume-attributes'][
|
||||||
|
'volume-snapshot-attributes'][
|
||||||
|
'snapshot-policy'] = snapshot_policy
|
||||||
|
|
||||||
|
self.send_request('volume-modify-iter', api_args)
|
||||||
|
|
||||||
|
# Efficiency options must be handled separately
|
||||||
|
self.update_volume_efficiency_attributes(volume_name,
|
||||||
|
dedup_enabled,
|
||||||
|
compression_enabled)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
|
||||||
|
compression_enabled):
|
||||||
|
"""Update dedupe & compression attributes to match desired values."""
|
||||||
|
efficiency_status = self.get_volume_efficiency_status(volume_name)
|
||||||
|
|
||||||
|
if efficiency_status['compression'] != compression_enabled:
|
||||||
|
if compression_enabled:
|
||||||
|
self.enable_compression(volume_name)
|
||||||
|
else:
|
||||||
|
self.disable_compression(volume_name)
|
||||||
|
|
||||||
|
if efficiency_status['dedupe'] != dedup_enabled:
|
||||||
|
if dedup_enabled:
|
||||||
|
self.enable_dedup(volume_name)
|
||||||
|
else:
|
||||||
|
self.disable_dedup(volume_name)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def volume_exists(self, volume_name):
|
def volume_exists(self, volume_name):
|
||||||
"""Checks if volume exists."""
|
"""Checks if volume exists."""
|
||||||
@ -1039,6 +1156,159 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
|
|
||||||
return aggregate
|
return aggregate
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def volume_has_luns(self, volume_name):
|
||||||
|
"""Checks if volume has LUNs."""
|
||||||
|
LOG.debug('Checking if volume %s has LUNs', volume_name)
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'lun-info': {
|
||||||
|
'volume': volume_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'lun-info': {
|
||||||
|
'path': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = self.send_request('lun-get-iter', api_args)
|
||||||
|
return self._has_records(result)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def volume_has_junctioned_volumes(self, volume_name):
|
||||||
|
"""Checks if volume has volumes mounted beneath its junction path."""
|
||||||
|
junction_path = self.get_volume_junction_path(volume_name)
|
||||||
|
if not junction_path:
|
||||||
|
return False
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'junction-path': junction_path + '/*',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'name': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = self.send_request('volume-get-iter', api_args)
|
||||||
|
return self._has_records(result)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def get_volume_at_junction_path(self, junction_path):
|
||||||
|
"""Returns the volume with the specified junction path, if present."""
|
||||||
|
if not junction_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'junction-path': junction_path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': None,
|
||||||
|
'junction-path': None,
|
||||||
|
'name': None,
|
||||||
|
'type': None,
|
||||||
|
'style': None,
|
||||||
|
},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'size': None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = self.send_request('volume-get-iter', api_args)
|
||||||
|
if not self._has_records(result):
|
||||||
|
return None
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name(
|
||||||
|
'attributes-list') or netapp_api.NaElement('none')
|
||||||
|
volume_attributes = attributes_list.get_child_by_name(
|
||||||
|
'volume-attributes') or netapp_api.NaElement('none')
|
||||||
|
volume_id_attributes = volume_attributes.get_child_by_name(
|
||||||
|
'volume-id-attributes') or netapp_api.NaElement('none')
|
||||||
|
volume_space_attributes = volume_attributes.get_child_by_name(
|
||||||
|
'volume-space-attributes') or netapp_api.NaElement('none')
|
||||||
|
|
||||||
|
volume = {
|
||||||
|
'aggregate': volume_id_attributes.get_child_content(
|
||||||
|
'containing-aggregate-name'),
|
||||||
|
'junction-path': volume_id_attributes.get_child_content(
|
||||||
|
'junction-path'),
|
||||||
|
'name': volume_id_attributes.get_child_content('name'),
|
||||||
|
'type': volume_id_attributes.get_child_content('type'),
|
||||||
|
'style': volume_id_attributes.get_child_content('style'),
|
||||||
|
'size': volume_space_attributes.get_child_content('size'),
|
||||||
|
}
|
||||||
|
return volume
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def get_volume_to_manage(self, aggregate_name, volume_name):
|
||||||
|
"""Get flexvol to be managed by Manila."""
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': aggregate_name,
|
||||||
|
'name': volume_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': None,
|
||||||
|
'junction-path': None,
|
||||||
|
'name': None,
|
||||||
|
'type': None,
|
||||||
|
'style': None,
|
||||||
|
},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'size': None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = self.send_request('volume-get-iter', api_args)
|
||||||
|
if not self._has_records(result):
|
||||||
|
return None
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name(
|
||||||
|
'attributes-list') or netapp_api.NaElement('none')
|
||||||
|
volume_attributes = attributes_list.get_child_by_name(
|
||||||
|
'volume-attributes') or netapp_api.NaElement('none')
|
||||||
|
volume_id_attributes = volume_attributes.get_child_by_name(
|
||||||
|
'volume-id-attributes') or netapp_api.NaElement('none')
|
||||||
|
volume_space_attributes = volume_attributes.get_child_by_name(
|
||||||
|
'volume-space-attributes') or netapp_api.NaElement('none')
|
||||||
|
|
||||||
|
volume = {
|
||||||
|
'aggregate': volume_id_attributes.get_child_content(
|
||||||
|
'containing-aggregate-name'),
|
||||||
|
'junction-path': volume_id_attributes.get_child_content(
|
||||||
|
'junction-path'),
|
||||||
|
'name': volume_id_attributes.get_child_content('name'),
|
||||||
|
'type': volume_id_attributes.get_child_content('type'),
|
||||||
|
'style': volume_id_attributes.get_child_content('style'),
|
||||||
|
'size': volume_space_attributes.get_child_content('size'),
|
||||||
|
}
|
||||||
|
return volume
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_volume_clone(self, volume_name, parent_volume_name,
|
def create_volume_clone(self, volume_name, parent_volume_name,
|
||||||
parent_snapshot_name=None):
|
parent_snapshot_name=None):
|
||||||
@ -1067,6 +1337,16 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
result = self.send_request('volume-get-volume-path', api_args)
|
result = self.send_request('volume-get-volume-path', api_args)
|
||||||
return result.get_child_content('junction')
|
return result.get_child_content('junction')
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def mount_volume(self, volume_name, junction_path=None):
|
||||||
|
"""Mounts a volume on a junction path."""
|
||||||
|
api_args = {
|
||||||
|
'volume-name': volume_name,
|
||||||
|
'junction-path': (junction_path if junction_path
|
||||||
|
else '/%s' % volume_name)
|
||||||
|
}
|
||||||
|
self.send_request('volume-mount', api_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def offline_volume(self, volume_name):
|
def offline_volume(self, volume_name):
|
||||||
"""Offlines a volume."""
|
"""Offlines a volume."""
|
||||||
|
@ -70,6 +70,12 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
|||||||
def ensure_share(self, context, share, **kwargs):
|
def ensure_share(self, context, share, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def manage_existing(self, share, driver_options):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def unmanage(self, share):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def allow_access(self, context, share, access, **kwargs):
|
def allow_access(self, context, share, access, **kwargs):
|
||||||
self.library.allow_access(context, share, access, **kwargs)
|
self.library.allow_access(context, share, access, **kwargs)
|
||||||
|
|
||||||
|
@ -70,6 +70,12 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
|||||||
def ensure_share(self, context, share, **kwargs):
|
def ensure_share(self, context, share, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def manage_existing(self, share, driver_options):
|
||||||
|
return self.library.manage_existing(share, driver_options)
|
||||||
|
|
||||||
|
def unmanage(self, share):
|
||||||
|
self.library.unmanage(share)
|
||||||
|
|
||||||
def allow_access(self, context, share, access, **kwargs):
|
def allow_access(self, context, share, access, **kwargs):
|
||||||
self.library.allow_access(context, share, access, **kwargs)
|
self.library.allow_access(context, share, access, **kwargs)
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ single-SVM or multi-SVM functionality needed by the cDOT Manila drivers.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import math
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
|
|
||||||
self.driver_name = driver_name
|
self.driver_name = driver_name
|
||||||
|
|
||||||
|
self.private_storage = kwargs['private_storage']
|
||||||
self.configuration = kwargs['configuration']
|
self.configuration = kwargs['configuration']
|
||||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
@ -483,6 +485,23 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
result.update(string_args)
|
result.update(string_args)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _check_aggregate_extra_specs_validity(self, aggregate_name, specs):
|
||||||
|
|
||||||
|
for specs_key in ('netapp_disk_type', 'netapp_raid_type'):
|
||||||
|
aggr_value = self._ssc_stats.get(aggregate_name, {}).get(specs_key)
|
||||||
|
specs_value = specs.get(specs_key)
|
||||||
|
|
||||||
|
if aggr_value and specs_value and aggr_value != specs_value:
|
||||||
|
msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" '
|
||||||
|
'in aggregate %(aggr)s.')
|
||||||
|
msg_args = {
|
||||||
|
'value': specs_value,
|
||||||
|
'key': specs_key,
|
||||||
|
'aggr': aggregate_name
|
||||||
|
}
|
||||||
|
raise exception.NetAppException(msg % msg_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _allocate_container_from_snapshot(self, share, snapshot,
|
def _allocate_container_from_snapshot(self, share, snapshot,
|
||||||
vserver_client):
|
vserver_client):
|
||||||
@ -631,6 +650,103 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
|
|
||||||
raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name)
|
raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def manage_existing(self, share, driver_options):
|
||||||
|
vserver, vserver_client = self._get_vserver(share_server=None)
|
||||||
|
share_size = self._manage_container(share, vserver_client)
|
||||||
|
export_locations = self._create_export(share, vserver, vserver_client)
|
||||||
|
return {'size': share_size, 'export_locations': export_locations}
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def unmanage(self, share):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _manage_container(self, share, vserver_client):
|
||||||
|
"""Bring existing volume under management as a share."""
|
||||||
|
|
||||||
|
protocol_helper = self._get_helper(share)
|
||||||
|
protocol_helper.set_client(vserver_client)
|
||||||
|
|
||||||
|
volume_name = protocol_helper.get_share_name_for_share(share)
|
||||||
|
if not volume_name:
|
||||||
|
msg = _('Volume could not be determined from export location '
|
||||||
|
'%(export)s.')
|
||||||
|
msg_args = {'export': share['export_location']}
|
||||||
|
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||||
|
|
||||||
|
share_name = self._get_valid_share_name(share['id'])
|
||||||
|
aggregate_name = share_utils.extract_host(share['host'], level='pool')
|
||||||
|
|
||||||
|
# Get existing volume info
|
||||||
|
volume = vserver_client.get_volume_to_manage(aggregate_name,
|
||||||
|
volume_name)
|
||||||
|
if not volume:
|
||||||
|
msg = _('Volume %(volume)s not found on aggregate %(aggr)s.')
|
||||||
|
msg_args = {'volume': volume_name, 'aggr': aggregate_name}
|
||||||
|
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure volume is manageable
|
||||||
|
self._validate_volume_for_manage(volume, vserver_client)
|
||||||
|
|
||||||
|
# Validate extra specs
|
||||||
|
extra_specs = share_types.get_extra_specs_from_share(share)
|
||||||
|
try:
|
||||||
|
self._check_extra_specs_validity(share, extra_specs)
|
||||||
|
self._check_aggregate_extra_specs_validity(aggregate_name,
|
||||||
|
extra_specs)
|
||||||
|
except exception.ManilaException as ex:
|
||||||
|
raise exception.ManageExistingShareTypeMismatch(
|
||||||
|
reason=six.text_type(ex))
|
||||||
|
provisioning_options = self._get_provisioning_options(extra_specs)
|
||||||
|
|
||||||
|
debug_args = {
|
||||||
|
'share': share_name,
|
||||||
|
'aggr': aggregate_name,
|
||||||
|
'options': provisioning_options
|
||||||
|
}
|
||||||
|
LOG.debug('Managing share %(share)s on aggregate %(aggr)s with '
|
||||||
|
'provisioning options %(options)s', debug_args)
|
||||||
|
|
||||||
|
# Rename & remount volume on new path
|
||||||
|
vserver_client.unmount_volume(volume_name)
|
||||||
|
vserver_client.set_volume_name(volume_name, share_name)
|
||||||
|
vserver_client.mount_volume(share_name)
|
||||||
|
|
||||||
|
# Modify volume to match extra specs
|
||||||
|
vserver_client.manage_volume(aggregate_name, share_name,
|
||||||
|
**provisioning_options)
|
||||||
|
|
||||||
|
# Save original volume info to private storage
|
||||||
|
original_data = {
|
||||||
|
'original_name': volume['name'],
|
||||||
|
'original_junction_path': volume['junction-path']
|
||||||
|
}
|
||||||
|
self.private_storage.update(share['id'], original_data)
|
||||||
|
|
||||||
|
# When calculating the size, round up to the next GB.
|
||||||
|
return int(math.ceil(float(volume['size']) / units.Gi))
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _validate_volume_for_manage(self, volume, vserver_client):
|
||||||
|
"""Ensure volume is a candidate for becoming a share."""
|
||||||
|
|
||||||
|
# Check volume info, extra specs validity
|
||||||
|
if volume['type'] != 'rw' or volume['style'] != 'flex':
|
||||||
|
msg = _('Volume %(volume)s must be a read-write flexible volume.')
|
||||||
|
msg_args = {'volume': volume['name']}
|
||||||
|
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||||
|
|
||||||
|
if vserver_client.volume_has_luns(volume['name']):
|
||||||
|
msg = _('Volume %(volume)s must not contain LUNs.')
|
||||||
|
msg_args = {'volume': volume['name']}
|
||||||
|
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||||
|
|
||||||
|
if vserver_client.volume_has_junctioned_volumes(volume['name']):
|
||||||
|
msg = _('Volume %(volume)s must not have junctioned volumes.')
|
||||||
|
msg_args = {'volume': volume['name']}
|
||||||
|
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def extend_share(self, share, new_size, share_server=None):
|
def extend_share(self, share, new_size, share_server=None):
|
||||||
"""Extends size of existing share."""
|
"""Extends size of existing share."""
|
||||||
|
@ -47,3 +47,7 @@ class NetAppBaseHelper(object):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_target(self, share):
|
def get_target(self, share):
|
||||||
"""Returns host where the share located."""
|
"""Returns host where the share located."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_share_name_for_share(self, share):
|
||||||
|
"""Returns the flexvol name that hosts a share."""
|
@ -88,6 +88,12 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
|||||||
"""Returns OnTap target IP based on share export location."""
|
"""Returns OnTap target IP based on share export location."""
|
||||||
return self._get_export_location(share)[0]
|
return self._get_export_location(share)[0]
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def get_share_name_for_share(self, share):
|
||||||
|
"""Returns the flexvol name that hosts a share."""
|
||||||
|
_, share_name = self._get_export_location(share)
|
||||||
|
return share_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_export_location(share):
|
def _get_export_location(share):
|
||||||
"""Returns host ip and share name for a given CIFS share."""
|
"""Returns host ip and share name for a given CIFS share."""
|
||||||
|
@ -84,11 +84,18 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
|
|||||||
"""Returns ID of target OnTap device based on export location."""
|
"""Returns ID of target OnTap device based on export location."""
|
||||||
return self._get_export_location(share)[0]
|
return self._get_export_location(share)[0]
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def get_share_name_for_share(self, share):
|
||||||
|
"""Returns the flexvol name that hosts a share."""
|
||||||
|
_, volume_junction_path = self._get_export_location(share)
|
||||||
|
volume = self._client.get_volume_at_junction_path(volume_junction_path)
|
||||||
|
return volume.get('name') if volume else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_export_location(share):
|
def _get_export_location(share):
|
||||||
"""Returns IP address and export location of an NFS share."""
|
"""Returns IP address and export location of an NFS share."""
|
||||||
export_location = share['export_location'] or ':'
|
export_location = share['export_location'] or ':'
|
||||||
return export_location.split(':')
|
return export_location.rsplit(':', 1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_export_policy_name(share):
|
def _get_export_policy_name(share):
|
||||||
|
@ -35,10 +35,13 @@ SHARE_AGGREGATE_NAMES = ('fake_aggr1', 'fake_aggr2')
|
|||||||
SHARE_AGGREGATE_RAID_TYPES = ('raid4', 'raid_dp')
|
SHARE_AGGREGATE_RAID_TYPES = ('raid4', 'raid_dp')
|
||||||
SHARE_AGGREGATE_DISK_TYPE = 'FCAL'
|
SHARE_AGGREGATE_DISK_TYPE = 'FCAL'
|
||||||
SHARE_NAME = 'fake_share'
|
SHARE_NAME = 'fake_share'
|
||||||
|
SHARE_SIZE = '1000000000'
|
||||||
SNAPSHOT_NAME = 'fake_snapshot'
|
SNAPSHOT_NAME = 'fake_snapshot'
|
||||||
PARENT_SHARE_NAME = 'fake_parent_share'
|
PARENT_SHARE_NAME = 'fake_parent_share'
|
||||||
PARENT_SNAPSHOT_NAME = 'fake_parent_snapshot'
|
PARENT_SNAPSHOT_NAME = 'fake_parent_snapshot'
|
||||||
MAX_FILES = 5000
|
MAX_FILES = 5000
|
||||||
|
LANGUAGE = 'fake_language'
|
||||||
|
SNAPSHOT_POLICY_NAME = 'fake_snapshot_policy'
|
||||||
EXPORT_POLICY_NAME = 'fake_export_policy'
|
EXPORT_POLICY_NAME = 'fake_export_policy'
|
||||||
DELETED_EXPORT_POLICIES = {
|
DELETED_EXPORT_POLICIES = {
|
||||||
VSERVER_NAME: [
|
VSERVER_NAME: [
|
||||||
@ -1366,3 +1369,76 @@ DELETED_EXPORT_POLICY_GET_ITER_RESPONSE = etree.XML("""
|
|||||||
'policy2': DELETED_EXPORT_POLICIES[VSERVER_NAME][1],
|
'policy2': DELETED_EXPORT_POLICIES[VSERVER_NAME][1],
|
||||||
'policy3': DELETED_EXPORT_POLICIES[VSERVER_NAME_2][0],
|
'policy3': DELETED_EXPORT_POLICIES[VSERVER_NAME_2][0],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
LUN_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<lun-info>
|
||||||
|
<path>/vol/%(volume)s/fakelun</path>
|
||||||
|
<qtree />
|
||||||
|
<volume>%(volume)s</volume>
|
||||||
|
<vserver>%(vserver)s</vserver>
|
||||||
|
</lun-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % {
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
})
|
||||||
|
|
||||||
|
VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<volume-attributes>
|
||||||
|
<volume-id-attributes>
|
||||||
|
<name>fake_volume</name>
|
||||||
|
<owning-vserver-name>test</owning-vserver-name>
|
||||||
|
</volume-id-attributes>
|
||||||
|
</volume-attributes>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""")
|
||||||
|
|
||||||
|
VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<volume-attributes>
|
||||||
|
<volume-id-attributes>
|
||||||
|
<containing-aggregate-name>%(aggr)s</containing-aggregate-name>
|
||||||
|
<junction-path>/%(volume)s</junction-path>
|
||||||
|
<name>%(volume)s</name>
|
||||||
|
<owning-vserver-name>%(vserver)s</owning-vserver-name>
|
||||||
|
<style>flex</style>
|
||||||
|
<type>rw</type>
|
||||||
|
</volume-id-attributes>
|
||||||
|
<volume-space-attributes>
|
||||||
|
<size>%(size)s</size>
|
||||||
|
</volume-space-attributes>
|
||||||
|
</volume-attributes>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % {
|
||||||
|
'aggr': SHARE_AGGREGATE_NAME,
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
'size': SHARE_SIZE,
|
||||||
|
})
|
||||||
|
|
||||||
|
SIS_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<sis-status-info>
|
||||||
|
<is-compression-enabled>true</is-compression-enabled>
|
||||||
|
<path>/vol/%(volume)s</path>
|
||||||
|
<state>enabled</state>
|
||||||
|
<vserver>%(vserver)s</vserver>
|
||||||
|
</sis-status-info>
|
||||||
|
</attributes-list>
|
||||||
|
</results>
|
||||||
|
""" % {
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
})
|
||||||
|
@ -1668,6 +1668,96 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME)
|
self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME)
|
||||||
self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME)
|
self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
def test_enable_dedup(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.enable_dedup(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
sis_enable_args = {'path': '/vol/%s' % fake.SHARE_NAME}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with('sis-enable',
|
||||||
|
sis_enable_args)
|
||||||
|
|
||||||
|
def test_disable_dedup(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.disable_dedup(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
sis_disable_args = {'path': '/vol/%s' % fake.SHARE_NAME}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with('sis-disable',
|
||||||
|
sis_disable_args)
|
||||||
|
|
||||||
|
def test_enable_compression(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.enable_compression(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
sis_set_config_args = {
|
||||||
|
'path': '/vol/%s' % fake.SHARE_NAME,
|
||||||
|
'enable-compression': 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with('sis-set-config',
|
||||||
|
sis_set_config_args)
|
||||||
|
|
||||||
|
def test_disable_compression(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.disable_compression(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
sis_set_config_args = {
|
||||||
|
'path': '/vol/%s' % fake.SHARE_NAME,
|
||||||
|
'enable-compression': 'false'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with('sis-set-config',
|
||||||
|
sis_set_config_args)
|
||||||
|
|
||||||
|
def test_get_volume_efficiency_status(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.SIS_GET_ITER_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.get_volume_efficiency_status(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
sis_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'sis-status-info': {
|
||||||
|
'path': '/vol/%s' % fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'sis-status-info': {
|
||||||
|
'state': None,
|
||||||
|
'is-compression-enabled': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('sis-get-iter', sis_get_iter_args)])
|
||||||
|
|
||||||
|
expected = {'dedupe': True, 'compression': True}
|
||||||
|
self.assertDictEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_volume_efficiency_status_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.get_volume_efficiency_status(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
expected = {'dedupe': False, 'compression': False}
|
||||||
|
self.assertDictEqual(expected, result)
|
||||||
|
|
||||||
def test_set_volume_max_files(self):
|
def test_set_volume_max_files(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
@ -1694,30 +1784,154 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
self.client.send_request.assert_called_once_with(
|
self.client.send_request.assert_called_once_with(
|
||||||
'volume-modify-iter', volume_modify_iter_api_args)
|
'volume-modify-iter', volume_modify_iter_api_args)
|
||||||
|
|
||||||
def test_enable_dedup(self):
|
def test_set_volume_name(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.enable_dedup(fake.SHARE_NAME)
|
self.client.set_volume_name(fake.SHARE_NAME, 'new_name')
|
||||||
|
|
||||||
sis_enable_args = {'path': '/vol/%s' % fake.SHARE_NAME}
|
volume_rename_api_args = {
|
||||||
|
'volume': fake.SHARE_NAME,
|
||||||
self.client.send_request.assert_called_once_with('sis-enable',
|
'new-volume-name': 'new_name',
|
||||||
sis_enable_args)
|
|
||||||
|
|
||||||
def test_enable_compression(self):
|
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
|
||||||
|
|
||||||
self.client.enable_compression(fake.SHARE_NAME)
|
|
||||||
|
|
||||||
sis_set_config_args = {
|
|
||||||
'path': '/vol/%s' % fake.SHARE_NAME,
|
|
||||||
'enable-compression': 'true'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.send_request.assert_called_once_with('sis-set-config',
|
self.client.send_request.assert_called_once_with(
|
||||||
sis_set_config_args)
|
'volume-rename', volume_rename_api_args)
|
||||||
|
|
||||||
|
def test_manage_volume_no_optional_args(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
mock_update_volume_efficiency_attributes = self.mock_object(
|
||||||
|
self.client, 'update_volume_efficiency_attributes')
|
||||||
|
|
||||||
|
self.client.manage_volume(fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME)
|
||||||
|
|
||||||
|
volume_modify_iter_api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME,
|
||||||
|
'name': fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-inode-attributes': {},
|
||||||
|
'volume-language-attributes': {},
|
||||||
|
'volume-snapshot-attributes': {},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'space-guarantee': 'volume',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'volume-modify-iter', volume_modify_iter_api_args)
|
||||||
|
mock_update_volume_efficiency_attributes.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME, False, False)
|
||||||
|
|
||||||
|
def test_manage_volume_all_optional_args(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
mock_update_volume_efficiency_attributes = self.mock_object(
|
||||||
|
self.client, 'update_volume_efficiency_attributes')
|
||||||
|
|
||||||
|
self.client.manage_volume(fake.SHARE_AGGREGATE_NAME,
|
||||||
|
fake.SHARE_NAME,
|
||||||
|
thin_provisioned=True,
|
||||||
|
snapshot_policy=fake.SNAPSHOT_POLICY_NAME,
|
||||||
|
language=fake.LANGUAGE,
|
||||||
|
dedup_enabled=True,
|
||||||
|
compression_enabled=False,
|
||||||
|
max_files=fake.MAX_FILES)
|
||||||
|
|
||||||
|
volume_modify_iter_api_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME,
|
||||||
|
'name': fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-inode-attributes': {
|
||||||
|
'files-total': fake.MAX_FILES,
|
||||||
|
},
|
||||||
|
'volume-language-attributes': {
|
||||||
|
'language': fake.LANGUAGE,
|
||||||
|
},
|
||||||
|
'volume-snapshot-attributes': {
|
||||||
|
'snapshot-policy': fake.SNAPSHOT_POLICY_NAME,
|
||||||
|
},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'space-guarantee': 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'volume-modify-iter', volume_modify_iter_api_args)
|
||||||
|
mock_update_volume_efficiency_attributes.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME, True, False)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{'existing': (True, True), 'desired': (True, True)},
|
||||||
|
{'existing': (True, True), 'desired': (False, False)},
|
||||||
|
{'existing': (True, True), 'desired': (True, False)},
|
||||||
|
{'existing': (True, False), 'desired': (True, False)},
|
||||||
|
{'existing': (True, False), 'desired': (False, False)},
|
||||||
|
{'existing': (True, False), 'desired': (True, True)},
|
||||||
|
{'existing': (False, False), 'desired': (False, False)},
|
||||||
|
{'existing': (False, False), 'desired': (True, False)},
|
||||||
|
{'existing': (False, False), 'desired': (True, True)},
|
||||||
|
)
|
||||||
|
@ddt.unpack
|
||||||
|
def test_update_volume_efficiency_attributes(self, existing, desired):
|
||||||
|
|
||||||
|
existing_dedupe = existing[0]
|
||||||
|
existing_compression = existing[1]
|
||||||
|
desired_dedupe = desired[0]
|
||||||
|
desired_compression = desired[1]
|
||||||
|
|
||||||
|
self.mock_object(
|
||||||
|
self.client,
|
||||||
|
'get_volume_efficiency_status',
|
||||||
|
mock.Mock(return_value={'dedupe': existing_dedupe,
|
||||||
|
'compression': existing_compression}))
|
||||||
|
mock_enable_compression = self.mock_object(self.client,
|
||||||
|
'enable_compression')
|
||||||
|
mock_disable_compression = self.mock_object(self.client,
|
||||||
|
'disable_compression')
|
||||||
|
mock_enable_dedup = self.mock_object(self.client, 'enable_dedup')
|
||||||
|
mock_disable_dedup = self.mock_object(self.client, 'disable_dedup')
|
||||||
|
|
||||||
|
self.client.update_volume_efficiency_attributes(
|
||||||
|
fake.SHARE_NAME, desired_dedupe, desired_compression)
|
||||||
|
|
||||||
|
if existing_dedupe == desired_dedupe:
|
||||||
|
self.assertFalse(mock_enable_dedup.called)
|
||||||
|
self.assertFalse(mock_disable_dedup.called)
|
||||||
|
elif existing_dedupe and not desired_dedupe:
|
||||||
|
self.assertFalse(mock_enable_dedup.called)
|
||||||
|
self.assertTrue(mock_disable_dedup.called)
|
||||||
|
elif not existing_dedupe and desired_dedupe:
|
||||||
|
self.assertTrue(mock_enable_dedup.called)
|
||||||
|
self.assertFalse(mock_disable_dedup.called)
|
||||||
|
|
||||||
|
if existing_compression == desired_compression:
|
||||||
|
self.assertFalse(mock_enable_compression.called)
|
||||||
|
self.assertFalse(mock_disable_compression.called)
|
||||||
|
elif existing_compression and not desired_compression:
|
||||||
|
self.assertFalse(mock_enable_compression.called)
|
||||||
|
self.assertTrue(mock_disable_compression.called)
|
||||||
|
elif not existing_compression and desired_compression:
|
||||||
|
self.assertTrue(mock_enable_compression.called)
|
||||||
|
self.assertFalse(mock_disable_compression.called)
|
||||||
|
|
||||||
def test_set_volume_size(self):
|
def test_set_volume_size(self):
|
||||||
|
|
||||||
@ -1842,6 +2056,227 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
self.client.get_aggregate_for_volume,
|
self.client.get_aggregate_for_volume,
|
||||||
fake.SHARE_NAME)
|
fake.SHARE_NAME)
|
||||||
|
|
||||||
|
def test_volume_has_luns(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.LUN_GET_ITER_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.volume_has_luns(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
lun_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'lun-info': {
|
||||||
|
'volume': fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'lun-info': {
|
||||||
|
'path': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('lun-get-iter', lun_get_iter_args)])
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def test_volume_has_luns_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.volume_has_luns(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_volume_has_junctioned_volumes(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
fake_junction_path = '/%s' % fake.SHARE_NAME
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'get_volume_junction_path',
|
||||||
|
mock.Mock(return_value=fake_junction_path))
|
||||||
|
|
||||||
|
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
volume_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'junction-path': fake_junction_path + '/*',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'name': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def test_volume_has_junctioned_volumes_no_junction_path(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'get_volume_junction_path',
|
||||||
|
mock.Mock(return_value=''))
|
||||||
|
|
||||||
|
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_volume_has_junctioned_volumes_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
fake_junction_path = '/%s' % fake.SHARE_NAME
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'get_volume_junction_path',
|
||||||
|
mock.Mock(return_value=fake_junction_path))
|
||||||
|
|
||||||
|
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_get_volume_at_junction_path(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
fake_junction_path = '/%s' % fake.SHARE_NAME
|
||||||
|
|
||||||
|
result = self.client.get_volume_at_junction_path(fake_junction_path)
|
||||||
|
|
||||||
|
volume_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'junction-path': fake_junction_path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': None,
|
||||||
|
'junction-path': None,
|
||||||
|
'name': None,
|
||||||
|
'type': None,
|
||||||
|
'style': None,
|
||||||
|
},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'size': None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'aggregate': fake.SHARE_AGGREGATE_NAME,
|
||||||
|
'junction-path': fake_junction_path,
|
||||||
|
'name': fake.SHARE_NAME,
|
||||||
|
'type': 'rw',
|
||||||
|
'style': 'flex',
|
||||||
|
'size': fake.SHARE_SIZE,
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||||
|
self.assertDictEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_volume_at_junction_path_not_specified(self):
|
||||||
|
|
||||||
|
result = self.client.get_volume_at_junction_path(None)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_get_volume_at_junction_path_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
fake_junction_path = '/%s' % fake.SHARE_NAME
|
||||||
|
|
||||||
|
result = self.client.get_volume_at_junction_path(fake_junction_path)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_get_volume_to_manage(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.get_volume_to_manage(fake.SHARE_AGGREGATE_NAME,
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
|
||||||
|
volume_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME,
|
||||||
|
'name': fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'volume-attributes': {
|
||||||
|
'volume-id-attributes': {
|
||||||
|
'containing-aggregate-name': None,
|
||||||
|
'junction-path': None,
|
||||||
|
'name': None,
|
||||||
|
'type': None,
|
||||||
|
'style': None,
|
||||||
|
},
|
||||||
|
'volume-space-attributes': {
|
||||||
|
'size': None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'aggregate': fake.SHARE_AGGREGATE_NAME,
|
||||||
|
'junction-path': '/%s' % fake.SHARE_NAME,
|
||||||
|
'name': fake.SHARE_NAME,
|
||||||
|
'type': 'rw',
|
||||||
|
'style': 'flex',
|
||||||
|
'size': fake.SHARE_SIZE,
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||||
|
self.assertDictEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_volume_to_manage_not_found(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.get_volume_to_manage(fake.SHARE_AGGREGATE_NAME,
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_create_volume_clone(self):
|
def test_create_volume_clone(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
@ -1910,6 +2345,35 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
mock.call('volume-get-volume-path', volume_get_volume_path_args)])
|
mock.call('volume-get-volume-path', volume_get_volume_path_args)])
|
||||||
self.assertEqual(fake.VOLUME_JUNCTION_PATH_CIFS, result)
|
self.assertEqual(fake.VOLUME_JUNCTION_PATH_CIFS, result)
|
||||||
|
|
||||||
|
def test_mount_volume_default_junction_path(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.mount_volume(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
volume_mount_args = {
|
||||||
|
'volume-name': fake.SHARE_NAME,
|
||||||
|
'junction-path': '/%s' % fake.SHARE_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-mount', volume_mount_args)])
|
||||||
|
|
||||||
|
def test_mount_volume(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
fake_path = '/fake_path'
|
||||||
|
|
||||||
|
self.client.mount_volume(fake.SHARE_NAME, junction_path=fake_path)
|
||||||
|
|
||||||
|
volume_mount_args = {
|
||||||
|
'volume-name': fake.SHARE_NAME,
|
||||||
|
'junction-path': fake_path,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-mount', volume_mount_args)])
|
||||||
|
|
||||||
def test_offline_volume(self):
|
def test_offline_volume(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
@ -17,6 +17,7 @@ Unit tests for the NetApp Data ONTAP cDOT base storage driver library.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import math
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ import ddt
|
|||||||
import mock
|
import mock
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.share.drivers.netapp.dataontap.client import client_cmode
|
from manila.share.drivers.netapp.dataontap.client import client_cmode
|
||||||
@ -63,6 +65,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'configuration': fake.get_config_cmode(),
|
'configuration': fake.get_config_cmode(),
|
||||||
|
'private_storage': mock.Mock(),
|
||||||
'app_version': fake.APP_VERSION
|
'app_version': fake.APP_VERSION
|
||||||
}
|
}
|
||||||
self.library = lib_base.NetAppCmodeFileStorageLibrary(fake.DRIVER_NAME,
|
self.library = lib_base.NetAppCmodeFileStorageLibrary(fake.DRIVER_NAME,
|
||||||
@ -730,6 +733,26 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
fake_share,
|
fake_share,
|
||||||
vserver_client)
|
vserver_client)
|
||||||
|
|
||||||
|
def test_check_aggregate_extra_specs_validity(self):
|
||||||
|
|
||||||
|
self.library._have_cluster_creds = True
|
||||||
|
self.library._ssc_stats = fake.SSC_INFO
|
||||||
|
|
||||||
|
result = self.library._check_aggregate_extra_specs_validity(
|
||||||
|
fake.AGGREGATES[0], fake.EXTRA_SPEC)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_check_aggregate_extra_specs_validity_no_match(self):
|
||||||
|
|
||||||
|
self.library._have_cluster_creds = True
|
||||||
|
self.library._ssc_stats = fake.SSC_INFO
|
||||||
|
|
||||||
|
self.assertRaises(exception.NetAppException,
|
||||||
|
self.library._check_aggregate_extra_specs_validity,
|
||||||
|
fake.AGGREGATES[1],
|
||||||
|
fake.EXTRA_SPEC)
|
||||||
|
|
||||||
def test_allocate_container_from_snapshot(self):
|
def test_allocate_container_from_snapshot(self):
|
||||||
|
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
@ -1086,6 +1109,231 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
mock_sleep.assert_has_calls([mock.call(3)] * 20)
|
mock_sleep.assert_has_calls([mock.call(3)] * 20)
|
||||||
self.assertEqual(20, lib_base.LOG.debug.call_count)
|
self.assertEqual(20, lib_base.LOG.debug.call_count)
|
||||||
|
|
||||||
|
def test_manage_existing(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
mock_manage_container = self.mock_object(
|
||||||
|
self.library,
|
||||||
|
'_manage_container',
|
||||||
|
mock.Mock(return_value=fake.SHARE_SIZE))
|
||||||
|
mock_create_export = self.mock_object(
|
||||||
|
self.library,
|
||||||
|
'_create_export',
|
||||||
|
mock.Mock(return_value=fake.NFS_EXPORTS))
|
||||||
|
|
||||||
|
result = self.library.manage_existing(fake.SHARE, {})
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'size': fake.SHARE_SIZE,
|
||||||
|
'export_locations': fake.NFS_EXPORTS
|
||||||
|
}
|
||||||
|
mock_manage_container.assert_called_once_with(fake.SHARE,
|
||||||
|
vserver_client)
|
||||||
|
mock_create_export.assert_called_once_with(fake.SHARE,
|
||||||
|
fake.VSERVER1,
|
||||||
|
vserver_client)
|
||||||
|
self.assertDictEqual(expected, result)
|
||||||
|
|
||||||
|
def test_unmanage(self):
|
||||||
|
|
||||||
|
result = self.library.unmanage(fake.SHARE)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_manage_container(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
|
share_to_manage = copy.deepcopy(fake.SHARE)
|
||||||
|
share_to_manage['export_location'] = fake.EXPORT_LOCATION
|
||||||
|
|
||||||
|
mock_helper = mock.Mock()
|
||||||
|
mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_helper',
|
||||||
|
mock.Mock(return_value=mock_helper))
|
||||||
|
|
||||||
|
mock_get_volume_to_manage = self.mock_object(
|
||||||
|
vserver_client,
|
||||||
|
'get_volume_to_manage',
|
||||||
|
mock.Mock(return_value=fake.FLEXVOL_TO_MANAGE))
|
||||||
|
mock_validate_volume_for_manage = self.mock_object(
|
||||||
|
self.library,
|
||||||
|
'_validate_volume_for_manage')
|
||||||
|
self.mock_object(share_types,
|
||||||
|
'get_extra_specs_from_share',
|
||||||
|
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||||
|
mock_check_extra_specs_validity = self.mock_object(
|
||||||
|
self.library,
|
||||||
|
'_check_extra_specs_validity')
|
||||||
|
mock_check_aggregate_extra_specs_validity = self.mock_object(
|
||||||
|
self.library,
|
||||||
|
'_check_aggregate_extra_specs_validity')
|
||||||
|
|
||||||
|
result = self.library._manage_container(share_to_manage,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
mock_get_volume_to_manage.assert_called_once_with(
|
||||||
|
fake.POOL_NAME, fake.FLEXVOL_NAME)
|
||||||
|
mock_validate_volume_for_manage.assert_called_once_with(
|
||||||
|
fake.FLEXVOL_TO_MANAGE, vserver_client)
|
||||||
|
mock_check_extra_specs_validity.assert_called_once_with(
|
||||||
|
share_to_manage, fake.EXTRA_SPEC)
|
||||||
|
mock_check_aggregate_extra_specs_validity.assert_called_once_with(
|
||||||
|
fake.POOL_NAME, fake.EXTRA_SPEC)
|
||||||
|
vserver_client.unmount_volume.assert_called_once_with(
|
||||||
|
fake.FLEXVOL_NAME)
|
||||||
|
vserver_client.set_volume_name.assert_called_once_with(
|
||||||
|
fake.FLEXVOL_NAME, fake.SHARE_NAME)
|
||||||
|
vserver_client.mount_volume.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
vserver_client.manage_volume.assert_called_once_with(
|
||||||
|
fake.POOL_NAME, fake.SHARE_NAME,
|
||||||
|
**self.library._get_provisioning_options(fake.EXTRA_SPEC))
|
||||||
|
|
||||||
|
original_data = {
|
||||||
|
'original_name': fake.FLEXVOL_TO_MANAGE['name'],
|
||||||
|
'original_junction_path': fake.FLEXVOL_TO_MANAGE['junction-path'],
|
||||||
|
}
|
||||||
|
self.library.private_storage.update.assert_called_once_with(
|
||||||
|
fake.SHARE['id'], original_data)
|
||||||
|
|
||||||
|
expected_size = int(
|
||||||
|
math.ceil(float(fake.FLEXVOL_TO_MANAGE['size']) / units.Gi))
|
||||||
|
self.assertEqual(expected_size, result)
|
||||||
|
|
||||||
|
def test_manage_container_invalid_export_location(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
|
share_to_manage = copy.deepcopy(fake.SHARE)
|
||||||
|
share_to_manage['export_location'] = fake.EXPORT_LOCATION
|
||||||
|
|
||||||
|
mock_helper = mock.Mock()
|
||||||
|
mock_helper.get_share_name_for_share.return_value = None
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_helper',
|
||||||
|
mock.Mock(return_value=mock_helper))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageInvalidShare,
|
||||||
|
self.library._manage_container,
|
||||||
|
share_to_manage,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
def test_manage_container_not_found(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
|
share_to_manage = copy.deepcopy(fake.SHARE)
|
||||||
|
share_to_manage['export_location'] = fake.EXPORT_LOCATION
|
||||||
|
|
||||||
|
mock_helper = mock.Mock()
|
||||||
|
mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_helper',
|
||||||
|
mock.Mock(return_value=mock_helper))
|
||||||
|
|
||||||
|
self.mock_object(vserver_client,
|
||||||
|
'get_volume_to_manage',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageInvalidShare,
|
||||||
|
self.library._manage_container,
|
||||||
|
share_to_manage,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
def test_manage_container_invalid_extra_specs(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
|
share_to_manage = copy.deepcopy(fake.SHARE)
|
||||||
|
share_to_manage['export_location'] = fake.EXPORT_LOCATION
|
||||||
|
|
||||||
|
mock_helper = mock.Mock()
|
||||||
|
mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_helper',
|
||||||
|
mock.Mock(return_value=mock_helper))
|
||||||
|
|
||||||
|
self.mock_object(vserver_client,
|
||||||
|
'get_volume_to_manage',
|
||||||
|
mock.Mock(return_value=fake.FLEXVOL_TO_MANAGE))
|
||||||
|
self.mock_object(self.library, '_validate_volume_for_manage')
|
||||||
|
self.mock_object(share_types,
|
||||||
|
'get_extra_specs_from_share',
|
||||||
|
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_check_extra_specs_validity',
|
||||||
|
mock.Mock(side_effect=exception.NetAppException))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageExistingShareTypeMismatch,
|
||||||
|
self.library._manage_container,
|
||||||
|
share_to_manage,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
def test_validate_volume_for_manage(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.volume_has_luns = mock.Mock(return_value=False)
|
||||||
|
vserver_client.volume_has_junctioned_volumes = mock.Mock(
|
||||||
|
return_value=False)
|
||||||
|
|
||||||
|
result = self.library._validate_volume_for_manage(
|
||||||
|
fake.FLEXVOL_TO_MANAGE, vserver_client)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
@ddt.data({
|
||||||
|
'attribute': 'type',
|
||||||
|
'value': 'dp',
|
||||||
|
}, {
|
||||||
|
'attribute': 'style',
|
||||||
|
'value': 'infinitevol',
|
||||||
|
})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_validate_volume_for_manage_invalid_volume(self, attribute, value):
|
||||||
|
|
||||||
|
flexvol_to_manage = copy.deepcopy(fake.FLEXVOL_TO_MANAGE)
|
||||||
|
flexvol_to_manage[attribute] = value
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.volume_has_luns = mock.Mock(return_value=False)
|
||||||
|
vserver_client.volume_has_junctioned_volumes = mock.Mock(
|
||||||
|
return_value=False)
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageInvalidShare,
|
||||||
|
self.library._validate_volume_for_manage,
|
||||||
|
flexvol_to_manage,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
def test_validate_volume_for_manage_luns_present(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.volume_has_luns = mock.Mock(return_value=True)
|
||||||
|
vserver_client.volume_has_junctioned_volumes = mock.Mock(
|
||||||
|
return_value=False)
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageInvalidShare,
|
||||||
|
self.library._validate_volume_for_manage,
|
||||||
|
fake.FLEXVOL_TO_MANAGE,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
|
def test_validate_volume_for_manage_junctioned_volumes_present(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.volume_has_luns = mock.Mock(return_value=False)
|
||||||
|
vserver_client.volume_has_junctioned_volumes = mock.Mock(
|
||||||
|
return_value=True)
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageInvalidShare,
|
||||||
|
self.library._validate_volume_for_manage,
|
||||||
|
fake.FLEXVOL_TO_MANAGE,
|
||||||
|
vserver_client)
|
||||||
|
|
||||||
def test_extend_share(self):
|
def test_extend_share(self):
|
||||||
|
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
|
@ -51,6 +51,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'configuration': fake.get_config_cmode(),
|
'configuration': fake.get_config_cmode(),
|
||||||
|
'private_storage': mock.Mock(),
|
||||||
'app_version': fake.APP_VERSION
|
'app_version': fake.APP_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'configuration': config,
|
'configuration': config,
|
||||||
|
'private_storage': mock.Mock(),
|
||||||
'app_version': fake.APP_VERSION
|
'app_version': fake.APP_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,9 @@ VOLUME_NAME_TEMPLATE = 'share_%(share_id)s'
|
|||||||
VSERVER_NAME_TEMPLATE = 'os_%s'
|
VSERVER_NAME_TEMPLATE = 'os_%s'
|
||||||
AGGREGATE_NAME_SEARCH_PATTERN = '(.*)'
|
AGGREGATE_NAME_SEARCH_PATTERN = '(.*)'
|
||||||
SHARE_NAME = 'share_7cf7c200_d3af_4e05_b87e_9167c95dfcad'
|
SHARE_NAME = 'share_7cf7c200_d3af_4e05_b87e_9167c95dfcad'
|
||||||
|
FLEXVOL_NAME = 'fake_volume'
|
||||||
|
JUNCTION_PATH = '/%s' % FLEXVOL_NAME
|
||||||
|
EXPORT_LOCATION = '%s:%s' % (HOST_NAME, JUNCTION_PATH)
|
||||||
SNAPSHOT_NAME = 'fake_snapshot'
|
SNAPSHOT_NAME = 'fake_snapshot'
|
||||||
SHARE_SIZE = 10
|
SHARE_SIZE = 10
|
||||||
TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57'
|
TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57'
|
||||||
@ -73,7 +76,16 @@ SHARE = {
|
|||||||
'share_server_id': '7e6a2cc8-871f-4b1d-8364-5aad0f98da86',
|
'share_server_id': '7e6a2cc8-871f-4b1d-8364-5aad0f98da86',
|
||||||
'network_info': {
|
'network_info': {
|
||||||
'network_allocations': [{'ip_address': 'ip'}]
|
'network_allocations': [{'ip_address': 'ip'}]
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
FLEXVOL_TO_MANAGE = {
|
||||||
|
'aggregate': POOL_NAME,
|
||||||
|
'junction-path': '/%s' % FLEXVOL_NAME,
|
||||||
|
'name': FLEXVOL_NAME,
|
||||||
|
'type': 'rw',
|
||||||
|
'style': 'flex',
|
||||||
|
'size': '1610612736', # rounds down to 1 GB
|
||||||
}
|
}
|
||||||
|
|
||||||
EXTRA_SPEC = {
|
EXTRA_SPEC = {
|
||||||
@ -83,6 +95,8 @@ EXTRA_SPEC = {
|
|||||||
'netapp:dedup': 'True',
|
'netapp:dedup': 'True',
|
||||||
'netapp:compression': 'false',
|
'netapp:compression': 'false',
|
||||||
'netapp:max_files': 5000,
|
'netapp:max_files': 5000,
|
||||||
|
'netapp_disk_type': 'FCAL',
|
||||||
|
'netapp_raid_type': 'raid4',
|
||||||
}
|
}
|
||||||
|
|
||||||
PROVISIONING_OPTIONS = {
|
PROVISIONING_OPTIONS = {
|
||||||
|
@ -45,3 +45,7 @@ USER_ACCESS = {
|
|||||||
'access_to': 'fake_user',
|
'access_to': 'fake_user',
|
||||||
'access_level': constants.ACCESS_LEVEL_RW,
|
'access_level': constants.ACCESS_LEVEL_RW,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VOLUME = {
|
||||||
|
'name': SHARE_NAME,
|
||||||
|
}
|
||||||
|
@ -189,6 +189,12 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||||||
target = self.helper.get_target({'export_location': ''})
|
target = self.helper.get_target({'export_location': ''})
|
||||||
self.assertEqual('', target)
|
self.assertEqual('', target)
|
||||||
|
|
||||||
|
def test_get_share_name_for_share(self):
|
||||||
|
|
||||||
|
share_name = self.helper.get_share_name_for_share(fake.CIFS_SHARE)
|
||||||
|
|
||||||
|
self.assertEqual(fake.SHARE_NAME, share_name)
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
{
|
{
|
||||||
'location': r'\\%s\%s' % (fake.SHARE_ADDRESS_1, fake.SHARE_NAME),
|
'location': r'\\%s\%s' % (fake.SHARE_ADDRESS_1, fake.SHARE_NAME),
|
||||||
|
@ -172,6 +172,27 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
|||||||
target = self.helper.get_target(fake.NFS_SHARE)
|
target = self.helper.get_target(fake.NFS_SHARE)
|
||||||
self.assertEqual(fake.SHARE_ADDRESS_1, target)
|
self.assertEqual(fake.SHARE_ADDRESS_1, target)
|
||||||
|
|
||||||
|
def test_get_share_name_for_share(self):
|
||||||
|
|
||||||
|
self.mock_client.get_volume_at_junction_path.return_value = (
|
||||||
|
fake.VOLUME)
|
||||||
|
|
||||||
|
share_name = self.helper.get_share_name_for_share(fake.NFS_SHARE)
|
||||||
|
|
||||||
|
self.assertEqual(fake.SHARE_NAME, share_name)
|
||||||
|
self.mock_client.get_volume_at_junction_path.assert_called_once_with(
|
||||||
|
fake.NFS_SHARE_PATH)
|
||||||
|
|
||||||
|
def test_get_share_name_for_share_not_found(self):
|
||||||
|
|
||||||
|
self.mock_client.get_volume_at_junction_path.return_value = None
|
||||||
|
|
||||||
|
share_name = self.helper.get_share_name_for_share(fake.NFS_SHARE)
|
||||||
|
|
||||||
|
self.assertIsNone(share_name)
|
||||||
|
self.mock_client.get_volume_at_junction_path.assert_called_once_with(
|
||||||
|
fake.NFS_SHARE_PATH)
|
||||||
|
|
||||||
def test_get_target_missing_location(self):
|
def test_get_target_missing_location(self):
|
||||||
|
|
||||||
target = self.helper.get_target({'export_location': ''})
|
target = self.helper.get_target({'export_location': ''})
|
||||||
|
@ -124,7 +124,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
|||||||
config = na_fakes.create_configuration()
|
config = na_fakes.create_configuration()
|
||||||
config.local_conf.set_override('driver_handles_share_servers',
|
config.local_conf.set_override('driver_handles_share_servers',
|
||||||
mode == na_common.MULTI_SVM)
|
mode == na_common.MULTI_SVM)
|
||||||
kwargs = {'configuration': config, 'app_version': 'fake_info'}
|
kwargs = {
|
||||||
|
'configuration': config,
|
||||||
|
'private_storage': mock.Mock(),
|
||||||
|
'app_version': 'fake_info'
|
||||||
|
}
|
||||||
|
|
||||||
driver = na_common.NetAppDriver._create_driver(
|
driver = na_common.NetAppDriver._create_driver(
|
||||||
family, mode, **kwargs)
|
family, mode, **kwargs)
|
||||||
@ -136,7 +140,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
|||||||
config = na_fakes.create_configuration()
|
config = na_fakes.create_configuration()
|
||||||
config.local_conf.set_override('driver_handles_share_servers', True)
|
config.local_conf.set_override('driver_handles_share_servers', True)
|
||||||
|
|
||||||
kwargs = {'configuration': config, 'app_version': 'fake_info'}
|
kwargs = {
|
||||||
|
'configuration': config,
|
||||||
|
'private_storage': mock.Mock(),
|
||||||
|
'app_version': 'fake_info'
|
||||||
|
}
|
||||||
|
|
||||||
driver = na_common.NetAppDriver._create_driver('ONTAP_CLUSTER',
|
driver = na_common.NetAppDriver._create_driver('ONTAP_CLUSTER',
|
||||||
na_common.MULTI_SVM,
|
na_common.MULTI_SVM,
|
||||||
|
Loading…
Reference in New Issue
Block a user