Coverage for manila/tests/scheduler/test_manager.py: 100%

212 statements  

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

1# Copyright 2010 United States Government as represented by the 

2# Administrator of the National Aeronautics and Space Administration. 

3# All Rights Reserved. 

4# 

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

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

7# a copy of the License at 

8# 

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

10# 

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

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

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

14# License for the specific language governing permissions and limitations 

15# under the License. 

16""" 

17Tests For Scheduler Manager 

18""" 

19 

20from importlib import reload 

21from unittest import mock 

22 

23import ddt 

24from oslo_config import cfg 

25 

26from manila.common import constants 

27from manila import context 

28from manila import db 

29from manila import exception 

30from manila.message import message_field 

31from manila import quota 

32from manila.scheduler.drivers import base 

33from manila.scheduler.drivers import filter 

34from manila.scheduler import manager 

35from manila.share import rpcapi as share_rpcapi 

36from manila.share import share_types 

37from manila import test 

38from manila.tests import db_utils 

39from manila.tests import fake_share as fakes 

40from manila import utils 

41 

42CONF = cfg.CONF 

43 

44 

45@ddt.ddt 

46class SchedulerManagerTestCase(test.TestCase): 

47 """Test case for scheduler manager.""" 

48 

49 driver_cls = base.Scheduler 

50 driver_cls_name = 'manila.scheduler.drivers.base.Scheduler' 

51 

52 def setUp(self): 

53 super(SchedulerManagerTestCase, self).setUp() 

54 self.periodic_tasks = [] 

55 

56 def _periodic_task(*args, **kwargs): 

57 def decorator(f): 

58 self.periodic_tasks.append(f) 

59 return f 

60 return mock.Mock(side_effect=decorator) 

61 

62 self.mock_periodic_task = self.mock_object( 

63 manager.periodic_task, 'periodic_task', 

64 mock.Mock(side_effect=_periodic_task)) 

65 reload(manager) 

66 

67 self.flags(scheduler_driver=self.driver_cls_name) 

68 self.manager = manager.SchedulerManager() 

69 self.context = context.RequestContext('fake_user', 'fake_project') 

70 self.topic = 'fake_topic' 

71 self.fake_args = (1, 2, 3) 

72 self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'} 

73 

74 def raise_no_valid_host(self, *args, **kwargs): 

75 raise exception.NoValidHost(reason="") 

76 

77 def test_1_correct_init(self): 

78 # Correct scheduler driver 

79 manager = self.manager 

80 self.assertIsInstance(manager.driver, self.driver_cls) 

81 

82 @ddt.data('manila.scheduler.filter_scheduler.FilterScheduler', 

83 'manila.scheduler.drivers.filter.FilterScheduler') 

84 def test_scheduler_driver_mapper(self, driver_class): 

85 

86 test_manager = manager.SchedulerManager(scheduler_driver=driver_class) 

87 

88 self.assertIsInstance(test_manager.driver, filter.FilterScheduler) 

89 

90 def test_init_host_with_rpc(self): 

91 

92 self.mock_object(context, 

93 'get_admin_context', 

94 mock.Mock(return_value='fake_admin_context')) 

95 self.mock_object(self.manager, 'request_service_capabilities') 

96 

97 self.manager.init_host_with_rpc() 

98 

99 self.manager.request_service_capabilities.assert_called_once_with( 

100 'fake_admin_context') 

101 

102 def test_get_host_list(self): 

103 

104 self.mock_object(self.manager.driver, 'get_host_list') 

105 

106 self.manager.get_host_list(context) 

107 

108 self.manager.driver.get_host_list.assert_called_once_with() 

109 

110 def test_get_service_capabilities(self): 

111 

112 self.mock_object(self.manager.driver, 'get_service_capabilities') 

113 

114 self.manager.get_service_capabilities(context) 

115 

116 self.manager.driver.get_service_capabilities.assert_called_once_with() 

117 

118 def test_update_service_capabilities(self): 

119 service_name = 'fake_service' 

120 host = 'fake_host' 

121 with mock.patch.object(self.manager.driver, 

122 'update_service_capabilities', mock.Mock()): 

123 self.manager.update_service_capabilities( 

124 self.context, service_name=service_name, host=host) 

125 (self.manager.driver.update_service_capabilities. 

126 assert_called_once_with(service_name, host, {}, None)) 

127 with mock.patch.object(self.manager.driver, 

128 'update_service_capabilities', mock.Mock()): 

129 capabilities = {'fake_capability': 'fake_value'} 

130 self.manager.update_service_capabilities( 

131 self.context, service_name=service_name, host=host, 

132 capabilities=capabilities) 

