]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
NetApp NFS and iSCSI: move zapi client logic into modules
authorAlex Meade <mr.alex.meade@gmail.com>
Thu, 17 Apr 2014 14:34:38 +0000 (10:34 -0400)
committerAlex Meade <mr.alex.meade@gmail.com>
Mon, 24 Nov 2014 03:21:19 +0000 (22:21 -0500)
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

19 files changed:
cinder/tests/test_netapp.py
cinder/tests/test_netapp_nfs.py
cinder/tests/volume/drivers/netapp/client/__init__.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/client/test_base.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/client/test_cmode.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/client/test_seven_mode.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/test_iscsi.py
cinder/tests/volume/drivers/netapp/test_utils.py
cinder/volume/drivers/netapp/api.py
cinder/volume/drivers/netapp/client/__init__.py [new file with mode: 0644]
cinder/volume/drivers/netapp/client/base.py [new file with mode: 0644]
cinder/volume/drivers/netapp/client/cmode.py [new file with mode: 0644]
cinder/volume/drivers/netapp/client/seven_mode.py [new file with mode: 0644]
cinder/volume/drivers/netapp/common.py
cinder/volume/drivers/netapp/eseries/iscsi.py
cinder/volume/drivers/netapp/iscsi.py
cinder/volume/drivers/netapp/nfs.py
cinder/volume/drivers/netapp/ssc_utils.py
cinder/volume/drivers/netapp/utils.py

