Coverage for manila/tests/volume/test_cinder.py: 100%

206 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright 2014 Mirantis, Inc. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); you may 

4# not use this file except in compliance with the License. You may obtain 

5# a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15from unittest import mock 

16 

17from cinderclient import exceptions as cinder_exception 

18import ddt 

19 

20from manila.common import constants as const 

21from manila import context 

22from manila import exception 

23from manila import test 

24from manila.tests import fake_volume 

25from manila.tests import utils as test_utils 

26from manila.volume import cinder 

27 

28 

29class FakeCinderClient(object): 

30 class Volumes(object): 

31 def get(self, volume_id): 

32 return {'id': volume_id} 

33 

34 def list(self, detailed, search_opts={}): 

35 return [{'id': 'id1'}, {'id': 'id2'}] 

36 

37 def create(self, *args, **kwargs): 

38 return {'id': 'created_id'} 

39 

40 def __getattr__(self, item): 

41 return None 

42 

43 def __init__(self): 

44 self.volumes = self.Volumes() 

45 self.volume_snapshots = self.volumes 

46 

47 

48def get_fake_auth_obj(): 

49 return type('FakeAuthObj', (object, ), {'get_client': mock.Mock()}) 

50 

51 

52class CinderclientTestCase(test.TestCase): 

53 

54 @mock.patch('manila.volume.cinder.AUTH_OBJ', None) 

55 def test_no_auth_obj(self): 

56 mock_client_loader = self.mock_object( 

57 cinder.client_auth, 'AuthClientLoader') 

58 fake_context = 'fake_context' 

59 data = { 

60 'cinder': { 

61 'http_retries': 3, 

62 'endpoint_type': 'internalURL', 

63 'region_name': 'foo_region_name', 

64 } 

65 } 

66 

67 with test_utils.create_temp_config_with_opts(data): 

68 cinder.cinderclient(fake_context) 

69 

70 mock_client_loader.assert_called_once_with( 

71 client_class=cinder.cinder_client.Client, 

72 cfg_group=cinder.CINDER_GROUP 

73 ) 

74 mock_client_loader.return_value.get_client.assert_called_once_with( 

75 fake_context, 

76 retries=data['cinder']['http_retries'], 

77 endpoint_type=data['cinder']['endpoint_type'], 

78 region_name=data['cinder']['region_name'], 

79 ) 

80 

81 @mock.patch('manila.volume.cinder.AUTH_OBJ', get_fake_auth_obj()) 

82 def test_with_auth_obj(self): 

83 fake_context = 'fake_context' 

84 data = { 

85 'cinder': { 

86 'http_retries': 3, 

87 'endpoint_type': 'internalURL', 

88 'region_name': 'foo_region_name', 

89 } 

90 } 

91 

92 with test_utils.create_temp_config_with_opts(data): 

93 cinder.cinderclient(fake_context) 

94 

95 cinder.AUTH_OBJ.get_client.assert_called_once_with( 

96 fake_context, 

97 retries=data['cinder']['http_retries'], 

98 endpoint_type=data['cinder']['endpoint_type'], 

99 region_name=data['cinder']['region_name'], 

100 ) 

101 

102 

103@ddt.ddt 

104class CinderApiTestCase(test.TestCase): 

105 def setUp(self): 

106 super(CinderApiTestCase, self).setUp() 

107 

108 self.api = cinder.API() 

109 self.cinderclient = FakeCinderClient() 

110 self.ctx = context.get_admin_context() 

111 self.mock_object(cinder, 'cinderclient', 

112 mock.Mock(return_value=self.cinderclient)) 

113 self.mock_object(cinder, '_untranslate_volume_summary_view', 

114 lambda ctx, vol: vol) 

115 self.mock_object(cinder, '_untranslate_snapshot_summary_view', 

116 lambda ctx, snap: snap) 

117 

118 def test_get(self): 

119 volume_id = 'volume_id1' 

