]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
NexentaStor5 iSCSI driver unit tests
authorAlexey Khodos <alexey.khodos@nexenta.com>
Fri, 22 Jan 2016 21:55:45 +0000 (00:55 +0300)
committerAlexey Khodos <alexey.khodos@nexenta.com>
Fri, 5 Feb 2016 20:12:23 +0000 (23:12 +0300)
Additional unit tests to cover jsonrpc and iscsi exceptions.
Updating the delete behavior to not throw an exception if the LU
was already deleted on the appliance. Also fixing an issue with
the total and free space in get_stats.

Change-Id: I272a4085726e1162ace0b1a5cad9989e294bdf93

cinder/tests/unit/test_nexenta5_iscsi.py
cinder/volume/drivers/nexenta/ns5/iscsi.py
cinder/volume/drivers/nexenta/ns5/jsonrpc.py

index 91074b8b6d93ef4167642bea6808733eab1ae6c5..e8e47083689321761c8685a6a1e02c7d24ba9a46 100644 (file)
@@ -18,7 +18,9 @@ Unit tests for OpenStack Cinder volume driver
 
 import mock
 from mock import patch
+from oslo_serialization import jsonutils
 from oslo_utils import units
+import requests
 
 from cinder import context
 from cinder import db
@@ -92,12 +94,26 @@ class TestNexentaISCSIDriver(test.TestCase):
         }
         return db.volume_create(self.ctxt, vol)['id']
 
-    def check_for_setup_error(self):
+    def test_do_setup(self):
+        self.nef_mock.post.side_effect = exception.NexentaException(
+            'Could not create volume group')
+        self.assertRaises(
+            exception.NexentaException,
+            self.drv.do_setup, self.ctxt)
+
+        self.nef_mock.post.side_effect = exception.NexentaException(
+            '{"code": "EEXIST"}')
+        self.assertIsNone(self.drv.do_setup(self.ctxt))
+
+    def test_check_for_setup_error(self):
         self.nef_mock.get.return_value = {
-            'services': {'data': {'iscsit': {'state': 'offline'}}}}
+            'data': [{'name': 'iscsit', 'state': 'offline'}]}
         self.assertRaises(
             exception.NexentaException, self.drv.check_for_setup_error)
 
+        self.nef_mock.get.side_effect = exception.NexentaException()
+        self.assertRaises(LookupError, self.drv.check_for_setup_error)
+
     def test_create_volume(self):
         self.drv.create_volume(self.TEST_VOLUME_REF)
         url = 'storage/pools/pool/volumeGroups/dsg/volumes'
@@ -108,19 +124,45 @@ class TestNexentaISCSIDriver(test.TestCase):
             'sparseVolume': self.cfg.nexenta_sparse})
 
     def test_delete_volume(self):
-        self.drv.delete_volume(self.TEST_VOLUME_REF)
+        self.nef_mock.delete.side_effect = exception.NexentaException()
+        self.assertIsNone(self.drv.delete_volume(self.TEST_VOLUME_REF))
         url = 'storage/pools/pool/volumeGroups'
         data = {'name': 'dsg', 'volumeBlockSize': 32768}
         self.nef_mock.post.assert_called_with(url, data)
 
-    def test_create_cloned_volume(self):
+    def test_extend_volume(self):
+        self.nef_mock.put.side_effect = exception.NexentaException()
+        self.assertRaises(
+            exception.NexentaException,
+            self.drv.extend_volume, self.TEST_VOLUME_REF, 2)
+
+    def test_delete_snapshot(self):
+        self._create_volume_db_entry()
+        self.nef_mock.delete.side_effect = exception.NexentaException('EBUSY')
+        self.drv.delete_snapshot(self.TEST_SNAPSHOT_REF)
+        url = ('storage/pools/pool/volumeGroups/dsg/'
+               'volumes/volume-1/snapshots/snapshot1')
+        self.nef_mock.delete.assert_called_with(url)
+
+        self.nef_mock.delete.side_effect = exception.NexentaException('Error')
+        self.drv.delete_snapshot(self.TEST_SNAPSHOT_REF)
+        self.nef_mock.delete.assert_called_with(url)
+
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver.create_snapshot')
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver.delete_snapshot')
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver.create_volume_from_snapshot')
+    def test_create_cloned_volume(self, crt_vol, dlt_snap, crt_snap):
         self._create_volume_db_entry()
         vol = self.TEST_VOLUME_REF2
         src_vref = self.TEST_VOLUME_REF
