NetApp ONTAP: Add core functions on REST client

This patch contains the implementation of the core functions of the
NetApp drivers (NFS, iSCSI and FCP). The functions were migrated
from ZAPI to REST API, but ZAPI client was not removed since it is
being used as a fallback mechamism when a function does not have an
equivalent in the REST API.

In summary, the features implemented in this patch are related to:
  > Periodic tasks - methods used during driver initialization and
    executed periodically to get information about the volumes,
    performance metrics and stats
  > Basic volume operations - methods used to create, delete,
    attach, detach and extend volumes

Co-authored-by: Fábio Oliveira <fabioaurelio1269@gmail.com>
Co-authored-by: Fernando Ferraz <sfernand@netapp.com>
Co-authored-by: Luisa Amaral <luisaa@netapp.com>
Co-authored-by: Matheus Andrade <matheus.handrade15@gmail.com>
Co-authored-by: Vinícius Angiolucci Reis <angiolucci@gmail.com>

Change-Id: I67eb7f6264cf5eea94c0a364082b530b9cdc1ae3
partially-implements: blueprint netapp-ontap-rest-api-client
This commit is contained in:
Nahim Alves de Souza 2022-08-04 13:05:59 +00:00 committed by Felipe Rodrigues
parent 4775ca9370
commit 00481aed74
14 changed files with 5304 additions and 176 deletions

View File

