Browse Source

Added Handling Newer Quobyte API Error Codes

This added the capability to expect specific error codes when doing a
RPC call in the Quobyte driver. This is used to handle the newer
API error codes in newer (1.4+) Quobyte API versions. This also adds
explicitely creating a Quobyte tenant.

Closes-Bug: #1733807

Change-Id: I65e87e6f50e12bfbe5d7a8fd988ca14bddd212da
(cherry picked from commit 269942101b)
(cherry picked from commit 566578e8fd)
Silvan Kaiser 1 year ago
parent
commit
238c332e0f

+ 9
- 5
manila/share/drivers/quobyte/jsonrpc.py View File

@@ -34,6 +34,8 @@ from manila import utils
34 34
 LOG = log.getLogger(__name__)
35 35
 
36 36
 ERROR_ENOENT = 2
37
+ERROR_ENTITY_NOT_FOUND = -24
38
+ERROR_GARBAGE_ARGS = -3
37 39
 
38 40
 
39 41
 class JsonRpc(object):
@@ -58,7 +60,7 @@ class JsonRpc(object):
58 60
         self._cert_file = cert_file
59 61
 
60 62
     @utils.synchronized('quobyte-request')
61
-    def call(self, method_name, user_parameters):
63
+    def call(self, method_name, user_parameters, expected_errors=[]):
62 64
         # prepare request
63 65
         self._id += 1
64 66
         parameters = {'retry': 'INFINITELY'}  # Backend specific setting
@@ -95,17 +97,19 @@ class JsonRpc(object):
95 97
         if result.status_code == codes['OK']:
96 98
             LOG.debug("Retrieved data from Quobyte backend: %s", result.text)
97 99
             response = result.json()
98
-            return self._checked_for_application_error(response)
100
+            return self._checked_for_application_error(response,
101
+                                                       expected_errors)
99 102
 
100 103
         # If things did not work out provide error info
101 104
         LOG.debug("Backend request resulted in error: %s" % result.text)
102 105
         result.raise_for_status()
103 106
 
104
-    def _checked_for_application_error(self, result):
107
+    def _checked_for_application_error(self, result, expected_errors=[]):
105 108
         if 'error' in result and result['error']:
106 109
             if 'message' in result['error'] and 'code' in result['error']:
107
-                if result["error"]["code"] == ERROR_ENOENT:
108
-                    return None  # No Entry
110
+                if result["error"]["code"] in expected_errors:
111
+                    # hit an expected error, return empty result
112
+                    return None
109 113
                 else:
