Coverage for manila/quota.py: 97%

257 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 

17"""Quotas for shares.""" 

18 

19import datetime 

20 

21from oslo_config import cfg 

22from oslo_log import log 

23from oslo_utils import importutils 

24from oslo_utils import timeutils 

25 

26from manila import db 

27from manila import exception 

28 

29LOG = log.getLogger(__name__) 

30 

31QUOTA_GROUP = 'quota' 

32 

33quota_opts = [ 

34 cfg.IntOpt('shares', 

35 default=50, 

36 help='Number of shares allowed per project.', 

37 deprecated_group='DEFAULT', 

38 deprecated_name='quota_shares'), 

39 cfg.IntOpt('snapshots', 

40 default=50, 

41 help='Number of share snapshots allowed per project.', 

42 deprecated_group='DEFAULT', 

43 deprecated_name='quota_snapshots'), 

44 cfg.IntOpt('gigabytes', 

45 default=1000, 

46 help='Number of share gigabytes allowed per project.', 

47 deprecated_group='DEFAULT', 

48 deprecated_name='quota_gigabytes'), 

49 cfg.IntOpt('per_share_gigabytes', 

50 default=-1, 

51 help='Max size allowed per share, in gigabytes.', 

52 deprecated_group='DEFAULT', 

53 deprecated_name='quota_per_share_gigabytes'), 

54 cfg.IntOpt('snapshot_gigabytes', 

55 default=1000, 

56 help='Number of snapshot gigabytes allowed per project.', 

57 deprecated_group='DEFAULT', 

58 deprecated_name='quota_snapshot_gigabytes'), 

59 cfg.IntOpt('share_networks', 

60 default=10, 

61 help='Number of share-networks allowed per project.', 

62 deprecated_group='DEFAULT', 

63 deprecated_name='quota_share_networks'), 

64 cfg.IntOpt('share_replicas', 

65 default=100, 

66 help='Number of share-replicas allowed per project.', 

67 deprecated_group='DEFAULT', 

68 deprecated_name='quota_share_replicas'), 

69 cfg.IntOpt('replica_gigabytes', 

70 default=1000, 

71 help='Number of replica gigabytes allowed per project.', 

72 deprecated_group='DEFAULT', 

73 deprecated_name='quota_replica_gigabytes'), 

74 cfg.IntOpt('share_groups', 

75 default=50, 

76 help='Number of share groups allowed.', 

77 deprecated_group='DEFAULT', 

78 deprecated_name='quota_share_groups'), 

79 cfg.IntOpt('share_group_snapshots', 

80 default=50, 

81 help='Number of share group snapshots allowed.', 

82 deprecated_group='DEFAULT', 

83 deprecated_name='quota_share_group_snapshots'), 

84 cfg.IntOpt('reservation_expire', 

85 default=86400, 

86 help='Number of seconds until a reservation expires.', 

87 deprecated_group='DEFAULT'), 

88 cfg.IntOpt('until_refresh', 

89 default=0, 

90 help='Count of reservations until usage is refreshed.', 

91 deprecated_group='DEFAULT'), 

92 cfg.IntOpt('max_age', 

93 default=0, 

94 help='Number of seconds between subsequent usage refreshes.', 

95 deprecated_group='DEFAULT'), 

96 cfg.StrOpt('driver', 

97 default='manila.quota.DbQuotaDriver', 

98 help='Default driver to use for quota checks.', 

99 deprecated_group='DEFAULT', 

100 deprecated_name='quota_driver'), 

101 cfg.IntOpt('backups', 

102 default=10, 

103 help='Number of share backups allowed per project.', 

104 deprecated_group='DEFAULT', 

105 deprecated_name='quota_backups'), 

106 cfg.IntOpt('backup_gigabytes', 

107 default=1000, 

108 help='Total amount of storage, in gigabytes, allowed ' 

109 'for backups per project.', 

110 deprecated_group='DEFAULT', 

111 deprecated_name='quota_backup_gigabytes'), 

112 cfg.IntOpt('encryption_keys', 

113 default=100, 

114 help='Number of encryption keys allowed per project.'), ] 

115 

116CONF = cfg.CONF 

117CONF.register_opts(quota_opts, QUOTA_GROUP) 

118 

119 

120class DbQuotaDriver(object): 

121 """Database Quota driver. 

122 

123 Driver to perform necessary checks to enforce quotas and obtain 

124 quota information. The default driver utilizes the local 

125 database. 

126 """ 

127 

128 def get_by_class(self, context, quota_class, resource): 

129 """Get a specific quota by quota class.""" 

130 

131 return db.quota_class_get(context, quota_class, resource) 

132 

133 def get_defaults(self, context, resources): 

134 """Given a list of resources, retrieve the default quotas. 

135 

136 :param context: The request context, for access checks. 

137 :param resources: A dictionary of the registered resources. 

138 """ 

139 

140 quotas = {} 

141 default_quotas = db.quota_class_get_default(context) 

142 for resource in resources.values(): 

143 quotas[resource.name] = default_quotas.get(resource.name, 

144 resource.default) 

145 return quotas 

146 

147 def get_class_quotas(self, context, resources, quota_class, 

148 defaults=True): 

149 """Retrieve quotas for a quota class. 

150 

151 Given a list of resources, retrieve the quotas for the given 

152 quota class. 

153 

154 :param context: The request context, for access checks. 

155 :param resources: A dictionary of the registered resources. 

156 :param quota_class: The name of the quota class to return 

157 quotas for. 

158 :param defaults: If True, the default value will be reported 

159 if there is no specific value for the 

160 resource. 

161 """ 