120 result = self.api.get(self.ctx, volume_id) 

121 self.assertEqual(volume_id, result['id']) 

122 

123 @ddt.data( 

124 {'cinder_e': cinder_exception.NotFound(404), 

125 'manila_e': exception.VolumeNotFound}, 

126 {'cinder_e': cinder_exception.BadRequest(400), 

127 'manila_e': exception.InvalidInput}, 

128 ) 

129 @ddt.unpack 

130 def test_get_failed(self, cinder_e, manila_e): 

131 cinder.cinderclient.side_effect = cinder_e 

132 volume_id = 'volume_id' 

133 self.assertRaises(manila_e, self.api.get, self.ctx, volume_id) 

134 

135 def test_create(self): 

136 result = self.api.create(self.ctx, 1, '', '') 

137 self.assertEqual('created_id', result['id']) 

138 

139 def test_create_failed(self): 

140 cinder.cinderclient.side_effect = cinder_exception.BadRequest(400) 

141 self.assertRaises(exception.InvalidInput, 

142 self.api.create, self.ctx, 1, '', '') 

143 

144 def test_create_not_found_error(self): 

145 cinder.cinderclient.side_effect = cinder_exception.NotFound(404) 

146 self.assertRaises(exception.NotFound, 

147 self.api.create, self.ctx, 1, '', '') 

148 

149 def test_create_failed_exception(self): 

150 cinder.cinderclient.side_effect = Exception("error msg") 

151 self.assertRaises(exception.ManilaException, 

152 self.api.create, self.ctx, 1, '', '') 

153 

154 def test_get_all(self): 

155 cinder._untranslate_volume_summary_view.return_value = ['id1', 'id2'] 

156 self.assertEqual([{'id': 'id1'}, {'id': 'id2'}], 

157 self.api.get_all(self.ctx)) 

158 

159 def test_check_attach_volume_status_error(self): 

160 volume = {'status': 'error'} 

161 self.assertRaises(exception.InvalidVolume, 

162 self.api.check_attach, self.ctx, volume) 

163 

164 def test_check_attach_volume_already_attached(self): 

165 volume = {'status': 'available'} 

166 volume['attach_status'] = "attached" 

167 self.assertRaises(exception.InvalidVolume, 

168 self.api.check_attach, self.ctx, volume) 

169 

170 def test_check_attach_availability_zone_differs(self): 

171 volume = {'status': 'available'} 

172 volume['attach_status'] = "detached" 

173 instance = {'availability_zone': 'zone1'} 

174 volume['availability_zone'] = 'zone2' 

175 cinder.CONF.set_override('cross_az_attach', False, 'cinder') 

176 self.assertRaises(exception.InvalidVolume, 

177 self.api.check_attach, self.ctx, volume, instance) 

178 volume['availability_zone'] = 'zone1' 

179 self.assertIsNone(self.api.check_attach(self.ctx, volume, instance)) 

180 cinder.CONF.reset() 

181 

182 def test_check_attach(self): 

183 volume = {'status': 'available'} 

184 volume['attach_status'] = "detached" 

185 volume['availability_zone'] = 'zone1' 

186 instance = {'availability_zone': 'zone1'} 

187 cinder.CONF.set_override('cross_az_attach', False, 'cinder') 

188 self.assertIsNone(self.api.check_attach(self.ctx, volume, instance)) 

189 cinder.CONF.reset() 

190 

191 def test_check_detach(self): 

192 volume = {'status': 'available'} 

193 self.assertRaises(exception.InvalidVolume, 

194 self.api.check_detach, self.ctx, volume) 

195 volume['status'] = 'non-available' 

196 self.assertIsNone(self.api.check_detach(self.ctx, volume)) 

197 

198 def test_update(self): 

199 fake_volume = {'fake': 'fake'} 

200 self.mock_object(self.cinderclient.volumes, 'get', 

201 mock.Mock(return_value=fake_volume)) 

