Coverage for nova/objects/quotas.py: 74%

353 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +0000

1# Copyright 2013 Rackspace Hosting. 

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 

15import collections 

16 

17from oslo_db import exception as db_exc 

18 

19from nova.db.api import api as api_db_api 

20from nova.db.api import models as api_models 

21from nova.db.main import api as main_db_api 

22from nova.db.main import models as main_models 

23from nova.db import utils as db_utils 

24from nova import exception 

25from nova.objects import base 

26from nova.objects import fields 

27from nova import quota 

28 

29 

30def ids_from_instance(context, instance): 

31 if (context.is_admin and 

32 context.project_id != instance['project_id']): 

33 project_id = instance['project_id'] 

34 else: 

35 project_id = context.project_id 

36 if context.user_id != instance['user_id']: 

37 user_id = instance['user_id'] 

38 else: 

39 user_id = context.user_id 

40 return project_id, user_id 

41 

42 

43# TODO(lyj): This method needs to be cleaned up once the 

44# ids_from_instance helper method is renamed or some common 

45# method is added for objects.quotas. 

46def ids_from_security_group(context, security_group): 

47 return ids_from_instance(context, security_group) 

48 

49 

50# TODO(PhilD): This method needs to be cleaned up once the 

51# ids_from_instance helper method is renamed or some common 

52# method is added for objects.quotas. 

53def ids_from_server_group(context, server_group): 

54 return ids_from_instance(context, server_group) 

55 

56 

57@base.NovaObjectRegistry.register 

58class Quotas(base.NovaObject): 

59 # Version 1.0: initial version 

60 # Version 1.1: Added create_limit() and update_limit() 

61 # Version 1.2: Added limit_check() and count() 

62 # Version 1.3: Added check_deltas(), limit_check_project_and_user(), 

63 # and count_as_dict() 

64 VERSION = '1.3' 

65 

66 fields = { 

67 # TODO(melwitt): Remove this field in version 2.0 of the object. 

68 'reservations': fields.ListOfStringsField(nullable=True, 

69 default=[]), 

70 'project_id': fields.StringField(nullable=True, 

71 default=None), 

72 'user_id': fields.StringField(nullable=True, 

73 default=None), 

74 } 

75 

76 def obj_load_attr(self, attr): 

77 self.obj_set_defaults(attr) 

78 # NOTE(danms): This is strange because resetting these would cause 

79 # them not to be saved to the database. I would imagine this is 

80 # from overzealous defaulting and that all three fields ultimately 

81 # get set all the time. However, quotas are weird, so replicate the 

82 # longstanding behavior of setting defaults and clearing their 

83 # dirty bit. 

84 self.obj_reset_changes(fields=[attr]) 

85 

86 @staticmethod 

87 @api_db_api.context_manager.reader 

88 def _get_from_db(context, project_id, resource, user_id=None): 

89 model = api_models.ProjectUserQuota if user_id else api_models.Quota 

90 query = context.session.query(model).\ 

91 filter_by(project_id=project_id).\ 

92 filter_by(resource=resource) 

93 if user_id: 

94 query = query.filter_by(user_id=user_id) 

95 result = query.first() 

96 if not result: 

97 if user_id: 

98 raise exception.ProjectUserQuotaNotFound(project_id=project_id, 

99 user_id=user_id) 

100 else: 

101 raise exception.ProjectQuotaNotFound(project_id=project_id) 

102 return result 

103 

104 @staticmethod 

105 @api_db_api.context_manager.reader 

106 def _get_all_from_db(context, project_id): 

107 return context.session.query(api_models.ProjectUserQuota).\ 

108 filter_by(project_id=project_id).\ 

109 all() 

110 

111 @staticmethod 

112 @api_db_api.context_manager.reader 

113 def _get_all_from_db_by_project(context, project_id): 

114 # by_project refers to the returned dict that has a 'project_id' key 

115 rows = context.session.query(api_models.Quota).\ 

116 filter_by(project_id=project_id).\ 

117 all() 

118 result = {'project_id': project_id} 

119 for row in rows: 

120 result[row.resource] = row.hard_limit 

121 return result 

122 

123 @staticmethod 

124 @api_db_api.context_manager.reader 

125 def _get_all_from_db_by_project_and_user(context, project_id, user_id): 

126 # by_project_and_user refers to the returned dict that has 