162 

163 quotas = {} 

164 class_quotas = db.quota_class_get_all_by_name(context, quota_class) 

165 for resource in resources.values(): 

166 if defaults or resource.name in class_quotas: 

167 quotas[resource.name] = class_quotas.get(resource.name, 

168 resource.default) 

169 

170 return quotas 

171 

172 def _process_quotas(self, context, resources, project_id, quotas, 

173 quota_class=None, defaults=True, usages=None, 

174 remains=False): 

175 modified_quotas = {} 

176 # Get the quotas for the appropriate class. If the project ID 

177 # matches the one in the context, we use the quota_class from 

178 # the context, otherwise, we use the provided quota_class (if 

179 # any) 

180 if project_id == context.project_id: 

181 quota_class = context.quota_class 

182 if quota_class: 

183 class_quotas = db.quota_class_get_all_by_name(context, quota_class) 

184 else: 

185 class_quotas = {} 

186 

187 default_quotas = self.get_defaults(context, resources) 

188 

189 for resource in resources.values(): 

190 # Omit default/quota class values 

191 if not defaults and resource.name not in quotas: 191 ↛ 192line 191 didn't jump to line 192 because the condition on line 191 was never true

192 continue 

193 

194 limit = quotas.get( 

195 resource.name, 

196 class_quotas.get(resource.name, default_quotas[resource.name])) 

197 modified_quotas[resource.name] = dict(limit=limit) 

198 

199 # Include usages if desired. This is optional because one 

200 # internal consumer of this interface wants to access the 

201 # usages directly from inside a transaction. 

202 if usages: 

203 usage = usages.get(resource.name, {}) 

204 modified_quotas[resource.name].update( 

205 in_use=usage.get('in_use', 0), 

206 reserved=usage.get('reserved', 0), 

207 ) 

208 # Initialize remains quotas. 

209 if remains: 

210 modified_quotas[resource.name].update(remains=limit) 

211 

212 if remains: 

213 all_quotas = db.quota_get_all(context, project_id) 

214 for quota in all_quotas: 214 ↛ 215line 214 didn't jump to line 215 because the loop on line 214 never started

215 if quota.resource in modified_quotas: 

216 modified_quotas[quota.resource]['remains'] -= ( 

217 quota.hard_limit) 

218 

219 return modified_quotas 

220 

221 def get_project_quotas(self, context, resources, project_id, 

222 quota_class=None, defaults=True, 

223 usages=True, remains=False): 

224 """Retrieve quotas for project. 

225 

226 Given a list of resources, retrieve the quotas for the given 

227 project. 

228 

229 :param context: The request context, for access checks. 

230 :param resources: A dictionary of the registered resources. 

231 :param project_id: The ID of the project to return quotas for. 

232 :param quota_class: If project_id != context.project_id, the 

233 quota class cannot be determined. This 

234 parameter allows it to be specified. It 

235 will be ignored if project_id == 

236 context.project_id. 

237 :param defaults: If True, the quota class value (or the 

238 default value, if there is no value from the 

239 quota class) will be reported if there is no 

240 specific value for the resource. 

241 :param usages: If True, the current in_use and reserved counts 

242 will also be returned. 

243 :param remains: If True, the current remains of the project will 

244 will be returned. 

245 """ 

246 project_quotas = db.quota_get_all_by_project(context, project_id) 

247 project_usages = None 

248 if usages: 

249 project_usages = db.quota_usage_get_all_by_project(context, 

250 project_id) 

251 return self._process_quotas(context, resources, project_id, 

252 project_quotas, quota_class, 

253 defaults=defaults, usages=project_usages, 

254 remains=remains) 

255 

256 def get_user_quotas(self, context, resources, project_id, user_id, 

257 quota_class=None, defaults=True, usages=True): 

258 """Retrieve quotas for user and project. 

259 

260 Given a list of resources, retrieve the quotas for the given 

261 user and project. 

262 

263 :param context: The request context, for access checks. 

264 :param resources: A dictionary of the registered resources. 

265 :param project_id: The ID of the project to return quotas for. 

266 :param user_id: The ID of the user to return quotas for. 

267 :param quota_class: If project_id != context.project_id, the 

268 quota class cannot be determined. This 

269 parameter allows it to be specified. It 

270 will be ignored if project_id == 

271 context.project_id. 

272 :param defaults: If True, the quota class value (or the 

273 default value, if there is no value from the 

274 quota class) will be reported if there is no 

275 specific value for the resource. 

276 :param usages: If True, the current in_use and reserved counts 

277 will also be returned. 

278 """ 

279 user_quotas = db.quota_get_all_by_project_and_user( 

280 context, project_id, user_id) 

281 # Use the project quota for default user quota. 

282 proj_quotas = db.quota_get_all_by_project(context, project_id) 

283 for key, value in proj_quotas.items(): 

284 if key not in user_quotas.keys(): 

285 user_quotas[key] = value 

286 user_usages = None 

287 if usages: 

288 user_usages = db.quota_usage_get_all_by_project_and_user( 

289 context, project_id, user_id) 

290 return self._process_quotas(context, resources, project_id, 

291 user_quotas, quota_class, 

292 defaults=defaults, usages=user_usages) 

293 

294 def get_share_type_quotas(self, context, resources, project_id, 

295 share_type_id, quota_class=None, defaults=True, 

296 usages=True): 