202 self.mock_object(self.cinderclient.volumes, 'update') 

203 fake_volume_id = 'fake_volume' 

204 fake_data = {'test': 'test'} 

205 

206 self.api.update(self.ctx, fake_volume_id, fake_data) 

207 

208 self.cinderclient.volumes.get.assert_called_once_with(fake_volume_id) 

209 self.cinderclient.volumes.update.assert_called_once_with(fake_volume, 

210 **fake_data) 

211 

212 def test_reserve_volume(self): 

213 self.mock_object(self.cinderclient.volumes, 'reserve') 

214 self.api.reserve_volume(self.ctx, 'id1') 

215 self.cinderclient.volumes.reserve.assert_called_once_with('id1') 

216 

217 def test_unreserve_volume(self): 

218 self.mock_object(self.cinderclient.volumes, 'unreserve') 

219 self.api.unreserve_volume(self.ctx, 'id1') 

220 self.cinderclient.volumes.unreserve.assert_called_once_with('id1') 

221 

222 def test_begin_detaching(self): 

223 self.mock_object(self.cinderclient.volumes, 'begin_detaching') 

224 self.api.begin_detaching(self.ctx, 'id1') 

225 self.cinderclient.volumes.begin_detaching.assert_called_once_with( 

226 'id1') 

227 

228 def test_roll_detaching(self): 

229 self.mock_object(self.cinderclient.volumes, 'roll_detaching') 

230 self.api.roll_detaching(self.ctx, 'id1') 

231 self.cinderclient.volumes.roll_detaching.assert_called_once_with('id1') 

232 

233 def test_attach(self): 

234 self.mock_object(self.cinderclient.volumes, 'attach') 

235 self.api.attach(self.ctx, 'id1', 'uuid', 'point') 

236 self.cinderclient.volumes.attach.assert_called_once_with('id1', 

237 'uuid', 

238 'point') 

239 

240 def test_detach(self): 

241 self.mock_object(self.cinderclient.volumes, 'detach') 

242 self.api.detach(self.ctx, 'id1') 

243 self.cinderclient.volumes.detach.assert_called_once_with('id1') 

244 

245 def test_initialize_connection(self): 

246 self.mock_object(self.cinderclient.volumes, 'initialize_connection') 

247 self.api.initialize_connection(self.ctx, 'id1', 'connector') 

248 (self.cinderclient.volumes.initialize_connection. 

249 assert_called_once_with('id1', 'connector')) 

250 

251 def test_terminate_connection(self): 

252 self.mock_object(self.cinderclient.volumes, 'terminate_connection') 

253 self.api.terminate_connection(self.ctx, 'id1', 'connector') 

254 (self.cinderclient.volumes.terminate_connection. 

255 assert_called_once_with('id1', 'connector')) 

256 

257 def test_delete(self): 

258 self.mock_object(self.cinderclient.volumes, 'delete') 

259 self.api.delete(self.ctx, 'id1') 

260 self.cinderclient.volumes.delete.assert_called_once_with('id1') 

261 

262 def test_get_snapshot(self): 

263 snapshot_id = 'snapshot_id1' 

264 result = self.api.get_snapshot(self.ctx, snapshot_id) 

265 self.assertEqual(snapshot_id, result['id']) 

266 

267 def test_get_snapshot_failed(self): 

268 cinder.cinderclient.side_effect = cinder_exception.NotFound(404) 

269 snapshot_id = 'snapshot_id' 

270 self.assertRaises(exception.VolumeSnapshotNotFound, 

271 self.api.get_snapshot, self.ctx, snapshot_id) 

272 

273 def test_get_all_snapshots(self): 

274 cinder._untranslate_snapshot_summary_view.return_value = ['id1', 'id2'] 

275 self.assertEqual([{'id': 'id1'}, {'id': 'id2'}], 

276 self.api.get_all_snapshots(self.ctx)) 

277 

