1 2# Copyright 2010 United States Government as represented by the 3# Administrator of the National Aeronautics and Space Administration. 4# All Rights Reserved. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); you may 7# not use this file except in compliance with the License. You may obtain 8# a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15# License for the specific language governing permissions and limitations 16# under the License. 17 18import datetime 19 20import mock 21from oslo_config import cfg 22from oslo_config import fixture as config_fixture 23from oslo_utils import timeutils 24import six 25 26from cinder import backup 27from cinder.backup import api as backup_api 28from cinder import context 29from cinder import db 30from cinder.db.sqlalchemy import api as sqa_api 31from cinder.db.sqlalchemy import models as sqa_models 32from cinder import exception 33from cinder import objects 34from cinder.objects import fields 35from cinder import quota 36from cinder import quota_utils 37from cinder.scheduler import rpcapi as scheduler_rpcapi 38from cinder import test 39from cinder.tests.unit import fake_constants as fake 40import cinder.tests.unit.image.fake 41from cinder.tests.unit import utils as tests_utils 42from cinder import volume 43 44 45CONF = cfg.CONF 46 47 48class QuotaIntegrationTestCase(test.TestCase): 49 50 def setUp(self): 51 objects.register_all() 52 super(QuotaIntegrationTestCase, self).setUp() 53 self.volume_type_name = CONF.default_volume_type 54 self.volume_type = objects.VolumeType(context.get_admin_context(), 55 name=self.volume_type_name, 56 description='', 57 is_public=False, 58 projects=[], 59 extra_specs={}) 60 self.volume_type.create() 61 62 self.addCleanup(db.volume_type_destroy, context.get_admin_context(), 63 self.volume_type['id']) 64 65 self.flags(quota_volumes=2, 66 quota_snapshots=2, 67 quota_gigabytes=20, 68 quota_backups=2, 69 quota_backup_gigabytes=20) 70 71 self.user_id = fake.USER_ID 72 self.project_id = fake.PROJECT_ID 73 self.context = context.RequestContext(self.user_id, 74 self.project_id, 75 is_admin=True) 76 77 # Destroy the 'default' quota_class in the database to avoid 78 # conflicts with the test cases here that are setting up their own 79 # defaults. 80 db.quota_class_destroy_all_by_name(self.context, 'default') 81 self.addCleanup(cinder.tests.unit.image.fake.FakeImageService_reset) 82 83 def _create_volume(self, size=1): 84 """Create a test volume.""" 85 vol = {} 86 vol['user_id'] = self.user_id 87 vol['project_id'] = self.project_id 88 vol['size'] = size 89 vol['status'] = 'available' 90 vol['volume_type_id'] = self.volume_type['id'] 91 vol['host'] = 'fake_host' 92 vol['availability_zone'] = 'fake_zone' 93 vol['attach_status'] = fields.VolumeAttachStatus.DETACHED 94 volume = objects.Volume(context=self.context, **vol) 95 volume.create() 96 return volume 97 98 def _create_snapshot(self, volume): 99 snapshot = objects.Snapshot(self.context) 100 snapshot.user_id = self.user_id or fake.USER_ID 101 snapshot.project_id = self.project_id or fake.PROJECT_ID 102 snapshot.volume_id = volume['id'] 103 snapshot.volume_size = volume['size'] 104 snapshot.status = fields.SnapshotStatus.AVAILABLE 105 snapshot.create() 106 return snapshot 107 108 def _create_backup(self, volume): 109 backup = {} 110 backup['user_id'] = self.user_id 111 backup['project_id'] = self.project_id 112 backup['volume_id'] = volume['id'] 113 backup['volume_size'] = volume['size'] 114 backup['status'] = fields.BackupStatus.AVAILABLE 115 return db.backup_create(self.context, backup) 116 117 def test_volume_size_limit_exceeds(self): 118 resource = 'volumes_%s' % self.volume_type_name 119 db.quota_class_create(self.context, 'default', resource, 1) 120 flag_args = { 121 'quota_volumes': 10, 122 'quota_gigabytes': 1000, 123 'per_volume_size_limit': 5 124 } 125 self.flags(**flag_args) 126 self.assertRaises(exception.VolumeSizeExceedsLimit, 127 volume.API().create, 128 self.context, 10, '', '',) 129 130 def test_too_many_volumes(self): 131 volume_ids = [] 132 for _i in range(CONF.quota_volumes): 133 vol_ref = self._create_volume() 134 volume_ids.append(vol_ref['id']) 135 ex = self.assertRaises(exception.VolumeLimitExceeded, 136 volume.API().create, 137 self.context, 1, '', '', 138 volume_type=self.volume_type) 139 msg = ("Maximum number of volumes allowed (%d) exceeded for" 140 " quota 'volumes'." % CONF.quota_volumes) 141 self.assertEqual(msg, six.text_type(ex)) 142 for volume_id in volume_ids: 143 db.volume_destroy(self.context, volume_id) 144 145 def test_too_many_volumes_of_type(self): 146 resource = 'volumes_%s' % self.volume_type_name 147 db.quota_class_create(self.context, 'default', resource, 1) 148 flag_args = { 149 'quota_volumes': 2000, 150 'quota_gigabytes': 2000 151 } 152 self.flags(**flag_args) 153 vol_ref = self._create_volume() 154 ex = self.assertRaises(exception.VolumeLimitExceeded, 155 volume.API().create, 156 self.context, 1, '', '', 157 volume_type=self.volume_type) 158 msg = ("Maximum number of volumes allowed (1) exceeded for" 159 " quota '%s'." % resource) 160 self.assertEqual(msg, six.text_type(ex)) 161 vol_ref.destroy() 162 163 def test__snapshots_quota_value(self): 164 test_volume1 = tests_utils.create_volume( 165 self.context, 166 status='available', 167 host=CONF.host) 168 test_volume2 = tests_utils.create_volume( 169 self.context, 170 status='available', 171 host=CONF.host) 172 volume_api = cinder.volume.api.API() 173 volume_api.create_snapshots_in_db(self.context, 174 [test_volume1, test_volume2], 175 'fake_name', 176 'fake_description', 177 fake.CONSISTENCY_GROUP_ID) 178 usages = db.quota_usage_get_all_by_project(self.context, 179 self.project_id) 180 self.assertEqual(2, usages['snapshots']['in_use']) 181 182 def test_too_many_snapshots_of_type(self): 183 resource = 'snapshots_%s' % self.volume_type_name 184 db.quota_class_create(self.context, 'default', resource, 1) 185 flag_args = { 186 'quota_volumes': 2000, 187 'quota_gigabytes': 2000, 188 } 189 self.flags(**flag_args) 190 vol_ref = self._create_volume() 191 snap_ref = self._create_snapshot(vol_ref) 192 self.assertRaises(exception.SnapshotLimitExceeded, 193 volume.API().create_snapshot, 194 self.context, vol_ref, '', '') 195 snap_ref.destroy() 196 vol_ref.destroy() 197 198 def test_too_many_backups(self): 199 resource = 'backups' 200 db.quota_class_create(self.context, 'default', resource, 1) 201 flag_args = { 202 'quota_backups': 2000, 203 'quota_backup_gigabytes': 2000 204 } 205 self.flags(**flag_args) 206 vol_ref = self._create_volume() 207 backup_ref = self._create_backup(vol_ref) 208 with mock.patch.object(backup_api.API, 209 '_get_available_backup_service_host') as \ 210 mock__get_available_backup_service: 211 mock__get_available_backup_service.return_value = 'host' 212 self.assertRaises(exception.BackupLimitExceeded, 213 backup.API().create, 214 self.context, 215 'name', 216 'description', 217 vol_ref['id'], 218 'container', 219 False, 220 None) 221 db.backup_destroy(self.context, backup_ref['id']) 222 db.volume_destroy(self.context, vol_ref['id']) 223 224 def test_too_many_gigabytes(self): 225 volume_ids = [] 226 vol_ref = self._create_volume(size=20) 227 volume_ids.append(vol_ref['id']) 228 raised_exc = self.assertRaises( 229 exception.VolumeSizeExceedsAvailableQuota, volume.API().create, 230 self.context, 1, '', '', volume_type=self.volume_type) 231 expected = exception.VolumeSizeExceedsAvailableQuota( 232 requested=1, quota=20, consumed=20) 233 self.assertEqual(str(expected), str(raised_exc)) 234 for volume_id in volume_ids: 235 db.volume_destroy(self.context, volume_id) 236 237 def test_too_many_combined_gigabytes(self): 238 vol_ref = self._create_volume(size=10) 239 snap_ref = self._create_snapshot(vol_ref) 240 self.assertRaises(exception.QuotaError, 241 volume.API().create_snapshot, 242 self.context, vol_ref, '', '') 243 usages = db.quota_usage_get_all_by_project(self.context, 244 self.project_id) 245 self.assertEqual(20, usages['gigabytes']['in_use']) 246 snap_ref.destroy() 247 vol_ref.destroy() 248 249 def test_too_many_combined_backup_gigabytes(self): 250 vol_ref = self._create_volume(size=10000) 251 backup_ref = self._create_backup(vol_ref) 252 with mock.patch.object(backup_api.API, 253 '_get_available_backup_service_host') as \ 254 mock__get_available_backup_service: 255 mock__get_available_backup_service.return_value = 'host' 256 self.assertRaises( 257 exception.VolumeBackupSizeExceedsAvailableQuota, 258 backup.API().create, 259 context=self.context, 260 name='name', 261 description='description', 262 volume_id=vol_ref['id'], 263 container='container', 264 incremental=False) 265 db.backup_destroy(self.context, backup_ref['id']) 266 vol_ref.destroy() 267 268 def test_no_snapshot_gb_quota_flag(self): 269 self.mock_object(scheduler_rpcapi.SchedulerAPI, 'create_snapshot') 270 self.flags(quota_volumes=2, 271 quota_snapshots=2, 272 quota_gigabytes=20, 273 no_snapshot_gb_quota=True) 274 vol_ref = self._create_volume(size=10) 275 snap_ref = self._create_snapshot(vol_ref) 276 snap_ref2 = volume.API().create_snapshot(self.context, 277 vol_ref, '', '') 278 279 # Make sure the snapshot volume_size isn't included in usage. 280 vol_ref2 = volume.API().create(self.context, 10, '', '') 281 usages = db.quota_usage_get_all_by_project(self.context, 282 self.project_id) 283 self.assertEqual(20, usages['gigabytes']['in_use']) 284 self.assertEqual(0, usages['gigabytes']['reserved']) 285 286 snap_ref.destroy() 287 snap_ref2.destroy() 288 vol_ref.destroy() 289 vol_ref2.destroy() 290 291 def test_backup_gb_quota_flag(self): 292 self.flags(quota_volumes=2, 293 quota_snapshots=2, 294 quota_backups=2, 295 quota_gigabytes=20 296 ) 297 vol_ref = self._create_volume(size=10) 298 backup_ref = self._create_backup(vol_ref) 299 with mock.patch.object(backup_api.API, 300 '_get_available_backup_service_host') as \ 301 mock_mock__get_available_backup_service: 302 mock_mock__get_available_backup_service.return_value = 'host' 303 backup_ref2 = backup.API().create(self.context, 304 'name', 305 'description', 306 vol_ref['id'], 307 'container', 308 False, 309 None) 310 311 # Make sure the backup volume_size isn't included in usage. 312 vol_ref2 = volume.API().create(self.context, 10, '', '') 313 usages = db.quota_usage_get_all_by_project(self.context, 314 self.project_id) 315 self.assertEqual(20, usages['gigabytes']['in_use']) 316 self.assertEqual(0, usages['gigabytes']['reserved']) 317 318 db.backup_destroy(self.context, backup_ref['id']) 319 db.backup_destroy(self.context, backup_ref2['id']) 320 vol_ref.destroy() 321 vol_ref2.destroy() 322 323 def test_too_many_gigabytes_of_type(self): 324 resource = 'gigabytes_%s' % self.volume_type_name 325 db.quota_class_create(self.context, 'default', resource, 10) 326 flag_args = { 327 'quota_volumes': 2000, 328 'quota_gigabytes': 2000, 329 } 330 self.flags(**flag_args) 331 vol_ref = self._create_volume(size=10) 332 raised_exc = self.assertRaises( 333 exception.VolumeSizeExceedsAvailableQuota, volume.API().create, 334 self.context, 1, '', '', volume_type=self.volume_type) 335 expected = exception.VolumeSizeExceedsAvailableQuota( 336 requested=1, quota=10, consumed=10, name=resource) 337 self.assertEqual(str(expected), str(raised_exc)) 338 vol_ref.destroy() 339 340 341class FakeContext(object): 342 def __init__(self, project_id, quota_class): 343 self.is_admin = False 344 self.user_id = 'fake_user' 345 self.project_id = project_id 346 self.quota_class = quota_class 347 348 def elevated(self): 349 elevated = self.__class__(self.project_id, self.quota_class) 350 elevated.is_admin = True 351 return elevated 352 353 354class FakeDriver(object): 355 def __init__(self, by_project=None, by_class=None, reservations=None): 356 self.called = [] 357 self.by_project = by_project or {} 358 self.by_class = by_class or {} 359 self.reservations = reservations or [] 360 361 def get_by_project(self, context, project_id, resource): 362 self.called.append(('get_by_project', context, project_id, resource)) 363 try: 364 return self.by_project[project_id][resource] 365 except KeyError: 366 raise exception.ProjectQuotaNotFound(project_id=project_id) 367 368 def get_by_class(self, context, quota_class, resource): 369 self.called.append(('get_by_class', context, quota_class, resource)) 370 try: 371 return self.by_class[quota_class][resource] 372 except KeyError: 373 raise exception.QuotaClassNotFound(class_name=quota_class) 374 375 def get_default(self, context, resource, parent_project_id=None): 376 self.called.append(('get_default', context, resource, 377 parent_project_id)) 378 return resource.default 379 380 def get_defaults(self, context, resources, parent_project_id=None): 381 self.called.append(('get_defaults', context, resources, 382 parent_project_id)) 383 return resources 384 385 def get_class_quotas(self, context, resources, quota_class, 386 defaults=True): 387 self.called.append(('get_class_quotas', context, resources, 388 quota_class, defaults)) 389 return resources 390 391 def get_project_quotas(self, context, resources, project_id, 392 quota_class=None, defaults=True, usages=True): 393 self.called.append(('get_project_quotas', context, resources, 394 project_id, quota_class, defaults, usages)) 395 return resources 396 397 def limit_check(self, context, resources, values, project_id=None): 398 self.called.append(('limit_check', context, resources, 399 values, project_id)) 400 401 def reserve(self, context, resources, deltas, expire=None, 402 project_id=None): 403 self.called.append(('reserve', context, resources, deltas, 404 expire, project_id)) 405 return self.reservations 406 407 def commit(self, context, reservations, project_id=None): 408 self.called.append(('commit', context, reservations, project_id)) 409 410 def rollback(self, context, reservations, project_id=None): 411 self.called.append(('rollback', context, reservations, project_id)) 412 413 def destroy_by_project(self, context, project_id): 414 self.called.append(('destroy_by_project', context, project_id)) 415 416 def expire(self, context): 417 self.called.append(('expire', context)) 418 419 420class BaseResourceTestCase(test.TestCase): 421 def test_no_flag(self): 422 resource = quota.BaseResource('test_resource') 423 self.assertEqual('test_resource', resource.name) 424 self.assertIsNone(resource.flag) 425 self.assertEqual(-1, resource.default) 426 427 def test_with_flag(self): 428 # We know this flag exists, so use it... 429 self.flags(quota_volumes=10) 430 resource = quota.BaseResource('test_resource', 'quota_volumes') 431 self.assertEqual('test_resource', resource.name) 432 self.assertEqual('quota_volumes', resource.flag) 433 self.assertEqual(10, resource.default) 434 435 def test_with_flag_no_quota(self): 436 self.flags(quota_volumes=-1) 437 resource = quota.BaseResource('test_resource', 'quota_volumes') 438 439 self.assertEqual('test_resource', resource.name) 440 self.assertEqual('quota_volumes', resource.flag) 441 self.assertEqual(-1, resource.default) 442 443 def test_quota_no_project_no_class(self): 444 self.flags(quota_volumes=10) 445 resource = quota.BaseResource('test_resource', 'quota_volumes') 446 driver = FakeDriver() 447 context = FakeContext(None, None) 448 quota_value = resource.quota(driver, context) 449 450 self.assertEqual(10, quota_value) 451 452 def test_quota_with_project_no_class(self): 453 self.flags(quota_volumes=10) 454 resource = quota.BaseResource('test_resource', 'quota_volumes') 455 driver = FakeDriver( 456 by_project=dict( 457 test_project=dict(test_resource=15), )) 458 context = FakeContext('test_project', None) 459 quota_value = resource.quota(driver, context) 460 461 self.assertEqual(15, quota_value) 462 463 def test_quota_no_project_with_class(self): 464 self.flags(quota_volumes=10) 465 resource = quota.BaseResource('test_resource', 'quota_volumes') 466 driver = FakeDriver( 467 by_class=dict( 468 test_class=dict(test_resource=20), )) 469 context = FakeContext(None, 'test_class') 470 quota_value = resource.quota(driver, context) 471 472 self.assertEqual(20, quota_value) 473 474 def test_quota_with_project_with_class(self): 475 self.flags(quota_volumes=10) 476 resource = quota.BaseResource('test_resource', 'quota_volumes') 477 driver = FakeDriver(by_project=dict( 478 test_project=dict(test_resource=15), ), 479 by_class=dict(test_class=dict(test_resource=20), )) 480 context = FakeContext('test_project', 'test_class') 481 quota_value = resource.quota(driver, context) 482 483 self.assertEqual(15, quota_value) 484 485 def test_quota_override_project_with_class(self): 486 self.flags(quota_volumes=10) 487 resource = quota.BaseResource('test_resource', 'quota_volumes') 488 driver = FakeDriver(by_project=dict( 489 test_project=dict(test_resource=15), 490 override_project=dict(test_resource=20), )) 491 context = FakeContext('test_project', 'test_class') 492 quota_value = resource.quota(driver, context, 493 project_id='override_project') 494 495 self.assertEqual(20, quota_value) 496 497 def test_quota_override_subproject_no_class(self): 498 self.flags(quota_volumes=10) 499 resource = quota.BaseResource('test_resource', 'quota_volumes', 500 parent_project_id='test_parent_project') 501 driver = FakeDriver() 502 context = FakeContext('test_project', None) 503 quota_value = resource.quota(driver, context) 504 505 self.assertEqual(0, quota_value) 506 507 def test_quota_with_project_override_class(self): 508 self.flags(quota_volumes=10) 509 resource = quota.BaseResource('test_resource', 'quota_volumes') 510 driver = FakeDriver(by_class=dict( 511 test_class=dict(test_resource=15), 512 override_class=dict(test_resource=20), )) 513 context = FakeContext('test_project', 'test_class') 514 quota_value = resource.quota(driver, context, 515 quota_class='override_class') 516 517 self.assertEqual(20, quota_value) 518 519 520class VolumeTypeResourceTestCase(test.TestCase): 521 def test_name_and_flag(self): 522 volume_type_name = 'foo' 523 volume = {'name': volume_type_name, 'id': 'myid'} 524 resource = quota.VolumeTypeResource('volumes', volume) 525 526 self.assertEqual('volumes_%s' % volume_type_name, resource.name) 527 self.assertIsNone(resource.flag) 528 self.assertEqual(-1, resource.default) 529 530 531class QuotaEngineTestCase(test.TestCase): 532 def test_init(self): 533 quota_obj = quota.QuotaEngine() 534 535 self.assertEqual({}, quota_obj.resources) 536 self.assertIsInstance(quota_obj._driver, quota.DbQuotaDriver) 537 538 def test_init_override_string(self): 539 quota_obj = quota.QuotaEngine( 540 quota_driver_class='cinder.tests.unit.test_quota.FakeDriver') 541 542 self.assertEqual({}, quota_obj.resources) 543 self.assertIsInstance(quota_obj._driver, FakeDriver) 544 545 def test_init_override_obj(self): 546 quota_obj = quota.QuotaEngine(quota_driver_class=FakeDriver) 547 548 self.assertEqual({}, quota_obj.resources) 549 self.assertEqual(FakeDriver, quota_obj._driver) 550 551 def test_register_resource(self): 552 quota_obj = quota.QuotaEngine() 553 resource = quota.AbsoluteResource('test_resource') 554 quota_obj.register_resource(resource) 555 556 self.assertEqual(dict(test_resource=resource), quota_obj.resources) 557 558 def test_register_resources(self): 559 quota_obj = quota.QuotaEngine() 560 resources = [ 561 quota.AbsoluteResource('test_resource1'), 562 quota.AbsoluteResource('test_resource2'), 563 quota.AbsoluteResource('test_resource3'), ] 564 quota_obj.register_resources(resources) 565 566 self.assertEqual(dict(test_resource1=resources[0], 567 test_resource2=resources[1], 568 test_resource3=resources[2], ), 569 quota_obj.resources) 570 571 def test_get_by_project(self): 572 context = FakeContext('test_project', 'test_class') 573 driver = FakeDriver( 574 by_project=dict( 575 test_project=dict(test_resource=42))) 576 quota_obj = quota.QuotaEngine(quota_driver_class=driver) 577 result = quota_obj.get_by_project(context, 'test_project', 578 'test_resource') 579 580 self.assertEqual([('get_by_project', 581 context, 582 'test_project', 583 'test_resource'), ], driver.called) 584 self.assertEqual(42, result) 585 586 def test_get_by_class(self): 587 context = FakeContext('test_project', 'test_class') 588 driver = FakeDriver( 589 by_class=dict( 590 test_class=dict(test_resource=42))) 591 quota_obj = quota.QuotaEngine(quota_driver_class=driver) 592 result = quota_obj.get_by_class(context, 'test_class', 'test_resource') 593 594 self.assertEqual([('get_by_class', 595 context, 596 'test_class', 597 'test_resource'), ], driver.called) 598 self.assertEqual(42, result) 599 600 def _make_quota_obj(self, driver): 601 quota_obj = quota.QuotaEngine(quota_driver_class=driver) 602 resources = [ 603 quota.AbsoluteResource('test_resource4'), 604 quota.AbsoluteResource('test_resource3'), 605 quota.AbsoluteResource('test_resource2'), 606 quota.AbsoluteResource('test_resource1'), ] 607 quota_obj.register_resources(resources) 608 609 return quota_obj 610 611 def test_get_defaults(self): 612 context = FakeContext(None, None) 613 parent_project_id = None 614 driver = FakeDriver() 615 quota_obj = self._make_quota_obj(driver) 616 result = quota_obj.get_defaults(context) 617 618 self.assertEqual([('get_defaults', 619 context, 620 quota_obj.resources, 621 parent_project_id), ], driver.called) 622 self.assertEqual(quota_obj.resources, result) 623 624 def test_get_class_quotas(self): 625 context = FakeContext(None, None) 626 driver = FakeDriver() 627 quota_obj = self._make_quota_obj(driver) 628 result1 = quota_obj.get_class_quotas(context, 'test_class') 629 result2 = quota_obj.get_class_quotas(context, 'test_class', False) 630 631 self.assertEqual([ 632 ('get_class_quotas', 633 context, 634 quota_obj.resources, 635 'test_class', True), 636 ('get_class_quotas', 637 context, quota_obj.resources, 638 'test_class', False), ], driver.called) 639 self.assertEqual(quota_obj.resources, result1) 640 self.assertEqual(quota_obj.resources, result2) 641 642 def test_get_project_quotas(self): 643 context = FakeContext(None, None) 644 driver = FakeDriver() 645 quota_obj = self._make_quota_obj(driver) 646 result1 = quota_obj.get_project_quotas(context, 'test_project') 647 result2 = quota_obj.get_project_quotas(context, 'test_project', 648 quota_class='test_class', 649 defaults=False, 650 usages=False) 651 652 self.assertEqual([ 653 ('get_project_quotas', 654 context, 655 quota_obj.resources, 656 'test_project', 657 None, 658 True, 659 True), 660 ('get_project_quotas', 661 context, 662 quota_obj.resources, 663 'test_project', 664 'test_class', 665 False, 666 False), ], driver.called) 667 self.assertEqual(quota_obj.resources, result1) 668 self.assertEqual(quota_obj.resources, result2) 669 670 def test_get_subproject_quotas(self): 671 context = FakeContext(None, None) 672 driver = FakeDriver() 673 quota_obj = self._make_quota_obj(driver) 674 result1 = quota_obj.get_project_quotas(context, 'test_project') 675 result2 = quota_obj.get_project_quotas(context, 'test_project', 676 quota_class='test_class', 677 defaults=False, 678 usages=False) 679 680 self.assertEqual([ 681 ('get_project_quotas', 682 context, 683 quota_obj.resources, 684 'test_project', 685 None, 686 True, 687 True), 688 ('get_project_quotas', 689 context, 690 quota_obj.resources, 691 'test_project', 692 'test_class', 693 False, 694 False), ], driver.called) 695 self.assertEqual(quota_obj.resources, result1) 696 self.assertEqual(quota_obj.resources, result2) 697 698 def test_count_no_resource(self): 699 context = FakeContext(None, None) 700 driver = FakeDriver() 701 quota_obj = self._make_quota_obj(driver) 702 self.assertRaises(exception.QuotaResourceUnknown, 703 quota_obj.count, context, 'test_resource5', 704 True, foo='bar') 705 706 def test_count_wrong_resource(self): 707 context = FakeContext(None, None) 708 driver = FakeDriver() 709 quota_obj = self._make_quota_obj(driver) 710 self.assertRaises(exception.QuotaResourceUnknown, 711 quota_obj.count, context, 'test_resource1', 712 True, foo='bar') 713 714 def test_count(self): 715 def fake_count(context, *args, **kwargs): 716 self.assertEqual((True,), args) 717 self.assertEqual(dict(foo='bar'), kwargs) 718 return 5 719 720 context = FakeContext(None, None) 721 driver = FakeDriver() 722 quota_obj = self._make_quota_obj(driver) 723 quota_obj.register_resource(quota.CountableResource('test_resource5', 724 fake_count)) 725 result = quota_obj.count(context, 'test_resource5', True, foo='bar') 726 727 self.assertEqual(5, result) 728 729 def test_limit_check(self): 730 context = FakeContext(None, None) 731 driver = FakeDriver() 732 quota_obj = self._make_quota_obj(driver) 733 quota_obj.limit_check(context, test_resource1=4, test_resource2=3, 734 test_resource3=2, test_resource4=1) 735 736 self.assertEqual([ 737 ('limit_check', 738 context, 739 quota_obj.resources, 740 dict( 741 test_resource1=4, 742 test_resource2=3, 743 test_resource3=2, 744 test_resource4=1,), 745 None), ], 746 driver.called) 747 748 def test_reserve(self): 749 context = FakeContext(None, None) 750 driver = FakeDriver(reservations=['resv-01', 751 'resv-02', 752 'resv-03', 753 'resv-04', ]) 754 quota_obj = self._make_quota_obj(driver) 755 result1 = quota_obj.reserve(context, test_resource1=4, 756 test_resource2=3, test_resource3=2, 757 test_resource4=1) 758 result2 = quota_obj.reserve(context, expire=3600, 759 test_resource1=1, test_resource2=2, 760 test_resource3=3, test_resource4=4) 761 result3 = quota_obj.reserve(context, project_id='fake_project', 762 test_resource1=1, test_resource2=2, 763 test_resource3=3, test_resource4=4) 764 765 self.assertEqual([ 766 ('reserve', 767 context, 768 quota_obj.resources, 769 dict( 770 test_resource1=4, 771 test_resource2=3, 772 test_resource3=2, 773 test_resource4=1, ), 774 None, 775 None), 776 ('reserve', 777 context, 778 quota_obj.resources, 779 dict( 780 test_resource1=1, 781 test_resource2=2, 782 test_resource3=3, 783 test_resource4=4, ), 784 3600, 785 None), 786 ('reserve', 787 context, 788 quota_obj.resources, 789 dict( 790 test_resource1=1, 791 test_resource2=2, 792 test_resource3=3, 793 test_resource4=4, ), 794 None, 795 'fake_project'), ], 796 driver.called) 797 self.assertEqual(['resv-01', 798 'resv-02', 799 'resv-03', 800 'resv-04', ], result1) 801 self.assertEqual(['resv-01', 802 'resv-02', 803 'resv-03', 804 'resv-04', ], result2) 805 self.assertEqual(['resv-01', 806 'resv-02', 807 'resv-03', 808 'resv-04', ], result3) 809 810 def test_commit(self): 811 context = FakeContext(None, None) 812 driver = FakeDriver() 813 quota_obj = self._make_quota_obj(driver) 814 quota_obj.commit(context, ['resv-01', 'resv-02', 'resv-03']) 815 816 self.assertEqual([('commit', 817 context, 818 ['resv-01', 819 'resv-02', 820 'resv-03'], 821 None), ], 822 driver.called) 823 824 def test_rollback(self): 825 context = FakeContext(None, None) 826 driver = FakeDriver() 827 quota_obj = self._make_quota_obj(driver) 828 quota_obj.rollback(context, ['resv-01', 'resv-02', 'resv-03']) 829 830 self.assertEqual([('rollback', 831 context, 832 ['resv-01', 833 'resv-02', 834 'resv-03'], 835 None), ], 836 driver.called) 837 838 def test_destroy_by_project(self): 839 context = FakeContext(None, None) 840 driver = FakeDriver() 841 quota_obj = self._make_quota_obj(driver) 842 quota_obj.destroy_by_project(context, 'test_project') 843 844 self.assertEqual([('destroy_by_project', 845 context, 846 'test_project'), ], 847 driver.called) 848 849 def test_expire(self): 850 context = FakeContext(None, None) 851 driver = FakeDriver() 852 quota_obj = self._make_quota_obj(driver) 853 quota_obj.expire(context) 854 855 self.assertEqual([('expire', context), ], driver.called) 856 857 def test_resource_names(self): 858 quota_obj = self._make_quota_obj(None) 859 860 self.assertEqual(['test_resource1', 'test_resource2', 861 'test_resource3', 'test_resource4'], 862 quota_obj.resource_names) 863 864 865class VolumeTypeQuotaEngineTestCase(test.TestCase): 866 def test_default_resources(self): 867 def fake_vtga(context, inactive=False, filters=None): 868 return {} 869 self.mock_object(db, 'volume_type_get_all', fake_vtga) 870 871 engine = quota.VolumeTypeQuotaEngine() 872 self.assertEqual(['backup_gigabytes', 'backups', 873 'gigabytes', 'per_volume_gigabytes', 874 'snapshots', 'volumes'], 875 engine.resource_names) 876 877 def test_volume_type_resources(self): 878 ctx = context.RequestContext('admin', 'admin', is_admin=True) 879 vtype = db.volume_type_create(ctx, {'name': 'type1'}) 880 vtype2 = db.volume_type_create(ctx, {'name': 'type_2'}) 881 882 def fake_vtga(context, inactive=False, filters=None): 883 return { 884 'type1': { 885 'id': vtype['id'], 886 'name': 'type1', 887 'extra_specs': {}, 888 }, 889 'type_2': { 890 'id': vtype['id'], 891 'name': 'type_2', 892 'extra_specs': {}, 893 }, 894 } 895 self.mock_object(db, 'volume_type_get_all', fake_vtga) 896 897 engine = quota.VolumeTypeQuotaEngine() 898 self.assertEqual(['backup_gigabytes', 'backups', 899 'gigabytes', 'gigabytes_type1', 'gigabytes_type_2', 900 'per_volume_gigabytes', 'snapshots', 901 'snapshots_type1', 'snapshots_type_2', 'volumes', 902 'volumes_type1', 'volumes_type_2', 903 ], engine.resource_names) 904 db.volume_type_destroy(ctx, vtype['id']) 905 db.volume_type_destroy(ctx, vtype2['id']) 906 907 def test_update_quota_resource(self): 908 ctx = context.RequestContext('admin', 'admin', is_admin=True) 909 910 engine = quota.VolumeTypeQuotaEngine() 911 engine.update_quota_resource(ctx, 'type1', 'type2') 912 913 914class DbQuotaDriverBaseTestCase(test.TestCase): 915 def setUp(self): 916 super(DbQuotaDriverBaseTestCase, self).setUp() 917 918 self.flags(quota_volumes=10, 919 quota_snapshots=10, 920 quota_gigabytes=1000, 921 quota_backups=10, 922 quota_backup_gigabytes=1000, 923 reservation_expire=86400, 924 until_refresh=0, 925 max_age=0, 926 ) 927 928 # These can be used for expected defaults for child/non-child 929 self._default_quotas_non_child = dict( 930 volumes=10, 931 snapshots=10, 932 gigabytes=1000, 933 backups=10, 934 backup_gigabytes=1000, 935 per_volume_gigabytes=-1) 936 self._default_quotas_child = dict( 937 volumes=0, 938 snapshots=0, 939 gigabytes=0, 940 backups=0, 941 backup_gigabytes=0, 942 per_volume_gigabytes=0) 943 944 self.calls = [] 945 946 patcher = mock.patch.object(timeutils, 'utcnow') 947 self.addCleanup(patcher.stop) 948 self.mock_utcnow = patcher.start() 949 self.mock_utcnow.return_value = datetime.datetime.utcnow() 950 951 def _mock_quota_class_get_default(self): 952 # Mock quota_class_get_default 953 def fake_qcgd(context): 954 self.calls.append('quota_class_get_defaults') 955 return dict(volumes=10, 956 snapshots=10, 957 gigabytes=1000, 958 backups=10, 959 backup_gigabytes=1000 960 ) 961 self.mock_object(db, 'quota_class_get_defaults', fake_qcgd) 962 963 def _mock_volume_type_get_all(self): 964 def fake_vtga(context, inactive=False, filters=None): 965 return {} 966 self.mock_object(db, 'volume_type_get_all', fake_vtga) 967 968 def _mock_quota_class_get_all_by_name(self): 969 # Mock quota_class_get_all_by_name 970 def fake_qcgabn(context, quota_class): 971 self.calls.append('quota_class_get_all_by_name') 972 self.assertEqual('test_class', quota_class) 973 return dict(gigabytes=500, volumes=10, snapshots=10, backups=10, 974 backup_gigabytes=500) 975 self.mock_object(db, 'quota_class_get_all_by_name', fake_qcgabn) 976 977 def _mock_allocated_get_all_by_project(self, allocated_quota=False): 978 def fake_qagabp(context, project_id, session=None): 979 self.calls.append('quota_allocated_get_all_by_project') 980 if allocated_quota: 981 return dict(project_id=project_id, volumes=3) 982 return dict(project_id=project_id) 983 984 self.mock_object(db, 'quota_allocated_get_all_by_project', fake_qagabp) 985 986 987class DbQuotaDriverTestCase(DbQuotaDriverBaseTestCase): 988 def setUp(self): 989 super(DbQuotaDriverTestCase, self).setUp() 990 991 self.driver = quota.DbQuotaDriver() 992 993 def test_get_defaults(self): 994 # Use our pre-defined resources 995 self._mock_quota_class_get_default() 996 self._mock_volume_type_get_all() 997 result = self.driver.get_defaults(None, quota.QUOTAS.resources) 998 999 self.assertEqual( 1000 dict( 1001 volumes=10, 1002 snapshots=10, 1003 gigabytes=1000, 1004 backups=10, 1005 backup_gigabytes=1000, 1006 per_volume_gigabytes=-1), result) 1007 1008 def test_get_class_quotas(self): 1009 self._mock_quota_class_get_all_by_name() 1010 self._mock_volume_type_get_all() 1011 result = self.driver.get_class_quotas(None, quota.QUOTAS.resources, 1012 'test_class') 1013 1014 self.assertEqual(['quota_class_get_all_by_name'], self.calls) 1015 self.assertEqual(dict(volumes=10, 1016 gigabytes=500, 1017 snapshots=10, 1018 backups=10, 1019 backup_gigabytes=500, 1020 per_volume_gigabytes=-1), result) 1021 1022 def test_get_class_quotas_no_defaults(self): 1023 self._mock_quota_class_get_all_by_name() 1024 result = self.driver.get_class_quotas(None, quota.QUOTAS.resources, 1025 'test_class', False) 1026 1027 self.assertEqual(['quota_class_get_all_by_name'], self.calls) 1028 self.assertEqual(dict(volumes=10, 1029 gigabytes=500, 1030 snapshots=10, 1031 backups=10, 1032 backup_gigabytes=500), result) 1033 1034 def _mock_get_by_project(self): 1035 def fake_qgabp(context, project_id): 1036 self.calls.append('quota_get_all_by_project') 1037 self.assertEqual('test_project', project_id) 1038 return dict(volumes=10, gigabytes=50, reserved=0, 1039 snapshots=10, backups=10, 1040 backup_gigabytes=50) 1041 1042 def fake_qugabp(context, project_id): 1043 self.calls.append('quota_usage_get_all_by_project') 1044 self.assertEqual('test_project', project_id) 1045 return dict(volumes=dict(in_use=2, reserved=0), 1046 snapshots=dict(in_use=2, reserved=0), 1047 gigabytes=dict(in_use=10, reserved=0), 1048 backups=dict(in_use=2, reserved=0), 1049 backup_gigabytes=dict(in_use=10, reserved=0) 1050 ) 1051 1052 self.mock_object(db, 'quota_get_all_by_project', fake_qgabp) 1053 self.mock_object(db, 'quota_usage_get_all_by_project', fake_qugabp) 1054 1055 self._mock_quota_class_get_all_by_name() 1056 self._mock_quota_class_get_default() 1057 1058 def test_get_project_quotas(self): 1059 self._mock_get_by_project() 1060 self._mock_volume_type_get_all() 1061 self._mock_allocated_get_all_by_project() 1062 result = self.driver.get_project_quotas( 1063 FakeContext('test_project', 'test_class'), 1064 quota.QUOTAS.resources, 'test_project') 1065 1066 self.assertEqual(['quota_get_all_by_project', 1067 'quota_usage_get_all_by_project', 1068 'quota_allocated_get_all_by_project', 1069 'quota_class_get_all_by_name', 1070 'quota_class_get_defaults', ], self.calls) 1071 self.assertEqual(dict(volumes=dict(limit=10, 1072 in_use=2, 1073 reserved=0, ), 1074 snapshots=dict(limit=10, 1075 in_use=2, 1076 reserved=0, ), 1077 gigabytes=dict(limit=50, 1078 in_use=10, 1079 reserved=0, ), 1080 backups=dict(limit=10, 1081 in_use=2, 1082 reserved=0, ), 1083 backup_gigabytes=dict(limit=50, 1084 in_use=10, 1085 reserved=0, ), 1086 per_volume_gigabytes=dict(in_use=0, 1087 limit=-1, 1088 reserved= 0) 1089 ), result) 1090 1091 @mock.patch('cinder.quota.db.quota_get_all_by_project') 1092 @mock.patch('cinder.quota.db.quota_class_get_defaults') 1093 def test_get_project_quotas_lazy_load_defaults( 1094 self, mock_defaults, mock_quotas): 1095 mock_quotas.return_value = self._default_quotas_non_child 1096 self.driver.get_project_quotas( 1097 FakeContext('test_project', None), 1098 quota.QUOTAS.resources, 'test_project', usages=False) 1099 # Shouldn't load a project's defaults if all the quotas are already 1100 # defined in the DB 1101 self.assertFalse(mock_defaults.called) 1102 1103 mock_quotas.return_value = {} 1104 self.driver.get_project_quotas( 1105 FakeContext('test_project', None), 1106 quota.QUOTAS.resources, 'test_project', usages=False) 1107 self.assertTrue(mock_defaults.called) 1108 1109 def test_get_root_project_with_subprojects_quotas(self): 1110 self._mock_get_by_project() 1111 self._mock_volume_type_get_all() 1112 self._mock_allocated_get_all_by_project(allocated_quota=True) 1113 result = self.driver.get_project_quotas( 1114 FakeContext('test_project', None), 1115 quota.QUOTAS.resources, 'test_project') 1116 1117 self.assertEqual(['quota_get_all_by_project', 1118 'quota_usage_get_all_by_project', 1119 'quota_allocated_get_all_by_project', 1120 'quota_class_get_defaults', ], self.calls) 1121 self.assertEqual(dict(volumes=dict(limit=10, 1122 in_use=2, 1123 reserved=0, 1124 allocated=3, ), 1125 snapshots=dict(limit=10, 1126 in_use=2, 1127 reserved=0, 1128 allocated=0, ), 1129 gigabytes=dict(limit=50, 1130 in_use=10, 1131 reserved=0, 1132 allocated=0, ), 1133 backups=dict(limit=10, 1134 in_use=2, 1135 reserved=0, 1136 allocated=0, ), 1137 backup_gigabytes=dict(limit=50, 1138 in_use=10, 1139 reserved=0, 1140 allocated=0, ), 1141 per_volume_gigabytes=dict(in_use=0, 1142 limit=-1, 1143 reserved=0, 1144 allocated=0) 1145 ), result) 1146 1147 def test_get_project_quotas_alt_context_no_class(self): 1148 self._mock_get_by_project() 1149 self._mock_volume_type_get_all() 1150 result = self.driver.get_project_quotas( 1151 FakeContext('other_project', 'other_class'), 1152 quota.QUOTAS.resources, 'test_project') 1153 1154 self.assertEqual(['quota_get_all_by_project', 1155 'quota_usage_get_all_by_project', 1156 'quota_class_get_defaults', ], self.calls) 1157 self.assertEqual(dict(volumes=dict(limit=10, 1158 in_use=2, 1159 reserved=0, ), 1160 snapshots=dict(limit=10, 1161 in_use=2, 1162 reserved=0, ), 1163 gigabytes=dict(limit=50, 1164 in_use=10, 1165 reserved=0, ), 1166 backups=dict(limit=10, 1167 in_use=2, 1168 reserved=0, ), 1169 backup_gigabytes=dict(limit=50, 1170 in_use=10, 1171 reserved=0, ), 1172 per_volume_gigabytes=dict(in_use=0, 1173 limit=-1, 1174 reserved=0) 1175 ), result) 1176 1177 def test_get_project_quotas_alt_context_with_class(self): 1178 self._mock_get_by_project() 1179 self._mock_volume_type_get_all() 1180 result = self.driver.get_project_quotas( 1181 FakeContext('other_project', 'other_class'), 1182 quota.QUOTAS.resources, 'test_project', quota_class='test_class') 1183 1184 self.assertEqual(['quota_get_all_by_project', 1185 'quota_usage_get_all_by_project', 1186 'quota_class_get_all_by_name', 1187 'quota_class_get_defaults', ], self.calls) 1188 self.assertEqual(dict(volumes=dict(limit=10, 1189 in_use=2, 1190 reserved=0, ), 1191 snapshots=dict(limit=10, 1192 in_use=2, 1193 reserved=0, ), 1194 gigabytes=dict(limit=50, 1195 in_use=10, 1196 reserved=0, ), 1197 backups=dict(limit=10, 1198 in_use=2, 1199 reserved=0, ), 1200 backup_gigabytes=dict(limit=50, 1201 in_use=10, 1202 reserved=0, ), 1203 per_volume_gigabytes=dict(in_use=0, 1204 limit=-1, 1205 reserved= 0)), 1206 result) 1207 1208 def test_get_project_quotas_no_defaults(self): 1209 self._mock_get_by_project() 1210 self._mock_volume_type_get_all() 1211 result = self.driver.get_project_quotas( 1212 FakeContext('test_project', 'test_class'), 1213 quota.QUOTAS.resources, 'test_project', defaults=False) 1214 1215 self.assertEqual(['quota_get_all_by_project', 1216 'quota_usage_get_all_by_project', 1217 'quota_class_get_all_by_name'], self.calls) 1218 self.assertEqual(dict(backups=dict(limit=10, 1219 in_use=2, 1220 reserved=0, ), 1221 backup_gigabytes=dict(limit=50, 1222 in_use=10, 1223 reserved=0, ), 1224 gigabytes=dict(limit=50, 1225 in_use=10, 1226 reserved=0, ), 1227 snapshots=dict(limit=10, 1228 in_use=2, 1229 reserved=0, ), 1230 volumes=dict(limit=10, 1231 in_use=2, 1232 reserved=0, ), 1233 1234 ), result) 1235 1236 def test_get_project_quotas_no_usages(self): 1237 self._mock_get_by_project() 1238 self._mock_volume_type_get_all() 1239 result = self.driver.get_project_quotas( 1240 FakeContext('test_project', 'test_class'), 1241 quota.QUOTAS.resources, 'test_project', usages=False) 1242 1243 self.assertEqual(['quota_get_all_by_project', 1244 'quota_class_get_all_by_name', 1245 'quota_class_get_defaults', ], self.calls) 1246 self.assertEqual(dict(volumes=dict(limit=10, ), 1247 snapshots=dict(limit=10, ), 1248 backups=dict(limit=10, ), 1249 gigabytes=dict(limit=50, ), 1250 backup_gigabytes=dict(limit=50, ), 1251 per_volume_gigabytes=dict(limit=-1, )), result) 1252 1253 def _mock_get_project_quotas(self): 1254 def fake_get_project_quotas(context, resources, project_id, 1255 quota_class=None, defaults=True, 1256 usages=True, parent_project_id=None): 1257 self.calls.append('get_project_quotas') 1258 return {k: dict(limit=v.default) for k, v in resources.items()} 1259 1260 self.mock_object(self.driver, 'get_project_quotas', 1261 fake_get_project_quotas) 1262 1263 def test_get_quotas_has_sync_unknown(self): 1264 self._mock_get_project_quotas() 1265 self.assertRaises(exception.QuotaResourceUnknown, 1266 self.driver._get_quotas, 1267 None, quota.QUOTAS.resources, 1268 ['unknown'], True) 1269 self.assertEqual([], self.calls) 1270 1271 def test_get_quotas_no_sync_unknown(self): 1272 self._mock_get_project_quotas() 1273 self.assertRaises(exception.QuotaResourceUnknown, 1274 self.driver._get_quotas, 1275 None, quota.QUOTAS.resources, 1276 ['unknown'], False) 1277 self.assertEqual([], self.calls) 1278 1279 def test_get_quotas_has_sync_no_sync_resource(self): 1280 self._mock_get_project_quotas() 1281 self.assertRaises(exception.QuotaResourceUnknown, 1282 self.driver._get_quotas, 1283 None, quota.QUOTAS.resources, 1284 ['metadata_items'], True) 1285 self.assertEqual([], self.calls) 1286 1287 def test_get_quotas_no_sync_has_sync_resource(self): 1288 self._mock_get_project_quotas() 1289 self.assertRaises(exception.QuotaResourceUnknown, 1290 self.driver._get_quotas, 1291 None, quota.QUOTAS.resources, 1292 ['volumes'], False) 1293 self.assertEqual([], self.calls) 1294 1295 def test_get_quotas_has_sync(self): 1296 self._mock_get_project_quotas() 1297 result = self.driver._get_quotas(FakeContext('test_project', 1298 'test_class'), 1299 quota.QUOTAS.resources, 1300 ['volumes', 'gigabytes'], 1301 True) 1302 1303 self.assertEqual(['get_project_quotas'], self.calls) 1304 self.assertEqual(dict(volumes=10, gigabytes=1000, ), result) 1305 1306 def _mock_quota_reserve(self): 1307 def fake_quota_reserve(context, resources, quotas, deltas, expire, 1308 until_refresh, max_age, project_id=None): 1309 self.calls.append(('quota_reserve', expire, until_refresh, 1310 max_age)) 1311 return ['resv-1', 'resv-2', 'resv-3'] 1312 self.mock_object(db, 'quota_reserve', fake_quota_reserve) 1313 1314 def test_reserve_bad_expire(self): 1315 self._mock_get_project_quotas() 1316 self._mock_quota_reserve() 1317 self.assertRaises(exception.InvalidReservationExpiration, 1318 self.driver.reserve, 1319 FakeContext('test_project', 'test_class'), 1320 quota.QUOTAS.resources, 1321 dict(volumes=2), expire='invalid') 1322 self.assertEqual([], self.calls) 1323 1324 def test_reserve_default_expire(self): 1325 self._mock_get_project_quotas() 1326 self._mock_quota_reserve() 1327 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1328 quota.QUOTAS.resources, 1329 dict(volumes=2)) 1330 1331 expire = timeutils.utcnow() + datetime.timedelta(seconds=86400) 1332 self.assertEqual(['get_project_quotas', 1333 ('quota_reserve', expire, 0, 0), ], self.calls) 1334 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1335 1336 def test_reserve_int_expire(self): 1337 self._mock_get_project_quotas() 1338 self._mock_quota_reserve() 1339 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1340 quota.QUOTAS.resources, 1341 dict(volumes=2), expire=3600) 1342 1343 expire = timeutils.utcnow() + datetime.timedelta(seconds=3600) 1344 self.assertEqual(['get_project_quotas', 1345 ('quota_reserve', expire, 0, 0), ], self.calls) 1346 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1347 1348 def test_reserve_timedelta_expire(self): 1349 self._mock_get_project_quotas() 1350 self._mock_quota_reserve() 1351 expire_delta = datetime.timedelta(seconds=60) 1352 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1353 quota.QUOTAS.resources, 1354 dict(volumes=2), expire=expire_delta) 1355 1356 expire = timeutils.utcnow() + expire_delta 1357 self.assertEqual(['get_project_quotas', 1358 ('quota_reserve', expire, 0, 0), ], self.calls) 1359 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1360 1361 def test_reserve_datetime_expire(self): 1362 self._mock_get_project_quotas() 1363 self._mock_quota_reserve() 1364 expire = timeutils.utcnow() + datetime.timedelta(seconds=120) 1365 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1366 quota.QUOTAS.resources, 1367 dict(volumes=2), expire=expire) 1368 1369 self.assertEqual(['get_project_quotas', 1370 ('quota_reserve', expire, 0, 0), ], self.calls) 1371 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1372 1373 def test_reserve_until_refresh(self): 1374 self._mock_get_project_quotas() 1375 self._mock_quota_reserve() 1376 self.flags(until_refresh=500) 1377 expire = timeutils.utcnow() + datetime.timedelta(seconds=120) 1378 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1379 quota.QUOTAS.resources, 1380 dict(volumes=2), expire=expire) 1381 1382 self.assertEqual(['get_project_quotas', 1383 ('quota_reserve', expire, 500, 0), ], self.calls) 1384 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1385 1386 def test_reserve_max_age(self): 1387 self._mock_get_project_quotas() 1388 self._mock_quota_reserve() 1389 self.flags(max_age=86400) 1390 expire = timeutils.utcnow() + datetime.timedelta(seconds=120) 1391 result = self.driver.reserve(FakeContext('test_project', 'test_class'), 1392 quota.QUOTAS.resources, 1393 dict(volumes=2), expire=expire) 1394 1395 self.assertEqual(['get_project_quotas', 1396 ('quota_reserve', expire, 0, 86400), ], self.calls) 1397 self.assertEqual(['resv-1', 'resv-2', 'resv-3'], result) 1398 1399 def _mock_quota_destroy_by_project(self): 1400 def fake_quota_destroy_by_project(context, project_id): 1401 self.calls.append(('quota_destroy_by_project', project_id)) 1402 return None 1403 self.mock_object(sqa_api, 'quota_destroy_by_project', 1404 fake_quota_destroy_by_project) 1405 1406 def test_destroy_quota_by_project(self): 1407 self._mock_quota_destroy_by_project() 1408 self.driver.destroy_by_project(FakeContext('test_project', 1409 'test_class'), 1410 'test_project') 1411 self.assertEqual([('quota_destroy_by_project', ('test_project')), ], 1412 self.calls) 1413 1414 1415class NestedDbQuotaDriverBaseTestCase(DbQuotaDriverBaseTestCase): 1416 def setUp(self): 1417 super(NestedDbQuotaDriverBaseTestCase, self).setUp() 1418 self.context = context.RequestContext('user_id', 1419 'project_id', 1420 is_admin=True, 1421 auth_token="fake_token") 1422 self.auth_url = 'http://localhost:5000' 1423 self._child_proj_id = 'child_id' 1424 self._non_child_proj_id = 'non_child_id' 1425 1426 keystone_mock = mock.Mock() 1427 keystone_mock.version = 'v3' 1428 1429 class FakeProject(object): 1430 def __init__(self, parent_id): 1431 self.parent_id = parent_id 1432 self.parents = {parent_id: None} 1433 self.domain_id = 'default' 1434 1435 def fake_get_project(project_id, subtree_as_ids=False, 1436 parents_as_ids=False): 1437 # Enable imitation of projects with and without parents 1438 if project_id == self._child_proj_id: 1439 return FakeProject('parent_id') 1440 else: 1441 return FakeProject(None) 1442 1443 keystone_mock.projects.get.side_effect = fake_get_project 1444 1445 def _keystone_mock(self): 1446 return keystone_mock 1447 1448 keystone_patcher = mock.patch('cinder.quota_utils._keystone_client', 1449 _keystone_mock) 1450 keystone_patcher.start() 1451 self.addCleanup(keystone_patcher.stop) 1452 1453 self.fixture = self.useFixture(config_fixture.Config(CONF)) 1454 self.fixture.config(auth_uri=self.auth_url, group='keystone_authtoken') 1455 self.driver = quota.NestedDbQuotaDriver() 1456 1457 def _mock_get_by_subproject(self): 1458 def fake_qgabp(context, project_id): 1459 self.calls.append('quota_get_all_by_project') 1460 return dict(volumes=10, gigabytes=50, reserved=0) 1461 1462 def fake_qugabp(context, project_id): 1463 self.calls.append('quota_usage_get_all_by_project') 1464 return dict(volumes=dict(in_use=2, reserved=0), 1465 gigabytes=dict(in_use=10, reserved=0)) 1466 1467 self.mock_object(db, 'quota_get_all_by_project', fake_qgabp) 1468 self.mock_object(db, 'quota_usage_get_all_by_project', fake_qugabp) 1469 1470 self._mock_quota_class_get_all_by_name() 1471 1472 1473class NestedDbQuotaDriverTestCase(NestedDbQuotaDriverBaseTestCase): 1474 def test_get_defaults(self): 1475 self._mock_volume_type_get_all() 1476 1477 # Test for child project defaults 1478 result = self.driver.get_defaults(self.context, 1479 quota.QUOTAS.resources, 1480 self._child_proj_id) 1481 self.assertEqual(self._default_quotas_child, result) 1482 1483 # Test for non-child project defaults 1484 result = self.driver.get_defaults(self.context, 1485 quota.QUOTAS.resources, 1486 self._non_child_proj_id) 1487 self.assertEqual(self._default_quotas_non_child, result) 1488 1489 def test_subproject_enforce_defaults(self): 1490 # Non-child defaults should allow volume to get created 1491 self.driver.reserve(self.context, 1492 quota.QUOTAS.resources, 1493 {'volumes': 1, 'gigabytes': 1}, 1494 project_id=self._non_child_proj_id) 1495 1496 # Child defaults should not allow volume to be created 1497 self.assertRaises(exception.OverQuota, 1498 self.driver.reserve, self.context, 1499 quota.QUOTAS.resources, 1500 {'volumes': 1, 'gigabytes': 1}, 1501 project_id=self._child_proj_id) 1502 1503 def test_get_subproject_quotas(self): 1504 self._mock_get_by_subproject() 1505 self._mock_volume_type_get_all() 1506 self._mock_allocated_get_all_by_project(allocated_quota=True) 1507 result = self.driver.get_project_quotas( 1508 self.context, 1509 quota.QUOTAS.resources, self._child_proj_id) 1510 1511 self.assertEqual(['quota_get_all_by_project', 1512 'quota_usage_get_all_by_project', 1513 'quota_allocated_get_all_by_project', ], self.calls) 1514 self.assertEqual(dict(volumes=dict(limit=10, 1515 in_use=2, 1516 reserved=0, 1517 allocated=3, ), 1518 snapshots=dict(limit=0, 1519 in_use=0, 1520 reserved=0, 1521 allocated=0, ), 1522 gigabytes=dict(limit=50, 1523 in_use=10, 1524 reserved=0, 1525 allocated=0, ), 1526 backups=dict(limit=0, 1527 in_use=0, 1528 reserved=0, 1529 allocated=0, ), 1530 backup_gigabytes=dict(limit=0, 1531 in_use=0, 1532 reserved=0, 1533 allocated=0, ), 1534 per_volume_gigabytes=dict(in_use=0, 1535 limit=0, 1536 reserved=0, 1537 allocated=0) 1538 ), result) 1539 1540 1541class NestedQuotaValidation(NestedDbQuotaDriverBaseTestCase): 1542 def setUp(self): 1543 super(NestedQuotaValidation, self).setUp() 1544 r""" 1545 Quota hierarchy setup like so 1546 +-----------+ 1547 | | 1548 | A | 1549 | / \ | 1550 | B C | 1551 | / | 1552 | D | 1553 +-----------+ 1554 """ 1555 self.project_tree = {'A': {'B': {'D': None}, 'C': None}} 1556 self.proj_vals = { 1557 'A': {'limit': 7, 'in_use': 1, 'alloc': 6}, 1558 'B': {'limit': 3, 'in_use': 1, 'alloc': 2}, 1559 'D': {'limit': 2, 'in_use': 0}, 1560 'C': {'limit': 3, 'in_use': 3}, 1561 } 1562 1563 # Just using one resource currently for simplicity of test 1564 self.resources = {'volumes': quota.ReservableResource( 1565 'volumes', '_sync_volumes', 'quota_volumes')} 1566 1567 to_patch = [('cinder.db.quota_allocated_get_all_by_project', 1568 self._fake_quota_allocated_get_all_by_project), 1569 ('cinder.db.quota_get_all_by_project', 1570 self._fake_quota_get_all_by_project), 1571 ('cinder.db.quota_usage_get_all_by_project', 1572 self._fake_quota_usage_get_all_by_project)] 1573 1574 for patch_path, patch_obj in to_patch: 1575 patcher = mock.patch(patch_path, patch_obj) 1576 patcher.start() 1577 self.addCleanup(patcher.stop) 1578 1579 def _fake_quota_get_all_by_project(self, context, project_id): 1580 return {'volumes': self.proj_vals[project_id]['limit']} 1581 1582 def _fake_quota_usage_get_all_by_project(self, context, project_id): 1583 return {'volumes': self.proj_vals[project_id]} 1584 1585 def _fake_quota_allocated_get_all_by_project(self, context, project_id, 1586 session=None): 1587 ret = {'project_id': project_id} 1588 proj_val = self.proj_vals[project_id] 1589 if 'alloc' in proj_val: 1590 ret['volumes'] = proj_val['alloc'] 1591 return ret 1592 1593 def test_validate_nested_quotas(self): 1594 self.driver.validate_nested_setup(self.context, 1595 self.resources, self.project_tree) 1596 1597 # Fail because 7 - 2 < 3 + 3 1598 self.proj_vals['A']['in_use'] = 2 1599 self.assertRaises(exception.InvalidNestedQuotaSetup, 1600 self.driver.validate_nested_setup, 1601 self.context, 1602 self.resources, self.project_tree) 1603 self.proj_vals['A']['in_use'] = 1 1604 1605 # Fail because 7 - 1 < 3 + 7 1606 self.proj_vals['C']['limit'] = 7 1607 self.assertRaises(exception.InvalidNestedQuotaSetup, 1608 self.driver.validate_nested_setup, 1609 self.context, 1610 self.resources, self.project_tree) 1611 self.proj_vals['C']['limit'] = 3 1612 1613 # Fail because 3 < 4 1614 self.proj_vals['D']['limit'] = 4 1615 self.assertRaises(exception.InvalidNestedQuotaSetup, 1616 self.driver.validate_nested_setup, 1617 self.context, 1618 self.resources, self.project_tree) 1619 self.proj_vals['D']['limit'] = 2 1620 1621 def test_validate_nested_quotas_usage_over_limit(self): 1622 self.proj_vals['D']['in_use'] = 5 1623 self.assertRaises(exception.InvalidNestedQuotaSetup, 1624 self.driver.validate_nested_setup, 1625 self.context, self.resources, self.project_tree) 1626 1627 def test_validate_nested_quota_bad_allocated_quotas(self): 1628 self.proj_vals['A']['alloc'] = 5 1629 self.proj_vals['B']['alloc'] = 8 1630 self.assertRaises(exception.InvalidNestedQuotaSetup, 1631 self.driver.validate_nested_setup, 1632 self.context, self.resources, self.project_tree) 1633 1634 def test_validate_nested_quota_negative_child_limits(self): 1635 # Redefining the project limits with -1, doing it all in this test 1636 # for readability 1637 self.proj_vals = { 1638 'A': {'limit': 8, 'in_use': 1}, 1639 'B': {'limit': -1, 'in_use': 3}, 1640 'D': {'limit': 4, 'in_use': 0}, 1641 'C': {'limit': 2, 'in_use': 2}, 1642 } 1643 1644 # A's child usage is 3 (from B) + 4 (from D) + 2 (from C) = 9 1645 self.assertRaises(exception.InvalidNestedQuotaSetup, 1646 self.driver.validate_nested_setup, 1647 self.context, self.resources, self.project_tree) 1648 1649 self.proj_vals['D']['limit'] = 2 1650 self.driver.validate_nested_setup( 1651 self.context, self.resources, self.project_tree, 1652 fix_allocated_quotas=True) 1653 1654 def test_get_cur_project_allocated(self): 1655 # Redefining the project limits with -1, doing it all in this test 1656 # for readability 1657 self.proj_vals = { 1658 # Allocated are here to simulate a bad existing value 1659 'A': {'limit': 8, 'in_use': 1, 'alloc': 6}, 1660 'B': {'limit': -1, 'in_use': 3, 'alloc': 2}, 1661 'D': {'limit': 1, 'in_use': 0}, 1662 'C': {'limit': 2, 'in_use': 2}, 1663 } 1664 1665 self.driver._allocated = {} 1666 allocated_a = self.driver._get_cur_project_allocated( 1667 self.context, self.resources['volumes'], 1668 self.project_tree) 1669 1670 # A's allocated will be: 1671 # 2 (from C's limit) + 3 (from B's in-use) + 1 (from D's limit) = 6 1672 self.assertEqual(6, allocated_a) 1673 1674 # B's allocated value should also be calculated and cached as part 1675 # of A's calculation 1676 self.assertEqual(1, self.driver._allocated['B']['volumes']) 1677 1678 1679class FakeSession(object): 1680 def begin(self): 1681 return self 1682 1683 def __enter__(self): 1684 return self 1685 1686 def __exit__(self, exc_type, exc_value, exc_traceback): 1687 return False 1688 1689 def query(self, *args, **kwargs): 1690 pass 1691 1692 1693class FakeUsage(sqa_models.QuotaUsage): 1694 def save(self, *args, **kwargs): 1695 pass 1696 1697 1698class QuotaReserveSqlAlchemyTestCase(test.TestCase): 1699 # cinder.db.sqlalchemy.api.quota_reserve is so complex it needs its 1700 # own test case, and since it's a quota manipulator, this is the 1701 # best place to put it... 1702 1703 def setUp(self): 1704 super(QuotaReserveSqlAlchemyTestCase, self).setUp() 1705 1706 self.sync_called = set() 1707 1708 def make_sync(res_name): 1709 def fake_sync(context, project_id, volume_type_id=None, 1710 volume_type_name=None, session=None): 1711 self.sync_called.add(res_name) 1712 if res_name in self.usages: 1713 if self.usages[res_name].in_use < 0: 1714 return {res_name: 2} 1715 else: 1716 return {res_name: self.usages[res_name].in_use - 1} 1717 return {res_name: 0} 1718 return fake_sync 1719 1720 self.resources = {} 1721 QUOTA_SYNC_FUNCTIONS = {} 1722 for res_name in ('volumes', 'gigabytes'): 1723 res = quota.ReservableResource(res_name, '_sync_%s' % res_name) 1724 QUOTA_SYNC_FUNCTIONS['_sync_%s' % res_name] = make_sync(res_name) 1725 self.resources[res_name] = res 1726 1727 self.mock_object(sqa_api, 'QUOTA_SYNC_FUNCTIONS', QUOTA_SYNC_FUNCTIONS) 1728 self.expire = timeutils.utcnow() + datetime.timedelta(seconds=3600) 1729 1730 self.usages = {} 1731 self.usages_created = {} 1732 self.reservations_created = {} 1733 1734 def fake_get_session(): 1735 return FakeSession() 1736 1737 def fake_get_quota_usages(context, session, project_id, 1738 resources=None): 1739 return self.usages.copy() 1740 1741 def fake_quota_usage_create(context, project_id, resource, in_use, 1742 reserved, until_refresh, session=None, 1743 save=True): 1744 quota_usage_ref = self._make_quota_usage( 1745 project_id, resource, in_use, reserved, until_refresh, 1746 timeutils.utcnow(), timeutils.utcnow()) 1747 1748 self.usages_created[resource] = quota_usage_ref 1749 1750 return quota_usage_ref 1751 1752 def fake_reservation_create(context, uuid, usage_id, project_id, 1753 resource, delta, expire, session=None, 1754 allocated_id=None): 1755 reservation_ref = self._make_reservation( 1756 uuid, usage_id, project_id, resource, delta, expire, 1757 timeutils.utcnow(), timeutils.utcnow(), allocated_id) 1758 1759 self.reservations_created[resource] = reservation_ref 1760 1761 return reservation_ref 1762 1763 self.mock_object(sqa_api, 'get_session', 1764 fake_get_session) 1765 self.mock_object(sqa_api, '_get_quota_usages', 1766 fake_get_quota_usages) 1767 self.mock_object(sqa_api, '_quota_usage_create', 1768 fake_quota_usage_create) 1769 self.mock_object(sqa_api, '_reservation_create', 1770 fake_reservation_create) 1771 1772 patcher = mock.patch.object(timeutils, 'utcnow') 1773 self.addCleanup(patcher.stop) 1774 self.mock_utcnow = patcher.start() 1775 self.mock_utcnow.return_value = datetime.datetime.utcnow() 1776 1777 def _make_quota_usage(self, project_id, resource, in_use, reserved, 1778 until_refresh, created_at, updated_at): 1779 quota_usage_ref = FakeUsage() 1780 quota_usage_ref.id = len(self.usages) + len(self.usages_created) 1781 quota_usage_ref.project_id = project_id 1782 quota_usage_ref.resource = resource 1783 quota_usage_ref.in_use = in_use 1784 quota_usage_ref.reserved = reserved 1785 quota_usage_ref.until_refresh = until_refresh 1786 quota_usage_ref.created_at = created_at 1787 quota_usage_ref.updated_at = updated_at 1788 quota_usage_ref.deleted_at = None 1789 quota_usage_ref.deleted = False 1790 1791 return quota_usage_ref 1792 1793 def init_usage(self, project_id, resource, in_use, reserved, 1794 until_refresh=None, created_at=None, updated_at=None): 1795 if created_at is None: 1796 created_at = timeutils.utcnow() 1797 if updated_at is None: 1798 updated_at = timeutils.utcnow() 1799 1800 quota_usage_ref = self._make_quota_usage(project_id, resource, in_use, 1801 reserved, until_refresh, 1802 created_at, updated_at) 1803 1804 self.usages[resource] = quota_usage_ref 1805 1806 def compare_usage(self, usage_dict, expected): 1807 for usage in expected: 1808 resource = usage['resource'] 1809 for key, value in usage.items(): 1810 actual = getattr(usage_dict[resource], key) 1811 self.assertEqual(value, actual, 1812 "%s != %s on usage for resource %s" % 1813 (actual, value, resource)) 1814 1815 def _make_reservation(self, uuid, usage_id, project_id, resource, 1816 delta, expire, created_at, updated_at, alloc_id): 1817 reservation_ref = sqa_models.Reservation() 1818 reservation_ref.id = len(self.reservations_created) 1819 reservation_ref.uuid = uuid 1820 reservation_ref.usage_id = usage_id 1821 reservation_ref.project_id = project_id 1822 reservation_ref.resource = resource 1823 reservation_ref.delta = delta 1824 reservation_ref.expire = expire 1825 reservation_ref.created_at = created_at 1826 reservation_ref.updated_at = updated_at 1827 reservation_ref.deleted_at = None 1828 reservation_ref.deleted = False 1829 reservation_ref.allocated_id = alloc_id 1830 1831 return reservation_ref 1832 1833 def compare_reservation(self, reservations, expected): 1834 reservations = set(reservations) 1835 for resv in expected: 1836 resource = resv['resource'] 1837 resv_obj = self.reservations_created[resource] 1838 1839 self.assertIn(resv_obj.uuid, reservations) 1840 reservations.discard(resv_obj.uuid) 1841 1842 for key, value in resv.items(): 1843 actual = getattr(resv_obj, key) 1844 self.assertEqual(value, actual, 1845 "%s != %s on reservation for resource %s" % 1846 (actual, value, resource)) 1847 1848 self.assertEqual(0, len(reservations)) 1849 1850 def _mock_allocated_get_all_by_project(self, allocated_quota=False): 1851 def fake_qagabp(context, project_id, session=None): 1852 self.assertEqual('test_project', project_id) 1853 self.assertIsNotNone(session) 1854 if allocated_quota: 1855 return dict(project_id=project_id, volumes=3, 1856 gigabytes = 2 * 1024) 1857 return dict(project_id=project_id) 1858 1859 self.mock_object(sqa_api, 'quota_allocated_get_all_by_project', 1860 fake_qagabp) 1861 1862 def test_quota_reserve_with_allocated(self): 1863 context = FakeContext('test_project', 'test_class') 1864 # Allocated quota for volume will be updated for 3 1865 self._mock_allocated_get_all_by_project(allocated_quota=True) 1866 # Quota limited for volume updated for 10 1867 quotas = dict(volumes=10, 1868 gigabytes=10 * 1024, ) 1869 # Try reserve 7 volumes 1870 deltas = dict(volumes=7, 1871 gigabytes=2 * 1024, ) 1872 result = sqa_api.quota_reserve(context, self.resources, quotas, 1873 deltas, self.expire, 5, 0) 1874 # The reservation works 1875 self.compare_reservation( 1876 result, 1877 [dict(resource='volumes', 1878 usage_id=self.usages_created['volumes'], 1879 project_id='test_project', 1880 delta=7), 1881 dict(resource='gigabytes', 1882 usage_id=self.usages_created['gigabytes'], 1883 delta=2 * 1024), ]) 1884 1885 # But if we try reserve 8 volumes(more free quota that we have) 1886 deltas = dict(volumes=8, 1887 gigabytes=2 * 1024, ) 1888 1889 self.assertRaises(exception.OverQuota, 1890 sqa_api.quota_reserve, 1891 context, self.resources, quotas, 1892 deltas, self.expire, 0, 0) 1893 1894 def test_quota_reserve_create_usages(self): 1895 context = FakeContext('test_project', 'test_class') 1896 quotas = dict(volumes=5, 1897 gigabytes=10 * 1024, ) 1898 deltas = dict(volumes=2, 1899 gigabytes=2 * 1024, ) 1900 self._mock_allocated_get_all_by_project() 1901 result = sqa_api.quota_reserve(context, self.resources, quotas, 1902 deltas, self.expire, 0, 0) 1903 1904 self.assertEqual(set(['volumes', 'gigabytes']), self.sync_called) 1905 self.compare_usage(self.usages_created, 1906 [dict(resource='volumes', 1907 project_id='test_project', 1908 in_use=0, 1909 reserved=2, 1910 until_refresh=None), 1911 dict(resource='gigabytes', 1912 project_id='test_project', 1913 in_use=0, 1914 reserved=2 * 1024, 1915 until_refresh=None), ]) 1916 self.compare_reservation( 1917 result, 1918 [dict(resource='volumes', 1919 usage_id=self.usages_created['volumes'], 1920 project_id='test_project', 1921 delta=2), 1922 dict(resource='gigabytes', 1923 usage_id=self.usages_created['gigabytes'], 1924 delta=2 * 1024), ]) 1925 1926 def test_quota_reserve_negative_in_use(self): 1927 self.init_usage('test_project', 'volumes', -1, 0, until_refresh=1) 1928 self.init_usage('test_project', 'gigabytes', -1, 0, until_refresh=1) 1929 context = FakeContext('test_project', 'test_class') 1930 quotas = dict(volumes=5, 1931 gigabytes=10 * 1024, ) 1932 deltas = dict(volumes=2, 1933 gigabytes=2 * 1024, ) 1934 self._mock_allocated_get_all_by_project() 1935 result = sqa_api.quota_reserve(context, self.resources, quotas, 1936 deltas, self.expire, 5, 0) 1937 1938 self.assertEqual(set(['volumes', 'gigabytes']), self.sync_called) 1939 self.compare_usage(self.usages, [dict(resource='volumes', 1940 project_id='test_project', 1941 in_use=2, 1942 reserved=2, 1943 until_refresh=5), 1944 dict(resource='gigabytes', 1945 project_id='test_project', 1946 in_use=2, 1947 reserved=2 * 1024, 1948 until_refresh=5), ]) 1949 self.assertEqual({}, self.usages_created) 1950 self.compare_reservation(result, 1951 [dict(resource='volumes', 1952 usage_id=self.usages['volumes'], 1953 project_id='test_project', 1954 delta=2), 1955 dict(resource='gigabytes', 1956 usage_id=self.usages['gigabytes'], 1957 delta=2 * 1024), ]) 1958 1959 def test_quota_reserve_until_refresh(self): 1960 self.init_usage('test_project', 'volumes', 3, 0, until_refresh=1) 1961 self.init_usage('test_project', 'gigabytes', 3, 0, until_refresh=1) 1962 context = FakeContext('test_project', 'test_class') 1963 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 1964 deltas = dict(volumes=2, gigabytes=2 * 1024, ) 1965 self._mock_allocated_get_all_by_project() 1966 result = sqa_api.quota_reserve(context, self.resources, quotas, 1967 deltas, self.expire, 5, 0) 1968 1969 self.assertEqual(set(['volumes', 'gigabytes']), self.sync_called) 1970 self.compare_usage(self.usages, [dict(resource='volumes', 1971 project_id='test_project', 1972 in_use=2, 1973 reserved=2, 1974 until_refresh=5), 1975 dict(resource='gigabytes', 1976 project_id='test_project', 1977 in_use=2, 1978 reserved=2 * 1024, 1979 until_refresh=5), ]) 1980 self.assertEqual({}, self.usages_created) 1981 self.compare_reservation(result, 1982 [dict(resource='volumes', 1983 usage_id=self.usages['volumes'], 1984 project_id='test_project', 1985 delta=2), 1986 dict(resource='gigabytes', 1987 usage_id=self.usages['gigabytes'], 1988 delta=2 * 1024), ]) 1989 1990 def test_quota_reserve_max_age(self): 1991 max_age = 3600 1992 record_created = (timeutils.utcnow() - 1993 datetime.timedelta(seconds=max_age)) 1994 self.init_usage('test_project', 'volumes', 3, 0, 1995 created_at=record_created, updated_at=record_created) 1996 self.init_usage('test_project', 'gigabytes', 3, 0, 1997 created_at=record_created, updated_at=record_created) 1998 context = FakeContext('test_project', 'test_class') 1999 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2000 deltas = dict(volumes=2, gigabytes=2 * 1024, ) 2001 self._mock_allocated_get_all_by_project() 2002 result = sqa_api.quota_reserve(context, self.resources, quotas, 2003 deltas, self.expire, 0, max_age) 2004 2005 self.assertEqual(set(['volumes', 'gigabytes']), self.sync_called) 2006 self.compare_usage(self.usages, [dict(resource='volumes', 2007 project_id='test_project', 2008 in_use=2, 2009 reserved=2, 2010 until_refresh=None), 2011 dict(resource='gigabytes', 2012 project_id='test_project', 2013 in_use=2, 2014 reserved=2 * 1024, 2015 until_refresh=None), ]) 2016 self.assertEqual({}, self.usages_created) 2017 self.compare_reservation(result, 2018 [dict(resource='volumes', 2019 usage_id=self.usages['volumes'], 2020 project_id='test_project', 2021 delta=2), 2022 dict(resource='gigabytes', 2023 usage_id=self.usages['gigabytes'], 2024 delta=2 * 1024), ]) 2025 2026 def test_quota_reserve_max_age_negative(self): 2027 max_age = 3600 2028 record_created = (timeutils.utcnow() + 2029 datetime.timedelta(seconds=max_age)) 2030 self.init_usage('test_project', 'volumes', 3, 0, 2031 created_at=record_created, updated_at=record_created) 2032 self.init_usage('test_project', 'gigabytes', 3, 0, 2033 created_at=record_created, updated_at=record_created) 2034 context = FakeContext('test_project', 'test_class') 2035 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2036 deltas = dict(volumes=2, gigabytes=2 * 1024, ) 2037 self._mock_allocated_get_all_by_project() 2038 result = sqa_api.quota_reserve(context, self.resources, quotas, 2039 deltas, self.expire, 0, max_age) 2040 2041 self.assertEqual(set(), self.sync_called) 2042 self.compare_usage(self.usages, [dict(resource='volumes', 2043 project_id='test_project', 2044 in_use=3, 2045 reserved=2, 2046 until_refresh=None), 2047 dict(resource='gigabytes', 2048 project_id='test_project', 2049 in_use=3, 2050 reserved=2 * 1024, 2051 until_refresh=None), ]) 2052 self.assertEqual({}, self.usages_created) 2053 self.compare_reservation(result, 2054 [dict(resource='volumes', 2055 usage_id=self.usages['volumes'], 2056 project_id='test_project', 2057 delta=2), 2058 dict(resource='gigabytes', 2059 usage_id=self.usages['gigabytes'], 2060 delta=2 * 1024), ]) 2061 2062 def test_quota_reserve_no_refresh(self): 2063 self.init_usage('test_project', 'volumes', 3, 0) 2064 self.init_usage('test_project', 'gigabytes', 3, 0) 2065 context = FakeContext('test_project', 'test_class') 2066 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2067 deltas = dict(volumes=2, gigabytes=2 * 1024, ) 2068 self._mock_allocated_get_all_by_project() 2069 result = sqa_api.quota_reserve(context, self.resources, quotas, 2070 deltas, self.expire, 0, 0) 2071 2072 self.assertEqual(set([]), self.sync_called) 2073 self.compare_usage(self.usages, [dict(resource='volumes', 2074 project_id='test_project', 2075 in_use=3, 2076 reserved=2, 2077 until_refresh=None), 2078 dict(resource='gigabytes', 2079 project_id='test_project', 2080 in_use=3, 2081 reserved=2 * 1024, 2082 until_refresh=None), ]) 2083 self.assertEqual({}, self.usages_created) 2084 self.compare_reservation(result, 2085 [dict(resource='volumes', 2086 usage_id=self.usages['volumes'], 2087 project_id='test_project', 2088 delta=2), 2089 dict(resource='gigabytes', 2090 usage_id=self.usages['gigabytes'], 2091 delta=2 * 1024), ]) 2092 2093 def test_quota_reserve_unders(self): 2094 self.init_usage('test_project', 'volumes', 1, 0) 2095 self.init_usage('test_project', 'gigabytes', 1 * 1024, 0) 2096 context = FakeContext('test_project', 'test_class') 2097 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2098 deltas = dict(volumes=-2, gigabytes=-2 * 1024, ) 2099 self._mock_allocated_get_all_by_project() 2100 result = sqa_api.quota_reserve(context, self.resources, quotas, 2101 deltas, self.expire, 0, 0) 2102 2103 self.assertEqual(set([]), self.sync_called) 2104 self.compare_usage(self.usages, [dict(resource='volumes', 2105 project_id='test_project', 2106 in_use=1, 2107 reserved=0, 2108 until_refresh=None), 2109 dict(resource='gigabytes', 2110 project_id='test_project', 2111 in_use=1 * 1024, 2112 reserved=0, 2113 until_refresh=None), ]) 2114 self.assertEqual({}, self.usages_created) 2115 self.compare_reservation(result, 2116 [dict(resource='volumes', 2117 usage_id=self.usages['volumes'], 2118 project_id='test_project', 2119 delta=-2), 2120 dict(resource='gigabytes', 2121 usage_id=self.usages['gigabytes'], 2122 delta=-2 * 1024), ]) 2123 2124 def test_quota_reserve_overs(self): 2125 self.init_usage('test_project', 'volumes', 4, 0) 2126 self.init_usage('test_project', 'gigabytes', 10 * 1024, 0) 2127 context = FakeContext('test_project', 'test_class') 2128 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2129 deltas = dict(volumes=2, gigabytes=2 * 1024, ) 2130 self._mock_allocated_get_all_by_project() 2131 self.assertRaises(exception.OverQuota, 2132 sqa_api.quota_reserve, 2133 context, self.resources, quotas, 2134 deltas, self.expire, 0, 0) 2135 2136 self.assertEqual(set([]), self.sync_called) 2137 self.compare_usage(self.usages, [dict(resource='volumes', 2138 project_id='test_project', 2139 in_use=4, 2140 reserved=0, 2141 until_refresh=None), 2142 dict(resource='gigabytes', 2143 project_id='test_project', 2144 in_use=10 * 1024, 2145 reserved=0, 2146 until_refresh=None), ]) 2147 self.assertEqual({}, self.usages_created) 2148 self.assertEqual({}, self.reservations_created) 2149 2150 def test_quota_reserve_reduction(self): 2151 self.init_usage('test_project', 'volumes', 10, 0) 2152 self.init_usage('test_project', 'gigabytes', 20 * 1024, 0) 2153 context = FakeContext('test_project', 'test_class') 2154 quotas = dict(volumes=5, gigabytes=10 * 1024, ) 2155 deltas = dict(volumes=-2, gigabytes=-2 * 1024, ) 2156 self._mock_allocated_get_all_by_project() 2157 result = sqa_api.quota_reserve(context, self.resources, quotas, 2158 deltas, self.expire, 0, 0) 2159 2160 self.assertEqual(set([]), self.sync_called) 2161 self.compare_usage(self.usages, [dict(resource='volumes', 2162 project_id='test_project', 2163 in_use=10, 2164 reserved=0, 2165 until_refresh=None), 2166 dict(resource='gigabytes', 2167 project_id='test_project', 2168 in_use=20 * 1024, 2169 reserved=0, 2170 until_refresh=None), ]) 2171 self.assertEqual({}, self.usages_created) 2172 self.compare_reservation(result, 2173 [dict(resource='volumes', 2174 usage_id=self.usages['volumes'], 2175 project_id='test_project', 2176 delta=-2), 2177 dict(resource='gigabytes', 2178 usage_id=self.usages['gigabytes'], 2179 project_id='test_project', 2180 delta=-2 * 1024), ]) 2181 2182 2183class QuotaVolumeTypeReservationTestCase(test.TestCase): 2184 2185 def setUp(self): 2186 super(QuotaVolumeTypeReservationTestCase, self).setUp() 2187 2188 self.volume_type_name = CONF.default_volume_type 2189 self.volume_type = db.volume_type_create( 2190 context.get_admin_context(), 2191 dict(name=self.volume_type_name)) 2192 2193 @mock.patch.object(quota.QUOTAS, 'reserve') 2194 @mock.patch.object(quota.QUOTAS, 'add_volume_type_opts') 2195 def test_volume_type_reservation(self, 2196 mock_add_volume_type_opts, 2197 mock_reserve): 2198 my_context = FakeContext('MyProject', None) 2199 volume = {'name': 'my_vol_name', 2200 'id': 'my_vol_id', 2201 'size': '1', 2202 'project_id': 'vol_project_id', 2203 } 2204 reserve_opts = {'volumes': 1, 'gigabytes': volume['size']} 2205 quota_utils.get_volume_type_reservation(my_context, 2206 volume, 2207 self.volume_type['id']) 2208 mock_add_volume_type_opts.assert_called_once_with( 2209 my_context, 2210 reserve_opts, 2211 self.volume_type['id']) 2212 mock_reserve.assert_called_once_with(my_context, 2213 project_id='vol_project_id', 2214 gigabytes='1', 2215 volumes=1) 2216 2217 @mock.patch.object(quota.QUOTAS, 'reserve') 2218 def test_volume_type_reservation_with_type_only(self, mock_reserve): 2219 my_context = FakeContext('MyProject', None) 2220 volume = {'name': 'my_vol_name', 2221 'id': 'my_vol_id', 2222 'size': '1', 2223 'project_id': 'vol_project_id', 2224 } 2225 quota_utils.get_volume_type_reservation(my_context, 2226 volume, 2227 self.volume_type['id'], 2228 reserve_vol_type_only=True) 2229 vtype_volume_quota = "%s_%s" % ('volumes', self.volume_type['name']) 2230 vtype_size_quota = "%s_%s" % ('gigabytes', self.volume_type['name']) 2231 reserve_opts = {vtype_volume_quota: 1, 2232 vtype_size_quota: volume['size']} 2233 mock_reserve.assert_called_once_with(my_context, 2234 project_id='vol_project_id', 2235 **reserve_opts) 2236