297 """Retrieve quotas for share_type and project. 

298 

299 Given a list of resources, retrieve the quotas for the given 

300 share_type and project. 

301 

302 :param context: The request context, for access checks. 

303 :param resources: A dictionary of the registered resources. 

304 :param project_id: The UUID of the project to return quotas for. 

305 :param share_type: UUID/name of a share type to return quotas for. 

306 :param quota_class: If project_id != context.project_id, the 

307 quota class cannot be determined. This 

308 parameter allows it to be specified. It 

309 will be ignored if project_id == 

310 context.project_id. 

311 :param defaults: If True, the quota class value (or the 

312 default value, if there is no value from the 

313 quota class) will be reported if there is no 

314 specific value for the resource. 

315 :param usages: If True, the current in_use and reserved counts 

316 will also be returned. 

317 """ 

318 st_quotas = db.quota_get_all_by_project_and_share_type( 

319 context, project_id, share_type_id) 

320 # Use the project quota for default share_type quota. 

321 project_quotas = db.quota_get_all_by_project(context, project_id) 

322 for key, value in project_quotas.items(): 

323 if key not in st_quotas.keys(): 

324 st_quotas[key] = value 

325 st_usages = None 

326 if usages: 

327 st_usages = db.quota_usage_get_all_by_project_and_share_type( 

328 context, project_id, share_type_id) 

329 return self._process_quotas( 

330 context, resources, project_id, st_quotas, quota_class, 

331 defaults=defaults, usages=st_usages) 

332 

333 def get_settable_quotas(self, context, resources, project_id, 

334 user_id=None, share_type_id=None): 

335 """Retrieve range of settable quotas. 

336 

337 Given a list of resources, retrieve the range of settable quotas for 

338 the given user or project. 

339 

340 :param context: The request context, for access checks. 

341 :param resources: A dictionary of the registered resources. 

342 :param project_id: The ID of the project to return quotas for. 

343 :param user_id: The ID of the user to return quotas for. 

344 :param share_type_id: The UUID of the share_type to return quotas for. 

345 """ 

346 settable_quotas = {} 

347 project_quotas = self.get_project_quotas( 

348 context, resources, project_id, remains=True) 

349 if user_id or share_type_id: 

350 if user_id: 

351 subquotas = self.get_user_quotas( 

352 context, resources, project_id, user_id) 

353 else: 

354 subquotas = self.get_share_type_quotas( 

355 context, resources, project_id, share_type_id) 

356 for key, value in subquotas.items(): 

357 settable_quotas[key] = { 

358 "minimum": value['in_use'] + value['reserved'], 

359 "maximum": project_quotas[key]["limit"], 

360 } 

361 else: 

362 for key, value in project_quotas.items(): 

363 minimum = max( 

364 int(value['limit'] - value['remains']), 

365 int(value['in_use'] + value['reserved']) 

366 ) 

367 settable_quotas[key] = {"minimum": minimum, "maximum": -1} 

368 return settable_quotas 

369 

370 def _get_quotas(self, context, resources, keys, has_sync, project_id=None, 

371 user_id=None, share_type_id=None): 

372 """Retrieve quotas for a resource. 

373 

374 A helper method which retrieves the quotas for the specific 

375 resources identified by keys, and which apply to the current 

376 context. 

377 

378 :param context: The request context, for access checks. 

379 :param resources: A dictionary of the registered resources. 

380 :param keys: A list of the desired quotas to retrieve. 

381 :param has_sync: If True, indicates that the resource must 

382 have a sync attribute; if False, indicates 

383 that the resource must NOT have a sync 

384 attribute. 

385 :param project_id: Specify the project_id if current context 

386 is admin and admin wants to impact on 

387 common user's tenant. 

388 :param user_id: Specify the user_id if current context 

389 is admin and admin wants to impact on 

390 common user. (Special case: user operates on 

391 resource, owned/created by different user) 

392 """ 

393 

394 # Filter resources 

395 if has_sync: 

396 sync_filt = lambda x: hasattr(x, 'sync') # noqa: E731 

397 else: 

398 sync_filt = lambda x: not hasattr(x, 'sync') # noqa: E731 

399 desired = set(keys) 

400 sub_resources = {k: v for k, v in resources.items() 

401 if k in desired and sync_filt(v)} 

402 

403 # Make sure we accounted for all of them... 

404 if len(keys) != len(sub_resources): 

405 unknown = desired - set(sub_resources.keys()) 

406 raise exception.QuotaResourceUnknown(unknown=sorted(unknown)) 

407 

408 if user_id: 

409 # Grab and return the quotas (without usages) 

410 quotas = self.get_user_quotas(context, sub_resources, 

411 project_id, user_id, 

412 context.quota_class, usages=False) 

413 elif share_type_id: 

414 # Grab and return the quotas (without usages) 

415 quotas = self.get_share_type_quotas( 

416 context, sub_resources, project_id, share_type_id, 

417 context.quota_class, usages=False) 

418 else: 

419 # Grab and return the quotas (without usages) 

420 quotas = self.get_project_quotas(context, sub_resources, 

421 project_id, 

422 context.quota_class, 

423 usages=False) 

424 

425 return {k: v['limit'] for k, v in quotas.items()} 

426 

427 def limit_check(self, context, resources, values, project_id=None): 

428 """Check simple quota limits. 

429 

430 For limits--those quotas for which there is no usage 

431 synchronization function--this method checks that a set of 

432 proposed values are permitted by the limit restriction. 

433 

434 This method will raise a QuotaResourceUnknown exception if a 

435 given resource is unknown or if it is not a simple limit 

436 resource. 

437 

438 If any of the proposed values is over the defined quota, an 

439 OverQuota exception will be raised with the sorted list of the 

440 resources which are too high. Otherwise, the method returns 

441 nothing. 

442 

443 :param context: The request context, for access checks. 

444 :param resources: A dictionary of the registered resources. 

445 :param values: A dictionary of the values to check against the 

446 quota. 

447 :param project_id: Specify the project_id if current context 

448 is admin and admin wants to impact on 

449 common user's tenant. 

450 """ 