-
-        self.drv.create_cloned_volume(vol, src_vref)
-        url = 'storage/pools/pool/volumeGroups/dsg/volumes/volume2/promote'
-        self.nef_mock.post.assert_called_with(url)
+        crt_vol.side_effect = exception.NexentaException()
+        dlt_snap.side_effect = exception.NexentaException()
+        self.assertRaises(
+            exception.NexentaException,
+            self.drv.create_cloned_volume, vol, src_vref)
 
     def test_create_snapshot(self):
         self._create_volume_db_entry()
@@ -149,13 +191,18 @@ class TestNexentaISCSIDriver(test.TestCase):
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
            'NexentaISCSIDriver._get_target_by_alias')
     def test_create_target(self, target):
+        self.nef_mock.get.return_value = {}
         target.return_value = {'name': 'iqn-0'}
         self.assertEqual('iqn-0', self.drv._create_target(0))
 
+        target.return_value = None
+        self.assertRaises(TypeError, self.drv._create_target, 0)
+
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
            'NexentaISCSIDriver._create_target')
     def test_get_target_name(self, target_name):
         self._create_volume_db_entry()
+        self.drv.targets = {}
         target_name.return_value = 'iqn-0'
         self.drv.targets['iqn-0'] = []
         self.assertEqual(
@@ -168,6 +215,16 @@ class TestNexentaISCSIDriver(test.TestCase):
             'iqn-0', self.drv._get_target_name(self.TEST_VOLUME_REF))
         self.assertEqual('1.1.1.1-0', self.drv.targetgroups['iqn-0'])
 
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver._create_target')
+    def test_get_targetgroup_name(self, target_name):
+        self.TEST_VOLUME_REF['provider_location'] = '1.1.1.1:8080,1 iqn-0 0'
+        self._create_volume_db_entry()
+        target_name = 'iqn-0'
+        self.drv.targetgroups[target_name] = '1.1.1.1-0'
+        self.assertEqual(
+            '1.1.1.1-0', self.drv._get_targetgroup_name(self.TEST_VOLUME_REF))
+
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
            'NexentaISCSIDriver._get_targetgroup_name')
     def test_get_lun_id(self, targetgroup):
@@ -175,6 +232,10 @@ class TestNexentaISCSIDriver(test.TestCase):
         self.nef_mock.get.return_value = {'data': [{'guid': '0'}]}
         self.assertEqual('0', self.drv._get_lun_id(self.TEST_VOLUME_REF))
 
+        self.nef_mock.get.return_value = {}
+        self.assertRaises(
+            LookupError, self.drv._get_lun_id, self.TEST_VOLUME_REF)
+
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
            'NexentaISCSIDriver._get_lun_id')
     def test_lu_exists(self, lun_id):
@@ -193,6 +254,13 @@ class TestNexentaISCSIDriver(test.TestCase):
         self.nef_mock.get.return_value = {'data': [{'lunNumber': 0}]}
         self.assertEqual(0, self.drv._get_lun(self.TEST_VOLUME_REF))
 
+        self.nef_mock.get.return_value = {}
+        self.assertRaises(
+            LookupError, self.drv._get_lun, self.TEST_VOLUME_REF)
+
+        lun_id.side_effect = LookupError()
+        self.assertIsNone(self.drv._get_lun(self.TEST_VOLUME_REF))
+
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
            'NexentaISCSIDriver._get_target_name')
     @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
@@ -209,3 +277,98 @@ class TestNexentaISCSIDriver(test.TestCase):
         self.assertEqual(
             {'provider_location': '1.1.1.1:8080,1 iqn-0 0'},
             self.drv._do_export({}, self.TEST_VOLUME_REF))