@ -1441,6 +1441,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
<num-records>1</num-records>
<attributes-list>
<net-interface-info>
<vserver>fake_vserver</vserver>
</net-interface-info>
</attributes-list>
</results>"""))
@ -4494,3 +4495,46 @@ class NetAppCmodeClientTestCase(test.TestCase):
}
self.client.connection.send_request.assert_called_once_with(
'file-rename-file', api_args)
def test_check_api_permissions(self):
mock_log = self.mock_object(client_cmode.LOG, 'warning')
self.mock_object(self.client, 'check_cluster_api', return_value=True)
self.client.check_api_permissions()
self.client.check_cluster_api.assert_has_calls(
[mock.call(*key) for key in client_cmode.SSC_API_MAP.keys()])
self.assertEqual(0, mock_log.call_count)
def test_check_api_permissions_failed_ssc_apis(self):
def check_cluster_api(object_name, operation_name, api):
if api != 'volume-get-iter':
return False
return True
self.mock_object(self.client, 'check_cluster_api',
side_effect=check_cluster_api)
mock_log = self.mock_object(client_cmode.LOG, 'warning')
self.client.check_api_permissions()
self.assertEqual(1, mock_log.call_count)
def test_check_api_permissions_failed_volume_api(self):
def check_cluster_api(object_name, operation_name, api):
if api == 'volume-get-iter':
return False
return True
self.mock_object(self.client, 'check_cluster_api',
side_effect=check_cluster_api)
mock_log = self.mock_object(client_cmode.LOG, 'warning')
self.assertRaises(exception.VolumeBackendAPIException,
self.client.check_api_permissions)
self.assertEqual(0, mock_log.call_count)

View File

@ -275,8 +275,9 @@ IGROUP1 = {'initiator-group-os-type': 'linux',
QOS_SPECS = {}
EXTRA_SPECS = {}
MAX_THROUGHPUT = '21734278B/s'
MIN_IOPS = '256IOPS'
MAX_IOPS = '512IOPS'
MIN_IOPS = '256iops'
MAX_IOPS = '512iops'
MAX_BPS = '1000000B/s'
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
QOS_POLICY_GROUP_INFO_LEGACY = {
@ -290,6 +291,11 @@ QOS_POLICY_GROUP_SPEC = {
'policy_name': QOS_POLICY_GROUP_NAME,
}
QOS_POLICY_GROUP_SPEC_BPS = {
'max_throughput': MAX_BPS,
'policy_name': QOS_POLICY_GROUP_NAME,
}
QOS_POLICY_GROUP_SPEC_MAX = {
'max_throughput': MAX_THROUGHPUT,
'policy_name': QOS_POLICY_GROUP_NAME,
@ -417,6 +423,19 @@ FAKE_LUN = netapp_api.NaElement.create_node_with_children(
'volume': 'fakeLUN',
'vserver': 'fake_vserver'})
FAKE_LUN_GET_ITER_RESULT = [
{
'Vserver': 'fake_vserver',
'Volume': 'fake_volume',
'Size': 123,
'Qtree': 'fake_qtree',
'Path': 'fake_path',
'OsType': 'fake_os',
'SpaceReserved': 'true',
'UUID': 'fake-uuid',
},
]
CG_VOLUME_NAME = 'fake_cg_volume'
CG_GROUP_NAME = 'fake_consistency_group'
CG_POOL_NAME = 'cdot'
@ -740,12 +759,219 @@ def get_fake_net_interface_get_iter_response():
def get_fake_ifs():
list_of_ifs = [
etree.XML("""<net-interface-info>
<address>FAKE_IP</address></net-interface-info>"""),
etree.XML("""<net-interface-info>
<address>FAKE_IP2</address></net-interface-info>"""),
etree.XML("""<net-interface-info>
<address>FAKE_IP3</address></net-interface-info>"""),
]
return [netapp_api.NaElement(el) for el in list_of_ifs]
return [{'vserver': VSERVER_NAME}]
AFF_SYSTEM_NODE_GET_ITER_RESPONSE_REST = {
"records": [
{
"uuid": "9eff6c76-fc13-11ea-8799-525400",
"name": "aff-node1",
"model": "AFFA400",
"is_all_flash_optimized": True,
"is_all_flash_select_optimized": False,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
},
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "aff-node2",
"model": "AFFA400",
"is_all_flash_optimized": True,
"is_all_flash_select_optimized": False,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
}
],
"num_records": 2,
"_links": {
"self": {
"href": "/api/cluster/nodes?fields=model,name,"
"is_all_flash_optimized,is_all_flash_select_optimized"
}
}
}
FAS_SYSTEM_NODE_GET_ITER_RESPONSE_REST = {
"records": [
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "fas-node1",
"model": "FAS2554",
"is_all_flash_optimized": False,
"is_all_flash_select_optimized": False,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
},
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "fas-node2",
"model": "FAS2554",
"is_all_flash_optimized": False,
"is_all_flash_select_optimized": False,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
}
],
"num_records": 2,
"_links": {
"self": {
"href": "/api/cluster/nodes?fields=model,name,"
"is_all_flash_optimized,is_all_flash_select_optimized"
}
}
}
HYBRID_SYSTEM_NODE_GET_ITER_RESPONSE_REST = {
"records": [
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "select-node",
"model": "FDvM300",
"is_all_flash_optimized": False,
"is_all_flash_select_optimized": True,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
},
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "c190-node",
"model": "AFF-C190",
"is_all_flash_optimized": True,
"is_all_flash_select_optimized": False,
"_links": {
"self": {
"href": "/api/cluster/nodes/9eff6c76-fc13-11ea-8799-525400"
}
}
}
],
"num_records": 2,
"_links": {
"self": {
"href": "/api/cluster/nodes?fields=model,name,"
"is_all_flash_optimized,is_all_flash_select_optimized"
}
}
}
QOS_POLICY_BY_NAME_RESPONSE_REST = {
"records": [
{
"uuid": "9eff6c76-fc13-11ea-8799-52540006bba9",
"name": "openstack-cd-uuid",
"_links": {
"self": {
"href": "/api/storage/qos/policies/"
"9eff6c76-fc13-11ea-8799-52540006bba9"
}
}
}
],
"num_records": 1,
"_links": {
"self": {
"href": "/api/storage/qos/policies?fields=name"
}
}
}
QOS_SPECS_REST = {}
MAX_THROUGHPUT_REST = '21734278'
MIN_IOPS_REST = '256'
MAX_IOPS_REST = '512'
MAX_BPS_REST = '1'
QOS_POLICY_GROUP_INFO_LEGACY_REST = {
'legacy': 'legacy-' + QOS_POLICY_GROUP_NAME,
'spec': None,
}
QOS_POLICY_GROUP_SPEC_REST = {
'min_throughput': MIN_IOPS_REST,
'max_throughput': MAX_IOPS_REST,
'policy_name': QOS_POLICY_GROUP_NAME,
}
QOS_POLICY_GROUP_API_ARGS_REST = {
'name': QOS_POLICY_GROUP_NAME,
'svm': {
'name': VSERVER_NAME
},
'fixed': {
'max_throughput_iops': int(MAX_IOPS_REST),
'min_throughput_iops': int(MIN_IOPS_REST)
}
}
QOS_POLICY_GROUP_API_ARGS_REST_BPS = {
'name': QOS_POLICY_GROUP_NAME,
'svm': {
'name': VSERVER_NAME
},
'fixed': {
'max_throughput_mbps': int(MAX_BPS_REST),
}
}
QOS_POLICY_GROUP_SPEC_MAX_REST = {
'max_throughput': MAX_THROUGHPUT_REST,
'policy_name': QOS_POLICY_GROUP_NAME,
}
EXPECTED_IOPS_PER_GB_REST = '128'
PEAK_IOPS_PER_GB_REST = '512'
PEAK_IOPS_ALLOCATION_REST = 'used-space'
EXPECTED_IOPS_ALLOCATION_REST = 'used-space'
ABSOLUTE_MIN_IOPS_REST = '75'
BLOCK_SIZE_REST = 'ANY'
ADAPTIVE_QOS_SPEC_REST = {
'policy_name': QOS_POLICY_GROUP_NAME,
'expected_iops': EXPECTED_IOPS_PER_GB_REST,
'expected_iops_allocation': EXPECTED_IOPS_ALLOCATION_REST,
'peak_iops': PEAK_IOPS_PER_GB_REST,
'peak_iops_allocation': PEAK_IOPS_ALLOCATION_REST,
'absolute_min_iops': ABSOLUTE_MIN_IOPS_REST,
'block_size': BLOCK_SIZE_REST,
}
ADAPTIVE_QOS_API_ARGS_REST = {
'name': QOS_POLICY_GROUP_NAME,
'svm': {
'name': VSERVER_NAME
},
'adaptive': {
'absolute_min_iops': int(ABSOLUTE_MIN_IOPS_REST),
'expected_iops': int(EXPECTED_IOPS_PER_GB_REST),
'expected_iops_allocation': EXPECTED_IOPS_ALLOCATION_REST,
'peak_iops': int(PEAK_IOPS_PER_GB_REST),
'peak_iops_allocation': PEAK_IOPS_ALLOCATION_REST,
'block_size': BLOCK_SIZE_REST,
}
}
QOS_POLICY_GROUP_INFO_REST = {
'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC_REST}
QOS_POLICY_GROUP_INFO_MAX_REST = {
'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC_MAX_REST}
ADAPTIVE_QOS_POLICY_GROUP_INFO_REST = {
'legacy': None,
'spec': ADAPTIVE_QOS_SPEC_REST,
}
REST_FIELDS = 'uuid,name,style'

View File

@ -19,7 +19,6 @@
from unittest import mock
import ddt
import six
from cinder import exception
from cinder.objects import fields
@ -279,10 +278,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library._get_lun_attr = mock.Mock(return_value={'Volume':
'fakeLUN'})
self.library.zapi_client = mock.Mock()
self.library.zapi_client.get_lun_by_args.return_value = [
mock.Mock(spec=netapp_api.NaElement)]
lun = fake.FAKE_LUN
self.library._get_lun_by_args = mock.Mock(return_value=[lun])
lun = fake.FAKE_LUN_GET_ITER_RESULT
self.library.zapi_client.get_lun_by_args.return_value = lun
self.library._add_lun_to_table = mock.Mock()
self.library._clone_lun('fakeLUN', 'newFakeLUN', 'false')
@ -302,10 +299,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library._get_lun_attr = mock.Mock(return_value={'Volume':
'fakeLUN'})
self.library.zapi_client = mock.Mock()
self.library.zapi_client.get_lun_by_args.return_value = [
mock.Mock(spec=netapp_api.NaElement)]
lun = fake.FAKE_LUN
self.library._get_lun_by_args = mock.Mock(return_value=[lun])
lun = fake.FAKE_LUN_GET_ITER_RESULT
self.library.zapi_client.get_lun_by_args.return_value = lun
self.library._add_lun_to_table = mock.Mock()
self.library._clone_lun('fakeLUN', 'newFakeLUN', 'false',
@ -326,10 +321,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
'fakeLUN'})
self.library.zapi_client = mock.Mock()
self.library.lun_space_reservation = 'false'
self.library.zapi_client.get_lun_by_args.return_value = [
mock.Mock(spec=netapp_api.NaElement)]
lun = fake.FAKE_LUN
self.library._get_lun_by_args = mock.Mock(return_value=[lun])
lun = fake.FAKE_LUN_GET_ITER_RESULT
self.library.zapi_client.get_lun_by_args.return_value = lun
self.library._add_lun_to_table = mock.Mock()
self.library._clone_lun('fakeLUN', 'newFakeLUN', is_snapshot=True)
@ -1537,27 +1530,22 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
fake.LUN_WITH_METADATA['metadata'])
new_snap_name = 'new-%s' % fake.SNAPSHOT['name']
snapshot_path = lun_obj.metadata['Path']
flexvol_name = lun_obj.metadata['Volume']
block_count = 40960
mock__get_lun_from_table = self.mock_object(
self.library, '_get_lun_from_table', return_value=lun_obj)
mock__get_lun_block_count = self.mock_object(
self.library, '_get_lun_block_count', return_value=block_count)
mock_create_lun = self.mock_object(self.library.zapi_client,
'create_lun')
mock__clone_lun = self.mock_object(self.library, '_clone_lun')
self.library._clone_snapshot(fake.SNAPSHOT['name'])
mock__get_lun_from_table.assert_called_once_with(fake.SNAPSHOT['name'])
mock__get_lun_block_count.assert_called_once_with(snapshot_path)
mock_create_lun.assert_called_once_with(flexvol_name, new_snap_name,
six.text_type(lun_obj.size),
lun_obj.metadata)
mock__clone_lun.assert_called_once_with(fake.SNAPSHOT['name'],
new_snap_name,
block_count=block_count)
space_reserved='false',
is_snapshot=True)
def test__clone_snapshot_invalid_block_count(self):
lun_obj = block_base.NetAppLun(fake.LUN_WITH_METADATA['handle'],
@ -1589,8 +1577,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library, '_get_lun_from_table', return_value=lun_obj)
mock__get_lun_block_count = self.mock_object(
self.library, '_get_lun_block_count', return_value=block_count)
mock_create_lun = self.mock_object(self.library.zapi_client,
'create_lun')
side_effect = exception.VolumeBackendAPIException(data='data')
mock__clone_lun = self.mock_object(self.library, '_clone_lun',
side_effect=side_effect)
@ -1603,12 +1589,10 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
mock__get_lun_from_table.assert_called_once_with(fake.SNAPSHOT['name'])
mock__get_lun_block_count.assert_called_once_with(snapshot_path)
mock_create_lun.assert_called_once_with(flexvol_name, new_snap_name,
six.text_type(lun_obj.size),
lun_obj.metadata)
mock__clone_lun.assert_called_once_with(fake.SNAPSHOT['name'],
new_snap_name,
block_count=block_count)
space_reserved='false',
is_snapshot=True)
mock_destroy_lun.assert_called_once_with(new_lun_path)
def test__swap_luns(self):

View File

@ -452,7 +452,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
vserver = self.driver._get_vserver_for_ip('FAKE_IP')
self.assertIsNone(vserver)
self.assertEqual(fake.VSERVER_NAME, vserver)
def test_check_for_setup_error(self):
mock_add_looping_tasks = self.mock_object(
@ -891,9 +891,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
is_snapshot=is_snapshot)
def test__clone_backing_file_for_volume(self):
body = fake.get_fake_net_interface_get_iter_response()
self.driver.zapi_client.get_if_info_by_ip = mock.Mock(
return_value=[netapp_api.NaElement(body)])
return_value=[{'ip': 'fake_ip'}])
self.driver.zapi_client.get_vol_by_junc_vserver = mock.Mock(
return_value='nfsvol')
self.mock_object(self.driver, '_get_export_ip_path',

View File

@ -20,7 +20,6 @@ from unittest import mock
import ddt
import six
from cinder import exception
from cinder.tests.unit import test
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
fakes as fake_client)
@ -46,45 +45,6 @@ class CapabilitiesLibraryTestCase(test.TestCase):
config.volume_backend_name = 'fake_backend'
return config
def test_check_api_permissions(self):
mock_log = self.mock_object(capabilities.LOG, 'warning')
self.ssc_library.check_api_permissions()
self.zapi_client.check_cluster_api.assert_has_calls(
[mock.call(*key) for key in capabilities.SSC_API_MAP.keys()])
self.assertEqual(0, mock_log.call_count)
def test_check_api_permissions_failed_ssc_apis(self):
def check_cluster_api(object_name, operation_name, api):
if api != 'volume-get-iter':
return False
return True
self.zapi_client.check_cluster_api.side_effect = check_cluster_api
mock_log = self.mock_object(capabilities.LOG, 'warning')
self.ssc_library.check_api_permissions()
self.assertEqual(1, mock_log.call_count)
def test_check_api_permissions_failed_volume_api(self):
def check_cluster_api(object_name, operation_name, api):
if api == 'volume-get-iter':
return False
return True
self.zapi_client.check_cluster_api.side_effect = check_cluster_api
mock_log = self.mock_object(capabilities.LOG, 'warning')
self.assertRaises(exception.VolumeBackendAPIException,
self.ssc_library.check_api_permissions)
self.assertEqual(0, mock_log.call_count)
def test_get_ssc(self):
result = self.ssc_library.get_ssc()

View File

@ -410,12 +410,11 @@ class NetAppBlockStorageLibrary(object):
def _extract_lun_info(self, lun):
"""Extracts the LUNs from API and populates the LUN table."""
meta_dict = self._create_lun_meta(lun)
path = lun.get_child_content('path')
path = lun['Path']
(_rest, _splitter, name) = path.rpartition('/')
handle = self._create_lun_handle(meta_dict)
size = lun.get_child_content('size')
return NetAppLun(handle, name, size, meta_dict)
handle = self._create_lun_handle(lun)
size = lun['Size']
return NetAppLun(handle, name, size, lun)
def _extract_and_populate_luns(self, api_luns):
"""Extracts the LUNs from API and populates the LUN table."""
@ -547,9 +546,6 @@ class NetAppBlockStorageLibrary(object):
LOG.error("Error getting LUN attribute. Exception: %s", e)
return None
def _create_lun_meta(self, lun):
raise NotImplementedError()
def _get_fc_target_wwpns(self, include_partner=True):
raise NotImplementedError()
@ -725,8 +721,8 @@ class NetAppBlockStorageLibrary(object):
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'))
bs = int(lun_info['BlockSize'])
ls = int(lun_info['Size'])
block_count = ls / bs
return block_count

View File

@ -236,27 +236,14 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
if len(lun) == 0:
msg = _("No cloned LUN named %s found on the filer")
raise exception.VolumeBackendAPIException(data=msg % new_name)
clone_meta = self._create_lun_meta(lun[0])
self._add_lun_to_table(
block_base.NetAppLun('%s:%s' % (clone_meta['Vserver'],
clone_meta['Path']),
new_name,
lun[0].get_child_content('size'),
clone_meta))
def _create_lun_meta(self, lun):
"""Creates LUN metadata dictionary."""
self.zapi_client.check_is_naelement(lun)
meta_dict = {}
meta_dict['Vserver'] = lun.get_child_content('vserver')
meta_dict['Volume'] = lun.get_child_content('volume')
meta_dict['Qtree'] = lun.get_child_content('qtree')
meta_dict['Path'] = lun.get_child_content('path')
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = \
lun.get_child_content('is-space-reservation-enabled')
meta_dict['UUID'] = lun.get_child_content('uuid')
return meta_dict
clone_lun = lun[0]
self._add_lun_to_table(
block_base.NetAppLun('%s:%s' % (clone_lun['Vserver'],
clone_lun['Path']),
new_name,
clone_lun['Size'],
clone_lun))
def _get_fc_target_wwpns(self, include_partner=True):
return self.zapi_client.get_fc_target_wwpns()
@ -879,8 +866,6 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
LOG.info("Cloning LUN %s from snapshot %s in volume %s.", lun_name,
snapshot_name, flexvol_name)
metadata = snapshot_lun.metadata
block_count = self._get_lun_block_count(snapshot_path)
if block_count == 0:
msg = _("%s cannot be reverted using clone operation"
@ -889,12 +874,9 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
new_snap_name = "new-%s" % snapshot_name
self.zapi_client.create_lun(
flexvol_name, new_snap_name,
six.text_type(snapshot_lun.size), metadata)
try:
self._clone_lun(snapshot_name, new_snap_name,
block_count=block_count)
space_reserved='false', is_snapshot=True)
return new_snap_name
except Exception:
with excutils.save_and_reraise_exception():

View File

@ -644,6 +644,13 @@ class SSHUtil(object):
# REST API error codes.
REST_UNAUTHORIZED = '6'
REST_API_NOT_FOUND = '3'
REST_UPDATE_SNAPMIRROR_FAILED = '13303844'
REST_ERELATION_EXISTS = '6619637'
REST_SNAPMIRROR_IN_PROGRESS = '13303810'
REST_UPDATE_SNAPMIRROR_FAILED = '13303844'
REST_NO_SUCH_LUN_MAP = '5374922'
REST_NO_SUCH_FILE = '6684674'
class RestNaServer(object):

View File

@ -37,6 +37,28 @@ DEFAULT_MAX_PAGE_LENGTH = 50
ONTAP_SELECT_MODEL = 'FDvM300'
ONTAP_C190 = 'C190'
# NOTE(cknight): The keys in this map are tuples that contain arguments needed
# for efficient use of the system-user-capability-get-iter cDOT API. The
# values are SSC extra specs associated with the APIs listed in the keys.
SSC_API_MAP = {
('storage.aggregate', 'show', 'aggr-options-list-info'): [
'netapp_raid_type',
],
('storage.disk', 'show', 'storage-disk-get-iter'): [
'netapp_disk_type',
],
('snapmirror', 'show', 'snapmirror-get-iter'): [
'netapp_mirrored',
],
('volume.efficiency', 'show', 'sis-get-iter'): [
'netapp_dedup',
'netapp_compression',
],
('volume', '*show', 'volume-get-iter'): [
'netapp_flexvol_encryption',
],
}
@six.add_metaclass(volume_utils.TraceWrapperMetaclass)
class Client(client_base.Client):
@ -182,6 +204,32 @@ class Client(client_base.Client):
result.get_child_by_name('next-tag').set_content('')
return result
def check_api_permissions(self):
"""Check which APIs that support SSC functionality are available."""
inaccessible_apis = []
invalid_extra_specs = []
for api_tuple, extra_specs in SSC_API_MAP.items():
object_name, operation_name, api = api_tuple
if not self.check_cluster_api(object_name,
operation_name,
api):
inaccessible_apis.append(api)
invalid_extra_specs.extend(extra_specs)
if inaccessible_apis:
if 'volume-get-iter' in inaccessible_apis:
msg = _('User not permitted to query Data ONTAP volumes.')
raise exception.VolumeBackendAPIException(data=msg)
else:
LOG.warning('The configured user account does not have '
'sufficient privileges to use all needed '
'APIs. The following extra specs will fail '
'or be ignored: %s.', invalid_extra_specs)
return invalid_extra_specs
def _get_cluster_nodes_info(self):
"""Return a list of models of the nodes in the cluster"""
api_args = {
@ -481,7 +529,25 @@ class Client(client_base.Client):
tag = result.get_child_content('next-tag')
if tag is None:
break
return luns
lun_list = [self._create_lun_meta(lun) for lun in luns]
return lun_list
def _create_lun_meta(self, lun):
"""Creates LUN metadata dictionary."""
self.check_is_naelement(lun)
meta_dict = {}
meta_dict['Vserver'] = lun.get_child_content('vserver')
meta_dict['Volume'] = lun.get_child_content('volume')
meta_dict['Size'] = lun.get_child_content('size')
meta_dict['Qtree'] = lun.get_child_content('qtree')
meta_dict['Path'] = lun.get_child_content('path')
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = \
lun.get_child_content('is-space-reservation-enabled')
meta_dict['UUID'] = lun.get_child_content('uuid')
meta_dict['BlockSize'] = lun.get_child_content('block-size')
return meta_dict
def get_lun_map(self, path):
"""Gets the LUN map by LUN path."""
@ -853,7 +919,10 @@ class Client(client_base.Client):
attr_list = luns.get_child_by_name('attributes-list')
if not attr_list:
return []
return attr_list.get_children()
lun_list = [self._create_lun_meta(lun)
for lun in attr_list.get_children()]
return lun_list
def file_assign_qos(self, flex_vol, qos_policy_group_name,
qos_policy_group_is_adaptive, file_path):
@ -1061,7 +1130,8 @@ class Client(client_base.Client):
num_records = result.get_child_content('num-records')
if num_records and int(num_records) >= 1:
attr_list = result.get_child_by_name('attributes-list')
return attr_list.get_children()
return [{'vserver': attr.get_child_content('vserver')}
for attr in attr_list.get_children()]
raise exception.NotFound(
_('No interface found on cluster for ip %s') % ip)

View File

@ -314,7 +314,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
"""Gets the vserver and export volume for share."""
(host_ip, export_path) = self._get_export_ip_path(volume_id, share)
ifs = self.zapi_client.get_if_info_by_ip(host_ip)
vserver = ifs[0].get_child_content('vserver')
vserver = ifs[0].get('vserver')
exp_volume = self.zapi_client.get_vol_by_junc_vserver(vserver,
export_path)
return vserver, exp_volume
@ -512,7 +512,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
"""Get vserver for the mentioned ip."""
try:
ifs = self.zapi_client.get_if_info_by_ip(ip)
vserver = ifs[0].get_child_content('vserver')
vserver = ifs[0].get('vserver')
return vserver
except Exception:
return None

View File

@ -22,34 +22,9 @@ import re
from oslo_log import log as logging
import six
from cinder import exception
from cinder.i18n import _
LOG = logging.getLogger(__name__)
# NOTE(cknight): The keys in this map are tuples that contain arguments needed
# for efficient use of the system-user-capability-get-iter cDOT API. The
# values are SSC extra specs associated with the APIs listed in the keys.
SSC_API_MAP = {
('storage.aggregate', 'show', 'aggr-options-list-info'): [
'netapp_raid_type',
],
('storage.disk', 'show', 'storage-disk-get-iter'): [
'netapp_disk_type',
],
('snapmirror', 'show', 'snapmirror-get-iter'): [
'netapp_mirrored',
],
('volume.efficiency', 'show', 'sis-get-iter'): [
'netapp_dedup',
'netapp_compression',
],
('volume', '*show', 'volume-get-iter'): [
'netapp_flexvol_encryption',
],
}
class CapabilitiesLibrary(object):
@ -64,30 +39,7 @@ class CapabilitiesLibrary(object):
self.invalid_extra_specs = []
def check_api_permissions(self):
"""Check which APIs that support SSC functionality are available."""
inaccessible_apis = []
invalid_extra_specs = []
for api_tuple, extra_specs in SSC_API_MAP.items():
object_name, operation_name, api = api_tuple
if not self.zapi_client.check_cluster_api(object_name,
operation_name,
api):
inaccessible_apis.append(api)
invalid_extra_specs.extend(extra_specs)
if inaccessible_apis:
if 'volume-get-iter' in inaccessible_apis:
msg = _('User not permitted to query Data ONTAP volumes.')
raise exception.VolumeBackendAPIException(data=msg)
else:
LOG.warning('The configured user account does not have '
'sufficient privileges to use all needed '
'APIs. The following extra specs will fail '
'or be ignored: %s.', invalid_extra_specs)
self.invalid_extra_specs = invalid_extra_specs
self.invalid_extra_specs = self.zapi_client.check_api_permissions()
def cluster_user_supported(self):
return not self.invalid_extra_specs