451 

452 # Ensure no value is less than zero 

453 unders = [key for key, val in values.items() if val < 0] 

454 if unders: 454 ↛ 455line 454 didn't jump to line 455 because the condition on line 454 was never true

455 raise exception.InvalidQuotaValue(unders=sorted(unders)) 

456 

457 # If project_id is None, then we use the project_id in context 

458 if project_id is None: 

459 project_id = context.project_id 

460 

461 quotas = self._get_quotas(context, resources, values.keys(), 

462 has_sync=False, project_id=project_id) 

463 # Check the quotas and construct a list of the resources that 

464 # would be put over limit by the desired values 

465 overs = [key for key, val in values.items() 

466 if quotas[key] >= 0 and quotas[key] < val] 

467 if overs: 

468 raise exception.OverQuota(overs=sorted(overs), quotas=quotas, 

469 usages={}) 

470 

471 def reserve(self, context, resources, deltas, expire=None, 

472 project_id=None, user_id=None, share_type_id=None, 

473 overquota_allowed=False): 

474 """Check quotas and reserve resources. 

475 

476 For counting quotas--those quotas for which there is a usage 

477 synchronization function--this method checks quotas against 

478 current usage and the desired deltas. 

479 

480 This method will raise a QuotaResourceUnknown exception if a 

481 given resource is unknown or if it does not have a usage 

482 synchronization function. 

483 

484 If any of the proposed values is over the defined quota, an 

485 OverQuota exception will be raised with the sorted list of the 

486 resources which are too high. Otherwise, the method returns a 

487 list of reservation UUIDs which were created. 

488 

489 :param context: The request context, for access checks. 

490 :param resources: A dictionary of the registered resources. 

491 :param deltas: A dictionary of the proposed delta changes. 

492 :param expire: An optional parameter specifying an expiration 

493 time for the reservations. If it is a simple 

494 number, it is interpreted as a number of 

495 seconds and added to the current time; if it is 

496 a datetime.timedelta object, it will also be 

497 added to the current time. A datetime.datetime 

498 object will be interpreted as the absolute 

499 expiration time. If None is specified, the 

500 default expiration time set by 

501 --default-reservation-expire will be used (this 

502 value will be treated as a number of seconds). 

503 :param project_id: Specify the project_id if current context 

504 is admin and admin wants to impact on 

505 common user's tenant. 

506 :param user_id: Specify the user_id if current context 

507 is admin and admin wants to impact on 

508 common user. (Special case: user operates on 

509 resource, owned/created by different user) 

510 """ 

511 

512 # Set up the reservation expiration 

513 if expire is None: 

514 expire = CONF.quota.reservation_expire 

515 if isinstance(expire, int): 

516 expire = datetime.timedelta(seconds=expire) 

517 if isinstance(expire, datetime.timedelta): 

518 expire = timeutils.utcnow() + expire 

519 if not isinstance(expire, datetime.datetime): 

520 raise exception.InvalidReservationExpiration(expire=expire) 

521 

522 # If project_id is None, then we use the project_id in context 

523 if project_id is None: 

524 project_id = context.project_id 

525 # If user_id is None, then we use the user_id in context 

526 if user_id is None: 

527 user_id = context.user_id 

528 

529 # Get the applicable quotas. 

530 # NOTE(Vek): We're not worried about races at this point. 

531 # Yes, the admin may be in the process of reducing 

532 # quotas, but that's a pretty rare thing. 

533 quotas = self._get_quotas( 

534 context, resources, deltas, has_sync=True, project_id=project_id) 

535 user_quotas = self._get_quotas( 

536 context, resources, deltas, has_sync=True, 

537 project_id=project_id, user_id=user_id) 

538 if share_type_id: 

539 share_type_quotas = self._get_quotas( 

540 context, resources, deltas, has_sync=True, 

541 project_id=project_id, share_type_id=share_type_id) 

542 else: 

543 share_type_quotas = {} 

544 

545 # NOTE(Vek): Most of the work here has to be done in the DB 

546 # API, because we have to do it in a transaction, 

547 # which means access to the session. Since the 

548 # session isn't available outside the DBAPI, we 

549 # have to do the work there. 

550 return db.quota_reserve( 

551 context, resources, quotas, user_quotas, share_type_quotas, 

552 deltas, expire, CONF.quota.until_refresh, CONF.quota.max_age, 

553 project_id=project_id, user_id=user_id, 

554 share_type_id=share_type_id, overquota_allowed=overquota_allowed) 

555 

556 def commit(self, context, reservations, project_id=None, user_id=None, 

557 share_type_id=None): 

558 """Commit reservations. 

559 

560 :param context: The request context, for access checks. 

561 :param reservations: A list of the reservation UUIDs, as 

562 returned by the reserve() method. 

563 :param project_id: Specify the project_id if current context 

564 is admin and admin wants to impact on 

565 common user's tenant. 

566 :param user_id: Specify the user_id if current context 

567 is admin and admin wants to impact on 

568 common user. (Special case: user operates on 

569 resource, owned/created by different user) 

570 """ 

571 # If project_id is None, then we use the project_id in context 

572 if project_id is None: 

573 project_id = context.project_id 

574 # If user_id is None, then we use the user_id in context 

575 if user_id is None: 

576 user_id = context.user_id 

577 

