Coverage for manila/tests/share/drivers/vastdata/test_rest.py: 100%
193 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2024 VAST Data Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15import io
16import unittest
17from unittest import mock
19import ddt
20import requests
22from manila import exception as manila_exception
23from manila.share.drivers.vastdata import driver_util
24from manila.share.drivers.vastdata import rest as vast_rest
27fake_metrics = driver_util.Bunch.from_dict(
28 {
29 "object_ids": [1],
30 "prop_list": [
31 "timestamp",
32 "object_id",
33 "Capacity,drr",
34 "Capacity,physical_space_in_use",
35 "Capacity,physical_space",
36 "Capacity,logical_space",
37 "Capacity,logical_space_in_use",
38 ],
39 "data": [
40 [
41 "2024-04-13T14:47:48Z",
42 1,
43 1.2,
44 30635076602.0,
45 711246584217.0,
46 505850953728.0,
47 22370924820.0,
48 ],
49 [
50 "2024-04-13T14:47:38Z",
51 1,
52 1.2,
53 30635109399.0,
54 711246584217.0,
55 505850134528.0,
56 22370810131.0,
57 ],
58 [
59 "2024-04-13T14:47:28Z",
60 1,
61 1.2,
62 30635142195.0,
63 711246584217.0,
64 505849217024.0,
65 22370720020.0,
66 ],
67 [
68 "2024-04-13T14:47:18Z",
69 1,
70 1.2,
71 30635174991.0,
72 711246584217.0,
73 505848365056.0,
74 22370654484.0,
75 ],
76 [
77 "2024-04-13T14:47:08Z",
78 1,
79 1.2,
80 30635207787.0,
81 711246584217.0,
82 505847447552.0,
83 22420396308.0,
84 ],
85 [
86 "2024-04-13T14:46:58Z",
87 1,
88 1.2,
89 30635248783.0,
90 711246584217.0,
91 505846398976.0,
92 22420306196.0,
93 ],
94 ],
95 "granularity": None,
96 }
97)
100class TestSession(unittest.TestCase):
102 def setUp(self):
103 self.session = vast_rest.Session(
104 "host",
105 "username",
106 "password",
107 "",
108 False,
109 "1.0",
110 )
112 @mock.patch("requests.Session.request")
113 def test_refresh_auth_token_success(self, mock_request):
114 mock_request.return_value.json.return_value = {"access": "test_token"}
115 self.session.refresh_auth_token()
116 self.assertEqual(
117 self.session.headers["authorization"],
118 "Bearer test_token"
119 )
121 @mock.patch("requests.Session.request")
122 def test_refresh_auth_token_failure(self, mock_request):
123 mock_request.side_effect = ConnectionError()
124 with self.assertRaises(manila_exception.VastApiException):
125 self.session.refresh_auth_token()
127 @mock.patch("requests.Session.request")
128 def test_request_success(self, mock_request):
129 mock_request.return_value.status_code = 200
130 self.session.request(
131 "GET", "test_method",
132 log_result=False, params={"foo": "bar"}
133 )
134 mock_request.assert_called_once_with(
135 "GET", "https://host/api/test_method/",
136 verify=False, params={"foo": "bar"}
137 )
139 @mock.patch("requests.Session.request")
140 def test_request_failure_400(self, mock_request):
141 mock_request.return_value.status_code = 400
142 mock_request.return_value.text = "foo/bar"
143 with self.assertRaises(manila_exception.VastApiException):
144 self.session.request(
145 "POST", "test_method",
146 data={"data": {"foo": "bar"}}
147 )
149 def test_request_failure_500(self):
151 resp = requests.Response()
152 resp.status_code = 500
153 resp.raw = io.BytesIO(b"Server error")
155 with mock.patch(
156 "requests.Session.request", new=lambda *a, **k: resp
157 ):
158 with self.assertRaises(manila_exception.VastApiException) as exc:
159 self.session.request("GET", "test_method", log_result=False)
160 self.assertIn("Server Error", str(exc.exception))
162 def test_request_no_return_content(self):
163 resp = requests.Response()
164 resp.status_code = 200
165 resp.raw = io.BytesIO(b"")
167 with mock.patch(
168 "requests.Session.request", new=lambda *a, **k: resp
169 ):
170 res = self.session.request("GET", "test_method")
171 self.assertFalse(res)
173 @mock.patch(
174 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
175 mock.MagicMock()
176 )
177 def test_refresh_token_retries(self):
178 resp = requests.Response()
179 resp.status_code = 403
180 resp.raw = io.BytesIO(b"Token is invalid")
182 with mock.patch("requests.Session.request", new=lambda *a, **k: resp):
183 with self.assertRaises(manila_exception.VastApiRetry):
184 self.session.request("POST", "test_method", foo="bar")
186 def test_getattr_with_underscore(self):
187 with self.assertRaises(AttributeError):
188 self.session.__getattr__("_private")
190 @mock.patch.object(vast_rest.Session, "request")
191 def test_getattr_without_underscore(self, mock_request):
192 attr = "public"
193 params = {"key": "value"}
194 self.session.__getattr__(attr)(**params)
195 mock_request.assert_called_once_with("get", attr, params=params)
198class TestVastResource(unittest.TestCase):
199 def setUp(self):
200 self.mock_rest = mock.MagicMock()
201 self.vast_resource = vast_rest.VastResource(self.mock_rest)
203 def test_list_with_filtering_params(self):
204 self.vast_resource.list(name="test")
205 self.mock_rest.session.get.assert_called_with(
206 self.vast_resource.resource_name, params={"name": "test"}
207 )
209 def test_create_with_provided_params(self):
210 self.vast_resource.create(name="test", size=10)
211 self.mock_rest.session.post.assert_called_with(
212 self.vast_resource.resource_name, data={"name": "test", "size": 10}
213 )
215 def test_update_with_provided_params(self):
216 self.vast_resource.update("1", name="test", size=10)
217 self.mock_rest.session.patch.assert_called_with(
218 f"{self.vast_resource.resource_name}/1",
219 data={"name": "test", "size": 10}
220 )
222 def test_delete_when_entry_not_found(self):
223 self.vast_resource.one = mock.MagicMock(return_value=None)
224 self.vast_resource.delete("test")
225 self.mock_rest.session.delete.assert_not_called()
227 def test_delete_when_entry_found(self):
228 mock_entry = mock.MagicMock()
229 mock_entry.id = "1"
230 self.vast_resource.one = mock.MagicMock(return_value=mock_entry)
231 self.vast_resource.delete("test")
232 self.mock_rest.session.delete.assert_called_with(
233 f"{self.vast_resource.resource_name}/{mock_entry.id}"
234 )
236 def test_one_when_no_entries_found(self):
237 self.vast_resource.list = mock.MagicMock(return_value=[])
238 result = self.vast_resource.one("test")
239 self.assertIsNone(result)
241 def test_one_when_multiple_entries_found(self):
242 self.vast_resource.list = mock.MagicMock(
243 return_value=[mock.MagicMock(), mock.MagicMock()]
244 )
245 with self.assertRaises(manila_exception.VastDriverException):
246 self.vast_resource.one("test")
248 def test_one_when_single_entry_found(self):
249 mock_entry = mock.MagicMock()
250 self.vast_resource.list = mock.MagicMock(return_value=[mock_entry])
251 result = self.vast_resource.one("test")
252 self.assertEqual(result, mock_entry)
254 def test_ensure_when_entry_not_found(self):
255 self.vast_resource.one = mock.MagicMock(return_value=None)
256 mock_entry = mock.MagicMock()
257 self.vast_resource.create = mock.MagicMock(return_value=mock_entry)
258 result = self.vast_resource.ensure("test", size=10)
259 self.assertEqual(result, mock_entry)
261 def test_ensure_when_entry_found(self):
262 mock_entry = mock.MagicMock()
263 self.vast_resource.one = mock.MagicMock(return_value=mock_entry)
264 result = self.vast_resource.ensure("test", size=10)
265 self.assertEqual(result, mock_entry)
268class ViewTest(unittest.TestCase):
269 @mock.patch(
270 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
271 mock.MagicMock()
272 )
273 def test_view_create(self):
274 with mock.patch(
275 "manila.share.drivers.vastdata.rest.Session.post"
276 ) as mock_session:
277 rest_api = vast_rest.RestApi(
278 "host",
279 "username",
280 "password",
281 "",
282 True,
283 "1.0"
284 )
285 rest_api.views.create("test-view", "/test", 1)
287 self.assertEqual(("views",), mock_session.call_args.args)
288 self.assertDictEqual(
289 {
290 "data": {
291 "name": "test-view",
292 "path": "/test",
293 "policy_id": 1,
294 "create_dir": True,
295 "protocols": ["NFS"],
296 }
297 },
298 mock_session.call_args.kwargs,
299 )
302@mock.patch(
303 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
304 mock.MagicMock()
305)
306@mock.patch(
307 "manila.share.drivers.vastdata.rest.Session.get",
308 mock.MagicMock(return_value=fake_metrics),
309)
310class TestCapacityMetrics(unittest.TestCase):
312 def test_capacity_metrics(self):
313 metrics_list = [
314 "Capacity,drr",
315 "Capacity,logical_space",
316 "Capacity,logical_space_in_use",
317 "Capacity,physical_space",
318 "Capacity,physical_space_in_use",
319 ]
320 expected = {
321 "": 1,
322 "drr": 1.2,
323 "physical_space_in_use": 30635248783.0,
324 "physical_space": 711246584217.0,
325 "logical_space": 505846398976.0,
326 "logical_space_in_use": 22420306196.0,
327 }
328 rest_api = vast_rest.RestApi(
329 "host",
330 "username",
331 "password",
332 "",
333 True,
334 "1.0"
335 )
336 metrics = rest_api.capacity_metrics.get(metrics_list)
337 self.assertDictEqual(expected, metrics)
340@mock.patch(
341 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
342 mock.MagicMock()
343)
344@ddt.ddt
345class TestFolders(unittest.TestCase):
347 @mock.patch(
348 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
349 mock.MagicMock()
350 )
351 def setUp(self):
352 self.rest_api = vast_rest.RestApi(
353 "host",
354 "username",
355 "password",
356 "",
357 True,
358 "1.0",
359 )
361 @ddt.data(
362 "4.3.9",
363 "4.0.11.12",
364 "3.4.6.123.1",
365 "4.5.6-1",
366 "4.6.0",
367 "4.6.0-1",
368 "4.6.0-1.1",
369 "4.6.9",
370 )
371 def test_requisite_decorator(self, cluster_version):
372 """Test `requisite` decorator produces exception
374 when cluster version doesn't met requirements
375 """
376 with mock.patch(
377 "manila.share.drivers.vastdata.rest.RestApi.get_sw_version",
378 new=lambda s: cluster_version,
379 ):
380 self.assertRaises(
381 manila_exception.VastDriverException,
382 lambda: self.rest_api.folders.delete("/abc")
383 )
385 def test_trash_api_disabled(self):
386 def raise_http_err(*args, **kwargs):
387 resp = requests.Response()
388 resp.status_code = 400
389 resp.raw = io.BytesIO(b"trash folder disabled")
390 raise manila_exception.VastApiException(message=resp.text)
392 with (
393 mock.patch(
394 "manila.share.drivers.vastdata.rest.Session.delete",
395 side_effect=raise_http_err,
396 ),
397 mock.patch(
398 "manila.share.drivers.vastdata.rest.RestApi.get_sw_version",
399 new=lambda s: "5.0.0",
400 ),
401 ):
402 with self.assertRaises(
403 manila_exception.VastDriverException
404 ) as exc:
405 self.rest_api.folders.delete("/abc")
406 self.assertIn("Trash Folder Access is disabled", str(exc.exception))
408 def test_trash_api_unpredictable_error(self):
409 def raise_http_err(*args, **kwargs):
410 raise RuntimeError()
412 with (
413 mock.patch(
414 "manila.share.drivers.vastdata.rest.Session.delete",
415 side_effect=raise_http_err,
416 ),
417 mock.patch(
418 "manila.share.drivers.vastdata.rest.RestApi.get_sw_version",
419 new=lambda s: "5.0.0",
420 ),
421 ):
422 with self.assertRaises(RuntimeError):
423 self.rest_api.folders.delete("/abc")
425 def test_double_deletion(self):
426 def raise_http_err(*args, **kwargs):
427 resp = requests.Response()
428 resp.status_code = 400
429 resp.raw = io.BytesIO(b"no such directory")
430 raise manila_exception.VastApiException(message=resp.text)
432 with (
433 mock.patch(
434 "manila.share.drivers.vastdata.rest.Session.delete",
435 side_effect=raise_http_err,
436 ),
437 mock.patch(
438 "manila.share.drivers.vastdata.rest.RestApi.get_sw_version",
439 new=lambda s: "5.0.0",
440 ),
441 ):
442 with self.assertLogs(level="DEBUG") as cm:
443 self.rest_api.folders.delete("/abc")
444 self.assertIn(
445 "remote directory might have been removed earlier",
446 str(cm.output)
447 )
450class VipPoolTest(unittest.TestCase):
451 @mock.patch(
452 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token",
453 mock.MagicMock()
454 )
455 def setUp(self):
456 self.rest_api = vast_rest.RestApi(
457 "host",
458 "username",
459 "password",
460 "",
461 True,
462 "1.0"
463 )
465 def test_no_vipool(self):
466 with mock.patch(
467 "manila.share.drivers.vastdata.rest.Session.get",
468 return_value=[]
469 ):
470 with self.assertRaises(
471 manila_exception.VastDriverException
472 ) as exc:
473 self.rest_api.vip_pools.vips("test-vip")
474 self.assertIn("No vip pool found", str(exc.exception))
476 def test_no_vips(self):
477 vippool = driver_util.Bunch(ip_ranges=[])
478 with mock.patch(
479 "manila.share.drivers.vastdata.rest.Session.get",
480 return_value=[vippool]
481 ):
482 with self.assertRaises(
483 manila_exception.VastDriverException
484 ) as exc:
485 self.rest_api.vip_pools.vips("test-vip")
486 self.assertIn(
487 "Pool test-vip has no available vips",
488 str(exc.exception)
489 )
491 def test_vips_ok(self):
492 vippool = driver_util.Bunch(
493 ip_ranges=[["15.0.0.1", "15.0.0.4"], ["10.0.0.27", "10.0.0.30"]]
494 )
495 expected = [
496 "15.0.0.1",
497 "15.0.0.2",
498 "15.0.0.3",
499 "15.0.0.4",
500 "10.0.0.27",
501 "10.0.0.28",
502 "10.0.0.29",
503 "10.0.0.30",
504 ]
505 with mock.patch(
506 "manila.share.drivers.vastdata.rest.Session.get",
507 return_value=[vippool]
508 ):
509 vips = self.rest_api.vip_pools.vips("test-vip")
510 self.assertListEqual(vips, expected)
513class TestRestApi(unittest.TestCase):
515 @mock.patch("manila.share.drivers.vastdata.rest.Session")
516 def test_get_sw_version(self, mock_session):
517 mock_session.return_value.versions.return_value = [
518 mock.MagicMock(sys_version="1.0")
519 ]
520 rest_api = vast_rest.RestApi(
521 "host",
522 "username",
523 "password",
524 "",
525 True,
526 "1.0",
527 )
528 version = rest_api.get_sw_version()
529 self.assertEqual(version, "1.0")