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
This commit is contained in:
Alex Meade 2014-04-17 10:34:38 -04:00
parent f8dc04fe3f
commit b1829bace0
19 changed files with 2602 additions and 1099 deletions

View File

@ -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"""

View File

@ -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()

View File

@ -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("""<results status="passed">
<major-version>1</major-version>
<minor-version>19</minor-version>
</results>"""))
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)

View File

@ -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("""<results status="passed">
<num-records>1</num-records>
<attributes-list></attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<iscsi-interface-list-entry-info>
<ip-address>%(address)s</ip-address>
<ip-port>%(port)s</ip-port>
<is-interface-enabled>%(interface-enabled)s</is-interface-enabled>
<tpgroup-tag>%(tpgroup-tag)s</tpgroup-tag>
</iscsi-interface-list-entry-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>0</num-records>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<iscsi-service-info>
<node-name>%s</node-name>
</iscsi-service-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
<lun-info>
</lun-info>
<lun-info>
</lun-info>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
<lun-info> </lun-info>
<lun-info> </lun-info>
</attributes-list>
<next-tag>fake-next</next-tag>
</results>"""))
response_2 = netapp_api.NaElement(
etree.XML("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
<lun-info> </lun-info>
<lun-info> </lun-info>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>0</num-records>
<attributes-list></attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<lun-map-info>
<lun-id>%(lun-id)s</lun-id>
<initiator-group>%(initiator-group)s</initiator-group>
<vserver>%(vserver)s</vserver>
</lun-map-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<lun-map-info>
<lun-id>%(lun-id)s</lun-id>
<initiator-group>%(initiator-group)s</initiator-group>
<vserver>%(vserver)s</vserver>
</lun-map-info>
</attributes-list>
<next-tag>blah</next-tag>
</results>""" % expected_lun_map))
response_2 = netapp_api.NaElement(
etree.XML("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<lun-map-info>
<lun-id>%(lun-id)s</lun-id>
<initiator-group>%(initiator-group)s</initiator-group>
<vserver>%(vserver)s</vserver>
</lun-map-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>0</num-records>
<attributes-list></attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<initiator-group-info>
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
</initiator-group-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<initiator-group-info>
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
</initiator-group-info>
</attributes-list>
<next-tag>blah</next-tag>
</results>""" % expected_igroup))
response_2 = netapp_api.NaElement(
etree.XML("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<initiator-group-info>
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
</initiator-group-info>
</attributes-list>
</results>""" % 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("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
<lun-info>
</lun-info>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>2</num-records>
<attributes-list>
<lun-info>
</lun-info>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>0</num-records>
<attributes-list>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<net-interface-info>
</net-interface-info>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>0</num-records>
<attributes-list>
</attributes-list>
</results>"""))
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("""<results status="passed">
<num-records>1</num-records>
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<name>%(flex_vol)s</name>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
</results>""" % {'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("""<results status="passed">
<unique-bytes>%(unique-bytes)s</unique-bytes>
</results>""" % {'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)

View File

@ -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("""<results status="passed">
<iscsi-portal-list-entries>
</iscsi-portal-list-entries>
</results>"""))
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("""<results status="passed">
<iscsi-portal-list-entries>
<iscsi-portal-list-entry-info>
<ip-address>%(address)s</ip-address>
<ip-port>%(port)s</ip-port>
<tpgroup-tag>%(tpgroup-tag)s</tpgroup-tag>
</iscsi-portal-list-entry-info>
</iscsi-portal-list-entries>
</results>""" % 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("""<results status="passed">
</results>"""))
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("""<results status="passed">
<node-name>%s</node-name>
</results>""" % 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("""<results status="passed">
<luns>
<lun-info></lun-info>
<lun-info></lun-info>
</luns>
</results>"""))
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("""<results status="passed">
<initiator-groups>
</initiator-groups>
</results>"""))
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("""<results status="passed">
<initiator-groups>
<initiator-group-info>
<initiators>
<initiator-info>
<initiator-name>initiator</initiator-name>
</initiator-info>
</initiators>
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
</initiator-group-info>
</initiator-groups>
</results>""" % 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("""<results status="passed">
<clone-id>
<clone-id-info>
<clone-op-id>1337</clone-op-id>
<volume-uuid>volume-uuid</volume-uuid>
</clone-id-info>
</clone-id>
</results>"""))
fake_clone_status = netapp_api.NaElement(
etree.XML("""<results status="passed">
<status>
<ops-info>
<clone-state>completed</clone-state>
</ops-info>
</status>
</results>"""))
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("""<results status="passed">
<clone-id>
<clone-id-info>
<clone-op-id>1337</clone-op-id>
<volume-uuid>volume-uuid</volume-uuid>
</clone-id-info>
</clone-id>
</results>"""))
fake_clone_status = netapp_api.NaElement(
etree.XML("""<results status="passed">
<status>
<ops-info>
<clone-state>error</clone-state>
</ops-info>
</status>
</results>"""))
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("""<results status="passed">
<clone-id>
<clone-id-info>
<clone-op-id>1337</clone-op-id>
<volume-uuid>volume-uuid</volume-uuid>
</clone-id-info>
</clone-id>
</results>"""))
fake_clone_status = netapp_api.NaElement(
etree.XML("""<results status="passed">
<status>
<ops-info>
<clone-state>completed</clone-state>
</ops-info>
</status>
</results>"""))
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("""<results status="passed">
<clone-id>
<clone-id-info>
<clone-op-id>1337</clone-op-id>
<volume-uuid>volume-uuid</volume-uuid>
</clone-id-info>
</clone-id>
</results>"""))
fake_clone_status = netapp_api.NaElement(
etree.XML("""<results status="passed">
<status>
<ops-info>
<clone-state>running</clone-state>
</ops-info>
</status>
</results>"""))
fake_clone_status_completed = netapp_api.NaElement(
etree.XML("""<results status="passed">
<status>
<ops-info>
<clone-state>completed</clone-state>
</ops-info>
</status>
</results>"""))
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("""<results status="passed">
<luns>
<lun-info></lun-info>
</luns>
</results>"""))
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("""<results status="passed">
<luns>
</luns>
</results>"""))
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("""<results status="passed">
<luns>
<lun-info></lun-info>
</luns>
</results>"""))
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("""<results status="passed">
<volumes>
<volume-info></volume-info>
</volumes>
</results>"""))
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("""<results status="passed">
<volumes>
</volumes>
</results>"""))
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("""<results status="passed">
<actual-pathname>%(path)s</actual-pathname>
</results>""" % {'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("""<results status="passed">
<clone-id>
<clone-id-info>
<volume-uuid>%(volume)s</volume-uuid>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-id-info>
</clone-id>
</results>""" % {'volume': fake_volume_id,
'clone_id': fake_clone_op_id}))
fake_clone_list_response = netapp_api.NaElement(
etree.XML("""<results>
<clone-list-status>
<clone-id-info>
<volume-uuid>%(volume)s</volume-uuid>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-id-info>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-list-status>
<status>
<ops-info>
<clone-state>completed</clone-state>
</ops-info>
</status>
</results>""" % {'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("""<results status="passed">
<clone-id>
<clone-id-info>
<volume-uuid>%(volume)s</volume-uuid>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-id-info>
</clone-id>
</results>""" % {'volume': fake_volume_id,
'clone_id': fake_clone_op_id}))
fake_clone_list_response = netapp_api.NaElement(
etree.XML("""<results>
<clone-list-status>
<clone-id-info>
<volume-uuid>%(volume)s</volume-uuid>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-id-info>
<clone-op-id>%(clone_id)s</clone-op-id>
</clone-list-status>
<status>
<ops-info>
<clone-state>failed</clone-state>
</ops-info>
</status>
</results>""" % {'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("""<results status="passed">
<unique-bytes>%(unique-bytes)s</unique-bytes>
</results>""" % {'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)

View File

@ -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)

View File

@ -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):

View File

@ -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.'))

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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):