578 db.reservation_commit( 

579 context, reservations, project_id=project_id, user_id=user_id, 

580 share_type_id=share_type_id) 

581 

582 def rollback(self, context, reservations, project_id=None, user_id=None, 

583 share_type_id=None): 

584 """Roll back reservations. 

585 

586 :param context: The request context, for access checks. 

587 :param reservations: A list of the reservation UUIDs, as 

588 returned by the reserve() method. 

589 :param project_id: Specify the project_id if current context 

590 is admin and admin wants to impact on 

591 common user's tenant. 

592 :param user_id: Specify the user_id if current context 

593 is admin and admin wants to impact on 

594 common user. (Special case: user operates on 

595 resource, owned/created by different user) 

596 """ 

597 # If project_id is None, then we use the project_id in context 

598 if project_id is None: 

599 project_id = context.project_id 

600 # If user_id is None, then we use the user_id in context 

601 if user_id is None: 

602 user_id = context.user_id 

603 

604 db.reservation_rollback( 

605 context, reservations, project_id=project_id, user_id=user_id, 

606 share_type_id=share_type_id) 

607 

608 def usage_reset(self, context, resources): 

609 """Reset usage records. 

610 

611 Reset the usage records for a particular user on a list of 

612 resources. This will force that user's usage records to be 

613 refreshed the next time a reservation is made. 

614 

615 Note: this does not affect the currently outstanding 

616 reservations the user has; those reservations must be 

617 committed or rolled back (or expired). 

618 

619 :param context: The request context, for access checks. 

620 :param resources: A list of the resource names for which the 

621 usage must be reset. 

622 """ 

623 

624 # We need an elevated context for the calls to 

625 # quota_usage_update() 

626 elevated = context.elevated() 

627 

628 for resource in resources: 

629 try: 

630 # Reset the usage to -1, which will force it to be 

631 # refreshed 

632 db.quota_usage_update(elevated, context.project_id, 

633 context.user_id, 

634 resource, in_use=-1) 

635 except exception.QuotaUsageNotFound: 

636 # That means it'll be refreshed anyway 

637 pass 

638 

639 def destroy_all_by_project(self, context, project_id): 

640 """Destroy metadata associated with a project. 

641 

642 Destroy all quotas, usages, and reservations associated with a 

643 project. 

644 

645 :param context: The request context, for access checks. 

646 :param project_id: The ID of the project being deleted. 

647 """ 

648 

649 db.quota_destroy_all_by_project(context, project_id) 

650 

651 def destroy_all_by_project_and_user(self, context, project_id, user_id): 

652 """Destroy metadata associated with a project and user. 

653 

654 Destroy all quotas, usages, and reservations associated with a 

655 project and user. 

656 

657 :param context: The request context, for access checks. 

658 :param project_id: The ID of the project being deleted. 

659 :param user_id: The ID of the user being deleted. 

660 """ 

661 

662 db.quota_destroy_all_by_project_and_user(context, project_id, user_id) 

663 

664 def destroy_all_by_project_and_share_type(self, context, project_id, 

665 share_type_id): 

666 """Destroy metadata associated with a project and share_type. 

667 

668 Destroy all quotas, usages, and reservations associated with a 

669 project and share_type. 

670 

671 :param context: The request context, for access checks. 

672 :param project_id: The ID of the project. 

673 :param share_type_id: The UUID of the share type. 

674 """ 

675 

676 db.quota_destroy_all_by_share_type(context, share_type_id, 

677 project_id=project_id) 

678 

679 def expire(self, context): 

680 """Expire reservations. 

681 

682 Explores all currently existing reservations and rolls back 

683 any that have expired. 

684 

685 :param context: The request context, for access checks. 

686 """ 

687 

688 db.reservation_expire(context) 

689 

690 

691class BaseResource(object): 

692 """Describe a single resource for quota checking.""" 

693 

694 def __init__(self, name, flag=None): 

695 """Initializes a Resource. 

696 

697 :param name: The name of the resource, i.e., "shares". 

698 :param flag: The name of the flag or configuration option 

699 which specifies the default value of the quota 

700 for this resource. 

701 """ 

702 

703 self.name = name 

704 self.flag = flag 

705 

706 @property 

707 def default(self): 

708 """Return the default value of the quota.""" 

709 

710 return CONF.quota[self.flag] if self.flag else -1 

711 

712 

713class ReservableResource(BaseResource): 

714 """Describe a reservable resource.""" 

715 

716 def __init__(self, name, sync, flag=None): 

717 """Initializes a ReservableResource. 

718 

719 Reservable resources are those resources which directly 

720 correspond to objects in the database, i.e., shares, gigabytes, 

721 etc. A ReservableResource must be constructed with a usage 

722 synchronization function, which will be called to determine the 

723 current counts of one or more resources. 

724 

725 The usage synchronization function will be passed three 

726 arguments: an admin context, the project ID, and an opaque 

727 session object, which should in turn be passed to the 

728 underlying database function. Synchronization functions 

729 should return a dictionary mapping resource names to the 

730 current in_use count for those resources; more than one 

731 resource and resource count may be returned. Note that 

732 synchronization functions may be associated with more than one 

733 ReservableResource. 

734 

735 :param name: The name of the resource, i.e., "shares". 

736 :param sync: A callable which returns a dictionary to 

737 resynchronize the in_use count for one or more 

738 resources, as described above. 

739 :param flag: The name of the flag or configuration option 

740 which specifies the default value of the quota 

741 for this resource. 

742 """ 

743 

744 super(ReservableResource, self).__init__(name, flag=flag) 

745 if sync: 

746 self.sync = sync 