133 (self.manager.driver.update_service_capabilities. 

134 assert_called_once_with(service_name, host, 

135 capabilities, None)) 

136 

137 @mock.patch.object(db, 'share_update', mock.Mock()) 

138 @mock.patch('manila.message.api.API.create') 

139 def test_create_share_exception_puts_share_in_error_state( 

140 self, _mock_message_create): 

141 """Test NoValidHost exception for create_share. 

142 

143 Puts the share in 'error' state and eats the exception. 

144 """ 

145 fake_share_id = 1 

146 

147 request_spec = {'share_id': fake_share_id} 

148 ex = exception.NoValidHost(reason='') 

149 with mock.patch.object( 

150 self.manager.driver, 'schedule_create_share', 

151 mock.Mock(side_effect=ex)): 

152 self.mock_object(manager.LOG, 'error') 

153 

154 self.manager.create_share_instance( 

155 self.context, request_spec=request_spec, filter_properties={}) 

156 

157 db.share_update.assert_called_once_with( 

158 self.context, fake_share_id, {'status': 'error'}) 

159 (self.manager.driver.schedule_create_share. 

160 assert_called_once_with(self.context, request_spec, {})) 

161 manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) 

162 

163 _mock_message_create.assert_called_once_with( 

164 self.context, 

165 message_field.Action.ALLOCATE_HOST, 

166 self.context.project_id, resource_type='SHARE', 

167 exception=ex, resource_id=fake_share_id) 

168 

169 @mock.patch.object(db, 'share_update', mock.Mock()) 

170 def test_create_share_other_exception_puts_share_in_error_state(self): 

171 """Test any exception except NoValidHost for create_share. 

172 

173 Puts the share in 'error' state and re-raises the exception. 

174 """ 

175 fake_share_id = 1 

176 

177 request_spec = {'share_id': fake_share_id} 

178 with mock.patch.object(self.manager.driver, 

179 'schedule_create_share', 

180 mock.Mock(side_effect=exception.QuotaError)): 

181 self.mock_object(manager.LOG, 'error') 

182 

183 self.assertRaises(exception.QuotaError, 

184 self.manager.create_share_instance, 

185 self.context, 

186 request_spec=request_spec, 

187 filter_properties={}) 

188 

189 db.share_update.assert_called_once_with( 

190 self.context, fake_share_id, {'status': 'error'}) 

191 (self.manager.driver.schedule_create_share. 

192 assert_called_once_with(self.context, request_spec, {})) 

193 manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) 

194 

195 @mock.patch.object(quota.QUOTAS, 'expire') 

196 def test__expire_reservations(self, mock_expire): 

197 self.manager._expire_reservations(self.context) 

198 

199 mock_expire.assert_called_once_with(self.context) 

200 

201 @mock.patch('manila.message.api.API.cleanup_expired_messages') 

202 def test__clean_expired_messages(self, mock_expire): 

203 self.manager._clean_expired_messages(self.context) 

204 

205 mock_expire.assert_called_once_with(self.context) 

206 

207 @mock.patch.object(db, 'service_get_all', mock.Mock()) 

208 @mock.patch.object(db, 'service_update', mock.Mock()) 

209 @mock.patch.object(utils, "service_is_up", mock.Mock()) 

210 def test__mark_services_as_down(self): 

211 db.service_get_all.return_value = [ 

212 {"id": 1, "state": "up"}, 

213 {"id": 2, "state": "up"}, 

214 {"id": 3, "state": "stopped"}, 

215 {"id": 4, "state": "stopped"}, 

216 {"id": 5, "state": "down"}, 

217 {"id": 6, "state": "down"}, 

218 ] 

219 utils.service_is_up.side_effect = [False, True] * 3 

220 

221 self.manager._mark_services_as_down(self.context) 

222 

223 db.service_get_all.assert_called_once_with(self.context) 

224 self.assertEqual(6, utils.service_is_up.call_count) 

225 db.service_update.assert_called_once_with( 

226 self.context, 1, {"state": "down"}) 

227 

228 def test_periodic_tasks(self): 

229 self.assertEqual(3, self.mock_periodic_task.call_count) 

230 

231 self.assertEqual(3, len(self.periodic_tasks)) 

232 self.assertEqual( 

233 self.periodic_tasks[0].__name__, 

234 self.manager._expire_reservations.__name__) 

235 self.assertEqual( 

236 self.periodic_tasks[1].__name__, 

237 self.manager._clean_expired_messages.__name__) 

238 self.assertEqual( 

239 self.periodic_tasks[2].__name__, 

240 self.manager._mark_services_as_down.__name__) 

241 

242 def test_get_pools(self): 

243 """Ensure get_pools exists and calls base_scheduler.get_pools.""" 

