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

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 

18 

19import ddt 

20import requests 

21 

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 

25 

26 

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) 

98 

99 

100class TestSession(unittest.TestCase): 

101 

102 def setUp(self): 

103 self.session = vast_rest.Session( 

104 "host", 

105 "username", 

106 "password", 

107 "", 

108 False, 

109 "1.0", 

110 ) 

111 

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 ) 

120 

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

126 

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 ) 

138 

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 ) 

148 

149 def test_request_failure_500(self): 

150 

151 resp = requests.Response() 

152 resp.status_code = 500 

153 resp.raw = io.BytesIO(b"Server error") 

154 

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

161 

162 def test_request_no_return_content(self): 

163 resp = requests.Response() 

164 resp.status_code = 200 

165 resp.raw = io.BytesIO(b"") 

166 

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) 

172 

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

181 

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

185 

186 def test_getattr_with_underscore(self): 

187 with self.assertRaises(AttributeError): 

188 self.session.__getattr__("_private") 

189 

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) 

196 

197 

198class TestVastResource(unittest.TestCase): 

199 def setUp(self): 

200 self.mock_rest = mock.MagicMock() 

201 self.vast_resource = vast_rest.VastResource(self.mock_rest) 

202 

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 ) 

208 

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 ) 

214 

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 ) 

221 

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

226 

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 ) 

235 

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) 

240 

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

247 

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) 

253 

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) 

260 

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) 

266 

267 

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) 

286 

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 ) 

300 

301 

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

311 

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) 

338 

339 

340@mock.patch( 

341 "manila.share.drivers.vastdata.rest.Session.refresh_auth_token", 

342 mock.MagicMock() 

343) 

344@ddt.ddt 

345class TestFolders(unittest.TestCase): 

346 

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 ) 

360 

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 

373 

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 ) 

384 

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) 

391 

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

407 

408 def test_trash_api_unpredictable_error(self): 

409 def raise_http_err(*args, **kwargs): 

410 raise RuntimeError() 

411 

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

424 

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) 

431 

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 ) 

448 

449 

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 ) 

464 

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

475 

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 ) 

490 

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) 

511 

512 

513class TestRestApi(unittest.TestCase): 

514 

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