747 

748 

749class AbsoluteResource(BaseResource): 

750 """Describe a non-reservable resource.""" 

751 

752 pass 

753 

754 

755class CountableResource(AbsoluteResource): 

756 """Describe a countable resource. 

757 

758 Describe a resource where the counts aren't based solely on the 

759 project ID. 

760 """ 

761 

762 def __init__(self, name, count, flag=None): 

763 """Initializes a CountableResource. 

764 

765 Countable resources are those resources which directly 

766 correspond to objects in the database, i.e., shares, gigabytes, 

767 etc., but for which a count by project ID is inappropriate. A 

768 CountableResource must be constructed with a counting 

769 function, which will be called to determine the current counts 

770 of the resource. 

771 

772 The counting function will be passed the context, along with 

773 the extra positional and keyword arguments that are passed to 

774 Quota.count(). It should return an integer specifying the 

775 count. 

776 

777 Note that this counting is not performed in a transaction-safe 

778 manner. This resource class is a temporary measure to provide 

779 required functionality, until a better approach to solving 

780 this problem can be evolved. 

781 

782 :param name: The name of the resource, i.e., "shares". 

783 :param count: A callable which returns the count of the 

784 resource. The arguments passed are as described 

785 above. 

786 :param flag: The name of the flag or configuration option 

787 which specifies the default value of the quota 

788 for this resource. 

789 """ 

790 

791 super(CountableResource, self).__init__(name, flag=flag) 

792 self.count = count 

793 

794 

795class QuotaEngine(object): 

796 """Represent the set of recognized quotas.""" 

797 

798 def __init__(self, quota_driver_class=None): 

799 """Initialize a Quota object.""" 

800 self._resources = {} 

801 self._driver_cls = quota_driver_class 

802 self.__driver = None 

803 

804 @property 

805 def _driver(self): 

806 if self.__driver: 

807 return self.__driver 

808 if not self._driver_cls: 808 ↛ 810line 808 didn't jump to line 810 because the condition on line 808 was always true

809 self._driver_cls = CONF.quota.driver 

810 if isinstance(self._driver_cls, str): 810 ↛ 812line 810 didn't jump to line 812 because the condition on line 810 was always true

811 self._driver_cls = importutils.import_object(self._driver_cls) 

812 self.__driver = self._driver_cls 

813 return self.__driver 

814 

815 def __contains__(self, resource): 

816 return resource in self._resources 

817 

818 def register_resource(self, resource): 

819 """Register a resource.""" 

820 

821 self._resources[resource.name] = resource 

822 

823 def register_resources(self, resources): 

824 """Register a list of resources.""" 

825 

826 for resource in resources: 

827 self.register_resource(resource) 

828 

829 def get_by_class(self, context, quota_class, resource): 

830 """Get a specific quota by quota class.""" 

831 

832 return self._driver.get_by_class(context, quota_class, resource) 

833 

834 def get_defaults(self, context): 

835 """Retrieve the default quotas. 

836 

837 :param context: The request context, for access checks. 

838 """ 

839 

840 return self._driver.get_defaults(context, self._resources) 

841 

842 def get_class_quotas(self, context, quota_class, defaults=True): 

843 """Retrieve the quotas for the given quota class. 

844 

845 :param context: The request context, for access checks. 

846 :param quota_class: The name of the quota class to return 

847 quotas for. 

848 :param defaults: If True, the default value will be reported 

849 if there is no specific value for the 

850 resource. 

851 """ 

852 

853 return self._driver.get_class_quotas(context, self._resources, 

854 quota_class, defaults=defaults) 

855 

856 def get_user_quotas(self, context, project_id, user_id, quota_class=None, 

857 defaults=True, usages=True): 

858 """Retrieve the quotas for the given user and project. 

859 

860 :param context: The request context, for access checks. 

861 :param project_id: The ID of the project to return quotas for. 

862 :param user_id: The ID of the user to return quotas for. 

863 :param quota_class: If project_id != context.project_id, the 

864 quota class cannot be determined. This 

865 parameter allows it to be specified. 

866 :param defaults: If True, the quota class value (or the 

867 default value, if there is no value from the 

868 quota class) will be reported if there is no 

869 specific value for the resource. 

870 :param usages: If True, the current in_use and reserved counts 

871 will also be returned. 

872 """ 

873 

874 return self._driver.get_user_quotas(context, self._resources, 

875 project_id, user_id, 

876 quota_class=quota_class, 

877 defaults=defaults, 

878 usages=usages) 

879 

880 def get_share_type_quotas(self, context, project_id, share_type_id, 

881 quota_class=None, defaults=True, usages=True): 

882 """Retrieve the quotas for the given user and project. 

883 

884 :param context: The request context, for access checks. 

885 :param project_id: The ID of the project to return quotas for. 

886 :param share_type_id: The UUID of the user to return quotas for. 

887 :param quota_class: If project_id != context.project_id, the 

888 quota class cannot be determined. This 

889 parameter allows it to be specified. 

890 :param defaults: If True, the quota class value (or the 

891 default value, if there is no value from the 

892 quota class) will be reported if there is no 

893 specific value for the resource. 

894 :param usages: If True, the current in_use and reserved counts 

895 will also be returned. 

896 """ 

897 

898 return self._driver.get_share_type_quotas( 

899 context, self._resources, project_id, share_type_id, 

900 quota_class=quota_class, defaults=defaults, usages=usages) 

901 

902 def get_project_quotas(self, context, project_id, quota_class=None, 

903 defaults=True, usages=True, remains=False): 

