From 22f0e324f3d3172b563aa67e513fe4d9318de2e5 Mon Sep 17 00:00:00 2001 From: Mark McLoughlin Date: Sat, 1 Sep 2012 09:37:15 +0100 Subject: Fix quota reservation expiration Fixes bug #1040942 The db.reservation_expire() function assumes a 'usage' attribute on Reservation objects, but we don't actually define that relationship. The end result is that reservation_expire() currently traceback if it actually needs to expire any reservations. This happens pretty rarely since reservations should only need expiring if they are leaked because of another bug. Also define a test case to actually excercise the expiration code path. Add a missing chain-up to tearDown in test_limits which was causing the get_project_quotas() stub not to be unset and, in turn, the reservation expiration test to fail. Change-Id: Ib61dbf9fd5dfb5badaf05f20c423a69925d83754 --- nova/db/sqlalchemy/models.py | 6 ++++++ nova/tests/api/openstack/compute/test_limits.py | 1 + nova/tests/test_quota.py | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 65d5eb5e0..8b8322276 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -496,6 +496,12 @@ class Reservation(BASE, NovaBase): delta = Column(Integer) expire = Column(DateTime, nullable=False) + usage = relationship( + "QuotaUsage", + foreign_keys=usage_id, + primaryjoin='and_(Reservation.usage_id == QuotaUsage.id,' + 'QuotaUsage.deleted == False)') + class Snapshot(BASE, NovaBase): """Represents a block storage device that can be attached to a VM.""" diff --git a/nova/tests/api/openstack/compute/test_limits.py b/nova/tests/api/openstack/compute/test_limits.py index 059deabf7..2a3038267 100644 --- a/nova/tests/api/openstack/compute/test_limits.py +++ b/nova/tests/api/openstack/compute/test_limits.py @@ -794,6 +794,7 @@ class WsgiLimiterProxyTest(BaseLimitTestSuite): def tearDown(self): # restore original HTTPConnection object httplib.HTTPConnection = self.oldHTTPConnection + super(WsgiLimiterProxyTest, self).tearDown() class LimitsViewBuilderTest(test.TestCase): diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 794c578d6..8dddef173 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -228,6 +228,26 @@ class QuotaIntegrationTestCase(test.TestCase): self.assertRaises(exception.QuotaError, self._create_with_injected_files, files) + def test_reservation_expire(self): + timeutils.set_time_override() + + def assertInstancesReserved(reserved): + result = quota.QUOTAS.get_project_quotas(self.context, + self.context.project_id) + self.assertEqual(result['instances']['reserved'], reserved) + + quota.QUOTAS.reserve(self.context, + expire=60, + instances=2) + + assertInstancesReserved(2) + + timeutils.advance_time_seconds(80) + + result = quota.QUOTAS.expire(self.context) + + assertInstancesReserved(0) + class FakeContext(object): def __init__(self, project_id, quota_class): -- cgit