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