904 """Retrieve the quotas for the given project. 

905 

906 :param context: The request context, for access checks. 

907 :param project_id: The ID of the project to return quotas for. 

908 :param quota_class: If project_id != context.project_id, the 

909 quota class cannot be determined. This 

910 parameter allows it to be specified. 

911 :param defaults: If True, the quota class value (or the 

912 default value, if there is no value from the 

913 quota class) will be reported if there is no 

914 specific value for the resource. 

915 :param usages: If True, the current in_use and reserved counts 

916 will also be returned. 

917 :param remains: If True, the current remains of the project will 

918 will be returned. 

919 """ 

920 

921 return self._driver.get_project_quotas(context, self._resources, 

922 project_id, 

923 quota_class=quota_class, 

924 defaults=defaults, 

925 usages=usages, 

926 remains=remains) 

927 

928 def get_settable_quotas(self, context, project_id, user_id=None, 

929 share_type_id=None): 

930 """Get settable quotas. 

931 

932 Given a list of resources, retrieve the range of settable quotas for 

933 the given user or project. 

934 

935 :param context: The request context, for access checks. 

936 :param resources: A dictionary of the registered resources. 

937 :param project_id: The ID of the project to return quotas for. 

938 :param user_id: The ID of the user to return quotas for. 

939 :param share_type_id: The UUID of the share_type to return quotas for. 

940 """ 

941 

942 return self._driver.get_settable_quotas( 

943 context, self._resources, project_id, user_id=user_id, 

944 share_type_id=share_type_id) 

945 

946 def count(self, context, resource, *args, **kwargs): 

947 """Count a resource. 

948 

949 For countable resources, invokes the count() function and 

950 returns its result. Arguments following the context and 

951 resource are passed directly to the count function declared by 

952 the resource. 

953 

954 :param context: The request context, for access checks. 

955 :param resource: The name of the resource, as a string. 

956 """ 

957 

958 # Get the resource 

959 res = self._resources.get(resource) 

960 if not res or not hasattr(res, 'count'): 

961 raise exception.QuotaResourceUnknown(unknown=[resource]) 

962 

963 return res.count(context, *args, **kwargs) 

964 

965 def limit_check(self, context, project_id=None, **values): 

966 """Check simple quota limits. 

967 

968 For limits--those quotas for which there is no usage 

969 synchronization function--this method checks that a set of 

970 proposed values are permitted by the limit restriction. The 

971 values to check are given as keyword arguments, where the key 

972 identifies the specific quota limit to check, and the value is 

973 the proposed value. 

974 

975 This method will raise a QuotaResourceUnknown exception if a 

976 given resource is unknown or if it is not a simple limit 

977 resource. 

978 

979 If any of the proposed values is over the defined quota, an 

980 OverQuota exception will be raised with the sorted list of the 

981 resources which are too high. Otherwise, the method returns 

982 nothing. 

983 

984 :param context: The request context, for access checks. 

985 :param project_id: Specify the project_id if current context 

986 is admin and admin wants to impact on 

987 common user's tenant. 

988 """ 

989 

990 return self._driver.limit_check(context, self._resources, values, 

991 project_id=project_id) 

992 

993 def reserve(self, context, expire=None, project_id=None, user_id=None, 

994 share_type_id=None, overquota_allowed=False, **deltas): 

995 """Check quotas and reserve resources. 

996 

997 For counting quotas--those quotas for which there is a usage 

998 synchronization function--this method checks quotas against 

999 current usage and the desired deltas. The deltas are given as 

1000 keyword arguments, and current usage and other reservations 

1001 are factored into the quota check. 

1002 

1003 This method will raise a QuotaResourceUnknown exception if a 

1004 given resource is unknown or if it does not have a usage 

1005 synchronization function. 

1006 

1007 If any of the proposed values is over the defined quota, an 

1008 OverQuota exception will be raised with the sorted list of the 

1009 resources which are too high. Otherwise, the method returns a 

1010 list of reservation UUIDs which were created. 

1011 

1012 :param context: The request context, for access checks. 

1013 :param expire: An optional parameter specifying an expiration 

1014 time for the reservations. If it is a simple 

1015 number, it is interpreted as a number of 

1016 seconds and added to the current time; if it is 

1017 a datetime.timedelta object, it will also be 

1018 added to the current time. A datetime.datetime 

1019 object will be interpreted as the absolute 

1020 expiration time. If None is specified, the 

1021 default expiration time set by 

1022 --default-reservation-expire will be used (this 

1023 value will be treated as a number of seconds). 

1024 :param project_id: Specify the project_id if current context 

1025 is admin and admin wants to impact on 

1026 common user's tenant. 

1027 """ 

1028 

1029 reservations = self._driver.reserve( 

1030 context, self._resources, deltas, 

1031 expire=expire, 

1032 project_id=project_id, 

1033 user_id=user_id, 

1034 share_type_id=share_type_id, 

1035 overquota_allowed=overquota_allowed 

1036 ) 

1037 

1038 LOG.debug("Created reservations %s", reservations) 

1039 

1040 return reservations 

1041 

1042 def commit(self, context, reservations, project_id=None, user_id=None, 

1043 share_type_id=None): 

1044 """Commit reservations. 

1045 

1046 :param context: The request context, for access checks. 

1047 :param reservations: A list of the reservation UUIDs, as 

1048 returned by the reserve() method. 

1049 :param project_id: Specify the project_id if current context 

1050 is admin and admin wants to impact on 

1051 common user's tenant. 

1052 """ 

1053 

1054 try: 

1055 self._driver.commit( 

1056 context, reservations, project_id=project_id, 

1057 user_id=user_id, share_type_id=share_type_id) 