110 114
                     raise exception.QBRpcException(
111 115
                         result=result["error"]["message"],

+ 8
- 2
manila/share/drivers/quobyte/quobyte.py View File

@@ -76,9 +76,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
76 76
         1.2.1   - Improved capacity calculation
77 77
         1.2.2   - Minor optimizations
78 78
         1.2.3   - Updated RPC layer for improved stability
79
+        1.2.4   - Fixed handling updated QB API error codes
79 80
     """
80 81
 
81
-    DRIVER_VERSION = '1.2.3'
82
+    DRIVER_VERSION = '1.2.4'
82 83
 
83 84
     def __init__(self, *args, **kwargs):
84 85
         super(QuobyteShareDriver, self).__init__(False, *args, **kwargs)
@@ -187,7 +188,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
187 188
         """Resolve a volume name to the global volume uuid."""
188 189
         result = self.rpc.call('resolveVolumeName', dict(
189 190
             volume_name=volume_name,
190
-            tenant_domain=tenant_domain))
191
+            tenant_domain=tenant_domain), [jsonrpc.ERROR_ENOENT,
192
+                                           jsonrpc.ERROR_ENTITY_NOT_FOUND])
191 193
         if result:
192 194
             return result['volume_uuid']
193 195
         return None  # not found
@@ -220,6 +222,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
220 222
             self._get_project_name(context, share['project_id']))
221 223
 
222 224
         if not volume_uuid:
225
+            # create tenant, expect ERROR_GARBAGE_ARGS if it already exists
226
+            self.rpc.call('setTenant',
227
+                          dict(tenant=dict(tenant_id=share['project_id'])),
228
+                          expected_errors=[jsonrpc.ERROR_GARBAGE_ARGS])
223 229
             result = self.rpc.call('createVolume', dict(
224 230
                 name=share['name'],
225 231
                 tenant_domain=share['project_id'],

+ 34
- 1
manila/tests/share/drivers/quobyte/test_jsonrpc.py View File

@@ -141,6 +141,29 @@ class QuobyteJsonRpcTestCase(test.TestCase):
141 141
             verify=fake_ca_file)
142 142
         self.assertEqual("Sweet gorilla of Manila", result)
143 143
 
144
+    @mock.patch.object(jsonrpc.JsonRpc, "_checked_for_application_error",
145
+                       return_value="Sweet gorilla of Manila")
146
+    @mock.patch.object(requests, "post",
147
+                       return_value=FakeResponse(
148
+                           200, {"result": "Sweet gorilla of Manila"}))
149
+    def test_https_call_verify_expected_error(self, mock_req_get, mock_check):
150
+        fake_ca_file = tempfile.TemporaryFile()
151
+        self.rpc = jsonrpc.JsonRpc(url="https://test",
152
+                                   user_credentials=("me", "team"),
153
+                                   ca_file=fake_ca_file)
154
+
155
+        result = self.rpc.call('method', {'param': 'value'},
156
+                               expected_errors=[42])
157
+
158
+        mock_req_get.assert_called_once_with(
159
+            url=self.rpc._url,
160
+            json=mock.ANY,  # not checking here as of undefined order in dict
161
+            auth=self.rpc._credentials,
162
+            verify=fake_ca_file)
163
+        mock_check.assert_called_once_with(
164
+            {'result': 'Sweet gorilla of Manila'}, [42])
165
+        self.assertEqual("Sweet gorilla of Manila", result)
166
+
144 167
     @mock.patch.object(requests, "post", side_effect=exceptions.HTTPError)
145 168
     def test_jsonrpc_call_http_exception(self, req_get_mock):
146 169
         self.assertRaises(exceptions.HTTPError,
@@ -169,12 +192,22 @@ class QuobyteJsonRpcTestCase(test.TestCase):
169 192
                          (self.rpc._checked_for_application_error(
170 193
                              result=resultdict)))
171 194
 
195
+    def test_checked_for_application_error_enf(self):
196
+        resultdict = {"result": "Sweet gorilla of Manila",
197
+                      "error": {"message": "No Gorilla",
198
+                                "code": jsonrpc.ERROR_ENTITY_NOT_FOUND}}
199
+        self.assertIsNone(
200
+            self.rpc._checked_for_application_error(
201
+                result=resultdict,
202
+                expected_errors=[jsonrpc.ERROR_ENTITY_NOT_FOUND]))
203
+
172 204
     def test_checked_for_application_error_no_entry(self):
173 205
         resultdict = {"result": "Sweet gorilla of Manila",
174 206
                       "error": {"message": "No Gorilla",
175 207
                                 "code": jsonrpc.ERROR_ENOENT}}
176 208
         self.assertIsNone(
177
-            self.rpc._checked_for_application_error(result=resultdict))
209
+            self.rpc._checked_for_application_error(
210
+                result=resultdict, expected_errors=[jsonrpc.ERROR_ENOENT]))
178 211
 
179 212
     def test_checked_for_application_error_exception(self):
180 213
         self.assertRaises(exception.QBRpcException,

+ 30
- 8
manila/tests/share/drivers/quobyte/test_quobyte.py View File

@@ -29,7 +29,7 @@ from manila.tests import fake_share
29 29
 CONF = cfg.CONF
30 30
 
31 31
 
32
-def fake_rpc_handler(name, *args):
32
+def fake_rpc_handler(name, *args, **kwargs):
33 33
     if name == 'resolveVolumeName':
34 34
         return None
35 35
     elif name == 'createVolume':
@@ -136,8 +136,23 @@ class QuobyteShareDriverTestCase(test.TestCase):
136 136
 
137 137
         self._driver.create_share(self._context, self.share)
138 138
 
139
-        self._driver.rpc.call.assert_called_with(
140
-            'exportVolume', dict(protocol='NFS', volume_uuid='voluuid'))
139
+        resolv_params = {'tenant_domain': 'fake_project_uuid',
140
+                         'volume_name': 'fakename'}
141
+        sett_params = {'tenant': {'tenant_id': 'fake_project_uuid'}}
142
+        create_params = dict(
143
+            name='fakename',
144
+            tenant_domain='fake_project_uuid',
145
+            root_user_id='root',
146
+            root_group_id='root',
147
+            configuration_name='BASE')
148
+        self._driver.rpc.call.assert_has_calls([
149
+            mock.call('resolveVolumeName', resolv_params,
150
+                      [jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]),
151
+            mock.call('setTenant', sett_params,
152
+                      expected_errors=[jsonrpc.ERROR_GARBAGE_ARGS]),
153
+            mock.call('createVolume', create_params),
154
+            mock.call('exportVolume', dict(protocol='NFS',
155
+                                           volume_uuid='voluuid'))])
141 156
 
142 157
     def test_create_share_wrong_protocol(self):
143 158
         share = {'share_proto': 'WRONG_PROTOCOL'}
@@ -162,7 +177,8 @@ class QuobyteShareDriverTestCase(test.TestCase):
162 177
         resolv_params = {'volume_name': 'fakename',
163 178
                          'tenant_domain': 'fake_project_uuid'}
164 179
         self._driver.rpc.call.assert_has_calls([
165
-            mock.call('resolveVolumeName', resolv_params),
180
+            mock.call('resolveVolumeName', resolv_params,
181
+                      [jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]),
166 182
             mock.call('deleteVolume', {'volume_uuid': 'voluuid'})])
167 183
 
168 184
     def test_delete_share_existing_volume_disabled(self):
@@ -178,8 +194,7 @@ class QuobyteShareDriverTestCase(test.TestCase):
178 194
         self._driver.delete_share(self._context, self.share)
179 195
 
180 196
         self._driver.rpc.call.assert_called_with(
181
-            'exportVolume', {'volume_uuid': 'voluuid',
182
-                             'remove_export': True})
197
+            'exportVolume', {'volume_uuid': 'voluuid', 'remove_export': True})
183 198
 
184 199
     @mock.patch.object(quobyte.LOG, 'warning')
185 200
     def test_delete_share_nonexisting_volume(self, mock_warning):
@@ -276,8 +291,9 @@ class QuobyteShareDriverTestCase(test.TestCase):
276 291
 
277 292
         exp_params = {'volume_name': 'fake_vol_name',
278 293
                       'tenant_domain': 'fake_domain_name'}
279
-        self._driver.rpc.call.assert_called_with('resolveVolumeName',
280
-                                                 exp_params)
294
+        self._driver.rpc.call.assert_called_with(
295
+            'resolveVolumeName', exp_params,
296
+            [jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND])
281 297
 
282 298
     def test_resolve_volume_name_NOENT(self):
283 299
         self._driver.rpc.call = mock.Mock(
@@ -286,6 +302,12 @@ class QuobyteShareDriverTestCase(test.TestCase):
286 302
         self.assertIsNone(
287 303
             self._driver._resolve_volume_name('fake_vol_name',
288 304
                                               'fake_domain_name'))
305
+        self._driver.rpc.call.assert_called_once_with(
306
+            'resolveVolumeName',
307
+            dict(volume_name='fake_vol_name',
308
+                 tenant_domain='fake_domain_name'),
309
+            [jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]
310
+        )
289 311
 
290 312
     def test_resolve_volume_name_other_error(self):
291 313
         self._driver.rpc.call = mock.Mock(

+ 5
- 0
releasenotes/notes/qb-bug-1733807-581e71e6581de28e.yaml View File

@@ -0,0 +1,5 @@
1
+---
2
+fixes:
3
+  - |
4
+    The Quobyte driver now handles updated error codes from Quobyte API
5
+    versions 1.4+ .

Loading…
Cancel
Save