index c98141a365a9880133dc4c90874f36dac06550e0..ebd1e4491e3a7cca9e045ff847472668e62c9903 100644 (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"""
index 82ebf642594b7da06aabe9e03013940d34114875..4f75bf5ef68876c82f663630ecb0d9f3d3d57b1d 100644 (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()
diff --git a/cinder/tests/volume/drivers/netapp/client/__init__.py b/cinder/tests/volume/drivers/netapp/client/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/tests/volume/drivers/netapp/client/test_base.py b/cinder/tests/volume/drivers/netapp/client/test_base.py
new file mode 100644 (file)
index 0000000..12e6011
--- /dev/null
@@ -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)
diff --git a/cinder/tests/volume/drivers/netapp/client/test_cmode.py b/cinder/tests/volume/drivers/netapp/client/test_cmode.py
new file mode 100644 (file)
index 0000000..18bc808
--- /dev/null
@@ -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)
diff --git a/cinder/tests/volume/drivers/netapp/client/test_seven_mode.py b/cinder/tests/volume/drivers/netapp/client/test_seven_mode.py
new file mode 100644 (file)
index 0000000..e409869
--- /dev/null
@@ -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)
index 836a9e08b520e3a6138c56a0ae0a91956a543bfe..887a231ba15ff14d9ab143ab3bc9c8ad78991038 100644 (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)
index 38c805165d55d3584d41859be1229038f8d98607..8e2e231b389c993f532592719e7dbcdbc91e43a7 100644 (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):
index 9f5f744722f9ef7ec954c7c97238a0f789e50882..3c419e5519121d10ab67bba8d5fc08e47dfa13aa 100644 (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.'))
diff --git a/cinder/volume/drivers/netapp/client/__init__.py b/cinder/volume/drivers/netapp/client/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/volume/drivers/netapp/client/base.py b/cinder/volume/drivers/netapp/client/base.py
new file mode 100644 (file)
index 0000000..4595dbd
--- /dev/null
@@ -0,0 +1,206 @@
+# Copyright (c) - 2014, Alex Meade.  All rights reserved.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import sys
+
+from oslo.utils import excutils
+import six
+
+from cinder.i18n import _LE, _LW, _LI
+from cinder.openstack.common import log as logging
+from cinder.volume.drivers.netapp import api as netapp_api
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Client(object):
+
+    def __init__(self, connection):
+        self.connection = connection
+
+    def get_ontapi_version(self):
+        """Gets the supported ontapi version."""
+        ontapi_version = netapp_api.NaElement('system-get-ontapi-version')
+        res = self.connection.invoke_successfully(ontapi_version, False)
+        major = res.get_child_content('major-version')
+        minor = res.get_child_content('minor-version')
+        return (major, minor)
+
+    def create_lun(self, volume_name, lun_name, size, metadata,
+                   qos_policy_group=None):
+        """Issues API request for creating LUN on volume."""
+
+        path = '/vol/%s/%s' % (volume_name, lun_name)
+        lun_create = netapp_api.NaElement.create_node_with_children(
+            'lun-create-by-size',
+            **{'path': path, 'size': six.text_type(size),
+               'ostype': metadata['OsType'],
+               'space-reservation-enabled': metadata['SpaceReserved']})
+        if qos_policy_group:
+            lun_create.add_new_child('qos-policy-group', qos_policy_group)
+
+        try:
+            self.connection.invoke_successfully(lun_create, True)
+        except netapp_api.NaApiError as ex:
+            with excutils.save_and_reraise_exception():
+                msg = _LE("Error provisioning volume %(lun_name)s on "
+                          "%(volume_name)s. Details: %(ex)s")
+                msg_args = {'lun_name': lun_name,
+                            'volume_name': volume_name,
+                            'ex': six.text_type(ex)}
+                LOG.error(msg % msg_args)
+
+    def destroy_lun(self, path, force=True):
+        """Destroys the lun at the path."""
+        lun_destroy = netapp_api.NaElement.create_node_with_children(
+            'lun-destroy',
+            **{'path': path})
+        if force:
+            lun_destroy.add_new_child('force', 'true')
+        self.connection.invoke_successfully(lun_destroy, True)
+        seg = path.split("/")
+        LOG.debug("Destroyed LUN %s" % seg[-1])
+
+    def map_lun(self, path, igroup_name, lun_id=None):
+        """Maps lun to the initiator and returns lun id assigned."""
+        lun_map = netapp_api.NaElement.create_node_with_children(
+            'lun-map', **{'path': path,
+                          'initiator-group': igroup_name})
+        if lun_id:
+            lun_map.add_new_child('lun-id', lun_id)
+        try:
+            result = self.connection.invoke_successfully(lun_map, True)
+            return result.get_child_content('lun-id-assigned')
+        except netapp_api.NaApiError as e:
+            code = e.code
+            message = e.message
+            msg = _LW('Error mapping lun. Code :%(code)s, Message:%(message)s')
+            msg_fmt = {'code': code, 'message': message}
+            LOG.warning(msg % msg_fmt)
+            raise
+
+    def unmap_lun(self, path, igroup_name):
+        """Unmaps a lun from given initiator."""
+        lun_unmap = netapp_api.NaElement.create_node_with_children(
+            'lun-unmap',
+            **{'path': path, 'initiator-group': igroup_name})
+        try:
+            self.connection.invoke_successfully(lun_unmap, True)
+        except netapp_api.NaApiError as e:
+            msg = _LW("Error unmapping lun. Code :%(code)s,"
+                      " Message:%(message)s")
+            msg_fmt = {'code': e.code, 'message': e.message}
+            exc_info = sys.exc_info()
+            LOG.warning(msg % msg_fmt)
+            # if the lun is already unmapped
+            if e.code == '13115' or e.code == '9016':
+                pass
+            else:
+                raise exc_info[0], exc_info[1], exc_info[2]
+
+    def create_igroup(self, igroup, igroup_type='iscsi', os_type='default'):
+        """Creates igroup with specified args."""
+        igroup_create = netapp_api.NaElement.create_node_with_children(
+            'igroup-create',
+            **{'initiator-group-name': igroup,
+               'initiator-group-type': igroup_type,
+               'os-type': os_type})
+        self.connection.invoke_successfully(igroup_create, True)
+
+    def add_igroup_initiator(self, igroup, initiator):
+        """Adds initiators to the specified igroup."""
+        igroup_add = netapp_api.NaElement.create_node_with_children(
+            'igroup-add',
+            **{'initiator-group-name': igroup,
+               'initiator': initiator})
+        self.connection.invoke_successfully(igroup_add, True)
+
+    def do_direct_resize(self, path, new_size_bytes, force=True):
+        """Resize the lun."""
+        seg = path.split("/")
+        LOG.info(_LI("Resizing lun %s directly to new size."), seg[-1])
+        lun_resize = netapp_api.NaElement.create_node_with_children(
+            'lun-resize',
+            **{'path': path,
+               'size': new_size_bytes})
+        if force:
+            lun_resize.add_new_child('force', 'true')
+        self.connection.invoke_successfully(lun_resize, True)
+
+    def get_lun_geometry(self, path):
+        """Gets the lun geometry."""
+        geometry = {}
+        lun_geo = netapp_api.NaElement("lun-get-geometry")
+        lun_geo.add_new_child('path', path)
+        try:
+            result = self.connection.invoke_successfully(lun_geo, True)
+            geometry['size'] = result.get_child_content("size")
+            geometry['bytes_per_sector'] =\
+                result.get_child_content("bytes-per-sector")
+            geometry['sectors_per_track'] =\
+                result.get_child_content("sectors-per-track")
+            geometry['tracks_per_cylinder'] =\
+                result.get_child_content("tracks-per-cylinder")
+            geometry['cylinders'] =\
+                result.get_child_content("cylinders")
+            geometry['max_resize'] =\
+                result.get_child_content("max-resize-size")
+        except Exception as e:
+            LOG.error(_LE("Lun %(path)s geometry failed. Message - %(msg)s")
+                      % {'path': path, 'msg': e.message})
+        return geometry
+
+    def get_volume_options(self, volume_name):
+        """Get the value for the volume option."""
+        opts = []
+        vol_option_list = netapp_api.NaElement("volume-options-list-info")
+        vol_option_list.add_new_child('volume', volume_name)
+        result = self.connection.invoke_successfully(vol_option_list, True)
+        options = result.get_child_by_name("options")
+        if options:
+            opts = options.get_children()
+        return opts
+
+    def move_lun(self, path, new_path):
+        """Moves the lun at path to new path."""
+        seg = path.split("/")
+        new_seg = new_path.split("/")
+        LOG.debug("Moving lun %(name)s to %(new_name)s."
+                  % {'name': seg[-1], 'new_name': new_seg[-1]})
+        lun_move = netapp_api.NaElement("lun-move")
+        lun_move.add_new_child("path", path)
+        lun_move.add_new_child("new-path", new_path)
+        self.connection.invoke_successfully(lun_move, True)
+
+    def get_target_details(self):
+        """Gets the target portal details."""
+        raise NotImplementedError()
+
+    def get_iscsi_service_details(self):
+        """Returns iscsi iqn."""
+        raise NotImplementedError()
+
+    def get_lun_list(self):
+        """Gets the list of luns on filer."""
+        raise NotImplementedError()
+
+    def get_igroup_by_initiator(self, initiator):
+        """Get igroups by initiator."""
+        raise NotImplementedError()
+
+    def get_lun_by_args(self, **args):
+        """Retrieves luns with specified args."""
+        raise NotImplementedError()
diff --git a/cinder/volume/drivers/netapp/client/cmode.py b/cinder/volume/drivers/netapp/client/cmode.py
new file mode 100644 (file)
index 0000000..2a7df88
--- /dev/null
@@ -0,0 +1,318 @@
+# Copyright (c) - 2014, Alex Meade.  All rights reserved.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+import copy
+import math
+
+import six
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.openstack.common import log as logging
+from cinder.volume.drivers.netapp import api as netapp_api
+from cinder.volume.drivers.netapp.client import base
+from cinder.volume.drivers.netapp import utils as na_utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Client(base.Client):
+
+    def __init__(self, connection, vserver):
+        super(Client, self).__init__(connection)
+        self.vserver = vserver
+
+    def _invoke_vserver_api(self, na_element, vserver):
+        server = copy.copy(self.connection)
+        server.set_vserver(vserver)
+        result = server.invoke_successfully(na_element, True)
+        return result
+
+    def get_target_details(self):
+        """Gets the target portal details."""
+        iscsi_if_iter = netapp_api.NaElement('iscsi-interface-get-iter')
+        result = self.connection.invoke_successfully(iscsi_if_iter, True)
+        tgt_list = []
+        num_records = result.get_child_content('num-records')
+        if num_records and int(num_records) >= 1:
+            attr_list = result.get_child_by_name('attributes-list')
+            iscsi_if_list = attr_list.get_children()
+            for iscsi_if in iscsi_if_list:
+                d = dict()
+                d['address'] = iscsi_if.get_child_content('ip-address')
+                d['port'] = iscsi_if.get_child_content('ip-port')
+                d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag')
+                d['interface-enabled'] = iscsi_if.get_child_content(
+                    'is-interface-enabled')
+                tgt_list.append(d)
+        return tgt_list
+
+    def get_iscsi_service_details(self):
+        """Returns iscsi iqn."""
+        iscsi_service_iter = netapp_api.NaElement('iscsi-service-get-iter')
+        result = self.connection.invoke_successfully(iscsi_service_iter, True)
+        if result.get_child_content('num-records') and\
+                int(result.get_child_content('num-records')) >= 1:
+            attr_list = result.get_child_by_name('attributes-list')
+            iscsi_service = attr_list.get_child_by_name('iscsi-service-info')
+            return iscsi_service.get_child_content('node-name')
+        LOG.debug('No iSCSI service found for vserver %s' % (self.vserver))
+        return None
+
+    def get_lun_list(self):
+        """Gets the list of luns on filer.
+
+        Gets the luns from cluster with vserver.
+        """
+
+        luns = []
+        tag = None
+        while True:
+            api = netapp_api.NaElement('lun-get-iter')
+            api.add_new_child('max-records', '100')
+            if tag:
+                api.add_new_child('tag', tag, True)
+            lun_info = netapp_api.NaElement('lun-info')
+            lun_info.add_new_child('vserver', self.vserver)
+            query = netapp_api.NaElement('query')
+            query.add_child_elem(lun_info)
+            api.add_child_elem(query)
+            result = self.connection.invoke_successfully(api)
+            if result.get_child_by_name('num-records') and\
+                    int(result.get_child_content('num-records')) >= 1:
+                attr_list = result.get_child_by_name('attributes-list')
+                luns.extend(attr_list.get_children())
+            tag = result.get_child_content('next-tag')
+            if tag is None:
+                break
+        return luns
+
+    def get_lun_map(self, path):
+        """Gets the lun map by lun path."""
+        tag = None
+        map_list = []
+        while True:
+            lun_map_iter = netapp_api.NaElement('lun-map-get-iter')
+            lun_map_iter.add_new_child('max-records', '100')
+            if tag:
+                lun_map_iter.add_new_child('tag', tag, True)
+            query = netapp_api.NaElement('query')
+            lun_map_iter.add_child_elem(query)
+            query.add_node_with_children('lun-map-info', **{'path': path})
+            result = self.connection.invoke_successfully(lun_map_iter, True)
+            tag = result.get_child_content('next-tag')
+            if result.get_child_content('num-records') and \
+                    int(result.get_child_content('num-records')) >= 1:
+                attr_list = result.get_child_by_name('attributes-list')
+                lun_maps = attr_list.get_children()
+                for lun_map in lun_maps:
+                    lun_m = dict()
+                    lun_m['initiator-group'] = lun_map.get_child_content(
+                        'initiator-group')
+                    lun_m['lun-id'] = lun_map.get_child_content('lun-id')
+                    lun_m['vserver'] = lun_map.get_child_content('vserver')
+                    map_list.append(lun_m)
+            if tag is None:
+                break
+        return map_list
+
+    def get_igroup_by_initiator(self, initiator):
+        """Get igroups by initiator."""
+        tag = None
+        igroup_list = []
+        while True:
+            igroup_iter = netapp_api.NaElement('igroup-get-iter')
+            igroup_iter.add_new_child('max-records', '100')
+            if tag:
+                igroup_iter.add_new_child('tag', tag, True)
+            query = netapp_api.NaElement('query')
+            igroup_iter.add_child_elem(query)
+            igroup_info = netapp_api.NaElement('initiator-group-info')
+            query.add_child_elem(igroup_info)
+            igroup_info.add_new_child('vserver', self.vserver)
+            initiators = netapp_api.NaElement('initiators')
+            igroup_info.add_child_elem(initiators)
+            initiators.add_node_with_children('initiator-info',
+                                              **{'initiator-name': initiator})
+            des_attrs = netapp_api.NaElement('desired-attributes')
+            des_ig_info = netapp_api.NaElement('initiator-group-info')
+            des_attrs.add_child_elem(des_ig_info)
+            des_ig_info.add_node_with_children('initiators',
+                                               **{'initiator-info': None})
+            des_ig_info.add_new_child('vserver', None)
+            des_ig_info.add_new_child('initiator-group-name', None)
+            des_ig_info.add_new_child('initiator-group-type', None)
+            des_ig_info.add_new_child('initiator-group-os-type', None)
+            igroup_iter.add_child_elem(des_attrs)
+            result = self.connection.invoke_successfully(igroup_iter, False)
+            tag = result.get_child_content('next-tag')
+            if result.get_child_content('num-records') and\
+                    int(result.get_child_content('num-records')) > 0:
+                attr_list = result.get_child_by_name('attributes-list')
+                igroups = attr_list.get_children()
+                for igroup in igroups:
+                    ig = dict()
+                    ig['initiator-group-os-type'] = igroup.get_child_content(
+                        'initiator-group-os-type')
+                    ig['initiator-group-type'] = igroup.get_child_content(
+                        'initiator-group-type')
+                    ig['initiator-group-name'] = igroup.get_child_content(
+                        'initiator-group-name')
+                    igroup_list.append(ig)
+            if tag is None:
+                break
+        return igroup_list
+
+    def clone_lun(self, volume, name, new_name, space_reserved='true',
+                  src_block=0, dest_block=0, block_count=0):
+        # zAPI can only handle 2^24 blocks per range
+        bc_limit = 2 ** 24  # 8GB
+        # zAPI can only handle 32 block ranges per call
+        br_limit = 32
+        z_limit = br_limit * bc_limit  # 256 GB
+        z_calls = int(math.ceil(block_count / float(z_limit)))
+        zbc = block_count
+        if z_calls == 0:
+            z_calls = 1
+        for call in range(0, z_calls):
+            if zbc > z_limit:
+                block_count = z_limit
+                zbc -= z_limit
+            else:
+                block_count = zbc
+            clone_create = netapp_api.NaElement.create_node_with_children(
+                'clone-create',
+                **{'volume': volume, 'source-path': name,
+                   'destination-path': new_name,
+                   'space-reserve': space_reserved})
+            if block_count > 0:
+                block_ranges = netapp_api.NaElement("block-ranges")
+                segments = int(math.ceil(block_count / float(bc_limit)))
+                bc = block_count
+                for segment in range(0, segments):
+                    if bc > bc_limit:
+                        block_count = bc_limit
+                        bc -= bc_limit
+                    else:
+                        block_count = bc
+                    block_range =\
+                        netapp_api.NaElement.create_node_with_children(
+                            'block-range',
+                            **{'source-block-number':
+                               six.text_type(src_block),
+                               'destination-block-number':
+                               six.text_type(dest_block),
+                               'block-count':
+                               six.text_type(block_count)})
+                    block_ranges.add_child_elem(block_range)
+                    src_block += int(block_count)
+                    dest_block += int(block_count)
+                clone_create.add_child_elem(block_ranges)
+            self.connection.invoke_successfully(clone_create, True)
+
+    def get_lun_by_args(self, **args):
+        """Retrieves lun with specified args."""
+        lun_iter = netapp_api.NaElement('lun-get-iter')
+        lun_iter.add_new_child('max-records', '100')
+        query = netapp_api.NaElement('query')
+        lun_iter.add_child_elem(query)
+        query.add_node_with_children('lun-info', **args)
+        luns = self.connection.invoke_successfully(lun_iter)
+        attr_list = luns.get_child_by_name('attributes-list')
+        return attr_list.get_children()
+
+    def file_assign_qos(self, flex_vol, qos_policy_group, file_path):
+        """Retrieves lun with specified args."""
+        file_assign_qos = netapp_api.NaElement.create_node_with_children(
+            'file-assign-qos',
+            **{'volume': flex_vol,
+               'qos-policy-group-name': qos_policy_group,
+               'file': file_path,
+               'vserver': self.vserver})
+        self.connection.invoke_successfully(file_assign_qos, True)
+
+    def get_if_info_by_ip(self, ip):
+        """Gets the network interface info by ip."""
+        net_if_iter = netapp_api.NaElement('net-interface-get-iter')
+        net_if_iter.add_new_child('max-records', '10')
+        query = netapp_api.NaElement('query')
+        net_if_iter.add_child_elem(query)
+        query.add_node_with_children(
+            'net-interface-info', **{'address': na_utils.resolve_hostname(ip)})
+        result = self.connection.invoke_successfully(net_if_iter, True)
+        num_records = result.get_child_content('num-records')
+        if num_records and int(num_records) >= 1:
+            attr_list = result.get_child_by_name('attributes-list')
+            return attr_list.get_children()
+        raise exception.NotFound(
+            _('No interface found on cluster for ip %s') % (ip))
+
+    def get_vol_by_junc_vserver(self, vserver, junction):
+        """Gets the volume by junction path and vserver."""
+        vol_iter = netapp_api.NaElement('volume-get-iter')
+        vol_iter.add_new_child('max-records', '10')
+        query = netapp_api.NaElement('query')
+        vol_iter.add_child_elem(query)
+        vol_attrs = netapp_api.NaElement('volume-attributes')
+        query.add_child_elem(vol_attrs)
+        vol_attrs.add_node_with_children(
+            'volume-id-attributes',
+            **{'junction-path': junction,
+               'owning-vserver-name': vserver})
+        des_attrs = netapp_api.NaElement('desired-attributes')
+        des_attrs.add_node_with_children('volume-attributes',
+                                         **{'volume-id-attributes': None})
+        vol_iter.add_child_elem(des_attrs)
+        result = self._invoke_vserver_api(vol_iter, vserver)
+        num_records = result.get_child_content('num-records')
+        if num_records and int(num_records) >= 1:
+            attr_list = result.get_child_by_name('attributes-list')
+            vols = attr_list.get_children()
+            vol_id = vols[0].get_child_by_name('volume-id-attributes')
+            return vol_id.get_child_content('name')
+        msg_fmt = {'vserver': vserver, 'junction': junction}
+        raise exception.NotFound(_("No volume on cluster with vserver "
+                                   "%(vserver)s and junction path "
+                                   "%(junction)s ") % msg_fmt)
+
+    def clone_file(self, flex_vol, src_path, dest_path, vserver,
+                   dest_exists=False):
+        """Clones file on vserver."""
+        msg = ("Cloning with params volume %(volume)s, src %(src_path)s,"
+               "dest %(dest_path)s, vserver %(vserver)s")
+        msg_fmt = {'volume': flex_vol, 'src_path': src_path,
+                   'dest_path': dest_path, 'vserver': vserver}
+        LOG.debug(msg % msg_fmt)
+        clone_create = netapp_api.NaElement.create_node_with_children(
+            'clone-create',
+            **{'volume': flex_vol, 'source-path': src_path,
+               'destination-path': dest_path})
+        major, minor = self.connection.get_api_version()
+        if major == 1 and minor >= 20 and dest_exists:
+            clone_create.add_new_child('destination-exists', 'true')
+        self._invoke_vserver_api(clone_create, vserver)
+
+    def get_file_usage(self, path, vserver):
+        """Gets the file unique bytes."""
+        LOG.debug('Getting file usage for %s', path)
+        file_use = netapp_api.NaElement.create_node_with_children(
+            'file-usage-get', **{'path': path})
+        res = self._invoke_vserver_api(file_use, vserver)
+        unique_bytes = res.get_child_content('unique-bytes')
+        LOG.debug('file-usage for path %(path)s is %(bytes)s'
+                  % {'path': path, 'bytes': unique_bytes})
+        return unique_bytes
diff --git a/cinder/volume/drivers/netapp/client/seven_mode.py b/cinder/volume/drivers/netapp/client/seven_mode.py
new file mode 100644 (file)
index 0000000..2ee8407
--- /dev/null
@@ -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)
index d6acd0640d048efdfbca91316772d173bbe1cb72..162dd73a4fbbf4270b612d60f249239ceb143beb 100644 (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
index b80384a94f9a46bca61f7c56f29721cb70e0429f..3c5057953705b96301be7053579b64dfdf39fe1c 100644 (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
index dccac08ca27dba7f0d99f4ecb25f454001afc761..aa94c4874a58f61ed3fd366c3674b8a998424c70 100644 (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)
 
index 4bb035f479a5fedb84069bf72b6d22a406d617ce..da45edd96754f0bf2d710b454b63c555bb6b45c8 100644 (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:
index 2c22ef6cfa5c9700fedad3eba531427afc2de1f2..f0798992cb9e169f264db80b913417148fa5eb93 100644 (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)
index da5f85807f0a20a795fd8aa4fbe2c68bb28d4abe..364eddcce84bd8a4d6a62fca15cdaf0e5303ee35 100644 (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):