127 # 'project_id' and 'user_id' keys 

128 columns = (api_models.ProjectUserQuota.resource, 

129 api_models.ProjectUserQuota.hard_limit) 

130 user_quotas = context.session.query(*columns).\ 

131 filter_by(project_id=project_id).\ 

132 filter_by(user_id=user_id).\ 

133 all() 

134 result = {'project_id': project_id, 'user_id': user_id} 

135 for user_quota in user_quotas: 

136 result[user_quota.resource] = user_quota.hard_limit 

137 return result 

138 

139 @staticmethod 

140 @api_db_api.context_manager.writer 

141 def _destroy_all_in_db_by_project(context, project_id): 

142 per_project = context.session.query(api_models.Quota).\ 

143 filter_by(project_id=project_id).\ 

144 delete(synchronize_session=False) 

145 per_user = context.session.query(api_models.ProjectUserQuota).\ 

146 filter_by(project_id=project_id).\ 

147 delete(synchronize_session=False) 

148 if not per_project and not per_user: 148 ↛ exitline 148 didn't return from function '_destroy_all_in_db_by_project' because the condition on line 148 was always true

149 raise exception.ProjectQuotaNotFound(project_id=project_id) 

150 

151 @staticmethod 

152 @api_db_api.context_manager.writer 

153 def _destroy_all_in_db_by_project_and_user(context, project_id, user_id): 

154 result = context.session.query(api_models.ProjectUserQuota).\ 

155 filter_by(project_id=project_id).\ 

156 filter_by(user_id=user_id).\ 

157 delete(synchronize_session=False) 

158 if not result: 158 ↛ exitline 158 didn't return from function '_destroy_all_in_db_by_project_and_user' because the condition on line 158 was always true

159 raise exception.ProjectUserQuotaNotFound(project_id=project_id, 

160 user_id=user_id) 

161 

162 @staticmethod 

163 @api_db_api.context_manager.reader 

164 def _get_class_from_db(context, class_name, resource): 

165 result = context.session.query(api_models.QuotaClass).\ 

166 filter_by(class_name=class_name).\ 

167 filter_by(resource=resource).\ 

168 first() 

169 if not result: 169 ↛ 171line 169 didn't jump to line 171 because the condition on line 169 was always true

170 raise exception.QuotaClassNotFound(class_name=class_name) 

171 return result 

172 

173 @staticmethod 

174 @api_db_api.context_manager.reader 

175 def _get_all_class_from_db_by_name(context, class_name): 

176 # by_name refers to the returned dict that has a 'class_name' key 

177 rows = context.session.query(api_models.QuotaClass).\ 

178 filter_by(class_name=class_name).\ 

179 all() 

180 result = {'class_name': class_name} 

181 for row in rows: 

182 result[row.resource] = row.hard_limit 

183 return result 

184 

185 @staticmethod 

186 @api_db_api.context_manager.writer 

187 def _create_limit_in_db(context, project_id, resource, limit, 

188 user_id=None): 

189 # TODO(melwitt): We won't have per project resources after nova-network 

190 # is removed. 

191 # TODO(stephenfin): We need to do something here now...but what? 

192 per_user = ( 

193 user_id and 

194 resource not in main_db_api.quota_get_per_project_resources() 

195 ) 

196 quota_ref = (api_models.ProjectUserQuota() if per_user 

197 else api_models.Quota()) 

198 if per_user: 

199 quota_ref.user_id = user_id 

200 quota_ref.project_id = project_id 

201 quota_ref.resource = resource 

202 quota_ref.hard_limit = limit 

203 try: 

204 quota_ref.save(context.session) 

205 except db_exc.DBDuplicateEntry: 

206 raise exception.QuotaExists(project_id=project_id, 

207 resource=resource) 

208 return quota_ref 

209 

210 @staticmethod 

211 @api_db_api.context_manager.writer 

212 def _update_limit_in_db(context, project_id, resource, limit, 

213 user_id=None): 

214 # TODO(melwitt): We won't have per project resources after nova-network 

215 # is removed. 

216 # TODO(stephenfin): We need to do something here now...but what? 

217 per_user = ( 

218 user_id and 

219 resource not in main_db_api.quota_get_per_project_resources() 

220 ) 

221 model = api_models.ProjectUserQuota if per_user else api_models.Quota 

