trove/trove/tests/unittests/quota/test_quota.py

618 lines
26 KiB
Python

# Copyright 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import Mock, MagicMock, patch
from testtools import skipIf
from trove.common import cfg
from trove.common import exception
from trove.db.models import DatabaseModelBase
from trove.extensions.mgmt.quota.service import QuotaController
from trove.quota.models import Quota
from trove.quota.models import QuotaUsage
from trove.quota.models import Reservation
from trove.quota.models import Resource
from trove.quota.quota import DbQuotaDriver
from trove.quota.quota import QUOTAS
from trove.quota.quota import run_with_quotas
from trove.tests.unittests import trove_testtools
"""
Unit tests for the classes and functions in DbQuotaDriver.py.
"""
CONF = cfg.CONF
resources = {
Resource.INSTANCES: Resource(Resource.INSTANCES,
'max_instances_per_tenant'),
Resource.VOLUMES: Resource(Resource.VOLUMES, 'max_volumes_per_tenant')
}
FAKE_TENANT1 = "123456"
FAKE_TENANT2 = "654321"
class Run_with_quotasTest(trove_testtools.TestCase):
def setUp(self):
super(Run_with_quotasTest, self).setUp()
self.quota_reserve_orig = QUOTAS.reserve
self.quota_rollback_orig = QUOTAS.rollback
self.quota_commit_orig = QUOTAS.commit
QUOTAS.reserve = Mock()
QUOTAS.rollback = Mock()
QUOTAS.commit = Mock()
def tearDown(self):
super(Run_with_quotasTest, self).tearDown()
QUOTAS.reserve = self.quota_reserve_orig
QUOTAS.rollback = self.quota_rollback_orig
QUOTAS.commit = self.quota_commit_orig
def test_run_with_quotas(self):
f = Mock()
run_with_quotas(FAKE_TENANT1, {'instances': 1, 'volumes': 5}, f)
self.assertTrue(QUOTAS.reserve.called)
self.assertTrue(QUOTAS.commit.called)
self.assertFalse(QUOTAS.rollback.called)
self.assertTrue(f.called)
def test_run_with_quotas_error(self):
f = Mock(side_effect=exception.TroveError())
self.assertRaises(exception.TroveError, run_with_quotas, FAKE_TENANT1,
{'instances': 1, 'volumes': 5}, f)
self.assertTrue(QUOTAS.reserve.called)
self.assertTrue(QUOTAS.rollback.called)
self.assertFalse(QUOTAS.commit.called)
self.assertTrue(f.called)
class QuotaControllerTest(trove_testtools.TestCase):
def setUp(self):
super(QuotaControllerTest, self).setUp()
context = MagicMock()
context.is_admin = True
req = MagicMock()
req.environ = MagicMock()
req.environ.get = MagicMock(return_value=context)
self.req = req
self.controller = QuotaController()
def tearDown(self):
super(QuotaControllerTest, self).tearDown()
def test_update_unknown_resource(self):
body = {'quotas': {'unknown_resource': 5}}
self.assertRaises(exception.QuotaResourceUnknown,
self.controller.update, self.req, body,
FAKE_TENANT1, FAKE_TENANT2)
def test_update_resource_no_value(self):
quota = MagicMock(spec=Quota)
with patch.object(DatabaseModelBase, 'find_by', return_value=quota):
body = {'quotas': {'instances': None}}
result = self.controller.update(self.req, body, FAKE_TENANT1,
FAKE_TENANT2)
self.assertEqual(0, quota.save.call_count)
self.assertEqual(200, result.status)
def test_update_resource_instance(self):
instance_quota = MagicMock(spec=Quota)
with patch.object(DatabaseModelBase, 'find_by',
return_value=instance_quota):
body = {'quotas': {'instances': 2}}
result = self.controller.update(self.req, body, FAKE_TENANT1,
FAKE_TENANT2)
self.assertEqual(1, instance_quota.save.call_count)
self.assertTrue('instances' in result._data['quotas'])
self.assertEqual(200, result.status)
self.assertEqual(2, result._data['quotas']['instances'])
@skipIf(not CONF.trove_volume_support, 'Volume support is not enabled')
def test_update_resource_volume(self):
instance_quota = MagicMock(spec=Quota)
volume_quota = MagicMock(spec=Quota)
def side_effect_func(*args, **kwargs):
return (instance_quota if kwargs['resource'] == 'instances'
else volume_quota)
with patch.object(DatabaseModelBase, 'find_by',
side_effect=side_effect_func):
body = {'quotas': {'instances': None, 'volumes': 10}}
result = self.controller.update(self.req, body, FAKE_TENANT1,
FAKE_TENANT2)
self.assertEqual(0, instance_quota.save.call_count)
self.assertFalse('instances' in result._data['quotas'])
self.assertEqual(1, volume_quota.save.call_count)
self.assertEqual(200, result.status)
self.assertEqual(10, result._data['quotas']['volumes'])
class DbQuotaDriverTest(trove_testtools.TestCase):
def setUp(self):
super(DbQuotaDriverTest, self).setUp()
self.driver = DbQuotaDriver(resources)
self.orig_Quota_find_all = Quota.find_all
self.orig_QuotaUsage_find_all = QuotaUsage.find_all
self.orig_QuotaUsage_find_by = QuotaUsage.find_by
self.orig_Reservation_create = Reservation.create
self.orig_QuotaUsage_create = QuotaUsage.create
self.orig_QuotaUsage_save = QuotaUsage.save
self.orig_Reservation_save = Reservation.save
self.mock_quota_result = Mock()
self.mock_usage_result = Mock()
Quota.find_all = Mock(return_value=self.mock_quota_result)
QuotaUsage.find_all = Mock(return_value=self.mock_usage_result)
def tearDown(self):
super(DbQuotaDriverTest, self).tearDown()
Quota.find_all = self.orig_Quota_find_all
QuotaUsage.find_all = self.orig_QuotaUsage_find_all
QuotaUsage.find_by = self.orig_QuotaUsage_find_by
Reservation.create = self.orig_Reservation_create
QuotaUsage.create = self.orig_QuotaUsage_create
QuotaUsage.save = self.orig_QuotaUsage_save
Reservation.save = self.orig_Reservation_save
def test_get_defaults(self):
defaults = self.driver.get_defaults(resources)
self.assertEqual(CONF.max_instances_per_tenant,
defaults[Resource.INSTANCES])
self.assertEqual(CONF.max_volumes_per_tenant,
defaults[Resource.VOLUMES])
def test_get_quota_by_tenant(self):
FAKE_QUOTAS = [Quota(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
hard_limit=12)]
self.mock_quota_result.all = Mock(return_value=FAKE_QUOTAS)
quota = self.driver.get_quota_by_tenant(FAKE_TENANT1,
Resource.VOLUMES)
self.assertEqual(FAKE_TENANT1, quota.tenant_id)
self.assertEqual(Resource.INSTANCES, quota.resource)
self.assertEqual(12, quota.hard_limit)
def test_get_quota_by_tenant_default(self):
self.mock_quota_result.all = Mock(return_value=[])
quota = self.driver.get_quota_by_tenant(FAKE_TENANT1,
Resource.VOLUMES)
self.assertEqual(FAKE_TENANT1, quota.tenant_id)
self.assertEqual(Resource.VOLUMES, quota.resource)
self.assertEqual(CONF.max_volumes_per_tenant, quota.hard_limit)
def test_get_all_quotas_by_tenant(self):
FAKE_QUOTAS = [Quota(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
hard_limit=22),
Quota(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
hard_limit=15)]
self.mock_quota_result.all = Mock(return_value=FAKE_QUOTAS)
quotas = self.driver.get_all_quotas_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, quotas[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
quotas[Resource.INSTANCES].resource)
self.assertEqual(22, quotas[Resource.INSTANCES].hard_limit)
self.assertEqual(FAKE_TENANT1, quotas[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, quotas[Resource.VOLUMES].resource)
self.assertEqual(15, quotas[Resource.VOLUMES].hard_limit)
def test_get_all_quotas_by_tenant_with_all_default(self):
self.mock_quota_result.all = Mock(return_value=[])
quotas = self.driver.get_all_quotas_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, quotas[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
quotas[Resource.INSTANCES].resource)
self.assertEqual(CONF.max_instances_per_tenant,
quotas[Resource.INSTANCES].hard_limit)
self.assertEqual(FAKE_TENANT1, quotas[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, quotas[Resource.VOLUMES].resource)
self.assertEqual(CONF.max_volumes_per_tenant,
quotas[Resource.VOLUMES].hard_limit)
def test_get_all_quotas_by_tenant_with_one_default(self):
FAKE_QUOTAS = [Quota(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
hard_limit=22)]
self.mock_quota_result.all = Mock(return_value=FAKE_QUOTAS)
quotas = self.driver.get_all_quotas_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, quotas[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
quotas[Resource.INSTANCES].resource)
self.assertEqual(22, quotas[Resource.INSTANCES].hard_limit)
self.assertEqual(FAKE_TENANT1, quotas[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, quotas[Resource.VOLUMES].resource)
self.assertEqual(CONF.max_volumes_per_tenant,
quotas[Resource.VOLUMES].hard_limit)
def test_get_quota_usage_by_tenant(self):
FAKE_QUOTAS = [QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=3,
reserved=1)]
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
usage = self.driver.get_quota_usage_by_tenant(FAKE_TENANT1,
Resource.VOLUMES)
self.assertEqual(FAKE_TENANT1, usage.tenant_id)
self.assertEqual(Resource.VOLUMES, usage.resource)
self.assertEqual(3, usage.in_use)
self.assertEqual(1, usage.reserved)
def test_get_quota_usage_by_tenant_default(self):
FAKE_QUOTA = QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)
self.mock_usage_result.all = Mock(return_value=[])
QuotaUsage.create = Mock(return_value=FAKE_QUOTA)
usage = self.driver.get_quota_usage_by_tenant(FAKE_TENANT1,
Resource.VOLUMES)
self.assertEqual(FAKE_TENANT1, usage.tenant_id)
self.assertEqual(Resource.VOLUMES, usage.resource)
self.assertEqual(0, usage.in_use)
self.assertEqual(0, usage.reserved)
def test_get_all_quota_usages_by_tenant(self):
FAKE_QUOTAS = [QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=2,
reserved=1),
QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=1,
reserved=1)]
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
usages = self.driver.get_all_quota_usages_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, usages[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
usages[Resource.INSTANCES].resource)
self.assertEqual(2, usages[Resource.INSTANCES].in_use)
self.assertEqual(1, usages[Resource.INSTANCES].reserved)
self.assertEqual(FAKE_TENANT1, usages[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, usages[Resource.VOLUMES].resource)
self.assertEqual(1, usages[Resource.VOLUMES].in_use)
self.assertEqual(1, usages[Resource.VOLUMES].reserved)
def test_get_all_quota_usages_by_tenant_with_all_default(self):
FAKE_QUOTAS = [QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=0,
reserved=0),
QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)]
def side_effect_func(*args, **kwargs):
return (FAKE_QUOTAS[0] if kwargs['resource'] == 'instances'
else FAKE_QUOTAS[1])
self.mock_usage_result.all = Mock(return_value=[])
QuotaUsage.create = Mock(side_effect=side_effect_func)
usages = self.driver.get_all_quota_usages_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, usages[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
usages[Resource.INSTANCES].resource)
self.assertEqual(0, usages[Resource.INSTANCES].in_use)
self.assertEqual(0, usages[Resource.INSTANCES].reserved)
self.assertEqual(FAKE_TENANT1, usages[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, usages[Resource.VOLUMES].resource)
self.assertEqual(0, usages[Resource.VOLUMES].in_use)
self.assertEqual(0, usages[Resource.VOLUMES].reserved)
def test_get_all_quota_usages_by_tenant_with_one_default(self):
FAKE_QUOTAS = [QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=0,
reserved=0)]
NEW_FAKE_QUOTA = QuotaUsage(tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
QuotaUsage.create = Mock(return_value=NEW_FAKE_QUOTA)
usages = self.driver.get_all_quota_usages_by_tenant(FAKE_TENANT1,
resources.keys())
self.assertEqual(FAKE_TENANT1, usages[Resource.INSTANCES].tenant_id)
self.assertEqual(Resource.INSTANCES,
usages[Resource.INSTANCES].resource)
self.assertEqual(0, usages[Resource.INSTANCES].in_use)
self.assertEqual(0, usages[Resource.INSTANCES].reserved)
self.assertEqual(FAKE_TENANT1, usages[Resource.VOLUMES].tenant_id)
self.assertEqual(Resource.VOLUMES, usages[Resource.VOLUMES].resource)
self.assertEqual(0, usages[Resource.VOLUMES].in_use)
self.assertEqual(0, usages[Resource.VOLUMES].reserved)
def test_reserve(self):
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=1,
reserved=2),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=1,
reserved=1)]
self.mock_quota_result.all = Mock(return_value=[])
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
QuotaUsage.save = Mock()
Reservation.create = Mock()
# Set up the deltas with the intention that after the reserve call
# the deltas should match usage_id + 1 for both instances and volumes
delta = {'instances': 2, 'volumes': 3}
self.driver.reserve(FAKE_TENANT1, resources, delta)
for _, kw in Reservation.create.call_args_list:
self.assertEqual(kw['usage_id'] + 1, kw['delta'])
self.assertEqual(Reservation.Statuses.RESERVED, kw['status'])
def test_reserve_resource_unknown(self):
delta = {'instances': 10, 'volumes': 2000, 'Fake_resource': 123}
self.assertRaises(exception.QuotaResourceUnknown,
self.driver.reserve,
FAKE_TENANT1,
resources,
delta)
def test_reserve_over_quota(self):
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=0,
reserved=0),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)]
self.mock_quota_result.all = Mock(return_value=[])
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
delta = {'instances': 1, 'volumes': CONF.max_volumes_per_tenant + 1}
self.assertRaises(exception.QuotaExceeded,
self.driver.reserve,
FAKE_TENANT1,
resources,
delta)
def test_reserve_over_quota_with_usage(self):
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=1,
reserved=0),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)]
self.mock_quota_result.all = Mock(return_value=[])
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
max_inst = CONF.max_instances_per_tenant
delta = {'instances': max_inst, 'volumes': 3}
self.assertRaises(exception.QuotaExceeded,
self.driver.reserve,
FAKE_TENANT1,
resources,
delta)
def test_reserve_over_quota_with_reserved(self):
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=1,
reserved=2),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=0,
reserved=0)]
self.mock_quota_result.all = Mock(return_value=[])
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
max_inst = CONF.max_instances_per_tenant
delta = {'instances': max_inst - 1, 'volumes': 2}
self.assertRaises(exception.QuotaExceeded,
self.driver.reserve,
FAKE_TENANT1,
resources,
delta)
def test_reserve_over_quota_but_can_apply_negative_deltas(self):
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=10,
reserved=0),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=50,
reserved=0)]
self.mock_quota_result.all = Mock(return_value=[])
self.mock_usage_result.all = Mock(return_value=FAKE_QUOTAS)
QuotaUsage.save = Mock()
Reservation.create = Mock()
# Set up the deltas with the intention that after the reserve call
# the deltas should match -usage_id for both instances and volumes
delta = {'instances': -1, 'volumes': -2}
self.driver.reserve(FAKE_TENANT1, resources, delta)
for _, kw in Reservation.create.call_args_list:
self.assertEqual(-kw['usage_id'], kw['delta'])
self.assertEqual(Reservation.Statuses.RESERVED, kw['status'])
def test_commit(self):
Reservation.save = Mock()
QuotaUsage.save = Mock()
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=5,
reserved=2),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=1,
reserved=2)]
FAKE_RESERVATIONS = [Reservation(usage_id=1,
delta=1,
status=Reservation.Statuses.RESERVED),
Reservation(usage_id=2,
delta=2,
status=Reservation.Statuses.RESERVED)]
QuotaUsage.find_by = Mock(side_effect=FAKE_QUOTAS)
self.driver.commit(FAKE_RESERVATIONS)
self.assertEqual(6, FAKE_QUOTAS[0].in_use)
self.assertEqual(1, FAKE_QUOTAS[0].reserved)
self.assertEqual(Reservation.Statuses.COMMITTED,
FAKE_RESERVATIONS[0].status)
self.assertEqual(3, FAKE_QUOTAS[1].in_use)
self.assertEqual(0, FAKE_QUOTAS[1].reserved)
self.assertEqual(Reservation.Statuses.COMMITTED,
FAKE_RESERVATIONS[1].status)
def test_commit_cannot_be_less_than_zero(self):
Reservation.save = Mock()
QuotaUsage.save = Mock()
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=0,
reserved=-1)]
FAKE_RESERVATIONS = [Reservation(usage_id=1,
delta=-1,
status=Reservation.Statuses.RESERVED)]
QuotaUsage.find_by = Mock(side_effect=FAKE_QUOTAS)
self.driver.commit(FAKE_RESERVATIONS)
self.assertEqual(0, FAKE_QUOTAS[0].in_use)
self.assertEqual(0, FAKE_QUOTAS[0].reserved)
self.assertEqual(Reservation.Statuses.COMMITTED,
FAKE_RESERVATIONS[0].status)
def test_rollback(self):
Reservation.save = Mock()
QuotaUsage.save = Mock()
FAKE_QUOTAS = [QuotaUsage(id=1,
tenant_id=FAKE_TENANT1,
resource=Resource.INSTANCES,
in_use=5,
reserved=2),
QuotaUsage(id=2,
tenant_id=FAKE_TENANT1,
resource=Resource.VOLUMES,
in_use=1,
reserved=2)]
FAKE_RESERVATIONS = [Reservation(usage_id=1,
delta=1,
status=Reservation.Statuses.RESERVED),
Reservation(usage_id=2,
delta=2,
status=Reservation.Statuses.RESERVED)]
QuotaUsage.find_by = Mock(side_effect=FAKE_QUOTAS)
self.driver.rollback(FAKE_RESERVATIONS)
self.assertEqual(5, FAKE_QUOTAS[0].in_use)
self.assertEqual(1, FAKE_QUOTAS[0].reserved)
self.assertEqual(Reservation.Statuses.ROLLEDBACK,
FAKE_RESERVATIONS[0].status)
self.assertEqual(1, FAKE_QUOTAS[1].in_use)
self.assertEqual(0, FAKE_QUOTAS[1].reserved)
self.assertEqual(Reservation.Statuses.ROLLEDBACK,
FAKE_RESERVATIONS[1].status)