1058 except Exception: 

1059 # NOTE(Vek): Ignoring exceptions here is safe, because the 

1060 # usage resynchronization and the reservation expiration 

1061 # mechanisms will resolve the issue. The exception is 

1062 # logged, however, because this is less than optimal. 

1063 LOG.exception("Failed to commit reservations %s", 

1064 reservations) 

1065 return 

1066 LOG.debug("Committed reservations %s", reservations) 

1067 

1068 def rollback(self, context, reservations, project_id=None, user_id=None, 

1069 share_type_id=None): 

1070 """Roll back reservations. 

1071 

1072 :param context: The request context, for access checks. 

1073 :param reservations: A list of the reservation UUIDs, as 

1074 returned by the reserve() method. 

1075 :param project_id: Specify the project_id if current context 

1076 is admin and admin wants to impact on 

1077 common user's tenant. 

1078 """ 

1079 

1080 try: 

1081 self._driver.rollback( 

1082 context, reservations, project_id=project_id, 

1083 user_id=user_id, share_type_id=share_type_id) 

1084 except Exception: 

1085 # NOTE(Vek): Ignoring exceptions here is safe, because the 

1086 # usage resynchronization and the reservation expiration 

1087 # mechanisms will resolve the issue. The exception is 

1088 # logged, however, because this is less than optimal. 

1089 LOG.exception("Failed to roll back reservations %s", 

1090 reservations) 

1091 return 

1092 LOG.debug("Rolled back reservations %s", reservations) 

1093 

1094 def usage_reset(self, context, resources): 

1095 """Reset usage records. 

1096 

1097 Reset the usage records for a particular user on a list of 

1098 resources. This will force that user's usage records to be 

1099 refreshed the next time a reservation is made. 

1100 

1101 Note: this does not affect the currently outstanding 

1102 reservations the user has; those reservations must be 

1103 committed or rolled back (or expired). 

1104 

1105 :param context: The request context, for access checks. 

1106 :param resources: A list of the resource names for which the 

1107 usage must be reset. 

1108 """ 

1109 

1110 self._driver.usage_reset(context, resources) 

1111 

1112 def destroy_all_by_project_and_user(self, context, project_id, user_id): 

1113 """Destroy metadata associated with a project and user. 

1114 

1115 Destroy all quotas, usages, and reservations associated with a 

1116 project and user. 

1117 

1118 :param context: The request context, for access checks. 

1119 :param project_id: The ID of the project being deleted. 

1120 :param user_id: The ID of the user being deleted. 

1121 """ 

1122 

1123 self._driver.destroy_all_by_project_and_user(context, 

1124 project_id, user_id) 

1125 

1126 def destroy_all_by_project_and_share_type(self, context, project_id, 

1127 share_type_id): 

1128 """Destroy metadata associated with a project and share_type. 

1129 

1130 Destroy all quotas, usages, and reservations associated with a 

1131 project and share_type. 

1132 

1133 :param context: The request context, for access checks. 

1134 :param project_id: The ID of the project. 

1135 :param share_type_id: The UUID of the share_type. 

1136 """ 

1137 

1138 self._driver.destroy_all_by_project_and_share_type( 

1139 context, project_id, share_type_id) 

1140 

1141 def destroy_all_by_project(self, context, project_id): 

1142 """Destroy metadata associated with a project. 

1143 

1144 Destroy all quotas, usages, and reservations associated with a 

1145 project. 

1146 

1147 :param context: The request context, for access checks. 

1148 :param project_id: The ID of the project being deleted. 

1149 """ 

1150 

1151 self._driver.destroy_all_by_project(context, project_id) 

1152 

1153 def expire(self, context): 

1154 """Expire reservations. 

1155 

1156 Explores all currently existing reservations and rolls back 

1157 any that have expired. 

1158 

1159 :param context: The request context, for access checks. 

1160 """ 

1161 

1162 self._driver.expire(context) 

1163 

1164 @property 

1165 def resources(self): 

1166 return sorted(self._resources.keys()) 

1167 

1168 

1169QUOTAS = QuotaEngine() 

1170 

1171 

1172resources = [ 

1173 ReservableResource('shares', '_sync_shares', 'shares'), 

1174 ReservableResource('snapshots', '_sync_snapshots', 'snapshots'), 

1175 ReservableResource('gigabytes', '_sync_gigabytes', 'gigabytes'), 

1176 ReservableResource('per_share_gigabytes', None, 

1177 'per_share_gigabytes'), 

1178 ReservableResource('snapshot_gigabytes', '_sync_snapshot_gigabytes', 

1179 'snapshot_gigabytes'), 

1180 ReservableResource('share_networks', '_sync_share_networks', 

1181 'share_networks'), 

1182 ReservableResource('share_groups', '_sync_share_groups', 

1183 'share_groups'), 

1184 ReservableResource('share_group_snapshots', '_sync_share_group_snapshots', 

1185 'share_group_snapshots'), 

1186 ReservableResource('share_replicas', '_sync_share_replicas', 

1187 'share_replicas'), 

1188 ReservableResource('replica_gigabytes', '_sync_replica_gigabytes', 

1189 'replica_gigabytes'), 

1190 ReservableResource('backups', '_sync_backups', 'backups'), 

1191 ReservableResource('backup_gigabytes', '_sync_backup_gigabytes', 

1192 'backup_gigabytes'), 

1193 ReservableResource('encryption_keys', 

1194 '_sync_encryption_keys', 

1195 'encryption_keys'), 

1196] 

1197 

1198 

1199QUOTAS.register_resources(resources)