222 query = context.session.query(model).\ 

223 filter_by(project_id=project_id).\ 

224 filter_by(resource=resource) 

225 if per_user: 

226 query = query.filter_by(user_id=user_id) 

227 

228 result = query.update({'hard_limit': limit}) 

229 if not result: 

230 if per_user: 

231 raise exception.ProjectUserQuotaNotFound(project_id=project_id, 

232 user_id=user_id) 

233 else: 

234 raise exception.ProjectQuotaNotFound(project_id=project_id) 

235 

236 @staticmethod 

237 @api_db_api.context_manager.writer 

238 def _create_class_in_db(context, class_name, resource, limit): 

239 # NOTE(melwitt): There's no unique constraint on the QuotaClass model, 

240 # so check for duplicate manually. 

241 try: 

242 Quotas._get_class_from_db(context, class_name, resource) 

243 except exception.QuotaClassNotFound: 

244 pass 

245 else: 

246 raise exception.QuotaClassExists(class_name=class_name, 

247 resource=resource) 

248 quota_class_ref = api_models.QuotaClass() 

249 quota_class_ref.class_name = class_name 

250 quota_class_ref.resource = resource 

251 quota_class_ref.hard_limit = limit 

252 quota_class_ref.save(context.session) 

253 return quota_class_ref 

254 

255 @staticmethod 

256 @api_db_api.context_manager.writer 

257 def _update_class_in_db(context, class_name, resource, limit): 

258 result = context.session.query(api_models.QuotaClass).\ 

259 filter_by(class_name=class_name).\ 

260 filter_by(resource=resource).\ 

261 update({'hard_limit': limit}) 

262 if not result: 262 ↛ exitline 262 didn't return from function '_update_class_in_db' because the condition on line 262 was always true

263 raise exception.QuotaClassNotFound(class_name=class_name) 

264 

265 # TODO(melwitt): Remove this method in version 2.0 of the object. 

266 @base.remotable 

267 def reserve(self, expire=None, project_id=None, user_id=None, 

268 **deltas): 

269 # Honor the expected attributes even though we're not reserving 

270 # anything anymore. This will protect against things exploding if 

271 # someone has an Ocata compute host running by accident, for example. 

272 self.reservations = None 

273 self.project_id = project_id 

274 self.user_id = user_id 

275 self.obj_reset_changes() 

276 

277 # TODO(melwitt): Remove this method in version 2.0 of the object. 

278 @base.remotable 

279 def commit(self): 

280 pass 

281 

282 # TODO(melwitt): Remove this method in version 2.0 of the object. 

283 @base.remotable 

284 def rollback(self): 

285 pass 

286 

287 @base.remotable_classmethod 

288 def limit_check(cls, context, project_id=None, user_id=None, **values): 

289 """Check quota limits.""" 

290 return quota.QUOTAS.limit_check( 

291 context, project_id=project_id, user_id=user_id, **values) 

292 

293 @base.remotable_classmethod 

294 def limit_check_project_and_user(cls, context, project_values=None, 

295 user_values=None, project_id=None, 

296 user_id=None): 

297 """Check values against quota limits.""" 

298 return quota.QUOTAS.limit_check_project_and_user(context, 

299 project_values=project_values, user_values=user_values, 

300 project_id=project_id, user_id=user_id) 

301 

302 # NOTE(melwitt): This can be removed once no old code can call count(). 

303 @base.remotable_classmethod 

304 def count(cls, context, resource, *args, **kwargs): 

305 """Count a resource.""" 

306 count = quota.QUOTAS.count_as_dict(context, resource, *args, **kwargs) 

307 key = 'user' if 'user' in count else 'project' 

308 return count[key][resource] 

309 

310 @base.remotable_classmethod 

311 def count_as_dict(cls, context, resource, *args, **kwargs): 

312 """Count a resource and return a dict.""" 

313 return quota.QUOTAS.count_as_dict( 

314 context, resource, *args, **kwargs) 

315 

316 @base.remotable_classmethod 

317 def check_deltas(cls, context, deltas, *count_args, **count_kwargs): 