244 mock_get_pools = self.mock_object(self.manager.driver, 

245 'get_pools', 

246 mock.Mock(return_value='fake_pools')) 

247 

248 result = self.manager.get_pools(self.context, filters='fake_filters') 

249 

250 mock_get_pools.assert_called_once_with(self.context, 'fake_filters', 

251 False) 

252 self.assertEqual('fake_pools', result) 

253 

254 @mock.patch.object(db, 'share_group_update', mock.Mock()) 

255 def test_create_group_no_valid_host_puts_group_in_error_state(self): 

256 """Test that NoValidHost is raised for create_share_group. 

257 

258 Puts the share in 'error' state and eats the exception. 

259 """ 

260 

261 fake_group_id = 1 

262 group_id = fake_group_id 

263 request_spec = {"share_group_id": group_id} 

264 with mock.patch.object( 

265 self.manager.driver, 'schedule_create_share_group', 

266 mock.Mock(side_effect=self.raise_no_valid_host)): 

267 self.manager.create_share_group(self.context, 

268 fake_group_id, 

269 request_spec=request_spec, 

270 filter_properties={}) 

271 db.share_group_update.assert_called_once_with( 

272 self.context, fake_group_id, {'status': 'error'}) 

273 (self.manager.driver.schedule_create_share_group. 

274 assert_called_once_with(self.context, group_id, request_spec, 

275 {})) 

276 

277 @mock.patch.object(db, 'share_group_update', mock.Mock()) 

278 def test_create_group_exception_puts_group_in_error_state(self): 

279 """Test that exceptions for create_share_group. 

280 

281 Puts the share in 'error' state and raises the exception. 

282 """ 

283 

284 fake_group_id = 1 

285 group_id = fake_group_id 

286 request_spec = {"share_group_id": group_id} 

287 with mock.patch.object(self.manager.driver, 

288 'schedule_create_share_group', 

289 mock.Mock(side_effect=exception.NotFound)): 

290 self.assertRaises(exception.NotFound, 

291 self.manager.create_share_group, 

292 self.context, fake_group_id, 

293 request_spec=request_spec, 

294 filter_properties={}) 

295 

296 def test_migrate_share_to_host(self): 

297 

298 class fake_host(object): 

299 host = 'fake@backend#pool' 

300 

301 share = db_utils.create_share() 

302 host = fake_host() 

303 

304 self.mock_object(db, 'share_get', mock.Mock(return_value=share)) 

305 self.mock_object(share_rpcapi.ShareAPI, 'migration_start', 

306 mock.Mock(side_effect=TypeError)) 

307 self.mock_object(base.Scheduler, 

308 'host_passes_filters', 

309 mock.Mock(return_value=host)) 

310 self.mock_object( 

311 share_types, 

312 'revert_allocated_share_type_quotas_during_migration') 

313 

314 self.assertRaises( 

315 TypeError, self.manager.migrate_share_to_host, 

316 self.context, share['id'], 'fake@backend#pool', False, True, True, 

317 False, True, 'fake_net_id', 'fake_type_id', {}, None) 

318 

319 db.share_get.assert_called_once_with(self.context, share['id']) 

320 base.Scheduler.host_passes_filters.assert_called_once_with( 

321 self.context, 'fake@backend#pool', {}, None) 

322 share_rpcapi.ShareAPI.migration_start.assert_called_once_with( 

323 self.context, share, host.host, False, True, True, False, True, 

324 'fake_net_id', 'fake_type_id') 

325 (share_types.revert_allocated_share_type_quotas_during_migration. 

326 assert_called_once_with(self.context, share, 'fake_type_id')) 

327 

328 @ddt.data(exception.NoValidHost(reason='fake'), TypeError) 

329 def test_migrate_share_to_host_exception(self, exc): 

330 

331 share = db_utils.create_share(status=constants.STATUS_MIGRATING) 

332 host = 'fake@backend#pool' 

333 request_spec = {'share_id': share['id']} 

334 

335 self.mock_object(db, 'share_get', mock.Mock(return_value=share)) 

336 self.mock_object( 

337 base.Scheduler, 'host_passes_filters', 

338 mock.Mock(side_effect=exc)) 

339 self.mock_object(db, 'share_update') 

340 self.mock_object(db, 'share_instance_update') 

341 self.mock_object( 

342 share_types, 

343 'revert_allocated_share_type_quotas_during_migration') 

344 

345 capture = (exception.NoValidHost if 

346 isinstance(exc, exception.NoValidHost) else TypeError) 

347 

348 self.assertRaises( 

349 capture, self.manager.migrate_share_to_host, 

350 self.context, share['id'], host, False, True, True, False, True, 

351 'fake_net_id', 'fake_type_id', request_spec, None) 