278 def test_create_snapshot(self): 

279 result = self.api.create_snapshot(self.ctx, {'id': 'id1'}, '', '') 

280 self.assertEqual('created_id', result['id']) 

281 

282 def test_create_force(self): 

283 result = self.api.create_snapshot_force(self.ctx, 

284 {'id': 'id1'}, '', '') 

285 self.assertEqual('created_id', result['id']) 

286 

287 def test_delete_snapshot(self): 

288 self.mock_object(self.cinderclient.volume_snapshots, 'delete') 

289 self.api.delete_snapshot(self.ctx, 'id1') 

290 self.cinderclient.volume_snapshots.delete.assert_called_once_with( 

291 'id1') 

292 

293 def test_wait_for_available_volume(self): 

294 fake_volume = {'status': 'creating', 'id': 'fake'} 

295 fake_available_volume = {'status': 'available', 'id': 'fake'} 

296 self.mock_object(self.cinderclient.volumes, 'get', 

297 mock.Mock(return_value=fake_available_volume)) 

298 

299 actual_result = self.api.wait_for_available_volume( 

300 fake_volume, 5, "error", "timeout") 

301 

302 self.assertEqual(fake_available_volume, actual_result) 

303 self.cinderclient.volumes.get.assert_called_once_with( 

304 fake_volume['id']) 

305 

306 def test_wait_for_available_volume_error_extending(self): 

307 fake_volume = {'status': const.STATUS_EXTENDING_ERROR, 'id': 'fake'} 

308 self.mock_object(self.cinderclient.volumes, 'get', 

309 mock.Mock(return_value=fake_volume)) 

310 self.assertRaises(exception.ManilaException, 

311 self.api.wait_for_available_volume, 

312 fake_volume, 5, 'error', 'timeout') 

313 

314 @mock.patch('time.sleep') 

315 def test_wait_for_extending_volume(self, mock_sleep): 

316 initial_size = 1 

317 expected_size = 2 

318 mock_volume = fake_volume.FakeVolume(status='available', 

319 size=initial_size) 

320 mock_extending_vol = fake_volume.FakeVolume(status='extending', 

321 size=initial_size) 

322 mock_extended_vol = fake_volume.FakeVolume(status='available', 

323 size=expected_size) 

324 

325 self.mock_object(self.cinderclient.volumes, 'get', 

326 mock.Mock(side_effect=[mock_extending_vol, 

327 mock_extended_vol])) 

328 

329 result = self.api.wait_for_available_volume( 

330 mock_volume, 5, "error", "timeout", 

331 expected_size=expected_size) 

332 

333 expected_get_count = 2 

334 

335 self.assertEqual(mock_extended_vol, result) 

336 self.cinderclient.volumes.get.assert_has_calls( 

337 [mock.call(mock_volume['id'])] * expected_get_count 

338 ) 

339 # utils.retry sleeps before retries, so 1 sleep for 2 get calls 

340 self.assertEqual(1, mock_sleep.call_count) 

341 

342 def test_wait_for_available_volume_timeout(self): 

343 fake_volume = {'status': 'creating', 'id': 'fake'} 

344 self.mock_object(self.cinderclient.volumes, 'get', 

345 mock.Mock(return_value=fake_volume)) 

346 

347 self.assertRaises( 

348 exception.ManilaException, 

349 self.api.wait_for_available_volume, 

350 fake_volume, 1, "error", "timeout" 

351 ) 

352 

353 def test_wait_for_available_volume_error(self): 

354 fake_volume = {'status': 'creating', 'id': 'fake'} 

355 error_volume = {'status': 'error', 'id': 'fake'} 

356 self.mock_object(self.cinderclient.volumes, 'get', 

357 mock.Mock(return_value=error_volume)) 

358 

359 self.assertRaises( 

360 exception.ManilaException, 

361 self.api.wait_for_available_volume, 

362 fake_volume, 1, "error", "timeout" 

363 )