318 """Check usage delta against quota limits. 

319 

320 This does a Quotas.count_as_dict() followed by a 

321 Quotas.limit_check_project_and_user() using the provided deltas. 

322 

323 :param context: The request context, for access checks 

324 :param deltas: A dict of {resource_name: delta, ...} to check against 

325 the quota limits 

326 :param count_args: Optional positional arguments to pass to 

327 count_as_dict() 

328 :param count_kwargs: Optional keyword arguments to pass to 

329 count_as_dict() 

330 :param check_project_id: Optional project_id for scoping the limit 

331 check to a different project than in the 

332 context 

333 :param check_user_id: Optional user_id for scoping the limit check to a 

334 different user than in the context 

335 :raises: exception.OverQuota if the limit check exceeds the quota 

336 limits 

337 """ 

338 # We can't do f(*args, kw=None, **kwargs) in python 2.x 

339 check_project_id = count_kwargs.pop('check_project_id', None) 

340 check_user_id = count_kwargs.pop('check_user_id', None) 

341 

342 check_kwargs = collections.defaultdict(dict) 

343 for resource in deltas: 

344 # If we already counted a resource in a batch count, avoid 

345 # unnecessary re-counting and avoid creating empty dicts in 

346 # the defaultdict. 

347 if (resource in check_kwargs.get('project_values', {}) or 

348 resource in check_kwargs.get('user_values', {})): 

349 continue 

350 count = cls.count_as_dict(context, resource, *count_args, 

351 **count_kwargs) 

352 for res in count.get('project', {}): 

353 if res in deltas: 

354 total = count['project'][res] + deltas[res] 

355 check_kwargs['project_values'][res] = total 

356 for res in count.get('user', {}): 

357 if res in deltas: 

358 total = count['user'][res] + deltas[res] 

359 check_kwargs['user_values'][res] = total 

360 if check_project_id is not None: 

361 check_kwargs['project_id'] = check_project_id 

362 if check_user_id is not None: 

363 check_kwargs['user_id'] = check_user_id 

364 try: 

365 cls.limit_check_project_and_user(context, **check_kwargs) 

366 except exception.OverQuota as exc: 

367 # Report usage in the exception when going over quota 

368 key = 'user' if 'user' in count else 'project' 

369 exc.kwargs['usages'] = count[key] 

370 raise exc 

371 

372 @base.remotable_classmethod 

373 def create_limit(cls, context, project_id, resource, limit, user_id=None): 

374 try: 

375 main_db_api.quota_get( 

376 context, project_id, resource, user_id=user_id) 

377 except exception.QuotaNotFound: 

378 cls._create_limit_in_db(context, project_id, resource, limit, 

379 user_id=user_id) 

380 else: 

381 raise exception.QuotaExists(project_id=project_id, 

382 resource=resource) 

383 

384 @base.remotable_classmethod 

385 def update_limit(cls, context, project_id, resource, limit, user_id=None): 

386 try: 

387 cls._update_limit_in_db(context, project_id, resource, limit, 

388 user_id=user_id) 

389 except exception.QuotaNotFound: 

390 main_db_api.quota_update(context, project_id, resource, limit, 

391 user_id=user_id) 

392 

393 @classmethod 

394 def create_class(cls, context, class_name, resource, limit): 

395 try: 

396 main_db_api.quota_class_get(context, class_name, resource) 

397 except exception.QuotaClassNotFound: 

398 cls._create_class_in_db(context, class_name, resource, limit) 

399 else: 

400 raise exception.QuotaClassExists(class_name=class_name, 

401 resource=resource) 

402 

403 @classmethod 

404 def update_class(cls, context, class_name, resource, limit): 

405 try: 

406 cls._update_class_in_db(context, class_name, resource, limit) 

407 except exception.QuotaClassNotFound: 

408 main_db_api.quota_class_update( 

409 context, class_name, resource, limit) 

410 

411 # NOTE(melwitt): The following methods are not remotable and return 

412 # dict-like database model objects. We are using classmethods to provide 

413 # a common interface for accessing the api/main databases. 

414 @classmethod 

415 def get(cls, context, project_id, resource, user_id=None): 

416 try: 

417 quota = cls._get_from_db(context, project_id, resource, 

418 user_id=user_id) 

419 except exception.QuotaNotFound: 

420 quota = main_db_api.quota_get(context, project_id, resource, 

421 user_id=user_id) 

422 return quota 

423 

424 @classmethod 

425 def get_all(cls, context, project_id): 

426 api_db_quotas = cls._get_all_from_db(context, project_id) 