352 

353 base.Scheduler.host_passes_filters.assert_called_once_with( 

354 self.context, host, request_spec, None) 

355 db.share_get.assert_called_once_with(self.context, share['id']) 

356 db.share_update.assert_called_once_with( 

357 self.context, share['id'], 

358 {'task_state': constants.TASK_STATE_MIGRATION_ERROR}) 

359 db.share_instance_update.assert_called_once_with( 

360 self.context, share.instance['id'], 

361 {'status': constants.STATUS_AVAILABLE}) 

362 (share_types.revert_allocated_share_type_quotas_during_migration. 

363 assert_called_once_with(self.context, share, 'fake_type_id')) 

364 

365 def test_manage_share(self): 

366 

367 share = db_utils.create_share() 

368 

369 self.mock_object(db, 'share_get', mock.Mock(return_value=share)) 

370 self.mock_object(share_rpcapi.ShareAPI, 'manage_share') 

371 self.mock_object(base.Scheduler, 'host_passes_filters') 

372 

373 self.manager.manage_share(self.context, share['id'], 'driver_options', 

374 {}, None) 

375 

376 def test_manage_share_exception(self): 

377 

378 share = db_utils.create_share() 

379 

380 db_update = self.mock_object(db, 'share_update', mock.Mock()) 

381 self.mock_object( 

382 base.Scheduler, 'host_passes_filters', 

383 mock.Mock(side_effect=exception.NoValidHost('fake'))) 

384 

385 share_id = share['id'] 

386 

387 self.assertRaises( 

388 exception.NoValidHost, self.manager.manage_share, 

389 self.context, share['id'], 'driver_options', 

390 {'share_id': share_id}, None) 

391 db_update.assert_called_once_with( 

392 self.context, share_id, 

393 {'status': constants.STATUS_MANAGE_ERROR, 'size': 0}) 

394 

395 def test_create_share_replica_exception_path(self): 

396 """Test 'raisable' exceptions for create_share_replica.""" 

397 db_update = self.mock_object(db, 'share_replica_update') 

398 self.mock_object(db, 'share_snapshot_instance_get_all_with_filters', 

399 mock.Mock(return_value=[{'id': '123'}])) 

400 snap_update = self.mock_object(db, 'share_snapshot_instance_update') 

401 request_spec = fakes.fake_replica_request_spec() 

402 replica_id = request_spec.get('share_instance_properties').get('id') 

403 expected_updates = { 

404 'status': constants.STATUS_ERROR, 

405 'replica_state': constants.STATUS_ERROR, 

406 } 

407 with mock.patch.object(self.manager.driver, 'schedule_create_replica', 

408 mock.Mock(side_effect=exception.NotFound)): 

409 

410 self.assertRaises(exception.NotFound, 

411 self.manager.create_share_replica, 

412 self.context, 

413 request_spec=request_spec, 

414 filter_properties={}) 

415 db_update.assert_called_once_with( 

416 self.context, replica_id, expected_updates) 

417 snap_update.assert_called_once_with( 

418 self.context, '123', {'status': constants.STATUS_ERROR}) 

419 

420 def test_create_share_replica_no_valid_host(self): 

421 """Test the NoValidHost exception for create_share_replica.""" 

422 db_update = self.mock_object(db, 'share_replica_update') 

423 request_spec = fakes.fake_replica_request_spec() 

424 replica_id = request_spec.get('share_instance_properties').get('id') 

425 expected_updates = { 

426 'status': constants.STATUS_ERROR, 

427 'replica_state': constants.STATUS_ERROR, 

428 } 

429 with mock.patch.object( 

430 self.manager.driver, 'schedule_create_replica', 

431 mock.Mock(side_effect=self.raise_no_valid_host)): 

432 

433 retval = self.manager.create_share_replica( 

434 self.context, request_spec=request_spec, filter_properties={}) 

435 

436 self.assertIsNone(retval) 

437 db_update.assert_called_once_with( 

438 self.context, replica_id, expected_updates) 

439 

440 def test_create_share_replica(self): 

441 """Test happy path for create_share_replica.""" 

442 db_update = self.mock_object(db, 'share_replica_update') 

443 mock_scheduler_driver_call = self.mock_object( 

444 self.manager.driver, 'schedule_create_replica') 

445 request_spec = fakes.fake_replica_request_spec() 

446 

447 retval = self.manager.create_share_replica( 

448 self.context, request_spec=request_spec, filter_properties={}) 

449 

450 mock_scheduler_driver_call.assert_called_once_with( 

451 self.context, request_spec, {}) 

452 self.assertFalse(db_update.called) 

453 self.assertIsNone(retval)