+
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver._get_targetgroup_name')
+    @patch('cinder.volume.drivers.nexenta.ns5.iscsi.'
+           'NexentaISCSIDriver._get_lun_id')
+    def test_remove_export(self, lun_id, tg_name):
+        lun_id.return_value = '0'
+        tg_name.return_value = '1.1.1.1-0'
+        self.nef_mock.delete.side_effect = exception.NexentaException(
+            'No such logical unit in target group')
+        self.assertIsNone(
+            self.drv.remove_export(self.ctxt, self.TEST_VOLUME_REF))
+
+        self.nef_mock.delete.side_effect = exception.NexentaException(
+            'Error')
+        self.assertRaises(
+            exception.NexentaException,
+            self.drv.remove_export, self.ctxt, self.TEST_VOLUME_REF)
+
+        lun_id.side_effect = LookupError()
+        self.assertIsNone(
+            self.drv.remove_export(self.ctxt, self.TEST_VOLUME_REF))
+
+    def test_update_volume_stats(self):
+        self.nef_mock.get.return_value = {
+            'bytesAvailable': 10 * units.Gi,
+            'bytesUsed': 2 * units.Gi
+        }
+        location_info = '%(driver)s:%(host)s:%(pool)s/%(group)s' % {
+            'driver': self.drv.__class__.__name__,
+            'host': self.cfg.nexenta_host,
+            'pool': self.cfg.nexenta_volume,
+            'group': self.cfg.nexenta_volume_group,
+        }
+        stats = {
+            'vendor_name': 'Nexenta',
+            'dedup': self.cfg.nexenta_dataset_dedup,
+            'compression': self.cfg.nexenta_dataset_compression,
+            'description': self.cfg.nexenta_dataset_description,
+            'driver_version': self.drv.VERSION,
+            'storage_protocol': 'iSCSI',
+            'total_capacity_gb': 10,
+            'free_capacity_gb': 8,
+            'reserved_percentage': self.cfg.reserved_percentage,
+            'QoS_support': False,
+            'volume_backend_name': self.drv.backend_name,
+            'location_info': location_info,
+            'iscsi_target_portal_port': (
+                self.cfg.nexenta_iscsi_target_portal_port),
+            'nef_url': self.drv.nef.url
+        }
+        self.drv._update_volume_stats()
+        self.assertEqual(stats, self.drv._stats)
+
+
+class TestNexentaJSONProxy(test.TestCase):
+
+    def __init__(self, method):
+        super(TestNexentaJSONProxy, self).__init__(method)
+
+    @patch('requests.Response.close')
+    @patch('requests.get')
+    @patch('requests.post')
+    def test_call(self, post, get, close):
+        nef_get = jsonrpc.NexentaJSONProxy(
+            'http', '1.1.1.1', '8080', 'user', 'pass', method='get')
+        nef_post = jsonrpc.NexentaJSONProxy(
+            'http', '1.1.1.1', '8080', 'user', 'pass', method='post')
+        data = {'key': 'value'}
+        get.return_value = requests.Response()
+        post.return_value = requests.Response()
+
+        get.return_value.__setstate__({
+            'status_code': 200, '_content': jsonutils.dumps(data)})
+        self.assertEqual({'key': 'value'}, nef_get('url'))
+
+        get.return_value.__setstate__({
+            'status_code': 201, '_content': ''})
+        self.assertEqual('Success', nef_get('url'))
+
+        data2 = {'links': [{'href': 'redirect_url'}]}
+        post.return_value.__setstate__({
+            'status_code': 202, '_content': jsonutils.dumps(data2)})
+        get.return_value.__setstate__({
+            'status_code': 200, '_content': jsonutils.dumps(data)})
+        self.assertEqual({'key': 'value'}, nef_post('url'))
+
+        get.return_value.__setstate__({
+            'status_code': 200, '_content': ''})
+        self.assertEqual('Success', nef_post('url', data))
+
+        get.return_value.__setstate__({
+            'status_code': 400,
+            '_content': jsonutils.dumps({'code': 'ENOENT'})})
+        self.assertRaises(exception.NexentaException, lambda: nef_get('url'))
index 2e82f0316e3ad560a5150a8c359778eb65365dc4..3faef5276bdcc26b77bb5f702dc4e14ace0ff043 100644 (file)
@@ -289,7 +289,7 @@ class NexentaISCSIDriver(driver.ISCSIDriver):  # pylint: disable=R0921
         try:
             self.nef.delete(url)
         except exception.NexentaException as exc:
-            if 'EBUSY' is exc:
+            if 'EBUSY' in exc.args[0]:
                 LOG.warning(_LW(
                     'Could not delete snapshot %s - it has dependencies'),
                     snapshot['name'])
@@ -476,7 +476,13 @@ class NexentaISCSIDriver(driver.ISCSIDriver):  # pylint: disable=R0921
         targetgroup_name = self._get_targetgroup_name(volume)
         url = 'san/targetgroups/%s/luns/%s' % (
             targetgroup_name, lun_id)
-        self.nef.delete(url)
+        try:
+            self.nef.delete(url)
+        except exception.NexentaException as exc:
+            if 'No such logical unit in target group' in exc.args[0]:
+                LOG.debug('LU already deleted from appliance')
+            else:
+                raise
 
     def get_volume_stats(self, refresh=False):
         """Get volume stats.
@@ -497,9 +503,9 @@ class NexentaISCSIDriver(driver.ISCSIDriver):  # pylint: disable=R0921
             'group': self.volume_group,
         }
         stats = self.nef.get(url)
-        total_amount = utils.str2gib_size(
-            stats['bytesAvailable'] + stats['bytesUsed'])
-        free_amount = utils.str2gib_size(stats['bytesAvailable'])
+        total_amount = utils.str2gib_size(stats['bytesAvailable'])
+        free_amount = utils.str2gib_size(
+            stats['bytesAvailable'] - stats['bytesUsed'])
 
         location_info = '%(driver)s:%(host)s:%(pool)s/%(group)s' % {
             'driver': self.__class__.__name__,
index c37c49cdd3a63316bcb975fd3eff0b627064931c..7689e7b4cc979e621906aed87b4af91c0acc2f0e 100644 (file)
@@ -19,6 +19,8 @@
 .. automodule:: nexenta.jsonrpc
 """
 
+import base64
+import json
 import time
 
 from oslo_log import log as logging
@@ -59,7 +61,8 @@ class NexentaJSONProxy(object):
         return 'NEF proxy: %s' % self.url
 
     def __call__(self, path, data=None):
-        auth = ('%s:%s' % (self.user, self.password)).encode('base64')[:-1]
+        auth = base64.b64encode(
+            ('%s:%s' % (self.user, self.password)).encode('utf-8'))[:-1]
         headers = {
             'Content-Type': 'application/json',
             'Authorization': 'Basic %s' % auth
@@ -71,21 +74,15 @@ class NexentaJSONProxy(object):
 
         LOG.debug('Sending JSON to url: %s, data: %s, method: %s',
                   path, data, self.method)
-        if self.method == 'get':
-            resp = requests.get(url, headers=headers)
-        if self.method == 'post':
-            resp = requests.post(url, data=data, headers=headers)
-        if self.method == 'put':
-            resp = requests.put(url, data=data, headers=headers)
-        if self.method == 'delete':
-            resp = requests.delete(url, data=data, headers=headers)
+
+        resp = getattr(requests, self.method)(url, data=data, headers=headers)
 
         if resp.status_code == 201 or (
                 resp.status_code == 200 and not resp.content):
             LOG.debug('Got response: Success')
             return 'Success'
 
-        response = resp.json()
+        response = json.loads(resp.content)
         resp.close()
         if response and resp.status_code == 202:
             url = self.url + response['links'][0]['href']
@@ -97,7 +94,7 @@ class NexentaJSONProxy(object):
                     LOG.debug('Got response: Success')
                     return 'Success'
                 else:
-                    response = resp.json()
+                    response = json.loads(resp.content)
                 resp.close()
         if response.get('code'):
             raise exception.NexentaException(response)