From b1829bace07aaddafbf3048dfed0a92658dcf1a0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 17 Apr 2014 10:34:38 -0400 Subject: [PATCH] NetApp NFS and iSCSI: move zapi client logic into modules This patch moves the logic for constructing zapi requests into its own modules in order to reduce coupling with driver logic, improve testability, and improve readability. This patch also adds unit tests around the zapi request logic. Implements bp improve-netapp-drivers Change-Id: I3939df9a55d77b14d723422c25bd3dd3bcef9fbe --- cinder/tests/test_netapp.py | 17 - cinder/tests/test_netapp_nfs.py | 69 +- .../volume/drivers/netapp/client/__init__.py | 0 .../volume/drivers/netapp/client/test_base.py | 384 +++++++++++ .../drivers/netapp/client/test_cmode.py | 540 +++++++++++++++ .../drivers/netapp/client/test_seven_mode.py | 536 +++++++++++++++ .../tests/volume/drivers/netapp/test_iscsi.py | 236 ++----- .../tests/volume/drivers/netapp/test_utils.py | 4 +- cinder/volume/drivers/netapp/api.py | 10 +- .../volume/drivers/netapp/client/__init__.py | 0 cinder/volume/drivers/netapp/client/base.py | 206 ++++++ cinder/volume/drivers/netapp/client/cmode.py | 318 +++++++++ .../drivers/netapp/client/seven_mode.py | 339 +++++++++ cinder/volume/drivers/netapp/common.py | 2 +- cinder/volume/drivers/netapp/eseries/iscsi.py | 32 +- cinder/volume/drivers/netapp/iscsi.py | 642 ++---------------- cinder/volume/drivers/netapp/nfs.py | 325 ++------- cinder/volume/drivers/netapp/ssc_utils.py | 18 +- cinder/volume/drivers/netapp/utils.py | 23 +- 19 files changed, 2602 insertions(+), 1099 deletions(-) create mode 100644 cinder/tests/volume/drivers/netapp/client/__init__.py create mode 100644 cinder/tests/volume/drivers/netapp/client/test_base.py create mode 100644 cinder/tests/volume/drivers/netapp/client/test_cmode.py create mode 100644 cinder/tests/volume/drivers/netapp/client/test_seven_mode.py create mode 100644 cinder/volume/drivers/netapp/client/__init__.py create mode 100644 cinder/volume/drivers/netapp/client/base.py create mode 100644 cinder/volume/drivers/netapp/client/cmode.py create mode 100644 cinder/volume/drivers/netapp/client/seven_mode.py diff --git a/cinder/tests/test_netapp.py b/cinder/tests/test_netapp.py index c98141a36..ebd1e4491 100644 --- a/cinder/tests/test_netapp.py +++ b/cinder/tests/test_netapp.py @@ -702,23 +702,6 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase): self.driver.create_volume(self.volume) self.driver.extend_volume(self.volume, 4) - def test_initialize_connection_no_target_details_found(self): - fake_volume = {'name': 'mock-vol'} - fake_connector = {'initiator': 'iqn.mock'} - self.driver._map_lun = mock.Mock(return_value='mocked-lun-id') - self.driver._get_iscsi_service_details = mock.Mock( - return_value='mocked-iqn') - self.driver._get_target_details = mock.Mock(return_value=[]) - expected = (_('No iscsi target details were found for LUN %s') - % fake_volume['name']) - try: - self.driver.initialize_connection(fake_volume, fake_connector) - except exception.VolumeBackendAPIException as exc: - if expected not in str(exc): - self.fail(_('Expected exception message is missing')) - else: - self.fail(_('VolumeBackendAPIException not raised')) - class NetAppDriverNegativeTestCase(test.TestCase): """Test case for NetAppDriver""" diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py index 82ebf6425..4f75bf5ef 100644 --- a/cinder/tests/test_netapp_nfs.py +++ b/cinder/tests/test_netapp_nfs.py @@ -1,4 +1,3 @@ - # Copyright (c) 2012 NetApp, Inc. # All Rights Reserved. # @@ -22,10 +21,11 @@ import mock import mox from mox import IgnoreArg from mox import IsA +import six from cinder import context from cinder import exception -from cinder.i18n import _ +from cinder.i18n import _LW from cinder.image import image_utils from cinder.openstack.common import log as logging from cinder import test @@ -243,20 +243,19 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase): volume = FakeVolume() setattr(volume, 'provider_location', '127.0.0.1:/nfs') + drv.zapi_client = mox.CreateMockAnything() mox.StubOutWithMock(drv, '_get_host_ip') mox.StubOutWithMock(drv, '_get_export_path') - mox.StubOutWithMock(drv, '_get_if_info_by_ip') - mox.StubOutWithMock(drv, '_get_vol_by_junc_vserver') - mox.StubOutWithMock(drv, '_clone_file') mox.StubOutWithMock(drv, '_post_prov_deprov_in_ssc') + drv.zapi_client.get_if_info_by_ip('127.0.0.1').AndReturn( + self._prepare_info_by_ip_response()) + drv.zapi_client.get_vol_by_junc_vserver('openstack', '/nfs').AndReturn( + 'nfsvol') + drv.zapi_client.clone_file('nfsvol', 'volume_name', 'clone_name', + 'openstack') drv._get_host_ip(IgnoreArg()).AndReturn('127.0.0.1') drv._get_export_path(IgnoreArg()).AndReturn('/nfs') - drv._get_if_info_by_ip('127.0.0.1').AndReturn( - self._prepare_info_by_ip_response()) - drv._get_vol_by_junc_vserver('openstack', '/nfs').AndReturn('nfsvol') - drv._clone_file('nfsvol', 'volume_name', 'clone_name', - 'openstack') drv._post_prov_deprov_in_ssc(IgnoreArg()) return mox @@ -298,7 +297,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase): volume_name = 'volume_name' clone_name = 'clone_name' - volume_id = volume_name + str(hash(volume_name)) + volume_id = volume_name + six.text_type(hash(volume_name)) share = 'ip:/share' drv._clone_volume(volume_name, clone_name, volume_id, share) @@ -362,8 +361,8 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase): if (share == 'testshare' and file_name == 'img-cache-id'): pass else: - LOG.warn(_("Share %(share)s and file name %(file_name)s") - % {'share': share, 'file_name': file_name}) + LOG.warning(_LW("Share %(share)s and file name %(file_name)s") + % {'share': share, 'file_name': file_name}) self.fail('Return result is unexpected') def test_find_old_cache_files_notexists(self): @@ -897,7 +896,7 @@ class NetappDirectCmodeNfsDriverOnlyTestCase(test.TestCase): volume_info = self._driver.create_volume(FakeVolume(host, 1)) self.assertEqual(volume_info.get('provider_location'), fake_share) - self.assertEqual(0, utils.LOG.warn.call_count) + self.assertEqual(0, utils.LOG.warning.call_count) @mock.patch.object(utils, 'LOG', mock.Mock()) @mock.patch.object(netapp_nfs, 'get_volume_extra_specs') @@ -913,7 +912,7 @@ class NetappDirectCmodeNfsDriverOnlyTestCase(test.TestCase): self._driver.create_volume(FakeVolume(host, 1)) warn_msg = 'Extra spec netapp:raid_type is obsolete. ' \ 'Use netapp_raid_type instead.' - utils.LOG.warn.assert_called_once_with(warn_msg) + utils.LOG.warning.assert_called_once_with(warn_msg) @mock.patch.object(utils, 'LOG', mock.Mock()) @mock.patch.object(netapp_nfs, 'get_volume_extra_specs') @@ -930,7 +929,7 @@ class NetappDirectCmodeNfsDriverOnlyTestCase(test.TestCase): self._driver.create_volume(FakeVolume(host, 1)) warn_msg = 'Extra spec netapp_thick_provisioned is ' \ 'deprecated. Use netapp_thin_provisioned instead.' - utils.LOG.warn.assert_called_once_with(warn_msg) + utils.LOG.warning.assert_called_once_with(warn_msg) def test_create_volume_no_pool_specified(self): drv = self._driver @@ -1279,33 +1278,24 @@ class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase): setattr(volume, 'provider_location', '127.0.0.1:/nfs') mox.StubOutWithMock(drv, '_get_export_ip_path') - mox.StubOutWithMock(drv, '_get_actual_path_for_export') - mox.StubOutWithMock(drv, '_start_clone') - mox.StubOutWithMock(drv, '_wait_for_clone_finish') - if status == 'fail': - mox.StubOutWithMock(drv, '_clear_clone') drv._get_export_ip_path( IgnoreArg(), IgnoreArg()).AndReturn(('127.0.0.1', '/nfs')) - drv._get_actual_path_for_export(IgnoreArg()).AndReturn('/vol/vol1/nfs') - drv._start_clone(IgnoreArg(), IgnoreArg()).AndReturn(('1', '2')) - if status == 'fail': - drv._wait_for_clone_finish('1', '2').AndRaise( - api.NaApiError('error', 'error')) - drv._clear_clone('1') - else: - drv._wait_for_clone_finish('1', '2') return mox def test_clone_volume_clear(self): drv = self._driver mox = self._prepare_clone_mock('fail') + drv.zapi_client = mox.CreateMockAnything() + drv.zapi_client.get_actual_path_for_export('/nfs').AndReturn( + '/vol/vol1/nfs') + drv.zapi_client.clone_file(IgnoreArg(), IgnoreArg()) mox.ReplayAll() volume_name = 'volume_name' clone_name = 'clone_name' - volume_id = volume_name + str(hash(volume_name)) + volume_id = volume_name + six.text_type(hash(volume_name)) try: drv._clone_volume(volume_name, clone_name, volume_id) except Exception as e: @@ -1325,3 +1315,22 @@ class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase): configuration) configuration.netapp_storage_family = 'ontap_7mode' return configuration + + def test_clone_volume(self): + drv = self._driver + mox = self._prepare_clone_mock('pass') + drv.zapi_client = mox.CreateMockAnything() + drv.zapi_client.get_actual_path_for_export('/nfs').AndReturn( + '/vol/vol1/nfs') + drv.zapi_client.clone_file(IgnoreArg(), IgnoreArg()) + + mox.ReplayAll() + + volume_name = 'volume_name' + clone_name = 'clone_name' + volume_id = volume_name + six.text_type(hash(volume_name)) + share = 'ip:/share' + + drv._clone_volume(volume_name, clone_name, volume_id, share) + + mox.VerifyAll() diff --git a/cinder/tests/volume/drivers/netapp/client/__init__.py b/cinder/tests/volume/drivers/netapp/client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/volume/drivers/netapp/client/test_base.py b/cinder/tests/volume/drivers/netapp/client/test_base.py new file mode 100644 index 000000000..12e6011c4 --- /dev/null +++ b/cinder/tests/volume/drivers/netapp/client/test_base.py @@ -0,0 +1,384 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from lxml import etree +import mock +import six + +from cinder import test +from cinder.volume.drivers.netapp import api as netapp_api +from cinder.volume.drivers.netapp.client import base + + +class NetAppBaseClientTestCase(test.TestCase): + + def setUp(self): + super(NetAppBaseClientTestCase, self).setUp() + self.connection = mock.MagicMock() + self.client = base.Client(self.connection) + self.fake_volume = six.text_type(uuid.uuid4()) + self.fake_lun = six.text_type(uuid.uuid4()) + self.fake_size = '1024' + self.fake_metadata = { + 'OsType': 'linux', + 'SpaceReserved': 'true', + } + + def tearDown(self): + super(NetAppBaseClientTestCase, self).tearDown() + + def test_get_ontapi_version(self): + version_response = netapp_api.NaElement( + etree.XML(""" + 1 + 19 + """)) + self.connection.invoke_successfully.return_value = version_response + + major, minor = self.client.get_ontapi_version() + + self.assertEqual('1', major) + self.assertEqual('19', minor) + + def test_create_lun(self): + expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.create_lun(self.fake_volume, + self.fake_lun, + self.fake_size, + self.fake_metadata) + + mock_create_node.assert_called_once_with( + 'lun-create-by-size', + **{'path': expected_path, + 'size': self.fake_size, + 'ostype': self.fake_metadata['OsType'], + 'space-reservation-enabled': + self.fake_metadata['SpaceReserved']}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_create_lun_with_qos_policy_group(self): + expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + expected_qos_group = 'qos_1' + mock_request = mock.Mock() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + return_value=mock_request + ) as mock_create_node: + self.client.create_lun(self.fake_volume, + self.fake_lun, + self.fake_size, + self.fake_metadata, + qos_policy_group=expected_qos_group) + + mock_create_node.assert_called_once_with( + 'lun-create-by-size', + **{'path': expected_path, 'size': self.fake_size, + 'ostype': self.fake_metadata['OsType'], + 'space-reservation-enabled': + self.fake_metadata['SpaceReserved']}) + mock_request.add_new_child.assert_called_once_with( + 'qos-policy-group', expected_qos_group) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_create_lun_raises_on_failure(self): + self.connection.invoke_successfully = mock.Mock( + side_effect=netapp_api.NaApiError) + self.assertRaises(netapp_api.NaApiError, + self.client.create_lun, + self.fake_volume, + self.fake_lun, + self.fake_size, + self.fake_metadata) + + def test_destroy_lun(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.destroy_lun(path) + + mock_create_node.assert_called_once_with( + 'lun-destroy', + **{'path': path}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_destroy_lun_force(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + mock_request = mock.Mock() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + return_value=mock_request + ) as mock_create_node: + self.client.destroy_lun(path) + + mock_create_node.assert_called_once_with('lun-destroy', + **{'path': path}) + mock_request.add_new_child.assert_called_once_with('force', 'true') + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_map_lun(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + expected_lun_id = 'my_lun' + mock_response = mock.Mock() + self.connection.invoke_successfully.return_value = mock_response + mock_response.get_child_content.return_value = expected_lun_id + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + actual_lun_id = self.client.map_lun(path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-map', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + self.assertEqual(expected_lun_id, actual_lun_id) + + def test_map_lun_with_lun_id(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + expected_lun_id = 'my_lun' + mock_response = mock.Mock() + self.connection.invoke_successfully.return_value = mock_response + mock_response.get_child_content.return_value = expected_lun_id + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + actual_lun_id = self.client.map_lun(path, igroup, + lun_id=expected_lun_id) + + mock_create_node.assert_called_once_with( + 'lun-map', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + self.assertEqual(expected_lun_id, actual_lun_id) + + def test_map_lun_with_api_error(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + self.connection.invoke_successfully.side_effect =\ + netapp_api.NaApiError() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.assertRaises(netapp_api.NaApiError, self.client.map_lun, + path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-map', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_unmap_lun(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + mock_response = mock.Mock() + self.connection.invoke_successfully.return_value = mock_response + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.unmap_lun(path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-unmap', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_unmap_lun_with_api_error(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + self.connection.invoke_successfully.side_effect =\ + netapp_api.NaApiError() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.assertRaises(netapp_api.NaApiError, self.client.unmap_lun, + path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-unmap', + **{'path': path, 'initiator-group': igroup}) + + def test_unmap_lun_already_unmapped(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + EINVALIDINPUTERROR = '13115' + self.connection.invoke_successfully.side_effect =\ + netapp_api.NaApiError(code=EINVALIDINPUTERROR) + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.unmap_lun(path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-unmap', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_unmap_lun_lun_not_mapped_in_group(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + igroup = 'igroup' + EVDISK_ERROR_NO_SUCH_LUNMAP = '9016' + self.connection.invoke_successfully.side_effect =\ + netapp_api.NaApiError(code=EVDISK_ERROR_NO_SUCH_LUNMAP) + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.unmap_lun(path, igroup) + + mock_create_node.assert_called_once_with( + 'lun-unmap', + **{'path': path, 'initiator-group': igroup}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_create_igroup(self): + igroup = 'igroup' + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.create_igroup(igroup) + + mock_create_node.assert_called_once_with( + 'igroup-create', + **{'initiator-group-name': igroup, + 'initiator-group-type': 'iscsi', + 'os-type': 'default'}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_add_igroup_initiator(self): + igroup = 'igroup' + initiator = 'initiator' + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + ) as mock_create_node: + self.client.add_igroup_initiator(igroup, initiator) + + mock_create_node.assert_called_once_with( + 'igroup-add', + **{'initiator-group-name': igroup, + 'initiator': initiator}) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_do_direct_resize(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + new_size = 1024 + mock_request = mock.Mock() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + return_value=mock_request + ) as mock_create_node: + self.client.do_direct_resize(path, new_size) + + mock_create_node.assert_called_once_with( + 'lun-resize', + **{'path': path, + 'size': new_size}) + mock_request.add_new_child.assert_called_once_with( + 'force', 'true') + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_do_direct_resize_not_forced(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + new_size = 1024 + mock_request = mock.Mock() + + with mock.patch.object(netapp_api.NaElement, + 'create_node_with_children', + return_value=mock_request + ) as mock_create_node: + self.client.do_direct_resize(path, new_size, force=False) + + mock_create_node.assert_called_once_with( + 'lun-resize', + **{'path': path, + 'size': new_size}) + self.assertFalse(mock_request.add_new_child.called) + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) + + def test_get_lun_geometry(self): + expected_keys = set(['size', 'bytes_per_sector', 'sectors_per_track', + 'tracks_per_cylinder', 'cylinders', 'max_resize']) + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + mock_response = mock.Mock() + self.connection.invoke_successfully.return_value = mock_response + + geometry = self.client.get_lun_geometry(path) + self.assertEqual(expected_keys, set(geometry.keys())) + + def test_get_lun_geometry_with_api_error(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + self.connection.invoke_successfully.side_effect =\ + netapp_api.NaApiError() + geometry = self.client.get_lun_geometry(path) + + self.assertEqual({}, geometry) + + def test_get_volume_options(self): + fake_response = netapp_api.NaElement('volume') + fake_response.add_node_with_children('options', test='blah') + self.connection.invoke_successfully.return_value = fake_response + options = self.client.get_volume_options('volume') + + self.assertEqual(1, len(options)) + + def test_get_volume_options_with_no_options(self): + fake_response = netapp_api.NaElement('options') + self.connection.invoke_successfully.return_value = fake_response + options = self.client.get_volume_options('volume') + + self.assertEqual([], options) + + def test_move_lun(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + new_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + fake_response = netapp_api.NaElement('options') + self.connection.invoke_successfully.return_value = fake_response + self.client.move_lun(path, new_path) + + self.connection.invoke_successfully.assert_called_once_with( + mock.ANY, True) diff --git a/cinder/tests/volume/drivers/netapp/client/test_cmode.py b/cinder/tests/volume/drivers/netapp/client/test_cmode.py new file mode 100644 index 000000000..18bc80892 --- /dev/null +++ b/cinder/tests/volume/drivers/netapp/client/test_cmode.py @@ -0,0 +1,540 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from lxml import etree +import mock +import six + +from cinder import exception +from cinder import test +from cinder.volume.drivers.netapp import api as netapp_api +from cinder.volume.drivers.netapp.client import cmode + + +class NetAppCmodeClientTestCase(test.TestCase): + + def setUp(self): + super(NetAppCmodeClientTestCase, self).setUp() + self.connection = mock.MagicMock() + self.vserver = 'fake_vserver' + self.client = cmode.Client(self.connection, self.vserver) + self.fake_volume = six.text_type(uuid.uuid4()) + self.fake_lun = six.text_type(uuid.uuid4()) + + def tearDown(self): + super(NetAppCmodeClientTestCase, self).tearDown() + + def test_get_target_details_no_targets(self): + response = netapp_api.NaElement( + etree.XML(""" + 1 + + """)) + self.connection.invoke_successfully.return_value = response + target_list = self.client.get_target_details() + + self.assertEqual([], target_list) + + def test_get_target_details(self): + expected_target = { + "address": "127.0.0.1", + "port": "1337", + "interface-enabled": "true", + "tpgroup-tag": "7777", + } + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(address)s + %(port)s + %(interface-enabled)s + %(tpgroup-tag)s + + + """ % expected_target)) + self.connection.invoke_successfully.return_value = response + + target_list = self.client.get_target_details() + + self.assertEqual([expected_target], target_list) + + def test_get_iscsi_service_details_with_no_iscsi_service(self): + response = netapp_api.NaElement( + etree.XML(""" + 0 + """)) + self.connection.invoke_successfully.return_value = response + + iqn = self.client.get_iscsi_service_details() + + self.assertEqual(None, iqn) + + def test_get_iscsi_service_details(self): + expected_iqn = 'iqn.1998-01.org.openstack.iscsi:name1' + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %s + + + """ % expected_iqn)) + self.connection.invoke_successfully.return_value = response + + iqn = self.client.get_iscsi_service_details() + + self.assertEqual(expected_iqn, iqn) + + def test_get_lun_list(self): + response = netapp_api.NaElement( + etree.XML(""" + 2 + + + + + + + """)) + self.connection.invoke_successfully.return_value = response + + luns = self.client.get_lun_list() + + self.assertEqual(2, len(luns)) + + def test_get_lun_list_with_multiple_pages(self): + response = netapp_api.NaElement( + etree.XML(""" + 2 + + + + + fake-next + """)) + response_2 = netapp_api.NaElement( + etree.XML(""" + 2 + + + + + """)) + self.connection.invoke_successfully.side_effect = [response, + response_2] + + luns = self.client.get_lun_list() + + self.assertEqual(4, len(luns)) + + def test_get_lun_map_no_luns_mapped(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + response = netapp_api.NaElement( + etree.XML(""" + 0 + + """)) + self.connection.invoke_successfully.return_value = response + + lun_map = self.client.get_lun_map(path) + + self.assertEqual([], lun_map) + + def test_get_lun_map(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + expected_lun_map = { + "initiator-group": "igroup", + "lun-id": "1337", + "vserver": "vserver", + } + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(lun-id)s + %(initiator-group)s + %(vserver)s + + + """ % expected_lun_map)) + self.connection.invoke_successfully.return_value = response + + lun_map = self.client.get_lun_map(path) + + self.assertEqual([expected_lun_map], lun_map) + + def test_get_lun_map_multiple_pages(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + expected_lun_map = { + "initiator-group": "igroup", + "lun-id": "1337", + "vserver": "vserver", + } + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(lun-id)s + %(initiator-group)s + %(vserver)s + + + blah + """ % expected_lun_map)) + response_2 = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(lun-id)s + %(initiator-group)s + %(vserver)s + + + """ % expected_lun_map)) + self.connection.invoke_successfully.side_effect = [response, + response_2] + + lun_map = self.client.get_lun_map(path) + + self.assertEqual([expected_lun_map, expected_lun_map], lun_map) + + def test_get_igroup_by_initiator_none_found(self): + initiator = 'initiator' + response = netapp_api.NaElement( + etree.XML(""" + 0 + + """)) + self.connection.invoke_successfully.return_value = response + + igroup = self.client.get_igroup_by_initiator(initiator) + + self.assertEqual([], igroup) + + def test_get_igroup_by_initiator(self): + initiator = 'initiator' + expected_igroup = { + "initiator-group-os-type": None, + "initiator-group-type": "1337", + "initiator-group-name": "vserver", + } + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(initiator-group-type)s + %(initiator-group-name)s + + + """ % expected_igroup)) + self.connection.invoke_successfully.return_value = response + + igroup = self.client.get_igroup_by_initiator(initiator) + + self.assertEqual([expected_igroup], igroup) + + def test_get_igroup_by_initiator_multiple_pages(self): + initiator = 'initiator' + expected_igroup = { + "initiator-group-os-type": None, + "initiator-group-type": "1337", + "initiator-group-name": "vserver", + } + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(initiator-group-type)s + %(initiator-group-name)s + + + blah + """ % expected_igroup)) + response_2 = netapp_api.NaElement( + etree.XML(""" + 1 + + + %(initiator-group-type)s + %(initiator-group-name)s + + + """ % expected_igroup)) + self.connection.invoke_successfully.side_effect = [response, + response_2] + + igroup = self.client.get_igroup_by_initiator(initiator) + + self.assertEqual([expected_igroup, expected_igroup], igroup) + + def test_clone_lun(self): + self.client.clone_lun('volume', 'fakeLUN', 'newFakeLUN') + self.assertEqual(1, self.connection.invoke_successfully.call_count) + + def test_clone_lun_multiple_zapi_calls(self): + """Test for when lun clone requires more than one zapi call.""" + + # Max block-ranges per call = 32, max blocks per range = 2^24 + # Force 2 calls + bc = 2 ** 24 * 32 * 2 + self.client.clone_lun('volume', 'fakeLUN', 'newFakeLUN', + block_count=bc) + self.assertEqual(2, self.connection.invoke_successfully.call_count) + + def test_get_lun_by_args(self): + response = netapp_api.NaElement( + etree.XML(""" + 2 + + + + + """)) + self.connection.invoke_successfully.return_value = response + + lun = self.client.get_lun_by_args() + + self.assertEqual(1, len(lun)) + + def test_get_lun_by_args_no_lun_found(self): + response = netapp_api.NaElement( + etree.XML(""" + 2 + + + """)) + self.connection.invoke_successfully.return_value = response + + lun = self.client.get_lun_by_args() + + self.assertEqual(0, len(lun)) + + def test_get_lun_by_args_with_args_specified(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + response = netapp_api.NaElement( + etree.XML(""" + 2 + + + + + """)) + self.connection.invoke_successfully.return_value = response + + lun = self.client.get_lun_by_args(path=path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + query = actual_request.get_child_by_name('query') + lun_info_args = query.get_child_by_name('lun-info').get_children() + + # Assert request is made with correct arguments + self.assertEqual('path', lun_info_args[0].get_name()) + self.assertEqual(path, lun_info_args[0].get_content()) + + self.assertEqual(1, len(lun)) + + def test_file_assign_qos(self): + expected_flex_vol = "fake_flex_vol" + expected_policy_group = "fake_policy_group" + expected_file_path = "fake_file_path" + + self.client.file_assign_qos(expected_flex_vol, expected_policy_group, + expected_file_path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_flex_vol = actual_request.get_child_by_name('volume') \ + .get_content() + actual_policy_group = actual_request \ + .get_child_by_name('qos-policy-group-name').get_content() + actual_file_path = actual_request.get_child_by_name('file') \ + .get_content() + actual_vserver = actual_request.get_child_by_name('vserver') \ + .get_content() + + self.assertEqual(expected_flex_vol, actual_flex_vol) + self.assertEqual(expected_policy_group, actual_policy_group) + self.assertEqual(expected_file_path, actual_file_path) + self.assertEqual(self.vserver, actual_vserver) + + @mock.patch('cinder.volume.drivers.netapp.utils.resolve_hostname', + return_value='192.168.1.101') + def test_get_if_info_by_ip_not_found(self, mock_resolve_hostname): + fake_ip = '192.168.1.101' + response = netapp_api.NaElement( + etree.XML(""" + 0 + + + """)) + self.connection.invoke_successfully.return_value = response + + self.assertRaises(exception.NotFound, self.client.get_if_info_by_ip, + fake_ip) + + @mock.patch('cinder.volume.drivers.netapp.utils.resolve_hostname', + return_value='192.168.1.101') + def test_get_if_info_by_ip(self, mock_resolve_hostname): + fake_ip = '192.168.1.101' + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + + + """)) + self.connection.invoke_successfully.return_value = response + + results = self.client.get_if_info_by_ip(fake_ip) + + self.assertEqual(1, len(results)) + + def test_get_vol_by_junc_vserver_not_found(self): + fake_vserver = 'fake_vserver' + fake_junc = 'fake_junction_path' + response = netapp_api.NaElement( + etree.XML(""" + 0 + + + """)) + self.connection.invoke_successfully.return_value = response + + self.assertRaises(exception.NotFound, + self.client.get_vol_by_junc_vserver, + fake_vserver, fake_junc) + + def test_get_vol_by_junc_vserver(self): + fake_vserver = 'fake_vserver' + fake_junc = 'fake_junction_path' + expected_flex_vol = 'fake_flex_vol' + response = netapp_api.NaElement( + etree.XML(""" + 1 + + + + %(flex_vol)s + + + + """ % {'flex_vol': expected_flex_vol})) + self.connection.invoke_successfully.return_value = response + + actual_flex_vol = self.client.get_vol_by_junc_vserver(fake_vserver, + fake_junc) + + self.assertEqual(expected_flex_vol, actual_flex_vol) + + def test_clone_file(self): + expected_flex_vol = "fake_flex_vol" + expected_src_path = "fake_src_path" + expected_dest_path = "fake_dest_path" + self.connection.get_api_version.return_value = (1, 20) + + self.client.clone_file(expected_flex_vol, expected_src_path, + expected_dest_path, self.vserver) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_flex_vol = actual_request.get_child_by_name('volume') \ + .get_content() + actual_src_path = actual_request \ + .get_child_by_name('source-path').get_content() + actual_dest_path = actual_request.get_child_by_name( + 'destination-path').get_content() + + self.assertEqual(expected_flex_vol, actual_flex_vol) + self.assertEqual(expected_src_path, actual_src_path) + self.assertEqual(expected_dest_path, actual_dest_path) + self.assertEqual(actual_request.get_child_by_name( + 'destination-exists'), None) + + def test_clone_file_when_destination_exists(self): + expected_flex_vol = "fake_flex_vol" + expected_src_path = "fake_src_path" + expected_dest_path = "fake_dest_path" + self.connection.get_api_version.return_value = (1, 20) + + self.client.clone_file(expected_flex_vol, expected_src_path, + expected_dest_path, self.vserver, + dest_exists=True) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_flex_vol = actual_request.get_child_by_name('volume') \ + .get_content() + actual_src_path = actual_request \ + .get_child_by_name('source-path').get_content() + actual_dest_path = actual_request.get_child_by_name( + 'destination-path').get_content() + + self.assertEqual(expected_flex_vol, actual_flex_vol) + self.assertEqual(expected_src_path, actual_src_path) + self.assertEqual(expected_dest_path, actual_dest_path) + self.assertEqual(actual_request.get_child_by_name( + 'destination-exists').get_content(), 'true') + + def test_clone_file_when_destination_exists_and_version_less_than_1_20( + self): + expected_flex_vol = "fake_flex_vol" + expected_src_path = "fake_src_path" + expected_dest_path = "fake_dest_path" + self.connection.get_api_version.return_value = (1, 19) + + self.client.clone_file(expected_flex_vol, expected_src_path, + expected_dest_path, self.vserver, + dest_exists=True) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_flex_vol = actual_request.get_child_by_name('volume') \ + .get_content() + actual_src_path = actual_request \ + .get_child_by_name('source-path').get_content() + actual_dest_path = actual_request.get_child_by_name( + 'destination-path').get_content() + + self.assertEqual(expected_flex_vol, actual_flex_vol) + self.assertEqual(expected_src_path, actual_src_path) + self.assertEqual(expected_dest_path, actual_dest_path) + self.assertEqual(actual_request.get_child_by_name( + 'destination-exists'), None) + + def test_get_file_usage(self): + expected_bytes = "2048" + fake_vserver = 'fake_vserver' + fake_path = 'fake_path' + response = netapp_api.NaElement( + etree.XML(""" + %(unique-bytes)s + """ % {'unique-bytes': expected_bytes})) + self.connection.invoke_successfully.return_value = response + + actual_bytes = self.client.get_file_usage(fake_vserver, fake_path) + + self.assertEqual(expected_bytes, actual_bytes) diff --git a/cinder/tests/volume/drivers/netapp/client/test_seven_mode.py b/cinder/tests/volume/drivers/netapp/client/test_seven_mode.py new file mode 100644 index 000000000..e4098697d --- /dev/null +++ b/cinder/tests/volume/drivers/netapp/client/test_seven_mode.py @@ -0,0 +1,536 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from lxml import etree +import mock +import six + +from cinder import test +from cinder.volume.drivers.netapp import api as netapp_api +from cinder.volume.drivers.netapp.client import seven_mode + + +class NetApp7modeClientTestCase(test.TestCase): + + def setUp(self): + super(NetApp7modeClientTestCase, self).setUp() + self.connection = mock.MagicMock() + self.fake_volume = six.text_type(uuid.uuid4()) + self.client = seven_mode.Client(self.connection, [self.fake_volume]) + self.fake_lun = six.text_type(uuid.uuid4()) + + def tearDown(self): + super(NetApp7modeClientTestCase, self).tearDown() + + def test_get_target_details_no_targets(self): + response = netapp_api.NaElement( + etree.XML(""" + + + """)) + self.connection.invoke_successfully.return_value = response + + target_list = self.client.get_target_details() + + self.assertEqual([], target_list) + + def test_get_target_details(self): + expected_target = { + "address": "127.0.0.1", + "port": "1337", + "tpgroup-tag": "7777", + } + response = netapp_api.NaElement( + etree.XML(""" + + + %(address)s + %(port)s + %(tpgroup-tag)s + + + """ % expected_target)) + self.connection.invoke_successfully.return_value = response + + target_list = self.client.get_target_details() + + self.assertEqual([expected_target], target_list) + + def test_get_iscsi_service_details_with_no_iscsi_service(self): + response = netapp_api.NaElement( + etree.XML(""" + """)) + self.connection.invoke_successfully.return_value = response + + iqn = self.client.get_iscsi_service_details() + + self.assertEqual(None, iqn) + + def test_get_iscsi_service_details(self): + expected_iqn = 'iqn.1998-01.org.openstack.iscsi:name1' + response = netapp_api.NaElement( + etree.XML(""" + %s + """ % expected_iqn)) + self.connection.invoke_successfully.return_value = response + + iqn = self.client.get_iscsi_service_details() + + self.assertEqual(expected_iqn, iqn) + + def test_get_lun_list(self): + response = netapp_api.NaElement( + etree.XML(""" + + + + + """)) + self.connection.invoke_successfully.return_value = response + + luns = self.client.get_lun_list() + + self.assertEqual(2, len(luns)) + + def test_get_igroup_by_initiator_none_found(self): + initiator = 'initiator' + response = netapp_api.NaElement( + etree.XML(""" + + + """)) + self.connection.invoke_successfully.return_value = response + + igroup = self.client.get_igroup_by_initiator(initiator) + + self.assertEqual([], igroup) + + def test_get_igroup_by_initiator(self): + initiator = 'initiator' + expected_igroup = { + "initiator-group-os-type": None, + "initiator-group-type": "1337", + "initiator-group-name": "vserver", + } + response = netapp_api.NaElement( + etree.XML(""" + + + + + initiator + + + %(initiator-group-type)s + %(initiator-group-name)s + + + """ % expected_igroup)) + self.connection.invoke_successfully.return_value = response + + igroup = self.client.get_igroup_by_initiator(initiator) + + self.assertEqual([expected_igroup], igroup) + + def test_clone_lun(self): + fake_clone_start = netapp_api.NaElement( + etree.XML(""" + + + 1337 + volume-uuid + + + """)) + fake_clone_status = netapp_api.NaElement( + etree.XML(""" + + + completed + + + """)) + + self.connection.invoke_successfully.side_effect = [fake_clone_start, + fake_clone_status] + + self.client.clone_lun('path', 'new_path', 'fakeLUN', 'newFakeLUN') + self.assertEqual(2, self.connection.invoke_successfully.call_count) + + def test_clone_lun_api_error(self): + fake_clone_start = netapp_api.NaElement( + etree.XML(""" + + + 1337 + volume-uuid + + + """)) + fake_clone_status = netapp_api.NaElement( + etree.XML(""" + + + error + + + """)) + + self.connection.invoke_successfully.side_effect = [fake_clone_start, + fake_clone_status] + + self.assertRaises(netapp_api.NaApiError, self.client.clone_lun, + 'path', 'new_path', 'fakeLUN', 'newFakeLUN') + + def test_clone_lun_multiple_zapi_calls(self): + # Max block-ranges per call = 32, max blocks per range = 2^24 + # Force 2 calls + bc = 2 ** 24 * 32 * 2 + fake_clone_start = netapp_api.NaElement( + etree.XML(""" + + + 1337 + volume-uuid + + + """)) + fake_clone_status = netapp_api.NaElement( + etree.XML(""" + + + completed + + + """)) + + self.connection.invoke_successfully.side_effect = [fake_clone_start, + fake_clone_status, + fake_clone_start, + fake_clone_status] + + self.client.clone_lun('path', 'new_path', 'fakeLUN', 'newFakeLUN', + block_count=bc) + + self.assertEqual(4, self.connection.invoke_successfully.call_count) + + def test_clone_lun_wait_for_clone_to_finish(self): + # Max block-ranges per call = 32, max blocks per range = 2^24 + # Force 2 calls + bc = 2 ** 24 * 32 * 2 + fake_clone_start = netapp_api.NaElement( + etree.XML(""" + + + 1337 + volume-uuid + + + """)) + fake_clone_status = netapp_api.NaElement( + etree.XML(""" + + + running + + + """)) + fake_clone_status_completed = netapp_api.NaElement( + etree.XML(""" + + + completed + + + """)) + + fake_responses = [fake_clone_start, + fake_clone_status, + fake_clone_status_completed, + fake_clone_start, + fake_clone_status_completed] + self.connection.invoke_successfully.side_effect = fake_responses + + with mock.patch('time.sleep') as mock_sleep: + self.client.clone_lun('path', 'new_path', 'fakeLUN', + 'newFakeLUN', block_count=bc) + + mock_sleep.assert_called_once_with(1) + self.assertEqual(5, self.connection.invoke_successfully.call_count) + + def test_get_lun_by_args(self): + response = netapp_api.NaElement( + etree.XML(""" + + + + """)) + self.connection.invoke_successfully.return_value = response + + luns = self.client.get_lun_by_args() + + self.assertEqual(1, len(luns)) + + def test_get_lun_by_args_no_lun_found(self): + response = netapp_api.NaElement( + etree.XML(""" + + + """)) + self.connection.invoke_successfully.return_value = response + + luns = self.client.get_lun_by_args() + + self.assertEqual(0, len(luns)) + + def test_get_lun_by_args_with_args_specified(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + response = netapp_api.NaElement( + etree.XML(""" + + + + """)) + self.connection.invoke_successfully.return_value = response + + lun = self.client.get_lun_by_args(path=path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + lun_info_args = actual_request.get_children() + + # Assert request is made with correct arguments + self.assertEqual('path', lun_info_args[0].get_name()) + self.assertEqual(path, lun_info_args[0].get_content()) + + self.assertEqual(1, len(lun)) + + def test_get_filer_volumes(self): + response = netapp_api.NaElement( + etree.XML(""" + + + + """)) + self.connection.invoke_successfully.return_value = response + + volumes = self.client.get_filer_volumes() + + self.assertEqual(1, len(volumes)) + + def test_get_filer_volumes_no_volumes(self): + response = netapp_api.NaElement( + etree.XML(""" + + + """)) + self.connection.invoke_successfully.return_value = response + + volumes = self.client.get_filer_volumes() + + self.assertEqual([], volumes) + + def test_get_lun_map(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + self.connection.invoke_successfully.return_value = mock.Mock() + + self.client.get_lun_map(path=path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + lun_info_args = actual_request.get_children() + + # Assert request is made with correct arguments + self.assertEqual('path', lun_info_args[0].get_name()) + self.assertEqual(path, lun_info_args[0].get_content()) + + def test_set_space_reserve(self): + path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) + self.connection.invoke_successfully.return_value = mock.Mock() + + self.client.set_space_reserve(path, 'true') + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + lun_info_args = actual_request.get_children() + + # Assert request is made with correct arguments + self.assertEqual('path', lun_info_args[0].get_name()) + self.assertEqual(path, lun_info_args[0].get_content()) + self.assertEqual('enable', lun_info_args[1].get_name()) + self.assertEqual('true', lun_info_args[1].get_content()) + + def test_get_actual_path_for_export(self): + fake_export_path = 'fake_export_path' + expected_actual_pathname = 'fake_actual_pathname' + response = netapp_api.NaElement( + etree.XML(""" + %(path)s + """ % {'path': expected_actual_pathname})) + self.connection.invoke_successfully.return_value = response + + actual_pathname = self.client.get_actual_path_for_export( + fake_export_path) + + self.assertEqual(expected_actual_pathname, actual_pathname) + + def test_clone_file(self): + expected_src_path = "fake_src_path" + expected_dest_path = "fake_dest_path" + fake_volume_id = '0309c748-0d94-41f0-af46-4fbbd76686cf' + fake_clone_op_id = 'c22ad299-ecec-4ec0-8de4-352b887bfce2' + fake_clone_id_response = netapp_api.NaElement( + etree.XML(""" + + + %(volume)s + %(clone_id)s + + + """ % {'volume': fake_volume_id, + 'clone_id': fake_clone_op_id})) + fake_clone_list_response = netapp_api.NaElement( + etree.XML(""" + + + %(volume)s + %(clone_id)s + + %(clone_id)s + + + + completed + + + """ % {'volume': fake_volume_id, + 'clone_id': fake_clone_op_id})) + self.connection.invoke_successfully.side_effect = [ + fake_clone_id_response, fake_clone_list_response] + + self.client.clone_file(expected_src_path, expected_dest_path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_src_path = actual_request \ + .get_child_by_name('source-path').get_content() + actual_dest_path = actual_request.get_child_by_name( + 'destination-path').get_content() + + self.assertEqual(expected_src_path, actual_src_path) + self.assertEqual(expected_dest_path, actual_dest_path) + self.assertEqual(actual_request.get_child_by_name( + 'destination-exists'), None) + + def test_clone_file_when_clone_fails(self): + """Ensure clone is cleaned up on failure.""" + expected_src_path = "fake_src_path" + expected_dest_path = "fake_dest_path" + fake_volume_id = '0309c748-0d94-41f0-af46-4fbbd76686cf' + fake_clone_op_id = 'c22ad299-ecec-4ec0-8de4-352b887bfce2' + fake_clone_id_response = netapp_api.NaElement( + etree.XML(""" + + + %(volume)s + %(clone_id)s + + + """ % {'volume': fake_volume_id, + 'clone_id': fake_clone_op_id})) + fake_clone_list_response = netapp_api.NaElement( + etree.XML(""" + + + %(volume)s + %(clone_id)s + + %(clone_id)s + + + + failed + + + """ % {'volume': fake_volume_id, + 'clone_id': fake_clone_op_id})) + fake_clone_clear_response = mock.Mock() + self.connection.invoke_successfully.side_effect = [ + fake_clone_id_response, fake_clone_list_response, + fake_clone_clear_response] + + self.assertRaises(netapp_api.NaApiError, + self.client.clone_file, + expected_src_path, + expected_dest_path) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + actual_src_path = actual_request \ + .get_child_by_name('source-path').get_content() + actual_dest_path = actual_request.get_child_by_name( + 'destination-path').get_content() + + self.assertEqual(expected_src_path, actual_src_path) + self.assertEqual(expected_dest_path, actual_dest_path) + self.assertEqual(actual_request.get_child_by_name( + 'destination-exists'), None) + + __, _args, __ = self.connection.invoke_successfully.mock_calls[1] + actual_request = _args[0] + actual_clone_id = actual_request.get_child_by_name('clone-id') + actual_clone_id_info = actual_clone_id.get_child_by_name( + 'clone-id-info') + actual_clone_op_id = actual_clone_id_info.get_child_by_name( + 'clone-op-id').get_content() + actual_volume_uuid = actual_clone_id_info.get_child_by_name( + 'volume-uuid').get_content() + + self.assertEqual(fake_clone_op_id, actual_clone_op_id) + self.assertEqual(fake_volume_id, actual_volume_uuid) + + # Ensure that the clone-clear call is made upon error + __, _args, __ = self.connection.invoke_successfully.mock_calls[2] + actual_request = _args[0] + actual_clone_id = actual_request \ + .get_child_by_name('clone-id').get_content() + + self.assertEqual(fake_clone_op_id, actual_clone_id) + + def test_get_file_usage(self): + expected_bytes = "2048" + fake_path = 'fake_path' + response = netapp_api.NaElement( + etree.XML(""" + %(unique-bytes)s + """ % {'unique-bytes': expected_bytes})) + self.connection.invoke_successfully.return_value = response + + actual_bytes = self.client.get_file_usage(fake_path) + + self.assertEqual(expected_bytes, actual_bytes) + + def test_get_ifconfig(self): + expected_response = mock.Mock() + self.connection.invoke_successfully.return_value = expected_response + + actual_response = self.client.get_ifconfig() + + __, _args, __ = self.connection.invoke_successfully.mock_calls[0] + actual_request = _args[0] + self.assertEqual('net-ifconfig-get', actual_request.get_name()) + self.assertEqual(expected_response, actual_response) diff --git a/cinder/tests/volume/drivers/netapp/test_iscsi.py b/cinder/tests/volume/drivers/netapp/test_iscsi.py index 836a9e08b..887a231ba 100644 --- a/cinder/tests/volume/drivers/netapp/test_iscsi.py +++ b/cinder/tests/volume/drivers/netapp/test_iscsi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) - 2014, Alex Meade. All rights reserved. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -19,8 +19,10 @@ Mock unit tests for the NetApp iSCSI driver import uuid import mock +import six from cinder import exception +from cinder.i18n import _ from cinder import test from cinder.tests.test_netapp import create_configuration import cinder.volume.drivers.netapp.api as ntapi @@ -35,6 +37,12 @@ import cinder.volume.drivers.netapp.ssc_utils as ssc_utils import cinder.volume.drivers.netapp.utils as na_utils +FAKE_VOLUME = six.text_type(uuid.uuid4()) +FAKE_LUN = six.text_type(uuid.uuid4()) +FAKE_SIZE = '1024' +FAKE_METADATA = {'OsType': 'linux', 'SpaceReserved': 'true'} + + class NetAppDirectISCSIDriverTestCase(test.TestCase): def setUp(self): @@ -43,10 +51,7 @@ class NetAppDirectISCSIDriverTestCase(test.TestCase): self.driver = ntap_iscsi.NetAppDirectISCSIDriver( configuration=configuration) self.driver.client = mock.Mock() - self.fake_volume = str(uuid.uuid4()) - self.fake_lun = str(uuid.uuid4()) - self.fake_size = '1024' - self.fake_metadata = {'OsType': 'linux', 'SpaceReserved': 'true'} + self.driver.zapi_client = mock.Mock() self.mock_request = mock.Mock() def _set_config(self, configuration): @@ -112,7 +117,7 @@ class NetAppDirectISCSIDriverTestCase(test.TestCase): 'host': 'hostname@backend#vol1'}) warn_msg = 'Extra spec netapp:raid_type is obsolete. ' \ 'Use netapp_raid_type instead.' - na_utils.LOG.warn.assert_called_once_with(warn_msg) + na_utils.LOG.warning.assert_called_once_with(warn_msg) @mock.patch.object(iscsiDriver, 'create_lun', mock.Mock()) @mock.patch.object(iscsiDriver, '_create_lun_handle', mock.Mock()) @@ -128,67 +133,29 @@ class NetAppDirectISCSIDriverTestCase(test.TestCase): 'host': 'hostname@backend#vol1'}) warn_msg = 'Extra spec netapp_thick_provisioned is deprecated. ' \ 'Use netapp_thin_provisioned instead.' - na_utils.LOG.warn.assert_called_once_with(warn_msg) - - def test_create_lun(self): - expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) - - with mock.patch.object(ntapi.NaElement, 'create_node_with_children', - return_value=self.mock_request - ) as mock_create_node: - self.driver.create_lun(self.fake_volume, - self.fake_lun, - self.fake_size, - self.fake_metadata) - - mock_create_node.assert_called_once_with( - 'lun-create-by-size', - **{'path': expected_path, - 'size': self.fake_size, - 'ostype': self.fake_metadata['OsType'], - 'space-reservation-enabled': - self.fake_metadata['SpaceReserved']}) - self.driver.client.invoke_successfully.assert_called_once_with( - mock.ANY, True) - - def test_create_lun_with_qos_policy_group(self): - expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) - expected_qos_group = 'qos_1' - - with mock.patch.object(ntapi.NaElement, 'create_node_with_children', - return_value=self.mock_request - ) as mock_create_node: - self.driver.create_lun(self.fake_volume, - self.fake_lun, - self.fake_size, - self.fake_metadata, - qos_policy_group=expected_qos_group) - - mock_create_node.assert_called_once_with( - 'lun-create-by-size', - **{'path': expected_path, 'size': self.fake_size, - 'ostype': self.fake_metadata['OsType'], - 'space-reservation-enabled': - self.fake_metadata['SpaceReserved']}) - self.mock_request.add_new_child.assert_called_once_with( - 'qos-policy-group', expected_qos_group) - self.driver.client.invoke_successfully.assert_called_once_with( - mock.ANY, True) - - def test_create_lun_raises_on_failure(self): - self.driver.client.invoke_successfully = mock.Mock( - side_effect=ntapi.NaApiError) - self.assertRaises(ntapi.NaApiError, - self.driver.create_lun, - self.fake_volume, - self.fake_lun, - self.fake_size, - self.fake_metadata) + na_utils.LOG.warning.assert_called_once_with(warn_msg) def test_update_volume_stats_is_abstract(self): self.assertRaises(NotImplementedError, self.driver._update_volume_stats) + def test_initialize_connection_no_target_details_found(self): + fake_volume = {'name': 'mock-vol'} + fake_connector = {'initiator': 'iqn.mock'} + self.driver._map_lun = mock.Mock(return_value='mocked-lun-id') + self.driver.zapi_client.get_iscsi_service_details = mock.Mock( + return_value='mocked-iqn') + self.driver.zapi_client.get_target_details = mock.Mock(return_value=[]) + expected = (_('No iscsi target details were found for LUN %s') + % fake_volume['name']) + try: + self.driver.initialize_connection(fake_volume, fake_connector) + except exception.VolumeBackendAPIException as exc: + if expected not in six.text_type(exc): + self.fail(_('Expected exception message is missing')) + else: + self.fail(_('VolumeBackendAPIException not raised')) + class NetAppiSCSICModeTestCase(test.TestCase): """Test case for NetApp's C-Mode iSCSI driver.""" @@ -198,59 +165,21 @@ class NetAppiSCSICModeTestCase(test.TestCase): self.driver = ntap_iscsi.NetAppDirectCmodeISCSIDriver( configuration=mock.Mock()) self.driver.client = mock.Mock() + self.driver.zapi_client = mock.Mock() self.driver.vserver = mock.Mock() self.driver.ssc_vols = None def tearDown(self): super(NetAppiSCSICModeTestCase, self).tearDown() - def test_clone_lun_multiple_zapi_calls(self): - """Test for when lun clone requires more than one zapi call.""" - - # Max block-ranges per call = 32, max blocks per range = 2^24 - # Force 2 calls - bc = 2 ** 24 * 32 * 2 - self.driver._get_lun_attr = mock.Mock(return_value={'Volume': - 'fakeLUN'}) - self.driver.client.invoke_successfully = mock.Mock() - lun = ntapi.NaElement.create_node_with_children( - 'lun-info', - **{'alignment': 'indeterminate', - 'block-size': '512', - 'comment': '', - 'creation-timestamp': '1354536362', - 'is-space-alloc-enabled': 'false', - 'is-space-reservation-enabled': 'true', - 'mapped': 'false', - 'multiprotocol-type': 'linux', - 'online': 'true', - 'path': '/vol/fakeLUN/lun1', - 'prefix-size': '0', - 'qtree': '', - 'read-only': 'false', - 'serial-number': '2FfGI$APyN68', - 'share-state': 'none', - 'size': '20971520', - 'size-used': '0', - 'staging': 'false', - 'suffix-size': '0', - 'uuid': 'cec1f3d7-3d41-11e2-9cf4-123478563412', - 'volume': 'fakeLUN', - 'vserver': 'fake_vserver'}) - self.driver._get_lun_by_args = mock.Mock(return_value=[lun]) - self.driver._add_lun_to_table = mock.Mock() - self.driver._update_stale_vols = mock.Mock() - - self.driver._clone_lun('fakeLUN', 'newFakeLUN', block_count=bc) - - self.assertEqual(2, self.driver.client.invoke_successfully.call_count) - def test_clone_lun_zero_block_count(self): """Test for when clone lun is not passed a block count.""" self.driver._get_lun_attr = mock.Mock(return_value={'Volume': 'fakeLUN'}) - self.driver.client.invoke_successfully = mock.Mock() + self.driver.zapi_client = mock.Mock() + self.driver.zapi_client.get_lun_by_args.return_value = [ + mock.Mock(spec=ntapi.NaElement)] lun = ntapi.NaElement.create_node_with_children( 'lun-info', **{'alignment': 'indeterminate', @@ -281,7 +210,9 @@ class NetAppiSCSICModeTestCase(test.TestCase): self.driver._clone_lun('fakeLUN', 'newFakeLUN') - self.assertEqual(1, self.driver.client.invoke_successfully.call_count) + self.driver.zapi_client.clone_lun.assert_called_once_with( + 'fakeLUN', 'fakeLUN', 'newFakeLUN', 'true', block_count=0, + dest_block=0, src_block=0) @mock.patch.object(ssc_utils, 'refresh_cluster_ssc', mock.Mock()) @mock.patch.object(iscsiCmodeDriver, '_get_pool_stats', mock.Mock()) @@ -290,6 +221,20 @@ class NetAppiSCSICModeTestCase(test.TestCase): self.driver.get_volume_stats(refresh=True) self.assertEqual(na_utils.provide_ems.call_count, 1) + def test_create_lun(self): + self.driver._update_stale_vols = mock.Mock() + + self.driver.create_lun(FAKE_VOLUME, + FAKE_LUN, + FAKE_SIZE, + FAKE_METADATA) + + self.driver.zapi_client.create_lun.assert_called_once_with( + FAKE_VOLUME, FAKE_LUN, FAKE_SIZE, + FAKE_METADATA, None) + + self.assertEqual(1, self.driver._update_stale_vols.call_count) + class NetAppiSCSI7ModeTestCase(test.TestCase): """Test case for NetApp's 7-Mode iSCSI driver.""" @@ -299,66 +244,15 @@ class NetAppiSCSI7ModeTestCase(test.TestCase): self.driver = ntap_iscsi.NetAppDirect7modeISCSIDriver( configuration=mock.Mock()) self.driver.client = mock.Mock() + self.driver.zapi_client = mock.Mock() self.driver.vfiler = mock.Mock() def tearDown(self): super(NetAppiSCSI7ModeTestCase, self).tearDown() - def test_clone_lun_multiple_zapi_calls(self): - """Test for when lun clone requires more than one zapi call.""" - - # Max block-ranges per call = 32, max blocks per range = 2^24 - # Force 2 calls - bc = 2 ** 24 * 32 * 2 - self.driver._get_lun_attr = mock.Mock(return_value={'Volume': - 'fakeLUN', - 'Path': - '/vol/fake/lun1'}) - self.driver.client.invoke_successfully = mock.Mock( - return_value=mock.MagicMock()) - lun = ntapi.NaElement.create_node_with_children( - 'lun-info', - **{'alignment': 'indeterminate', - 'block-size': '512', - 'comment': '', - 'creation-timestamp': '1354536362', - 'is-space-alloc-enabled': 'false', - 'is-space-reservation-enabled': 'true', - 'mapped': 'false', - 'multiprotocol-type': 'linux', - 'online': 'true', - 'path': '/vol/fakeLUN/lun1', - 'prefix-size': '0', - 'qtree': '', - 'read-only': 'false', - 'serial-number': '2FfGI$APyN68', - 'share-state': 'none', - 'size': '20971520', - 'size-used': '0', - 'staging': 'false', - 'suffix-size': '0', - 'uuid': 'cec1f3d7-3d41-11e2-9cf4-123478563412', - 'volume': 'fakeLUN', - 'vserver': 'fake_vserver'}) - self.driver._get_lun_by_args = mock.Mock(return_value=[lun]) - self.driver._add_lun_to_table = mock.Mock() - self.driver._update_stale_vols = mock.Mock() - self.driver._check_clone_status = mock.Mock() - self.driver._set_space_reserve = mock.Mock() - - self.driver._clone_lun('fakeLUN', 'newFakeLUN', block_count=bc) - - self.assertEqual(2, self.driver.client.invoke_successfully.call_count) - def test_clone_lun_zero_block_count(self): """Test for when clone lun is not passed a block count.""" - self.driver._get_lun_attr = mock.Mock(return_value={'Volume': - 'fakeLUN', - 'Path': - '/vol/fake/lun1'}) - self.driver.client.invoke_successfully = mock.Mock( - return_value=mock.MagicMock()) lun = ntapi.NaElement.create_node_with_children( 'lun-info', **{'alignment': 'indeterminate', @@ -370,7 +264,7 @@ class NetAppiSCSI7ModeTestCase(test.TestCase): 'mapped': 'false', 'multiprotocol-type': 'linux', 'online': 'true', - 'path': '/vol/fakeLUN/lun1', + 'path': '/vol/fakeLUN/fakeLUN', 'prefix-size': '0', 'qtree': '', 'read-only': 'false', @@ -383,15 +277,17 @@ class NetAppiSCSI7ModeTestCase(test.TestCase): 'uuid': 'cec1f3d7-3d41-11e2-9cf4-123478563412', 'volume': 'fakeLUN', 'vserver': 'fake_vserver'}) - self.driver._get_lun_by_args = mock.Mock(return_value=[lun]) + self.driver._get_lun_attr = mock.Mock(return_value={ + 'Volume': 'fakeLUN', 'Path': '/vol/fake/fakeLUN'}) + self.driver.zapi_client = mock.Mock() + self.driver.zapi_client.get_lun_by_args.return_value = [lun] self.driver._add_lun_to_table = mock.Mock() - self.driver._update_stale_vols = mock.Mock() - self.driver._check_clone_status = mock.Mock() - self.driver._set_space_reserve = mock.Mock() self.driver._clone_lun('fakeLUN', 'newFakeLUN') - self.assertEqual(1, self.driver.client.invoke_successfully.call_count) + self.driver.zapi_client.clone_lun.assert_called_once_with( + '/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN', + 'newFakeLUN', 'true', block_count=0, dest_block=0, src_block=0) @mock.patch.object(iscsi7modeDriver, '_refresh_volume_info', mock.Mock()) @mock.patch.object(iscsi7modeDriver, '_get_pool_stats', mock.Mock()) @@ -399,3 +295,17 @@ class NetAppiSCSI7ModeTestCase(test.TestCase): def test_vol_stats_calls_provide_ems(self): self.driver.get_volume_stats(refresh=True) self.assertEqual(na_utils.provide_ems.call_count, 1) + + def test_create_lun(self): + self.driver.vol_refresh_voluntary = False + + self.driver.create_lun(FAKE_VOLUME, + FAKE_LUN, + FAKE_SIZE, + FAKE_METADATA) + + self.driver.zapi_client.create_lun.assert_called_once_with( + FAKE_VOLUME, FAKE_LUN, FAKE_SIZE, + FAKE_METADATA, None) + + self.assertTrue(self.driver.vol_refresh_voluntary) diff --git a/cinder/tests/volume/drivers/netapp/test_utils.py b/cinder/tests/volume/drivers/netapp/test_utils.py index 38c805165..8e2e231b3 100644 --- a/cinder/tests/volume/drivers/netapp/test_utils.py +++ b/cinder/tests/volume/drivers/netapp/test_utils.py @@ -16,6 +16,8 @@ Mock unit tests for the NetApp driver utility module """ +import six + from cinder import test import cinder.volume.drivers.netapp.utils as na_utils @@ -46,7 +48,7 @@ class NetAppDriverUtilsTestCase(test.TestCase): def test_convert_es_fmt_to_uuid(self): value = '4Z7JGGVS5VEJBE4LHLGGUUL7VQ' - result = str(na_utils.convert_es_fmt_to_uuid(value)) + result = six.text_type(na_utils.convert_es_fmt_to_uuid(value)) self.assertEqual(result, 'e67e931a-b2ed-4890-938b-3acc6a517fac') def test_round_down(self): diff --git a/cinder/volume/drivers/netapp/api.py b/cinder/volume/drivers/netapp/api.py index 9f5f74472..3c419e551 100644 --- a/cinder/volume/drivers/netapp/api.py +++ b/cinder/volume/drivers/netapp/api.py @@ -22,6 +22,7 @@ Contains classes required to issue api calls to ONTAP and OnCommand DFM. import urllib2 from lxml import etree +import six from cinder.i18n import _ from cinder.openstack.common import log as logging @@ -121,7 +122,8 @@ class NaServer(object): try: self._api_major_version = int(major) self._api_minor_version = int(minor) - self._api_version = str(major) + "." + str(minor) + self._api_version = six.text_type(major) + "." + \ + six.text_type(minor) except ValueError: raise ValueError('Major and minor versions must be integers') self._refresh_conn = True @@ -138,7 +140,7 @@ class NaServer(object): int(port) except ValueError: raise ValueError('Port must be integer') - self._port = str(port) + self._port = six.text_type(port) self._refresh_conn = True def get_port(self): @@ -437,7 +439,7 @@ class NaElement(object): child.add_child_elem(value) self.add_child_elem(child) elif isinstance(value, (str, int, float, long)): - self.add_new_child(key, str(value)) + self.add_new_child(key, six.text_type(value)) elif isinstance(value, (list, tuple, dict)): child = NaElement(key) child.translate_struct(value) @@ -487,7 +489,7 @@ class NaElement(object): child.translate_struct(data_struct[k]) else: if data_struct[k]: - child.set_content(str(data_struct[k])) + child.set_content(six.text_type(data_struct[k])) self.add_child_elem(child) else: raise ValueError(_('Type cannot be converted into NaElement.')) diff --git a/cinder/volume/drivers/netapp/client/__init__.py b/cinder/volume/drivers/netapp/client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/volume/drivers/netapp/client/base.py b/cinder/volume/drivers/netapp/client/base.py new file mode 100644 index 000000000..4595dbd35 --- /dev/null +++ b/cinder/volume/drivers/netapp/client/base.py @@ -0,0 +1,206 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from oslo.utils import excutils +import six + +from cinder.i18n import _LE, _LW, _LI +from cinder.openstack.common import log as logging +from cinder.volume.drivers.netapp import api as netapp_api + + +LOG = logging.getLogger(__name__) + + +class Client(object): + + def __init__(self, connection): + self.connection = connection + + def get_ontapi_version(self): + """Gets the supported ontapi version.""" + ontapi_version = netapp_api.NaElement('system-get-ontapi-version') + res = self.connection.invoke_successfully(ontapi_version, False) + major = res.get_child_content('major-version') + minor = res.get_child_content('minor-version') + return (major, minor) + + def create_lun(self, volume_name, lun_name, size, metadata, + qos_policy_group=None): + """Issues API request for creating LUN on volume.""" + + path = '/vol/%s/%s' % (volume_name, lun_name) + lun_create = netapp_api.NaElement.create_node_with_children( + 'lun-create-by-size', + **{'path': path, 'size': six.text_type(size), + 'ostype': metadata['OsType'], + 'space-reservation-enabled': metadata['SpaceReserved']}) + if qos_policy_group: + lun_create.add_new_child('qos-policy-group', qos_policy_group) + + try: + self.connection.invoke_successfully(lun_create, True) + except netapp_api.NaApiError as ex: + with excutils.save_and_reraise_exception(): + msg = _LE("Error provisioning volume %(lun_name)s on " + "%(volume_name)s. Details: %(ex)s") + msg_args = {'lun_name': lun_name, + 'volume_name': volume_name, + 'ex': six.text_type(ex)} + LOG.error(msg % msg_args) + + def destroy_lun(self, path, force=True): + """Destroys the lun at the path.""" + lun_destroy = netapp_api.NaElement.create_node_with_children( + 'lun-destroy', + **{'path': path}) + if force: + lun_destroy.add_new_child('force', 'true') + self.connection.invoke_successfully(lun_destroy, True) + seg = path.split("/") + LOG.debug("Destroyed LUN %s" % seg[-1]) + + def map_lun(self, path, igroup_name, lun_id=None): + """Maps lun to the initiator and returns lun id assigned.""" + lun_map = netapp_api.NaElement.create_node_with_children( + 'lun-map', **{'path': path, + 'initiator-group': igroup_name}) + if lun_id: + lun_map.add_new_child('lun-id', lun_id) + try: + result = self.connection.invoke_successfully(lun_map, True) + return result.get_child_content('lun-id-assigned') + except netapp_api.NaApiError as e: + code = e.code + message = e.message + msg = _LW('Error mapping lun. Code :%(code)s, Message:%(message)s') + msg_fmt = {'code': code, 'message': message} + LOG.warning(msg % msg_fmt) + raise + + def unmap_lun(self, path, igroup_name): + """Unmaps a lun from given initiator.""" + lun_unmap = netapp_api.NaElement.create_node_with_children( + 'lun-unmap', + **{'path': path, 'initiator-group': igroup_name}) + try: + self.connection.invoke_successfully(lun_unmap, True) + except netapp_api.NaApiError as e: + msg = _LW("Error unmapping lun. Code :%(code)s," + " Message:%(message)s") + msg_fmt = {'code': e.code, 'message': e.message} + exc_info = sys.exc_info() + LOG.warning(msg % msg_fmt) + # if the lun is already unmapped + if e.code == '13115' or e.code == '9016': + pass + else: + raise exc_info[0], exc_info[1], exc_info[2] + + def create_igroup(self, igroup, igroup_type='iscsi', os_type='default'): + """Creates igroup with specified args.""" + igroup_create = netapp_api.NaElement.create_node_with_children( + 'igroup-create', + **{'initiator-group-name': igroup, + 'initiator-group-type': igroup_type, + 'os-type': os_type}) + self.connection.invoke_successfully(igroup_create, True) + + def add_igroup_initiator(self, igroup, initiator): + """Adds initiators to the specified igroup.""" + igroup_add = netapp_api.NaElement.create_node_with_children( + 'igroup-add', + **{'initiator-group-name': igroup, + 'initiator': initiator}) + self.connection.invoke_successfully(igroup_add, True) + + def do_direct_resize(self, path, new_size_bytes, force=True): + """Resize the lun.""" + seg = path.split("/") + LOG.info(_LI("Resizing lun %s directly to new size."), seg[-1]) + lun_resize = netapp_api.NaElement.create_node_with_children( + 'lun-resize', + **{'path': path, + 'size': new_size_bytes}) + if force: + lun_resize.add_new_child('force', 'true') + self.connection.invoke_successfully(lun_resize, True) + + def get_lun_geometry(self, path): + """Gets the lun geometry.""" + geometry = {} + lun_geo = netapp_api.NaElement("lun-get-geometry") + lun_geo.add_new_child('path', path) + try: + result = self.connection.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(_LE("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 = netapp_api.NaElement("volume-options-list-info") + vol_option_list.add_new_child('volume', volume_name) + result = self.connection.invoke_successfully(vol_option_list, True) + options = result.get_child_by_name("options") + if options: + opts = options.get_children() + return opts + + 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 = netapp_api.NaElement("lun-move") + lun_move.add_new_child("path", path) + lun_move.add_new_child("new-path", new_path) + self.connection.invoke_successfully(lun_move, True) + + def get_target_details(self): + """Gets the target portal details.""" + raise NotImplementedError() + + def get_iscsi_service_details(self): + """Returns iscsi iqn.""" + raise NotImplementedError() + + def get_lun_list(self): + """Gets the list of luns on filer.""" + raise NotImplementedError() + + def get_igroup_by_initiator(self, initiator): + """Get igroups by initiator.""" + raise NotImplementedError() + + def get_lun_by_args(self, **args): + """Retrieves luns with specified args.""" + raise NotImplementedError() diff --git a/cinder/volume/drivers/netapp/client/cmode.py b/cinder/volume/drivers/netapp/client/cmode.py new file mode 100644 index 000000000..2a7df8825 --- /dev/null +++ b/cinder/volume/drivers/netapp/client/cmode.py @@ -0,0 +1,318 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import copy +import math + +import six + +from cinder import exception +from cinder.i18n import _ +from cinder.openstack.common import log as logging +from cinder.volume.drivers.netapp import api as netapp_api +from cinder.volume.drivers.netapp.client import base +from cinder.volume.drivers.netapp import utils as na_utils + + +LOG = logging.getLogger(__name__) + + +class Client(base.Client): + + def __init__(self, connection, vserver): + super(Client, self).__init__(connection) + self.vserver = vserver + + def _invoke_vserver_api(self, na_element, vserver): + server = copy.copy(self.connection) + server.set_vserver(vserver) + result = server.invoke_successfully(na_element, True) + return result + + def get_target_details(self): + """Gets the target portal details.""" + iscsi_if_iter = netapp_api.NaElement('iscsi-interface-get-iter') + result = self.connection.invoke_successfully(iscsi_if_iter, True) + tgt_list = [] + 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') + iscsi_if_list = attr_list.get_children() + for iscsi_if in iscsi_if_list: + d = dict() + d['address'] = iscsi_if.get_child_content('ip-address') + d['port'] = iscsi_if.get_child_content('ip-port') + d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag') + d['interface-enabled'] = iscsi_if.get_child_content( + 'is-interface-enabled') + tgt_list.append(d) + return tgt_list + + def get_iscsi_service_details(self): + """Returns iscsi iqn.""" + iscsi_service_iter = netapp_api.NaElement('iscsi-service-get-iter') + result = self.connection.invoke_successfully(iscsi_service_iter, True) + if result.get_child_content('num-records') and\ + int(result.get_child_content('num-records')) >= 1: + attr_list = result.get_child_by_name('attributes-list') + iscsi_service = attr_list.get_child_by_name('iscsi-service-info') + return iscsi_service.get_child_content('node-name') + LOG.debug('No iSCSI service found for vserver %s' % (self.vserver)) + return None + + def get_lun_list(self): + """Gets the list of luns on filer. + + Gets the luns from cluster with vserver. + """ + + luns = [] + tag = None + while True: + api = netapp_api.NaElement('lun-get-iter') + api.add_new_child('max-records', '100') + if tag: + api.add_new_child('tag', tag, True) + lun_info = netapp_api.NaElement('lun-info') + lun_info.add_new_child('vserver', self.vserver) + query = netapp_api.NaElement('query') + query.add_child_elem(lun_info) + api.add_child_elem(query) + result = self.connection.invoke_successfully(api) + if result.get_child_by_name('num-records') and\ + int(result.get_child_content('num-records')) >= 1: + attr_list = result.get_child_by_name('attributes-list') + luns.extend(attr_list.get_children()) + tag = result.get_child_content('next-tag') + if tag is None: + break + return luns + + def get_lun_map(self, path): + """Gets the lun map by lun path.""" + tag = None + map_list = [] + while True: + lun_map_iter = netapp_api.NaElement('lun-map-get-iter') + lun_map_iter.add_new_child('max-records', '100') + if tag: + lun_map_iter.add_new_child('tag', tag, True) + query = netapp_api.NaElement('query') + lun_map_iter.add_child_elem(query) + query.add_node_with_children('lun-map-info', **{'path': path}) + result = self.connection.invoke_successfully(lun_map_iter, True) + tag = result.get_child_content('next-tag') + if result.get_child_content('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + attr_list = result.get_child_by_name('attributes-list') + lun_maps = attr_list.get_children() + for lun_map in lun_maps: + lun_m = dict() + lun_m['initiator-group'] = lun_map.get_child_content( + 'initiator-group') + lun_m['lun-id'] = lun_map.get_child_content('lun-id') + lun_m['vserver'] = lun_map.get_child_content('vserver') + map_list.append(lun_m) + if tag is None: + break + return map_list + + def get_igroup_by_initiator(self, initiator): + """Get igroups by initiator.""" + tag = None + igroup_list = [] + while True: + igroup_iter = netapp_api.NaElement('igroup-get-iter') + igroup_iter.add_new_child('max-records', '100') + if tag: + igroup_iter.add_new_child('tag', tag, True) + query = netapp_api.NaElement('query') + igroup_iter.add_child_elem(query) + igroup_info = netapp_api.NaElement('initiator-group-info') + query.add_child_elem(igroup_info) + igroup_info.add_new_child('vserver', self.vserver) + initiators = netapp_api.NaElement('initiators') + igroup_info.add_child_elem(initiators) + initiators.add_node_with_children('initiator-info', + **{'initiator-name': initiator}) + des_attrs = netapp_api.NaElement('desired-attributes') + des_ig_info = netapp_api.NaElement('initiator-group-info') + des_attrs.add_child_elem(des_ig_info) + des_ig_info.add_node_with_children('initiators', + **{'initiator-info': None}) + des_ig_info.add_new_child('vserver', None) + des_ig_info.add_new_child('initiator-group-name', None) + des_ig_info.add_new_child('initiator-group-type', None) + des_ig_info.add_new_child('initiator-group-os-type', None) + igroup_iter.add_child_elem(des_attrs) + result = self.connection.invoke_successfully(igroup_iter, False) + tag = result.get_child_content('next-tag') + if result.get_child_content('num-records') and\ + int(result.get_child_content('num-records')) > 0: + attr_list = result.get_child_by_name('attributes-list') + igroups = attr_list.get_children() + for igroup in igroups: + ig = dict() + ig['initiator-group-os-type'] = igroup.get_child_content( + 'initiator-group-os-type') + ig['initiator-group-type'] = igroup.get_child_content( + 'initiator-group-type') + ig['initiator-group-name'] = igroup.get_child_content( + 'initiator-group-name') + igroup_list.append(ig) + if tag is None: + break + return igroup_list + + def clone_lun(self, volume, name, new_name, space_reserved='true', + src_block=0, dest_block=0, block_count=0): + # zAPI can only handle 2^24 blocks per range + bc_limit = 2 ** 24 # 8GB + # zAPI can only handle 32 block ranges per call + br_limit = 32 + z_limit = br_limit * bc_limit # 256 GB + z_calls = int(math.ceil(block_count / float(z_limit))) + zbc = block_count + if z_calls == 0: + z_calls = 1 + for call in range(0, z_calls): + if zbc > z_limit: + block_count = z_limit + zbc -= z_limit + else: + block_count = zbc + clone_create = netapp_api.NaElement.create_node_with_children( + 'clone-create', + **{'volume': volume, 'source-path': name, + 'destination-path': new_name, + 'space-reserve': space_reserved}) + if block_count > 0: + block_ranges = netapp_api.NaElement("block-ranges") + segments = int(math.ceil(block_count / float(bc_limit))) + bc = block_count + for segment in range(0, segments): + if bc > bc_limit: + block_count = bc_limit + bc -= bc_limit + else: + block_count = bc + block_range =\ + netapp_api.NaElement.create_node_with_children( + 'block-range', + **{'source-block-number': + six.text_type(src_block), + 'destination-block-number': + six.text_type(dest_block), + 'block-count': + six.text_type(block_count)}) + block_ranges.add_child_elem(block_range) + src_block += int(block_count) + dest_block += int(block_count) + clone_create.add_child_elem(block_ranges) + self.connection.invoke_successfully(clone_create, True) + + def get_lun_by_args(self, **args): + """Retrieves lun with specified args.""" + lun_iter = netapp_api.NaElement('lun-get-iter') + lun_iter.add_new_child('max-records', '100') + query = netapp_api.NaElement('query') + lun_iter.add_child_elem(query) + query.add_node_with_children('lun-info', **args) + luns = self.connection.invoke_successfully(lun_iter) + attr_list = luns.get_child_by_name('attributes-list') + return attr_list.get_children() + + def file_assign_qos(self, flex_vol, qos_policy_group, file_path): + """Retrieves lun with specified args.""" + file_assign_qos = netapp_api.NaElement.create_node_with_children( + 'file-assign-qos', + **{'volume': flex_vol, + 'qos-policy-group-name': qos_policy_group, + 'file': file_path, + 'vserver': self.vserver}) + self.connection.invoke_successfully(file_assign_qos, True) + + def get_if_info_by_ip(self, ip): + """Gets the network interface info by ip.""" + net_if_iter = netapp_api.NaElement('net-interface-get-iter') + net_if_iter.add_new_child('max-records', '10') + query = netapp_api.NaElement('query') + net_if_iter.add_child_elem(query) + query.add_node_with_children( + 'net-interface-info', **{'address': na_utils.resolve_hostname(ip)}) + result = self.connection.invoke_successfully(net_if_iter, True) + 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() + raise exception.NotFound( + _('No interface found on cluster for ip %s') % (ip)) + + def get_vol_by_junc_vserver(self, vserver, junction): + """Gets the volume by junction path and vserver.""" + vol_iter = netapp_api.NaElement('volume-get-iter') + vol_iter.add_new_child('max-records', '10') + query = netapp_api.NaElement('query') + vol_iter.add_child_elem(query) + vol_attrs = netapp_api.NaElement('volume-attributes') + query.add_child_elem(vol_attrs) + vol_attrs.add_node_with_children( + 'volume-id-attributes', + **{'junction-path': junction, + 'owning-vserver-name': vserver}) + des_attrs = netapp_api.NaElement('desired-attributes') + des_attrs.add_node_with_children('volume-attributes', + **{'volume-id-attributes': None}) + vol_iter.add_child_elem(des_attrs) + result = self._invoke_vserver_api(vol_iter, vserver) + 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') + vols = attr_list.get_children() + vol_id = vols[0].get_child_by_name('volume-id-attributes') + return vol_id.get_child_content('name') + msg_fmt = {'vserver': vserver, 'junction': junction} + raise exception.NotFound(_("No volume on cluster with vserver " + "%(vserver)s and junction path " + "%(junction)s ") % msg_fmt) + + def clone_file(self, flex_vol, src_path, dest_path, vserver, + dest_exists=False): + """Clones file on vserver.""" + msg = ("Cloning with params volume %(volume)s, src %(src_path)s," + "dest %(dest_path)s, vserver %(vserver)s") + msg_fmt = {'volume': flex_vol, 'src_path': src_path, + 'dest_path': dest_path, 'vserver': vserver} + LOG.debug(msg % msg_fmt) + clone_create = netapp_api.NaElement.create_node_with_children( + 'clone-create', + **{'volume': flex_vol, 'source-path': src_path, + 'destination-path': dest_path}) + major, minor = self.connection.get_api_version() + if major == 1 and minor >= 20 and dest_exists: + clone_create.add_new_child('destination-exists', 'true') + self._invoke_vserver_api(clone_create, vserver) + + def get_file_usage(self, path, vserver): + """Gets the file unique bytes.""" + LOG.debug('Getting file usage for %s', path) + file_use = netapp_api.NaElement.create_node_with_children( + 'file-usage-get', **{'path': path}) + res = self._invoke_vserver_api(file_use, vserver) + unique_bytes = res.get_child_content('unique-bytes') + LOG.debug('file-usage for path %(path)s is %(bytes)s' + % {'path': path, 'bytes': unique_bytes}) + return unique_bytes diff --git a/cinder/volume/drivers/netapp/client/seven_mode.py b/cinder/volume/drivers/netapp/client/seven_mode.py new file mode 100644 index 000000000..2ee84078c --- /dev/null +++ b/cinder/volume/drivers/netapp/client/seven_mode.py @@ -0,0 +1,339 @@ +# Copyright (c) - 2014, Alex Meade. All rights reserved. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import copy +import math +import time + +import six + +from cinder import exception +from cinder.i18n import _, _LW +from cinder.openstack.common import log as logging +from cinder.volume.drivers.netapp import api as netapp_api +from cinder.volume.drivers.netapp.client import base + + +LOG = logging.getLogger(__name__) + + +class Client(base.Client): + + def __init__(self, connection, volume_list=None): + super(Client, self).__init__(connection) + self.volume_list = volume_list + + def _invoke_vfiler_api(self, na_element, vfiler): + server = copy.copy(self.connection) + server.set_vfiler(vfiler) + result = server.invoke_successfully(na_element, True) + return result + + def get_target_details(self): + """Gets the target portal details.""" + iscsi_if_iter = netapp_api.NaElement('iscsi-portal-list-info') + result = self.connection.invoke_successfully(iscsi_if_iter, True) + tgt_list = [] + portal_list_entries = result.get_child_by_name( + 'iscsi-portal-list-entries') + if portal_list_entries: + portal_list = portal_list_entries.get_children() + for iscsi_if in portal_list: + d = dict() + d['address'] = iscsi_if.get_child_content('ip-address') + d['port'] = iscsi_if.get_child_content('ip-port') + d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag') + tgt_list.append(d) + return tgt_list + + def get_iscsi_service_details(self): + """Returns iscsi iqn.""" + iscsi_service_iter = netapp_api.NaElement('iscsi-node-get-name') + result = self.connection.invoke_successfully(iscsi_service_iter, True) + return result.get_child_content('node-name') + + def get_lun_list(self): + """Gets the list of luns on filer.""" + lun_list = [] + if self.volume_list: + for vol in self.volume_list: + try: + luns = self._get_vol_luns(vol) + if luns: + lun_list.extend(luns) + except netapp_api.NaApiError: + LOG.warning(_LW("Error finding luns for volume %s." + " Verify volume exists.") % (vol)) + else: + luns = self._get_vol_luns(None) + lun_list.extend(luns) + return lun_list + + def _get_vol_luns(self, vol_name): + """Gets the luns for a volume.""" + api = netapp_api.NaElement('lun-list-info') + if vol_name: + api.add_new_child('volume-name', vol_name) + result = self.connection.invoke_successfully(api, True) + luns = result.get_child_by_name('luns') + return luns.get_children() + + def get_igroup_by_initiator(self, initiator): + """Get igroups by initiator.""" + igroup_list = netapp_api.NaElement('igroup-list-info') + result = self.connection.invoke_successfully(igroup_list, True) + igroups = [] + igs = result.get_child_by_name('initiator-groups') + if igs: + ig_infos = igs.get_children() + if ig_infos: + for info in ig_infos: + initiators = info.get_child_by_name('initiators') + init_infos = initiators.get_children() + if init_infos: + for init in init_infos: + if init.get_child_content('initiator-name')\ + == initiator: + d = dict() + d['initiator-group-os-type'] = \ + info.get_child_content( + 'initiator-group-os-type') + d['initiator-group-type'] = \ + info.get_child_content( + 'initiator-group-type') + d['initiator-group-name'] = \ + info.get_child_content( + 'initiator-group-name') + igroups.append(d) + return igroups + + def clone_lun(self, path, clone_path, name, new_name, + space_reserved='true', src_block=0, + dest_block=0, block_count=0): + # zAPI can only handle 2^24 blocks per range + bc_limit = 2 ** 24 # 8GB + # zAPI can only handle 32 block ranges per call + br_limit = 32 + z_limit = br_limit * bc_limit # 256 GB + z_calls = int(math.ceil(block_count / float(z_limit))) + zbc = block_count + if z_calls == 0: + z_calls = 1 + for call in range(0, z_calls): + if zbc > z_limit: + block_count = z_limit + zbc -= z_limit + else: + block_count = zbc + clone_start = netapp_api.NaElement.create_node_with_children( + 'clone-start', **{'source-path': path, + 'destination-path': clone_path, + 'no-snap': 'true'}) + if block_count > 0: + block_ranges = netapp_api.NaElement("block-ranges") + # zAPI can only handle 2^24 block ranges + bc_limit = 2 ** 24 # 8GB + segments = int(math.ceil(block_count / float(bc_limit))) + bc = block_count + for segment in range(0, segments): + if bc > bc_limit: + block_count = bc_limit + bc -= bc_limit + else: + block_count = bc + block_range =\ + netapp_api.NaElement.create_node_with_children( + 'block-range', + **{'source-block-number': + six.text_type(src_block), + 'destination-block-number': + six.text_type(dest_block), + 'block-count': + six.text_type(block_count)}) + block_ranges.add_child_elem(block_range) + src_block += int(block_count) + dest_block += int(block_count) + clone_start.add_child_elem(block_ranges) + result = self.connection.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') + vol_uuid = cl_id_info.get_child_content('volume-uuid') + 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) + + def _check_clone_status(self, clone_id, vol_uuid, name, new_name): + """Checks for the job till completed.""" + clone_status = netapp_api.NaElement('clone-list-status') + cl_id = netapp_api.NaElement('clone-id') + clone_status.add_child_elem(cl_id) + cl_id.add_node_with_children('clone-id-info', + **{'clone-op-id': clone_id, + 'volume-uuid': vol_uuid}) + running = True + clone_ops_info = None + while running: + result = self.connection.invoke_successfully(clone_status, True) + status = result.get_child_by_name('status') + ops_info = status.get_children() + if ops_info: + for info in ops_info: + if info.get_child_content('clone-state') == 'running': + time.sleep(1) + break + else: + running = False + clone_ops_info = info + break + else: + if clone_ops_info: + fmt = {'name': name, 'new_name': new_name} + if clone_ops_info.get_child_content('clone-state')\ + == 'completed': + LOG.debug("Clone operation with src %(name)s" + " and dest %(new_name)s completed" % fmt) + else: + LOG.debug("Clone operation with src %(name)s" + " and dest %(new_name)s failed" % fmt) + raise netapp_api.NaApiError( + clone_ops_info.get_child_content('error'), + clone_ops_info.get_child_content('reason')) + + def get_lun_by_args(self, **args): + """Retrieves luns with specified args.""" + lun_info = netapp_api.NaElement.create_node_with_children( + 'lun-list-info', **args) + result = self.connection.invoke_successfully(lun_info, True) + luns = result.get_child_by_name('luns') + return luns.get_children() + + def get_filer_volumes(self, volume=None): + """Returns list of filer volumes in api format.""" + vol_request = netapp_api.NaElement('volume-list-info') + res = self.connection.invoke_successfully(vol_request, True) + volumes = res.get_child_by_name('volumes') + if volumes: + return volumes.get_children() + return [] + + def get_lun_map(self, path): + lun_map_list = netapp_api.NaElement.create_node_with_children( + 'lun-map-list-info', + **{'path': path}) + return self.connection.invoke_successfully(lun_map_list, True) + + def set_space_reserve(self, path, enable): + """Sets the space reserve info.""" + space_res = netapp_api.NaElement.create_node_with_children( + 'lun-set-space-reservation-info', + **{'path': path, 'enable': enable}) + self.connection.invoke_successfully(space_res, True) + + def get_actual_path_for_export(self, export_path): + """Gets the actual path on the filer for export path.""" + storage_path = netapp_api.NaElement.create_node_with_children( + 'nfs-exportfs-storage-path', **{'pathname': export_path}) + result = self.connection.invoke_successfully(storage_path) + if result.get_child_content('actual-pathname'): + return result.get_child_content('actual-pathname') + raise exception.NotFound(_('No storage path found for export path %s') + % (export_path)) + + def clone_file(self, src_path, dest_path): + msg_fmt = {'src_path': src_path, 'dest_path': dest_path} + LOG.debug("""Cloning with src %(src_path)s, dest %(dest_path)s""" + % msg_fmt) + clone_start = netapp_api.NaElement.create_node_with_children( + 'clone-start', + **{'source-path': src_path, + 'destination-path': dest_path, + 'no-snap': 'true'}) + result = self.connection.invoke_successfully(clone_start) + clone_id_el = result.get_child_by_name('clone-id') + cl_id_info = clone_id_el.get_child_by_name('clone-id-info') + vol_uuid = cl_id_info.get_child_content('volume-uuid') + clone_id = cl_id_info.get_child_content('clone-op-id') + + if vol_uuid: + try: + self._wait_for_clone_finish(clone_id, vol_uuid) + except netapp_api.NaApiError as e: + if e.code != 'UnknownCloneId': + self._clear_clone(clone_id) + raise e + + def _wait_for_clone_finish(self, clone_op_id, vol_uuid): + """Waits till a clone operation is complete or errored out.""" + clone_ls_st = netapp_api.NaElement('clone-list-status') + clone_id = netapp_api.NaElement('clone-id') + clone_ls_st.add_child_elem(clone_id) + clone_id.add_node_with_children('clone-id-info', + **{'clone-op-id': clone_op_id, + 'volume-uuid': vol_uuid}) + task_running = True + while task_running: + result = self.connection.invoke_successfully(clone_ls_st) + status = result.get_child_by_name('status') + ops_info = status.get_children() + if ops_info: + state = ops_info[0].get_child_content('clone-state') + if state == 'completed': + task_running = False + elif state == 'failed': + code = ops_info[0].get_child_content('error') + reason = ops_info[0].get_child_content('reason') + raise netapp_api.NaApiError(code, reason) + else: + time.sleep(1) + else: + raise netapp_api.NaApiError( + 'UnknownCloneId', + 'No clone operation for clone id %s found on the filer' + % (clone_id)) + + def _clear_clone(self, clone_id): + """Clear the clone information. + + Invoke this in case of failed clone. + """ + + clone_clear = netapp_api.NaElement.create_node_with_children( + 'clone-clear', + **{'clone-id': clone_id}) + retry = 3 + while retry: + try: + self.connection.invoke_successfully(clone_clear) + break + except netapp_api.NaApiError: + # Filer might be rebooting + time.sleep(5) + retry = retry - 1 + + def get_file_usage(self, path): + """Gets the file unique bytes.""" + LOG.debug('Getting file usage for %s', path) + file_use = netapp_api.NaElement.create_node_with_children( + 'file-usage-get', **{'path': path}) + res = self.connection.invoke_successfully(file_use) + bytes = res.get_child_content('unique-bytes') + LOG.debug('file-usage for path %(path)s is %(bytes)s' + % {'path': path, 'bytes': bytes}) + return bytes + + def get_ifconfig(self): + ifconfig = netapp_api.NaElement('net-ifconfig-get') + return self.connection.invoke_successfully(ifconfig) diff --git a/cinder/volume/drivers/netapp/common.py b/cinder/volume/drivers/netapp/common.py index d6acd0640..162dd73a4 100644 --- a/cinder/volume/drivers/netapp/common.py +++ b/cinder/volume/drivers/netapp/common.py @@ -170,7 +170,7 @@ class Deprecated(driver.VolumeDriver): link = "https://communities.netapp.com/groups/openstack" msg = _("The configured NetApp driver is deprecated." " Please refer the link to resolve the issue '%s'.") - LOG.warn(msg % link) + LOG.warning(msg % link) def check_for_setup_error(self): pass diff --git a/cinder/volume/drivers/netapp/eseries/iscsi.py b/cinder/volume/drivers/netapp/eseries/iscsi.py index b80384a94..3c5057953 100644 --- a/cinder/volume/drivers/netapp/eseries/iscsi.py +++ b/cinder/volume/drivers/netapp/eseries/iscsi.py @@ -429,7 +429,7 @@ class Driver(driver.ISCSIDriver): except exception.NetAppDriverException as e: LOG.error(_LE("Failure deleting snap vol. Error: %s."), e) else: - LOG.warn(_LW("Snapshot volume not found.")) + LOG.warning(_LW("Snapshot volume not found.")) def _create_snapshot_volume(self, snapshot_id): """Creates snapshot volume for given group with snapshot_id.""" @@ -470,11 +470,11 @@ class Driver(driver.ISCSIDriver): try: self._client.delete_vol_copy_job(job['volcopyRef']) except exception.NetAppDriverException: - LOG.warn(_LW("Failure deleting " - "job %s."), job['volcopyRef']) + LOG.warning(_LW("Failure deleting " + "job %s."), job['volcopyRef']) else: - LOG.warn(_LW('Volume copy job for src vol %s not found.'), - src_vol['id']) + LOG.warning(_LW('Volume copy job for src vol %s not found.'), + src_vol['id']) LOG.info(_LI('Copy job to dest vol %s completed.'), dst_vol['label']) def create_cloned_volume(self, volume, src_vref): @@ -487,8 +487,8 @@ class Driver(driver.ISCSIDriver): try: self.delete_snapshot(snapshot) except exception.NetAppDriverException: - LOG.warn(_LW("Failure deleting temp snapshot %s."), - snapshot['id']) + LOG.warning(_LW("Failure deleting temp snapshot %s."), + snapshot['id']) def delete_volume(self, volume): """Deletes a volume.""" @@ -531,7 +531,7 @@ class Driver(driver.ISCSIDriver): try: snap_grp = self._get_cached_snapshot_grp(snapshot['id']) except KeyError: - LOG.warn(_LW("Snapshot %s already deleted.") % snapshot['id']) + LOG.warning(_LW("Snapshot %s already deleted.") % snapshot['id']) return self._client.delete_snapshot_group(snap_grp['pitGroupRef']) snapshot_name = snap_grp['label'] @@ -648,12 +648,12 @@ class Driver(driver.ISCSIDriver): return self._client.update_host_type( host['hostRef'], ht_def) except exception.NetAppDriverException as e: - msg = _("Unable to update host type for host with" - " label %(l)s. %(e)s") - LOG.warn(msg % {'l': host['label'], 'e': e.msg}) + msg = _LW("Unable to update host type for host with " + "label %(l)s. %(e)s") + LOG.warning(msg % {'l': host['label'], 'e': e.msg}) return host except exception.NotFound as e: - LOG.warn(_LW("Message - %s."), e.msg) + LOG.warning(_LW("Message - %s."), e.msg) return self._create_host(port_id, host_type) def _get_host_with_port(self, port_id): @@ -774,8 +774,8 @@ class Driver(driver.ISCSIDriver): (int(x.get('totalRaidedSpace', 0)) - int(x.get('usedSpace', 0) >= size))] if not avl_pools: - msg = _("No storage pool found with available capacity %s.") - LOG.warn(msg % size_gb) + msg = _LW("No storage pool found with available capacity %s.") + LOG.warning(msg % size_gb) return avl_pools def extend_volume(self, volume, new_size): @@ -807,8 +807,8 @@ class Driver(driver.ISCSIDriver): """Removes tmp vols with no snapshots.""" try: if not utils.set_safe_attr(self, 'clean_job_running', True): - LOG.warn(_LW('Returning as clean tmp ' - 'vol job already running.')) + LOG.warning(_LW('Returning as clean tmp ' + 'vol job already running.')) return for label in self._objects['volumes']['label_ref'].keys(): if (label.startswith('tmp-') and diff --git a/cinder/volume/drivers/netapp/iscsi.py b/cinder/volume/drivers/netapp/iscsi.py index dccac08ca..aa94c4874 100644 --- a/cinder/volume/drivers/netapp/iscsi.py +++ b/cinder/volume/drivers/netapp/iscsi.py @@ -21,9 +21,7 @@ storage systems with installed iSCSI licenses. """ import copy -import math import sys -import time import uuid from oslo.utils import excutils @@ -39,6 +37,8 @@ from cinder.volume import driver from cinder.volume.drivers.netapp.api import NaApiError from cinder.volume.drivers.netapp.api import NaElement from cinder.volume.drivers.netapp.api import NaServer +from cinder.volume.drivers.netapp.client import cmode +from cinder.volume.drivers.netapp.client import seven_mode from cinder.volume.drivers.netapp.options import netapp_7mode_opts from cinder.volume.drivers.netapp.options import netapp_basicauth_opts from cinder.volume.drivers.netapp.options import netapp_cluster_opts @@ -100,6 +100,7 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): self.configuration.append_config_values(netapp_transport_opts) self.configuration.append_config_values(netapp_provisioning_opts) self.lun_table = {} + self.zapi_client = None def _create_client(self, **kwargs): """Instantiate a client for NetApp server. @@ -154,7 +155,8 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """ self.lun_table = {} - self._get_lun_list() + lun_list = self.zapi_client.get_lun_list() + self._extract_and_populate_luns(lun_list) LOG.debug("Success getting LUN list from server") def get_pool(self, volume): @@ -212,24 +214,13 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): name = volume['name'] metadata = self._get_lun_attr(name, 'metadata') if not metadata: - msg = _("No entry in LUN table for volume/snapshot %(name)s.") + msg = _LW("No entry in LUN table for volume/snapshot %(name)s.") msg_fmt = {'name': name} - LOG.warn(msg % msg_fmt) + LOG.warning(msg % msg_fmt) return - self._destroy_lun(metadata['Path']) + self.zapi_client.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': path}) - if force: - lun_destroy.add_new_child('force', 'true') - self.client.invoke_successfully(lun_destroy, True) - 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.""" handle = self._get_lun_attr(volume['name'], 'handle') @@ -267,8 +258,8 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): msg = _("Mapped LUN %(name)s to the initiator %(initiator_name)s") msg_fmt = {'name': name, 'initiator_name': initiator_name} LOG.debug(msg % msg_fmt) - iqn = self._get_iscsi_service_details() - target_details_list = self._get_target_details() + iqn = self.zapi_client.get_iscsi_service_details() + target_details_list = self.zapi_client.get_target_details() msg = _("Successfully fetched target details for LUN %(name)s and " "initiator %(initiator_name)s") msg_fmt = {'name': name, 'initiator_name': initiator_name} @@ -367,54 +358,15 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): msg_fmt = {'name': name, 'initiator_name': initiator_name} LOG.debug(msg % msg_fmt) - def _get_ontapi_version(self): - """Gets the supported ontapi version.""" - ontapi_version = NaElement('system-get-ontapi-version') - res = self.client.invoke_successfully(ontapi_version, False) - major = res.get_child_content('major-version') - minor = res.get_child_content('minor-version') - return (major, minor) - def create_lun(self, volume_name, lun_name, size, metadata, qos_policy_group=None): - """Issues API request for creating LUN on volume.""" - - path = '/vol/%s/%s' % (volume_name, lun_name) - lun_create = NaElement.create_node_with_children( - 'lun-create-by-size', - **{'path': path, 'size': six.text_type(size), - 'ostype': metadata['OsType'], - 'space-reservation-enabled': metadata['SpaceReserved']}) - if qos_policy_group: - lun_create.add_new_child('qos-policy-group', qos_policy_group) - - try: - self.client.invoke_successfully(lun_create, True) - except NaApiError as ex: - with excutils.save_and_reraise_exception(): - msg = _("Error provisioning volume %(lun_name)s on " - "%(volume_name)s. Details: %(ex)s") - msg_args = {'lun_name': lun_name, - 'volume_name': volume_name, - 'ex': six.text_type(ex)} - LOG.error(msg % msg_args) - - def _get_iscsi_service_details(self): - """Returns iscsi iqn.""" - raise NotImplementedError() - - def _get_target_details(self): - """Gets the target portal details.""" + """Creates a LUN, handling ONTAP differences as needed.""" raise NotImplementedError() def _create_lun_handle(self, metadata): """Returns lun handle based on filer type.""" raise NotImplementedError() - def _get_lun_list(self): - """Gets the list of luns on filer.""" - raise NotImplementedError() - def _extract_and_populate_luns(self, api_luns): """Extracts the luns from api. @@ -447,21 +399,10 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): os = 'default' igroup_name = self._get_or_create_igroup(initiator, initiator_type, os) - lun_map = NaElement.create_node_with_children( - 'lun-map', **{'path': path, - 'initiator-group': igroup_name}) - if lun_id: - lun_map.add_new_child('lun-id', lun_id) try: - result = self.client.invoke_successfully(lun_map, True) - return result.get_child_content('lun-id-assigned') - except NaApiError as e: - code = e.code - message = e.message - msg = _('Error mapping lun. Code :%(code)s, Message:%(message)s') - msg_fmt = {'code': code, 'message': message} + return self.zapi_client.map_lun(path, igroup_name, lun_id=lun_id) + except NaApiError: exc_info = sys.exc_info() - LOG.warn(msg % msg_fmt) (_igroup, lun_id) = self._find_mapped_lun_igroup(path, initiator) if lun_id is not None: return lun_id @@ -471,22 +412,7 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): def _unmap_lun(self, path, initiator): """Unmaps a lun from given initiator.""" (igroup_name, _lun_id) = self._find_mapped_lun_igroup(path, initiator) - lun_unmap = NaElement.create_node_with_children( - 'lun-unmap', - **{'path': path, 'initiator-group': igroup_name}) - try: - self.client.invoke_successfully(lun_unmap, True) - except NaApiError as e: - msg = _("Error unmapping lun. Code :%(code)s," - " Message:%(message)s") - msg_fmt = {'code': e.code, 'message': e.message} - exc_info = sys.exc_info() - LOG.warn(msg % msg_fmt) - # if the lun is already unmapped - if e.code == '13115' or e.code == '9016': - pass - else: - raise exc_info[0], exc_info[1], exc_info[2] + self.zapi_client.unmap_lun(path, igroup_name) def _find_mapped_lun_igroup(self, path, initiator, os=None): """Find the igroup for mapped lun with initiator.""" @@ -499,7 +425,7 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): Creates igroup if not found. """ - igroups = self._get_igroup_by_initiator(initiator=initiator) + igroups = self.zapi_client.get_igroup_by_initiator(initiator=initiator) igroup_name = None for igroup in igroups: if igroup['initiator-group-os-type'] == os: @@ -510,15 +436,11 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): igroup_name = igroup['initiator-group-name'] break if not igroup_name: - igroup_name = self.IGROUP_PREFIX + str(uuid.uuid4()) - self._create_igroup(igroup_name, initiator_type, os) - self._add_igroup_initiator(igroup_name, initiator) + igroup_name = self.IGROUP_PREFIX + six.text_type(uuid.uuid4()) + self.zapi_client.create_igroup(igroup_name, initiator_type, os) + self.zapi_client.add_igroup_initiator(igroup_name, initiator) return igroup_name - def _get_igroup_by_initiator(self, initiator): - """Get igroups by initiator.""" - raise NotImplementedError() - def _check_allowed_os(self, os): """Checks if the os type supplied is NetApp supported.""" if os in ['linux', 'aix', 'hpux', 'windows', 'solaris', @@ -527,23 +449,6 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): else: return False - def _create_igroup(self, igroup, igroup_type='iscsi', os_type='default'): - """Creates igroup with specified args.""" - igroup_create = NaElement.create_node_with_children( - 'igroup-create', - **{'initiator-group-name': igroup, - 'initiator-group-type': igroup_type, - 'os-type': os_type}) - self.client.invoke_successfully(igroup_create, True) - - def _add_igroup_initiator(self, igroup, initiator): - """Adds initiators to the specified igroup.""" - igroup_add = NaElement.create_node_with_children( - 'igroup-add', - **{'initiator-group-name': igroup, - 'initiator': initiator}) - self.client.invoke_successfully(igroup_add, True) - def _add_lun_to_table(self, lun): """Adds LUN to cache table.""" if not isinstance(lun, NetAppLun): @@ -558,7 +463,8 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """ lun = self.lun_table.get(name) if lun is None: - self._get_lun_list() + lun_list = self.zapi_client.get_lun_list() + self._extract_and_populate_luns(lun_list) lun = self.lun_table.get(name) if lun is None: raise exception.VolumeNotFound(volume_id=name) @@ -569,10 +475,6 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """Clone LUN with the given name to the new name.""" raise NotImplementedError() - def _get_lun_by_args(self, **args): - """Retrieves luns with specified args.""" - raise NotImplementedError() - def _get_lun_attr(self, name, attr): """Get the lun attribute if found else None.""" try: @@ -624,16 +526,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): name = volume['name'] lun = self._get_lun_from_table(name) path = lun.metadata['Path'] - curr_size_bytes = str(lun.size) - new_size_bytes = str(int(new_size) * units.Gi) + curr_size_bytes = six.text_type(lun.size) + new_size_bytes = six.text_type(int(new_size) * units.Gi) # Reused by clone scenarios. # Hence comparing the stored size. if curr_size_bytes != new_size_bytes: - lun_geometry = self._get_lun_geometry(path) + lun_geometry = self.zapi_client.get_lun_geometry(path) if (lun_geometry and lun_geometry.get("max_resize") and int(lun_geometry.get("max_resize")) >= int(new_size_bytes)): - self._do_direct_resize(path, new_size_bytes) + self.zapi_client.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 @@ -641,72 +543,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): LOG.info(_LI("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(_LI("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(_LE("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) + options = self.zapi_client.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. @@ -732,14 +578,15 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): ' 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) + self.zapi_client.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) + self.zapi_client.destroy_lun(new_path) def _post_sub_clone_resize(self, path): """Try post sub clone resize in a transactional manner.""" @@ -751,16 +598,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): 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) + st_tm_mv = self.zapi_client.move_lun(path, tmp_path) + st_nw_mv = self.zapi_client.move_lun(new_path, path) + st_del_old = self.zapi_client.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) + self.zapi_client.move_lun(tmp_path, path) msg = _("Failure moving new cloned lun to %s.") raise exception.VolumeBackendAPIException( data=msg % (seg[-1])) @@ -776,7 +623,7 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """Gets block counts for the lun.""" LOG.debug("Getting lun block count.") block_count = 0 - lun_infos = self._get_lun_by_args(path=path) + lun_infos = self.zapi_client.get_lun_by_args(path=path) if not lun_infos: seg = path.split('/') msg = _('Failure getting lun info for %s.') @@ -801,12 +648,13 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): """Does custom setup for ontap cluster.""" self.vserver = self.configuration.netapp_vserver self.vserver = self.vserver if self.vserver else self.DEFAULT_VS + self.zapi_client = cmode.Client(self.client, self.vserver) # We set vserver in client permanently. # To use tunneling enable_tunneling while invoking api self.client.set_vserver(self.vserver) # Default values to run first api self.client.set_api_version(1, 15) - (major, minor) = self._get_ontapi_version() + (major, minor) = self.zapi_client.get_ontapi_version() self.client.set_api_version(major, minor) self.ssc_vols = None self.stale_vols = set() @@ -820,77 +668,21 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): metadata, qos_policy_group=None): """Creates a LUN, handling ONTAP differences as needed.""" - super(NetAppDirectCmodeISCSIDriver, self).create_lun( + self.zapi_client.create_lun( volume_name, lun_name, size, metadata, qos_policy_group) self._update_stale_vols( volume=ssc_utils.NetAppVolume(volume_name, self.vserver)) - def _get_target_details(self): - """Gets the target portal details.""" - iscsi_if_iter = NaElement('iscsi-interface-get-iter') - result = self.client.invoke_successfully(iscsi_if_iter, True) - tgt_list = [] - if result.get_child_content('num-records')\ - and int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - iscsi_if_list = attr_list.get_children() - for iscsi_if in iscsi_if_list: - d = dict() - d['address'] = iscsi_if.get_child_content('ip-address') - d['port'] = iscsi_if.get_child_content('ip-port') - d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag') - d['interface-enabled'] = iscsi_if.get_child_content( - 'is-interface-enabled') - tgt_list.append(d) - return tgt_list - - def _get_iscsi_service_details(self): - """Returns iscsi iqn.""" - iscsi_service_iter = NaElement('iscsi-service-get-iter') - result = self.client.invoke_successfully(iscsi_service_iter, True) - if result.get_child_content('num-records') and\ - int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - iscsi_service = attr_list.get_child_by_name('iscsi-service-info') - return iscsi_service.get_child_content('node-name') - LOG.debug('No iscsi service found for vserver %s' % (self.vserver)) - return None - def _create_lun_handle(self, metadata): """Returns lun handle based on filer type.""" return '%s:%s' % (self.vserver, metadata['Path']) - def _get_lun_list(self): - """Gets the list of luns on filer. - - Gets the luns from cluster with vserver. - """ - - tag = None - while True: - api = NaElement('lun-get-iter') - api.add_new_child('max-records', '100') - if tag: - api.add_new_child('tag', tag, True) - lun_info = NaElement('lun-info') - lun_info.add_new_child('vserver', self.vserver) - query = NaElement('query') - query.add_child_elem(lun_info) - api.add_child_elem(query) - result = self.client.invoke_successfully(api) - if result.get_child_by_name('num-records') and\ - int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - self._extract_and_populate_luns(attr_list.get_children()) - tag = result.get_child_content('next-tag') - if tag is None: - break - def _find_mapped_lun_igroup(self, path, initiator, os=None): """Find the igroup for mapped lun with initiator.""" - initiator_igroups = self._get_igroup_by_initiator(initiator=initiator) - lun_maps = self._get_lun_map(path) + initiator_igroups = self.zapi_client.get_igroup_by_initiator( + initiator=initiator) + lun_maps = self.zapi_client.get_lun_map(path) if initiator_igroups and lun_maps: for igroup in initiator_igroups: igroup_name = igroup['initiator-group-name'] @@ -900,130 +692,17 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): return (igroup_name, lun_map['lun-id']) return (None, None) - def _get_lun_map(self, path): - """Gets the lun map by lun path.""" - tag = None - map_list = [] - while True: - lun_map_iter = NaElement('lun-map-get-iter') - lun_map_iter.add_new_child('max-records', '100') - if tag: - lun_map_iter.add_new_child('tag', tag, True) - query = NaElement('query') - lun_map_iter.add_child_elem(query) - query.add_node_with_children('lun-map-info', **{'path': path}) - result = self.client.invoke_successfully(lun_map_iter, True) - tag = result.get_child_content('next-tag') - if result.get_child_content('num-records') and \ - int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - lun_maps = attr_list.get_children() - for lun_map in lun_maps: - lun_m = dict() - lun_m['initiator-group'] = lun_map.get_child_content( - 'initiator-group') - lun_m['lun-id'] = lun_map.get_child_content('lun-id') - lun_m['vserver'] = lun_map.get_child_content('vserver') - map_list.append(lun_m) - if tag is None: - break - return map_list - - def _get_igroup_by_initiator(self, initiator): - """Get igroups by initiator.""" - tag = None - igroup_list = [] - while True: - igroup_iter = NaElement('igroup-get-iter') - igroup_iter.add_new_child('max-records', '100') - if tag: - igroup_iter.add_new_child('tag', tag, True) - query = NaElement('query') - igroup_iter.add_child_elem(query) - igroup_info = NaElement('initiator-group-info') - query.add_child_elem(igroup_info) - igroup_info.add_new_child('vserver', self.vserver) - initiators = NaElement('initiators') - igroup_info.add_child_elem(initiators) - initiators.add_node_with_children('initiator-info', - **{'initiator-name': initiator}) - des_attrs = NaElement('desired-attributes') - des_ig_info = NaElement('initiator-group-info') - des_attrs.add_child_elem(des_ig_info) - des_ig_info.add_node_with_children('initiators', - **{'initiator-info': None}) - des_ig_info.add_new_child('vserver', None) - des_ig_info.add_new_child('initiator-group-name', None) - des_ig_info.add_new_child('initiator-group-type', None) - des_ig_info.add_new_child('initiator-group-os-type', None) - igroup_iter.add_child_elem(des_attrs) - result = self.client.invoke_successfully(igroup_iter, False) - tag = result.get_child_content('next-tag') - if result.get_child_content('num-records') and\ - int(result.get_child_content('num-records')) > 0: - attr_list = result.get_child_by_name('attributes-list') - igroups = attr_list.get_children() - for igroup in igroups: - ig = dict() - ig['initiator-group-os-type'] = igroup.get_child_content( - 'initiator-group-os-type') - ig['initiator-group-type'] = igroup.get_child_content( - 'initiator-group-type') - ig['initiator-group-name'] = igroup.get_child_content( - 'initiator-group-name') - igroup_list.append(ig) - if tag is None: - break - return igroup_list - def _clone_lun(self, name, new_name, space_reserved='true', src_block=0, dest_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'] - # zAPI can only handle 2^24 blocks per range - bc_limit = 2 ** 24 # 8GB - # zAPI can only handle 32 block ranges per call - br_limit = 32 - z_limit = br_limit * bc_limit # 256 GB - z_calls = int(math.ceil(block_count / float(z_limit))) - zbc = block_count - if z_calls == 0: - z_calls = 1 - for _call in range(0, z_calls): - if zbc > z_limit: - block_count = z_limit - zbc -= z_limit - else: - block_count = zbc - clone_create = NaElement.create_node_with_children( - 'clone-create', - **{'volume': volume, 'source-path': name, - 'destination-path': new_name, - 'space-reserve': space_reserved}) - if block_count > 0: - block_ranges = NaElement("block-ranges") - segments = int(math.ceil(block_count / float(bc_limit))) - bc = block_count - for _segment in range(0, segments): - if bc > bc_limit: - block_count = bc_limit - bc -= bc_limit - else: - block_count = bc - block_range = NaElement.create_node_with_children( - 'block-range', - **{'source-block-number': str(src_block), - 'destination-block-number': str(dest_block), - 'block-count': str(block_count)}) - block_ranges.add_child_elem(block_range) - src_block += int(block_count) - dest_block += int(block_count) - clone_create.add_child_elem(block_ranges) - self.client.invoke_successfully(clone_create, True) + self.zapi_client.clone_lun(volume, name, new_name, space_reserved, + src_block=0, dest_block=0, block_count=0) LOG.debug("Cloned LUN with new name %s" % new_name) - lun = self._get_lun_by_args(vserver=self.vserver, path='/vol/%s/%s' - % (volume, new_name)) + lun = self.zapi_client.get_lun_by_args(vserver=self.vserver, + path='/vol/%s/%s' + % (volume, new_name)) if len(lun) == 0: msg = _("No cloned lun named %s found on the filer") raise exception.VolumeBackendAPIException(data=msg % (new_name)) @@ -1036,17 +715,6 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): self._update_stale_vols( volume=ssc_utils.NetAppVolume(volume, self.vserver)) - def _get_lun_by_args(self, **args): - """Retrieves lun with specified args.""" - lun_iter = NaElement('lun-get-iter') - lun_iter.add_new_child('max-records', '100') - query = NaElement('query') - lun_iter.add_child_elem(query) - query.add_node_with_children('lun-info', **args) - luns = self.client.invoke_successfully(lun_iter) - attr_list = luns.get_child_by_name('attributes-list') - return attr_list.get_children() - def _create_lun_meta(self, lun): """Creates lun metadata dictionary.""" self._is_naelement(lun) @@ -1180,7 +848,8 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): if self.volume_list: self.volume_list = self.volume_list.split(',') self.volume_list = [el.strip() for el in self.volume_list] - (major, minor) = self._get_ontapi_version() + self.zapi_client = seven_mode.Client(self.client, self.volume_list) + (major, minor) = self.zapi_client.get_ontapi_version() self.client.set_api_version(major, minor) if self.vfiler: self.client.set_vfiler(self.vfiler) @@ -1208,85 +877,22 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): metadata, qos_policy_group=None): """Creates a LUN, handling ONTAP differences as needed.""" - super(NetAppDirect7modeISCSIDriver, self).create_lun( + self.zapi_client.create_lun( volume_name, lun_name, size, metadata, qos_policy_group) self.vol_refresh_voluntary = True - def _get_filer_volumes(self, volume=None): - """Returns list of filer volumes in api format.""" - vol_request = NaElement('volume-list-info') - if volume: - vol_request.add_new_child('volume', volume) - res = self.client.invoke_successfully(vol_request, True) - volumes = res.get_child_by_name('volumes') - if volumes: - return volumes.get_children() - return [] - def _get_root_volume_name(self): # switch to volume-get-root-name API when possible - vols = self._get_filer_volumes() + vols = self.zapi_client.get_filer_volumes() for vol in vols: volume_name = vol.get_child_content('name') if self._get_vol_option(volume_name, 'root') == 'true': return volume_name - LOG.warn(_LW('Could not determine root volume name ' - 'on %s.') % self._get_owner()) + LOG.warning(_LW('Could not determine root volume name ' + 'on %s.') % self._get_owner()) return None - def _get_igroup_by_initiator(self, initiator): - """Get igroups by initiator.""" - igroup_list = NaElement('igroup-list-info') - result = self.client.invoke_successfully(igroup_list, True) - igroups = [] - igs = result.get_child_by_name('initiator-groups') - if igs: - ig_infos = igs.get_children() - if ig_infos: - for info in ig_infos: - initiators = info.get_child_by_name('initiators') - init_infos = initiators.get_children() - if init_infos: - for init in init_infos: - if init.get_child_content('initiator-name')\ - == initiator: - d = dict() - d['initiator-group-os-type'] = \ - info.get_child_content( - 'initiator-group-os-type') - d['initiator-group-type'] = \ - info.get_child_content( - 'initiator-group-type') - d['initiator-group-name'] = \ - info.get_child_content( - 'initiator-group-name') - igroups.append(d) - return igroups - - def _get_target_details(self): - """Gets the target portal details.""" - iscsi_if_iter = NaElement('iscsi-portal-list-info') - result = self.client.invoke_successfully(iscsi_if_iter, True) - tgt_list = [] - portal_list_entries = result.get_child_by_name( - 'iscsi-portal-list-entries') - if portal_list_entries: - portal_list = portal_list_entries.get_children() - for iscsi_if in portal_list: - d = dict() - d['address'] = iscsi_if.get_child_content('ip-address') - d['port'] = iscsi_if.get_child_content('ip-port') - d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag') - tgt_list.append(d) - return tgt_list - - def _get_iscsi_service_details(self): - """Returns iscsi iqn.""" - iscsi_service_iter = NaElement('iscsi-node-get-name') - result = self.client.invoke_successfully(iscsi_service_iter, True) - return result.get_child_content('node-name') - def _get_owner(self): if self.vfiler: owner = '%s:%s' % (self.configuration.netapp_server_hostname, @@ -1300,38 +906,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): owner = self._get_owner() return '%s:%s' % (owner, metadata['Path']) - def _get_lun_list(self): - """Gets the list of luns on filer.""" - lun_list = [] - if self.volume_list: - for vol in self.volume_list: - try: - luns = self._get_vol_luns(vol) - if luns: - lun_list.extend(luns) - except NaApiError: - LOG.warn(_LW("Error finding luns for volume %s." - " Verify volume exists.") % (vol)) - else: - luns = self._get_vol_luns(None) - lun_list.extend(luns) - self._extract_and_populate_luns(lun_list) - - def _get_vol_luns(self, vol_name): - """Gets the luns for a volume.""" - api = NaElement('lun-list-info') - if vol_name: - api.add_new_child('volume-name', vol_name) - result = self.client.invoke_successfully(api, True) - luns = result.get_child_by_name('luns') - return luns.get_children() - def _find_mapped_lun_igroup(self, path, initiator, os=None): """Find the igroup for mapped lun with initiator.""" - lun_map_list = NaElement.create_node_with_children( - 'lun-map-list-info', - **{'path': path}) - result = self.client.invoke_successfully(lun_map_list, True) + result = self.zapi_client.get_lun_map(path) igroups = result.get_child_by_name('initiator-groups') if igroups: igroup = None @@ -1358,58 +935,16 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): path = metadata['Path'] (parent, _splitter, name) = path.rpartition('/') clone_path = '%s/%s' % (parent, new_name) - # zAPI can only handle 2^24 blocks per range - bc_limit = 2 ** 24 # 8GB - # zAPI can only handle 32 block ranges per call - br_limit = 32 - z_limit = br_limit * bc_limit # 256 GB - z_calls = int(math.ceil(block_count / float(z_limit))) - zbc = block_count - if z_calls == 0: - z_calls = 1 - for _call in range(0, z_calls): - if zbc > z_limit: - block_count = z_limit - zbc -= z_limit - else: - block_count = zbc - clone_start = NaElement.create_node_with_children( - 'clone-start', **{'source-path': path, - 'destination-path': clone_path, - 'no-snap': 'true'}) - if block_count > 0: - block_ranges = NaElement("block-ranges") - # zAPI can only handle 2^24 block ranges - bc_limit = 2 ** 24 # 8GB - segments = int(math.ceil(block_count / float(bc_limit))) - bc = block_count - for _segment in range(0, segments): - if bc > bc_limit: - block_count = bc_limit - bc -= bc_limit - else: - block_count = bc - block_range = NaElement.create_node_with_children( - 'block-range', - **{'source-block-number': str(src_block), - 'destination-block-number': str(dest_block), - 'block-count': str(block_count)}) - block_ranges.add_child_elem(block_range) - src_block += int(block_count) - dest_block += int(block_count) - 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') - vol_uuid = cl_id_info.get_child_content('volume-uuid') - 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) + + self.zapi_client.clone_lun(path, clone_path, name, new_name, + space_reserved, src_block=0, + dest_block=0, block_count=0) + self.vol_refresh_voluntary = True - luns = self._get_lun_by_args(path=clone_path) + luns = self.zapi_client.get_lun_by_args(path=clone_path) if luns: cloned_lun = luns[0] - self._set_space_reserve(clone_path, space_reserved) + self.zapi_client.set_space_reserve(clone_path, space_reserved) clone_meta = self._create_lun_meta(cloned_lun) handle = self._create_lun_handle(clone_meta) self._add_lun_to_table( @@ -1419,57 +954,6 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): else: raise NaApiError('ENOLUNENTRY', 'No Lun entry found on the filer') - def _set_space_reserve(self, path, enable): - """Sets the space reserve info.""" - space_res = NaElement.create_node_with_children( - 'lun-set-space-reservation-info', - **{'path': path, 'enable': enable}) - self.client.invoke_successfully(space_res, True) - - def _check_clone_status(self, clone_id, vol_uuid, name, new_name): - """Checks for the job till completed.""" - clone_status = NaElement('clone-list-status') - cl_id = NaElement('clone-id') - clone_status.add_child_elem(cl_id) - cl_id.add_node_with_children( - 'clone-id-info', - **{'clone-op-id': clone_id, 'volume-uuid': vol_uuid}) - running = True - clone_ops_info = None - while running: - result = self.client.invoke_successfully(clone_status, True) - status = result.get_child_by_name('status') - ops_info = status.get_children() - if ops_info: - for info in ops_info: - if info.get_child_content('clone-state') == 'running': - time.sleep(1) - break - else: - running = False - clone_ops_info = info - break - else: - if clone_ops_info: - fmt = {'name': name, 'new_name': new_name} - if clone_ops_info.get_child_content('clone-state')\ - == 'completed': - LOG.debug("Clone operation with src %(name)s" - " and dest %(new_name)s completed" % fmt) - else: - LOG.debug("Clone operation with src %(name)s" - " and dest %(new_name)s failed" % fmt) - raise NaApiError( - clone_ops_info.get_child_content('error'), - clone_ops_info.get_child_content('reason')) - - def _get_lun_by_args(self, **args): - """Retrieves 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') - return luns.get_children() - def _create_lun_meta(self, lun): """Creates lun metadata dictionary.""" self._is_naelement(lun) @@ -1570,15 +1054,15 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): try: job_set = set_safe_attr(self, 'vol_refresh_running', True) if not job_set: - LOG.warn(_LW("Volume refresh job already " - "running. Returning...")) + LOG.warning(_LW("Volume refresh job already " + "running. Returning...")) return self.vol_refresh_voluntary = False - self.vols = self._get_filer_volumes() + self.vols = self.zapi_client.get_filer_volumes() self.vol_refresh_time = timeutils.utcnow() except Exception as e: - LOG.warn(_LW("Error refreshing volume info. Message: %s"), - six.text_type(e)) + LOG.warning(_LW("Error refreshing volume info. Message: %s"), + six.text_type(e)) finally: set_safe_attr(self, 'vol_refresh_running', False) diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py index 4bb035f47..da45edd96 100644 --- a/cinder/volume/drivers/netapp/nfs.py +++ b/cinder/volume/drivers/netapp/nfs.py @@ -16,7 +16,6 @@ Volume driver for NetApp NFS storage. """ -import copy import os import re from threading import Timer @@ -34,9 +33,10 @@ from cinder.i18n import _, _LE, _LI, _LW from cinder.image import image_utils from cinder.openstack.common import log as logging from cinder import utils -from cinder.volume.drivers.netapp.api import NaApiError from cinder.volume.drivers.netapp.api import NaElement from cinder.volume.drivers.netapp.api import NaServer +from cinder.volume.drivers.netapp.client import cmode +from cinder.volume.drivers.netapp.client import seven_mode from cinder.volume.drivers.netapp.options import netapp_basicauth_opts from cinder.volume.drivers.netapp.options import netapp_cluster_opts from cinder.volume.drivers.netapp.options import netapp_connection_opts @@ -248,9 +248,9 @@ class NetAppNFSDriver(nfs.NfsDriver): volume['name'], file_name, volume['provider_location'], file_name) except Exception as e: - LOG.warn(_LW('Exception while registering image %(image_id)s' - ' in cache. Exception: %(exc)s') - % {'image_id': image_id, 'exc': e.__str__()}) + LOG.warning(_LW('Exception while registering image %(image_id)s' + ' in cache. Exception: %(exc)s') + % {'image_id': image_id, 'exc': e.__str__()}) def _find_image_in_cache(self, image_id): """Finds image in cache and returns list of shares with file name.""" @@ -296,11 +296,11 @@ class NetAppNFSDriver(nfs.NfsDriver): LOG.debug('Image cache cleaning in progress.') thres_size_perc_start =\ self.configuration.thres_avl_size_perc_start - thres_size_perc_stop =\ + thres_size_perc_stop = \ self.configuration.thres_avl_size_perc_stop for share in getattr(self, '_mounted_shares', []): try: - total_size, total_avl, _total_alc =\ + total_size, total_avl, _total_alc = \ self._get_capacity_info(share) avl_percent = int((total_avl / total_size) * 100) if avl_percent <= thres_size_perc_start: @@ -316,9 +316,9 @@ class NetAppNFSDriver(nfs.NfsDriver): else: continue except Exception as e: - LOG.warn(_LW('Exception during cache cleaning' - ' %(share)s. Message - %(ex)s') - % {'share': share, 'ex': e.__str__()}) + LOG.warning(_LW('Exception during cache cleaning' + ' %(share)s. Message - %(ex)s') + % {'share': share, 'ex': e.__str__()}) continue finally: LOG.debug('Image cache cleaning done.') @@ -361,6 +361,7 @@ class NetAppNFSDriver(nfs.NfsDriver): if self._delete_file(file_path): return True return False + if _do_delete(): bytes_to_free = bytes_to_free - int(f[1]) if bytes_to_free <= 0: @@ -436,8 +437,8 @@ class NetAppNFSDriver(nfs.NfsDriver): volume['provider_location'] = share break except Exception: - LOG.warn(_LW('Unexpected exception during' - ' image cloning in share %s'), share) + LOG.warning(_LW('Unexpected exception during' + ' image cloning in share %s'), share) return cloned def _direct_nfs_clone(self, volume, image_location, image_id): @@ -472,7 +473,7 @@ class NetAppNFSDriver(nfs.NfsDriver): if data.file_format != "raw": raise exception.InvalidResults( _("Converted to raw, but" - " format is now %s") % data.file_format) + " format is now %s") % data.file_format) else: cloned = True self._register_image_in_cache( @@ -526,7 +527,7 @@ class NetAppNFSDriver(nfs.NfsDriver): return True else: if retry_seconds <= 0: - LOG.warn(_LW('Discover file retries exhausted.')) + LOG.warning(_LW('Discover file retries exhausted.')) return False else: time.sleep(sleep_interval) @@ -547,7 +548,7 @@ class NetAppNFSDriver(nfs.NfsDriver): """ conn, dr = None, None if image_location: - nfs_loc_pattern =\ + nfs_loc_pattern = \ ('^nfs://(([\w\-\.]+:{1}[\d]+|[\w\-\.]+)(/[^\/].*)' '*(/[^\/\\\\]+)$)') matched = re.match(nfs_loc_pattern, image_location, flags=0) @@ -584,8 +585,8 @@ class NetAppNFSDriver(nfs.NfsDriver): share_candidates) return self._share_match_for_ip(ip, share_candidates) except Exception: - LOG.warn(_LW("Unexpected exception while short " - "listing used share.")) + LOG.warning(_LW("Unexpected exception while short " + "listing used share.")) return None def _construct_image_nfs_url(self, image_location): @@ -643,10 +644,11 @@ class NetAppNFSDriver(nfs.NfsDriver): def _move_nfs_file(self, source_path, dest_path): """Moves source to destination.""" + @utils.synchronized(dest_path, external=True) def _move_file(src, dst): if os.path.exists(dst): - LOG.warn(_LW("Destination %s already exists."), dst) + LOG.warning(_LW("Destination %s already exists."), dst) return False self._execute('mv', src, dst, run_as_root=self._execute_as_root) @@ -655,12 +657,12 @@ class NetAppNFSDriver(nfs.NfsDriver): try: return _move_file(source_path, dest_path) except Exception as e: - LOG.warn(_LW('Exception moving file %(src)s. Message - %(e)s') - % {'src': source_path, 'e': e}) + LOG.warning(_LW('Exception moving file %(src)s. Message - %(e)s') + % {'src': source_path, 'e': e}) return False -class NetAppDirectNfsDriver (NetAppNFSDriver): +class NetAppDirectNfsDriver(NetAppNFSDriver): """Executes commands related to volumes on NetApp filer.""" def __init__(self, *args, **kwargs): @@ -707,14 +709,6 @@ class NetAppDirectNfsDriver (NetAppNFSDriver): if not isinstance(elem, NaElement): raise ValueError('Expects NaElement') - def _get_ontapi_version(self): - """Gets the supported ontapi version.""" - ontapi_version = NaElement('system-get-ontapi-version') - res = self._client.invoke_successfully(ontapi_version, False) - major = res.get_child_content('major-version') - minor = res.get_child_content('minor-version') - return (major, minor) - def _get_export_ip_path(self, volume_id=None, share=None): """Returns export ip and path. @@ -755,7 +749,7 @@ class NetAppDirectNfsDriver (NetAppNFSDriver): 'apparent_available': apparent_available} -class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): +class NetAppDirectCmodeNfsDriver(NetAppDirectNfsDriver): """Executes commands related to volumes on c mode.""" def __init__(self, *args, **kwargs): @@ -767,18 +761,20 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): """Do the customized set up on client for cluster mode.""" # Default values to run first api client.set_api_version(1, 15) - (major, minor) = self._get_ontapi_version() - client.set_api_version(major, minor) self.vserver = self.configuration.netapp_vserver + self.zapi_client = cmode.Client(client, self.vserver) + (major, minor) = self.zapi_client.get_ontapi_version() + client.set_api_version(major, minor) self.ssc_vols = None self.stale_vols = set() if self.vserver: self.ssc_enabled = True LOG.info(_LI("Shares on vserver %s will only" - " be used for provisioning.") % (self.vserver)) + " be used for provisioning.") % self.vserver) else: self.ssc_enabled = False - LOG.warn(_LW("No vserver set in config. SSC will be disabled.")) + LOG.warning(_LW("No vserver set in config. " + "SSC will be disabled.")) def check_for_setup_error(self): """Check that the driver is working and can communicate.""" @@ -786,23 +782,6 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): if self.ssc_enabled: ssc_utils.check_ssc_api_permissions(self._client) - def _invoke_successfully(self, na_element, vserver=None): - """Invoke the api for successful result. - - If vserver is present then invokes vserver api - else Cluster api. - :param vserver: vserver name. - """ - - self._is_naelement(na_element) - server = copy.copy(self._client) - if vserver: - server.set_vserver(vserver) - else: - server.set_vserver(None) - result = server.invoke_successfully(na_element, True) - return result - def create_volume(self, volume): """Creates a volume. @@ -850,49 +829,30 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): def _set_qos_policy_group_on_volume(self, volume, share, qos_policy_group): target_path = '%s' % (volume['name']) export_path = share.split(':')[1] - flex_vol_name = self._get_vol_by_junc_vserver(self.vserver, - export_path) - file_assign_qos = NaElement.create_node_with_children( - 'file-assign-qos', - **{'volume': flex_vol_name, - 'qos-policy-group-name': qos_policy_group, - 'file': target_path, - 'vserver': self.vserver}) - self._invoke_successfully(file_assign_qos) + flex_vol_name = self.zapi_client.get_vol_by_junc_vserver(self.vserver, + export_path) + self.zapi_client.file_assign_qos(flex_vol_name, + qos_policy_group, + target_path) def _clone_volume(self, volume_name, clone_name, volume_id, share=None): """Clones mounted volume on NetApp Cluster.""" (vserver, exp_volume) = self._get_vserver_and_exp_vol(volume_id, share) - self._clone_file(exp_volume, volume_name, clone_name, vserver) + self.zapi_client.clone_file(exp_volume, volume_name, clone_name, + vserver) share = share if share else self._get_provider_location(volume_id) self._post_prov_deprov_in_ssc(share) def _get_vserver_and_exp_vol(self, volume_id=None, share=None): """Gets the vserver and export volume for share.""" (host_ip, export_path) = self._get_export_ip_path(volume_id, share) - ifs = self._get_if_info_by_ip(host_ip) + ifs = self.zapi_client.get_if_info_by_ip(host_ip) vserver = ifs[0].get_child_content('vserver') - exp_volume = self._get_vol_by_junc_vserver(vserver, export_path) + exp_volume = self.zapi_client.get_vol_by_junc_vserver(vserver, + export_path) return (vserver, exp_volume) - def _get_if_info_by_ip(self, ip): - """Gets the network interface info by ip.""" - net_if_iter = NaElement('net-interface-get-iter') - net_if_iter.add_new_child('max-records', '10') - query = NaElement('query') - net_if_iter.add_child_elem(query) - query.add_node_with_children( - 'net-interface-info', **{'address': na_utils.resolve_hostname(ip)}) - result = self._invoke_successfully(net_if_iter) - if result.get_child_content('num-records') and\ - int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - return attr_list.get_children() - raise exception.NotFound( - _('No interface found on cluster for ip %s') - % (ip)) - def _get_vserver_ips(self, vserver): """Get ips for the vserver.""" result = na_utils.invoke_api( @@ -907,51 +867,6 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): if_list.extend(ifs) return if_list - def _get_vol_by_junc_vserver(self, vserver, junction): - """Gets the volume by junction path and vserver.""" - vol_iter = NaElement('volume-get-iter') - vol_iter.add_new_child('max-records', '10') - query = NaElement('query') - vol_iter.add_child_elem(query) - vol_attrs = NaElement('volume-attributes') - query.add_child_elem(vol_attrs) - vol_attrs.add_node_with_children( - 'volume-id-attributes', - **{'junction-path': junction, - 'owning-vserver-name': vserver}) - des_attrs = NaElement('desired-attributes') - des_attrs.add_node_with_children('volume-attributes', - **{'volume-id-attributes': None}) - vol_iter.add_child_elem(des_attrs) - result = self._invoke_successfully(vol_iter, vserver) - if result.get_child_content('num-records') and\ - int(result.get_child_content('num-records')) >= 1: - attr_list = result.get_child_by_name('attributes-list') - vols = attr_list.get_children() - vol_id = vols[0].get_child_by_name('volume-id-attributes') - return vol_id.get_child_content('name') - msg_fmt = {'vserver': vserver, 'junction': junction} - raise exception.NotFound(_("""No volume on cluster with vserver - %(vserver)s and junction path %(junction)s - """) % msg_fmt) - - def _clone_file(self, volume, src_path, dest_path, vserver=None, - dest_exists=False): - """Clones file on vserver.""" - msg = _("""Cloning with params volume %(volume)s, src %(src_path)s, - dest %(dest_path)s, vserver %(vserver)s""") - msg_fmt = {'volume': volume, 'src_path': src_path, - 'dest_path': dest_path, 'vserver': vserver} - LOG.debug(msg % msg_fmt) - clone_create = NaElement.create_node_with_children( - 'clone-create', - **{'volume': volume, 'source-path': src_path, - 'destination-path': dest_path}) - major, minor = self._client.get_api_version() - if major == 1 and minor >= 20 and dest_exists: - clone_create.add_new_child('destination-exists', 'true') - self._invoke_successfully(clone_create, vserver) - def _update_volume_stats(self): """Retrieve stats info from vserver.""" @@ -1047,7 +962,7 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): def refresh_ssc_vols(self, vols): """Refreshes ssc_vols with latest entries.""" if not self._mounted_shares: - LOG.warn(_LW("No shares found hence skipping ssc refresh.")) + LOG.warning(_LW("No shares found hence skipping ssc refresh.")) return mnt_share_vols = set() vs_ifs = self._get_vserver_ips(self.vserver) @@ -1082,22 +997,11 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): volume_id=None, share=share) for file in old_files: path = '/vol/%s/%s' % (exp_volume, file) - u_bytes = self._get_cluster_file_usage(path, vserver) + u_bytes = self.zapi_client.get_file_usage(path, vserver) file_list.append((file, u_bytes)) LOG.debug('Shortlisted del elg files %s', file_list) return file_list - def _get_cluster_file_usage(self, path, vserver): - """Gets the file unique bytes.""" - LOG.debug('Getting file usage for %s', path) - file_use = NaElement.create_node_with_children( - 'file-usage-get', **{'path': path}) - res = self._invoke_successfully(file_use, vserver) - bytes = res.get_child_content('unique-bytes') - LOG.debug('file-usage for path %(path)s is %(bytes)s' - % {'path': path, 'bytes': bytes}) - return bytes - def _share_match_for_ip(self, ip, shares): """Returns the share that is served by ip. @@ -1119,7 +1023,7 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): def _get_vserver_for_ip(self, ip): """Get vserver for the mentioned ip.""" try: - ifs = self._get_if_info_by_ip(ip) + ifs = self.zapi_client.get_if_info_by_ip(ip) vserver = ifs[0].get_child_content('vserver') return vserver except Exception: @@ -1254,8 +1158,8 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): dest_exists=False): """Clone file even if dest exists.""" (vserver, exp_volume) = self._get_vserver_and_exp_vol(share=share) - self._clone_file(exp_volume, src_name, dst_name, vserver, - dest_exists=dest_exists) + self.zapi_client.clone_file(exp_volume, src_name, dst_name, vserver, + dest_exists=dest_exists) def _copy_from_img_service(self, context, volume, image_service, image_id): @@ -1273,7 +1177,7 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): dst_ip = self._get_ip_verify_on_cluster(self._get_host_ip( volume['id'])) # tmp file is required to deal with img formats - tmp_img_file = str(uuid.uuid4()) + tmp_img_file = six.text_type(uuid.uuid4()) col_path = self.configuration.netapp_copyoffload_tool_path img_info = image_service.show(context, image_id) dst_share = self._get_provider_location(volume['id']) @@ -1307,7 +1211,7 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): % {'img': image_id, 'vol': volume['id']}) else: LOG.debug('Image will be converted to raw %s.', image_id) - img_conv = str(uuid.uuid4()) + img_conv = six.text_type(uuid.uuid4()) dst_img_conv_local = os.path.join(dst_dir, img_conv) # Checking against image size which is approximate check @@ -1340,7 +1244,7 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver): self._delete_file(dst_img_local) -class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): +class NetAppDirect7modeNfsDriver(NetAppDirectNfsDriver): """Executes commands related to volumes on 7 mode.""" def __init__(self, *args, **kwargs): @@ -1348,7 +1252,8 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): def _do_custom_setup(self, client): """Do the customized set up on client if any for 7 mode.""" - (major, minor) = self._get_ontapi_version() + self.zapi_client = seven_mode.Client(client) + (major, minor) = self.zapi_client.get_ontapi_version() client.set_api_version(major, minor) def check_for_setup_error(self): @@ -1365,23 +1270,6 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): raise exception.VolumeBackendAPIException(data=msg) super(NetAppDirect7modeNfsDriver, self).check_for_setup_error() - def _invoke_successfully(self, na_element, vfiler=None): - """Invoke the api for successful result. - - If vfiler is present then invokes vfiler api - else filer api. - :param vfiler: vfiler name. - """ - - self._is_naelement(na_element) - server = copy.copy(self._client) - if vfiler: - server.set_vfiler(vfiler) - else: - server.set_vfiler(None) - result = server.invoke_successfully(na_element, True) - return result - def create_volume(self, volume): """Creates a volume. @@ -1419,97 +1307,10 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): volume_id, share=None): """Clones mounted volume with NetApp filer.""" (_host_ip, export_path) = self._get_export_ip_path(volume_id, share) - storage_path = self._get_actual_path_for_export(export_path) + storage_path = self.zapi_client.get_actual_path_for_export(export_path) target_path = '%s/%s' % (storage_path, clone_name) - (clone_id, vol_uuid) = self._start_clone('%s/%s' % (storage_path, - volume_name), - target_path) - if vol_uuid: - try: - self._wait_for_clone_finish(clone_id, vol_uuid) - except NaApiError as e: - if e.code != 'UnknownCloneId': - self._clear_clone(clone_id) - raise e - - def _get_actual_path_for_export(self, export_path): - """Gets the actual path on the filer for export path.""" - storage_path = NaElement.create_node_with_children( - 'nfs-exportfs-storage-path', **{'pathname': export_path}) - result = self._invoke_successfully(storage_path, None) - if result.get_child_content('actual-pathname'): - return result.get_child_content('actual-pathname') - raise exception.NotFound(_('No storage path found for export path %s') - % (export_path)) - - def _start_clone(self, src_path, dest_path): - """Starts the clone operation. - - :returns: clone-id - """ - - msg_fmt = {'src_path': src_path, 'dest_path': dest_path} - LOG.debug("""Cloning with src %(src_path)s, dest %(dest_path)s""" - % msg_fmt) - clone_start = NaElement.create_node_with_children( - 'clone-start', - **{'source-path': src_path, - 'destination-path': dest_path, - 'no-snap': 'true'}) - result = self._invoke_successfully(clone_start, None) - clone_id_el = result.get_child_by_name('clone-id') - cl_id_info = clone_id_el.get_child_by_name('clone-id-info') - vol_uuid = cl_id_info.get_child_content('volume-uuid') - clone_id = cl_id_info.get_child_content('clone-op-id') - return (clone_id, vol_uuid) - - def _wait_for_clone_finish(self, clone_op_id, vol_uuid): - """Waits till a clone operation is complete or errored out.""" - clone_ls_st = NaElement('clone-list-status') - clone_id = NaElement('clone-id') - clone_ls_st.add_child_elem(clone_id) - clone_id.add_node_with_children('clone-id-info', - **{'clone-op-id': clone_op_id, - 'volume-uuid': vol_uuid}) - task_running = True - while task_running: - result = self._invoke_successfully(clone_ls_st, None) - status = result.get_child_by_name('status') - ops_info = status.get_children() - if ops_info: - state = ops_info[0].get_child_content('clone-state') - if state == 'completed': - task_running = False - elif state == 'failed': - code = ops_info[0].get_child_content('error') - reason = ops_info[0].get_child_content('reason') - raise NaApiError(code, reason) - else: - time.sleep(1) - else: - raise NaApiError( - 'UnknownCloneId', - 'No clone operation for clone id %s found on the filer' - % (clone_id)) - - def _clear_clone(self, clone_id): - """Clear the clone information. - - Invoke this in case of failed clone. - """ - - clone_clear = NaElement.create_node_with_children( - 'clone-clear', - **{'clone-id': clone_id}) - retry = 3 - while retry: - try: - self._invoke_successfully(clone_clear, None) - break - except Exception: - # Filer might be rebooting - time.sleep(5) - retry = retry - 1 + self.zapi_client.clone_file('%s/%s' % (storage_path, volume_name), + target_path) def _update_volume_stats(self): """Retrieve stats info from vserver.""" @@ -1568,31 +1369,19 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): def _shortlist_del_eligible_files(self, share, old_files): """Prepares list of eligible files to be deleted from cache.""" file_list = [] - exp_volume = self._get_actual_path_for_export(share) + exp_volume = self.zapi_client.get_actual_path_for_export(share) for file in old_files: path = '/vol/%s/%s' % (exp_volume, file) - u_bytes = self._get_filer_file_usage(path) + u_bytes = self.zapi_client.get_file_usage(path) file_list.append((file, u_bytes)) LOG.debug('Shortlisted del elg files %s', file_list) return file_list - def _get_filer_file_usage(self, path): - """Gets the file unique bytes.""" - LOG.debug('Getting file usage for %s', path) - file_use = NaElement.create_node_with_children( - 'file-usage-get', **{'path': path}) - res = self._invoke_successfully(file_use) - bytes = res.get_child_content('unique-bytes') - LOG.debug('file-usage for path %(path)s is %(bytes)s' - % {'path': path, 'bytes': bytes}) - return bytes - def _is_filer_ip(self, ip): """Checks whether ip is on the same filer.""" try: - ifconfig = NaElement('net-ifconfig-get') - res = self._invoke_successfully(ifconfig, None) - if_info = res.get_child_by_name('interface-config-info') + ifconfig = self.zapi_client.get_ifconfig() + if_info = ifconfig.get_child_by_name('interface-config-info') if if_info: ifs = if_info.get_children() for intf in ifs: diff --git a/cinder/volume/drivers/netapp/ssc_utils.py b/cinder/volume/drivers/netapp/ssc_utils.py index 2c22ef6cf..f0798992c 100644 --- a/cinder/volume/drivers/netapp/ssc_utils.py +++ b/cinder/volume/drivers/netapp/ssc_utils.py @@ -24,7 +24,7 @@ from oslo.utils import timeutils import six from cinder import exception -from cinder.i18n import _ +from cinder.i18n import _, _LW from cinder.openstack.common import log as logging from cinder import utils from cinder.volume import driver @@ -410,7 +410,7 @@ def refresh_cluster_stale_ssc(*args, **kwargs): backend = args[0] na_server = args[1] vserver = args[2] - identity = str(id(backend)) + identity = six.text_type(id(backend)) lock_pr = '%s_%s' % ('refresh_ssc', identity) try: job_set = na_utils.set_safe_attr( @@ -469,7 +469,7 @@ def get_cluster_latest_ssc(*args, **kwargs): backend = args[0] na_server = args[1] vserver = args[2] - identity = str(id(backend)) + identity = six.text_type(id(backend)) lock_pr = '%s_%s' % ('refresh_ssc', identity) # As this depends on stale job running state @@ -505,7 +505,7 @@ def refresh_cluster_ssc(backend, na_server, vserver, synchronous=False): raise exception.InvalidInput(reason=_("Backend server not NaServer.")) delta_secs = getattr(backend, 'ssc_run_delta_secs', 1800) if getattr(backend, 'ssc_job_running', None): - LOG.warn(_('ssc job in progress. Returning... ')) + LOG.warning(_LW('ssc job in progress. Returning... ')) return elif (getattr(backend, 'ssc_run_time', None) is None or (backend.ssc_run_time and @@ -517,7 +517,7 @@ def refresh_cluster_ssc(backend, na_server, vserver, synchronous=False): args=[backend, na_server, vserver]) t.start() elif getattr(backend, 'refresh_stale_running', None): - LOG.warn(_('refresh stale ssc job in progress. Returning... ')) + LOG.warning(_LW('refresh stale ssc job in progress. Returning... ')) return else: if backend.stale_vols: @@ -620,7 +620,7 @@ def check_ssc_api_permissions(na_server): unsupp_ssc_features = [] for fail in failed_apis: unsupp_ssc_features.extend(api_map[fail]) - LOG.warn(_("The user does not have access or sufficient" - " privileges to use all netapp apis. The following" - " extra_specs will fail or be ignored: %s"), - unsupp_ssc_features) + LOG.warning(_LW("The user does not have access or sufficient " + "privileges to use all netapp apis. The " + "following extra_specs will fail or be ignored: " + "%s"), unsupp_ssc_features) diff --git a/cinder/volume/drivers/netapp/utils.py b/cinder/volume/drivers/netapp/utils.py index da5f85807..364eddcce 100644 --- a/cinder/volume/drivers/netapp/utils.py +++ b/cinder/volume/drivers/netapp/utils.py @@ -34,7 +34,7 @@ import six from cinder import context from cinder import exception -from cinder.i18n import _ +from cinder.i18n import _, _LW from cinder.openstack.common import log as logging from cinder import utils from cinder import version @@ -140,7 +140,7 @@ def provide_ems(requester, server, netapp_backend, app_version, na_server.invoke_successfully(ems, True) LOG.debug("ems executed successfully.") except NaApiError as e: - LOG.warn(_("Failed to invoke ems. Message : %s") % e) + LOG.warning(_LW("Failed to invoke ems. Message : %s") % e) finally: requester.last_ems = timeutils.utcnow() @@ -153,8 +153,8 @@ def validate_instantiation(**kwargs): """ if kwargs and kwargs.get('netapp_mode') == 'proxy': return - LOG.warn(_("It is not the recommended way to use drivers by NetApp. " - "Please use NetAppDriver to achieve the functionality.")) + LOG.warning(_LW("It is not the recommended way to use drivers by NetApp. " + "Please use NetAppDriver to achieve the functionality.")) def invoke_api(na_server, api_name, api_family='cm', query=None, @@ -231,7 +231,7 @@ def create_api_request(api_name, query=None, des_result=None, if additional_elems: api_el.translate_struct(additional_elems) if is_iter: - api_el.add_new_child('max-records', str(record_step)) + api_el.add_new_child('max-records', six.text_type(record_step)) if tag: api_el.add_new_child('tag', tag, True) return api_el @@ -240,7 +240,7 @@ def create_api_request(api_name, query=None, des_result=None, def to_bool(val): """Converts true, yes, y, 1 to True, False otherwise.""" if val: - strg = str(val).lower() + strg = six.text_type(val).lower() if (strg == 'true' or strg == 'y' or strg == 'yes' or strg == 'enabled' or strg == '1'): @@ -363,7 +363,7 @@ def decode_base32_to_hex(base32_string): def convert_uuid_to_es_fmt(uuid_str): """Converts uuid to e-series compatible name format.""" - uuid_base32 = encode_hex_to_base32(uuid.UUID(str(uuid_str)).hex) + uuid_base32 = encode_hex_to_base32(uuid.UUID(six.text_type(uuid_str)).hex) return uuid_base32.strip('=') @@ -381,14 +381,15 @@ def round_down(value, precision): def log_extra_spec_warnings(extra_specs): for spec in (set(extra_specs.keys() if extra_specs else []) & set(OBSOLETE_SSC_SPECS.keys())): - msg = _('Extra spec %(old)s is obsolete. Use %(new)s instead.') + msg = _LW('Extra spec %(old)s is obsolete. Use %(new)s instead.') args = {'old': spec, 'new': OBSOLETE_SSC_SPECS[spec]} - LOG.warn(msg % args) + LOG.warning(msg % args) for spec in (set(extra_specs.keys() if extra_specs else []) & set(DEPRECATED_SSC_SPECS.keys())): - msg = _('Extra spec %(old)s is deprecated. Use %(new)s instead.') + msg = _LW('Extra spec %(old)s is deprecated. Use %(new)s ' + 'instead.') args = {'old': spec, 'new': DEPRECATED_SSC_SPECS[spec]} - LOG.warn(msg % args) + LOG.warning(msg % args) class OpenStackInfo(object): -- 2.45.2