427 main_db_quotas = main_db_api.quota_get_all(context, project_id) 

428 return api_db_quotas + main_db_quotas 

429 

430 @classmethod 

431 def get_all_by_project(cls, context, project_id): 

432 api_db_quotas_dict = cls._get_all_from_db_by_project(context, 

433 project_id) 

434 main_db_quotas_dict = main_db_api.quota_get_all_by_project( 

435 context, project_id) 

436 for k, v in api_db_quotas_dict.items(): 

437 main_db_quotas_dict[k] = v 

438 return main_db_quotas_dict 

439 

440 @classmethod 

441 def get_all_by_project_and_user(cls, context, project_id, user_id): 

442 api_db_quotas_dict = cls._get_all_from_db_by_project_and_user( 

443 context, project_id, user_id) 

444 main_db_quotas_dict = main_db_api.quota_get_all_by_project_and_user( 

445 context, project_id, user_id) 

446 for k, v in api_db_quotas_dict.items(): 

447 main_db_quotas_dict[k] = v 

448 return main_db_quotas_dict 

449 

450 @classmethod 

451 def destroy_all_by_project(cls, context, project_id): 

452 try: 

453 cls._destroy_all_in_db_by_project(context, project_id) 

454 except exception.ProjectQuotaNotFound: 

455 main_db_api.quota_destroy_all_by_project(context, project_id) 

456 

457 @classmethod 

458 def destroy_all_by_project_and_user(cls, context, project_id, user_id): 

459 try: 

460 cls._destroy_all_in_db_by_project_and_user(context, project_id, 

461 user_id) 

462 except exception.ProjectUserQuotaNotFound: 

463 main_db_api.quota_destroy_all_by_project_and_user( 

464 context, project_id, user_id) 

465 

466 @classmethod 

467 def get_class(cls, context, class_name, resource): 

468 try: 

469 qclass = cls._get_class_from_db(context, class_name, resource) 

470 except exception.QuotaClassNotFound: 

471 qclass = main_db_api.quota_class_get(context, class_name, resource) 

472 return qclass 

473 

474 @classmethod 

475 def get_default_class(cls, context): 

476 try: 

477 qclass = cls._get_all_class_from_db_by_name( 

478 context, main_db_api._DEFAULT_QUOTA_NAME) 

479 except exception.QuotaClassNotFound: 

480 qclass = main_db_api.quota_class_get_default(context) 

481 return qclass 

482 

483 @classmethod 

484 def get_all_class_by_name(cls, context, class_name): 

485 api_db_quotas_dict = cls._get_all_class_from_db_by_name(context, 

486 class_name) 

487 main_db_quotas_dict = main_db_api.quota_class_get_all_by_name(context, 

488 class_name) 

489 for k, v in api_db_quotas_dict.items(): 

490 main_db_quotas_dict[k] = v 

491 return main_db_quotas_dict 

492 

493 

494@base.NovaObjectRegistry.register 

495class QuotasNoOp(Quotas): 

496 # TODO(melwitt): Remove this method in version 2.0 of the object. 

497 def reserve(context, expire=None, project_id=None, user_id=None, 

498 **deltas): 

499 pass 

500 

501 # TODO(melwitt): Remove this method in version 2.0 of the object. 

502 def commit(self, context=None): 

503 pass 

504 

505 # TODO(melwitt): Remove this method in version 2.0 of the object. 

506 def rollback(self, context=None): 

507 pass 

508 

509 def check_deltas(cls, context, deltas, *count_args, **count_kwargs): 

510 pass 

511 

512 

513@db_utils.require_context 

514@main_db_api.pick_context_manager_reader 

515def _get_main_per_project_limits(context, limit): 

516 return context.session.query(main_models.Quota).\ 

517 filter_by(deleted=0).\ 

518 limit(limit).\ 

519 all() 

520 

521 

522@db_utils.require_context 

523@main_db_api.pick_context_manager_reader 

524def _get_main_per_user_limits(context, limit): 

525 return context.session.query(main_models.ProjectUserQuota).\ 

526 filter_by(deleted=0).\ 

527 limit(limit).\ 

528 all() 

529 

530 

531@db_utils.require_context 

532@main_db_api.pick_context_manager_writer 

533def _destroy_main_per_project_limits(context, project_id, resource): 

534 context.session.query(main_models.Quota).\ 

535 filter_by(deleted=0).\ 

536 filter_by(project_id=project_id).\ 

537 filter_by(resource=resource).\ 

538 soft_delete(synchronize_session=False) 

539 

540 

541@db_utils.require_context 

542@main_db_api.pick_context_manager_writer 

543def _destroy_main_per_user_limits(context, project_id, resource, user_id): 

544 context.session.query(main_models.ProjectUserQuota).\ 

545 filter_by(deleted=0).\ 

546 filter_by(project_id=project_id).\ 

547 filter_by(user_id=user_id).\ 

548 filter_by(resource=resource).\ 

549 soft_delete(synchronize_session=False) 

550 

551 

552@api_db_api.context_manager.writer 

553def _create_limits_in_api_db(context, db_limits, per_user=False): 

554 for db_limit in db_limits: 

555 user_id = db_limit.user_id if per_user else None 

556 Quotas._create_limit_in_db(context, db_limit.project_id, 

557 db_limit.resource, db_limit.hard_limit, 

558 user_id=user_id) 

559 

560 

561def migrate_quota_limits_to_api_db(context, max_count): 

562 # Migrate per project limits 

563 main_per_project_limits = _get_main_per_project_limits(context, max_count) 

564 done = 0 

565 try: 

566 # Create all the limits in a single transaction. 

567 _create_limits_in_api_db(context, main_per_project_limits) 

568 except exception.QuotaExists: 

569 # NOTE(melwitt): This can happen if the migration is interrupted after 

570 # limits were created in the api db but before they were deleted from 

571 # the main db, and the migration is re-run. 

572 pass 

573 # Delete the limits separately. 

574 for db_limit in main_per_project_limits: 

575 _destroy_main_per_project_limits(context, db_limit.project_id, 

576 db_limit.resource) 

577 done += 1 

578 if done == max_count: 

579 return len(main_per_project_limits), done 

580 # Migrate per user limits 

581 max_count -= done 

582 main_per_user_limits = _get_main_per_user_limits(context, max_count) 

583 try: 

584 # Create all the limits in a single transaction. 

585 _create_limits_in_api_db(context, main_per_user_limits, per_user=True) 

586 except exception.QuotaExists: 

587 # NOTE(melwitt): This can happen if the migration is interrupted after 

588 # limits were created in the api db but before they were deleted from 

589 # the main db, and the migration is re-run. 

590 pass 

591 # Delete the limits separately. 

592 for db_limit in main_per_user_limits: 

593 _destroy_main_per_user_limits(context, db_limit.project_id, 

594 db_limit.resource, db_limit.user_id) 

595 done += 1 

596 return len(main_per_project_limits) + len(main_per_user_limits), done 

597 

598 

599@db_utils.require_context 

600@main_db_api.pick_context_manager_reader 

601def _get_main_quota_classes(context, limit): 

602 return context.session.query(main_models.QuotaClass).\ 

603 filter_by(deleted=0).\ 

604 limit(limit).\ 

605 all() 

606 

607 

608@main_db_api.pick_context_manager_writer 

609def _destroy_main_quota_classes(context, db_classes): 

610 for db_class in db_classes: 

611 context.session.query(main_models.QuotaClass).\ 

612 filter_by(deleted=0).\ 

613 filter_by(id=db_class.id).\ 

614 soft_delete(synchronize_session=False) 

615 

616 

617@api_db_api.context_manager.writer 

618def _create_classes_in_api_db(context, db_classes): 

619 for db_class in db_classes: 

620 Quotas._create_class_in_db(context, db_class.class_name, 

621 db_class.resource, db_class.hard_limit) 

622 

623 

624def migrate_quota_classes_to_api_db(context, max_count): 

625 main_quota_classes = _get_main_quota_classes(context, max_count) 

626 done = 0 

627 try: 

628 # Create all the classes in a single transaction. 

629 _create_classes_in_api_db(context, main_quota_classes) 

630 except exception.QuotaClassExists: 

631 # NOTE(melwitt): This can happen if the migration is interrupted after 

632 # classes were created in the api db but before they were deleted from 

633 # the main db, and the migration is re-run. 

634 pass 

635 # Delete the classes in a single transaction. 

636 _destroy_main_quota_classes(context, main_quota_classes) 

637 found = done = len(main_quota_classes) 

638 return found, done