Add Quota objects
Change-Id: I9f8d906a08267a50d628e060f9a05d8694c150b5 Partial-Implements: blueprint quota-support
This commit is contained in:
parent
697412bdb8
commit
cae313d166
@ -19,6 +19,8 @@ from zun.objects import image
|
||||
from zun.objects import numa
|
||||
from zun.objects import pci_device
|
||||
from zun.objects import pci_device_pool
|
||||
from zun.objects import quota
|
||||
from zun.objects import quota_class
|
||||
from zun.objects import resource_class
|
||||
from zun.objects import resource_provider
|
||||
from zun.objects import volume_mapping
|
||||
@ -37,6 +39,8 @@ ComputeNode = compute_node.ComputeNode
|
||||
Capsule = capsule.Capsule
|
||||
PciDevice = pci_device.PciDevice
|
||||
PciDevicePool = pci_device_pool.PciDevicePool
|
||||
Quota = quota.Quota
|
||||
QuotaClass = quota_class.QuotaClass
|
||||
ContainerPCIRequest = container_pci_requests.ContainerPCIRequest
|
||||
ContainerPCIRequests = container_pci_requests.ContainerPCIRequests
|
||||
ContainerAction = container_action.ContainerAction
|
||||
@ -55,6 +59,8 @@ __all__ = (
|
||||
Capsule,
|
||||
PciDevice,
|
||||
PciDevicePool,
|
||||
Quota,
|
||||
QuotaClass,
|
||||
ContainerPCIRequest,
|
||||
ContainerPCIRequests,
|
||||
ContainerAction,
|
||||
|
112
zun/objects/quota.py
Normal file
112
zun/objects/quota.py
Normal file
@ -0,0 +1,112 @@
|
||||
# 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 oslo_versionedobjects import fields
|
||||
|
||||
from zun.db import api as dbapi
|
||||
from zun.objects import base
|
||||
|
||||
|
||||
@base.ZunObjectRegistry.register
|
||||
class Quota(base.ZunPersistentObject, base.ZunObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
'project_id': fields.StringField(nullable=True),
|
||||
'resource': fields.StringField(),
|
||||
'hard_limit': fields.IntegerField(nullable=True)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(quota, db_quota):
|
||||
"""Converts a database entity to a formal object"""
|
||||
for field in quota.fields:
|
||||
setattr(quota, field, db_quota[field])
|
||||
|
||||
quota.obj_reset_changes()
|
||||
return quota
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, project_id, resource):
|
||||
"""Find a quota based on project_id and resource
|
||||
|
||||
:param project_id: the project id.
|
||||
:param context: security context.
|
||||
:param resource: the name of resource.
|
||||
:returns: a :class:`Quota` object.
|
||||
"""
|
||||
db_quota = dbapi.quota_get(context, project_id, resource)
|
||||
quota = Quota._from_db_object(cls(context), db_quota)
|
||||
return quota
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_all(cls, context, project_id):
|
||||
"""Find all quotas associated with project
|
||||
|
||||
:param context: security context.
|
||||
:param project_id: the project id.
|
||||
:returns: a dict
|
||||
"""
|
||||
return dbapi.quota_get_all_by_project(context, project_id)
|
||||
|
||||
@base.remotable
|
||||
def create(self, context):
|
||||
"""Create a Quota record in the DB.
|
||||
|
||||
:param context: security context. NOTE: This should only be
|
||||
used internally by the indirection api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Quota(context)
|
||||
"""
|
||||
values = self.obj_get_changes()
|
||||
project_id = values.get('project_id')
|
||||
resource = values.get('resource')
|
||||
limit = values.get('hard_limit')
|
||||
db_quota = dbapi.quota_create(context, project_id, resource, limit)
|
||||
self._from_db_object(self, db_quota)
|
||||
|
||||
@base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Quota from the DB.
|
||||
|
||||
:param context: security context. NOTE: This should only be
|
||||
used internally by the indirection api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Quota(context)
|
||||
"""
|
||||
dbapi.quota_destroy(context, self.project_id, self.resource)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def update(self, context=None):
|
||||
"""Save updates to this Quota.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: security context. NOTE: This should only be
|
||||
used internally by the indirection api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Quota(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
dbapi.quota_update(context, self.project_id, self.resource,
|
||||
updates.get('hard_limit'))
|
||||
self.obj_reset_changes()
|
101
zun/objects/quota_class.py
Normal file
101
zun/objects/quota_class.py
Normal file
@ -0,0 +1,101 @@
|
||||
# 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 oslo_versionedobjects import fields
|
||||
|
||||
from zun.db import api as dbapi
|
||||
from zun.objects import base
|
||||
|
||||
|
||||
@base.ZunObjectRegistry.register
|
||||
class QuotaClass(base.ZunPersistentObject, base.ZunObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
'class_name': fields.StringField(nullable=True),
|
||||
'resource': fields.StringField(nullable=True),
|
||||
'hard_limit': fields.IntegerField(nullable=True)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_method(quota_class, db_quota_class):
|
||||
"""Convert a database entity to a format object"""
|
||||
for field in quota_class.fields:
|
||||
setattr(quota_class, field, db_quota_class[field])
|
||||
|
||||
quota_class.obj_reset_changes()
|
||||
return quota_class
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, class_name, resource):
|
||||
"""Find a quota class based on class_name and resource name.
|
||||
|
||||
:param class_name: the name of class.
|
||||
:param context: security context.
|
||||
:param resource: the name of resource.
|
||||
:returns: a :class:`QuotaClass` object.
|
||||
"""
|
||||
db_quota_class = dbapi.quota_class_get(context, class_name, resource)
|
||||
quota_class = QuotaClass._from_db_method(cls(context), db_quota_class)
|
||||
return quota_class
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_all(cls, context, class_name=None):
|
||||
"""Find quota based on class_name
|
||||
|
||||
:param context: security context.
|
||||
:param class_name: the class name.
|
||||
:return a dict
|
||||
"""
|
||||
if class_name is None:
|
||||
res = dbapi.quota_class_get_default(context)
|
||||
else:
|
||||
res = dbapi.quota_class_get_all_by_name(context, class_name)
|
||||
return res
|
||||
|
||||
@base.remotable
|
||||
def create(self, context):
|
||||
"""Create a QuotaClass record in the DB.
|
||||
|
||||
:param context: security context. NOTE: This should only be
|
||||
used internally by the indirection api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: QuotaClass(context)
|
||||
"""
|
||||
values = self.obj_get_changes()
|
||||
class_name = values.get('class_name')
|
||||
resource = values.get('resource')
|
||||
limit = values.get('hard_limit')
|
||||
dbapi.quota_class_create(context, class_name, resource, limit)
|
||||
|
||||
@base.remotable
|
||||
def update(self, context=None):
|
||||
"""Save updates to this QuotaClass.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: security context. NOTE: This should only be
|
||||
used internally by the indirection api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: QuotaClass(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
limit = updates.get('hard_limit')
|
||||
dbapi.quota_class_update(context, self.class_name,
|
||||
self.resource, limit)
|
@ -513,3 +513,47 @@ def create_test_quota_class(**kwargs):
|
||||
limit = kwargs.get('limit', 100)
|
||||
dbapi = _get_dbapi()
|
||||
return dbapi.quota_class_create(context, class_name, resource, limit)
|
||||
|
||||
|
||||
def get_test_quota_value(**kwargs):
|
||||
quota_values = {
|
||||
'created_at': kwargs.get('created_at'),
|
||||
'updated_at': kwargs.get('updated_at'),
|
||||
'id': kwargs.get('id', 123),
|
||||
'project_id': kwargs.get('project_id', 'fake_project_id'),
|
||||
'resource': kwargs.get('resource', 'container'),
|
||||
'hard_limit': kwargs.get('hard_limit', 20)
|
||||
}
|
||||
|
||||
return quota_values
|
||||
|
||||
|
||||
def get_test_quota(**kwargs):
|
||||
quota_values = get_test_quota_value(**kwargs)
|
||||
fake_quota = FakeObject()
|
||||
for k, v in quota_values.items():
|
||||
setattr(fake_quota, k, v)
|
||||
|
||||
return fake_quota
|
||||
|
||||
|
||||
def get_test_quota_class_value(**kwargs):
|
||||
quota_values = {
|
||||
'created_at': kwargs.get('created_at'),
|
||||
'updated_at': kwargs.get('updated_at'),
|
||||
'id': kwargs.get('id', 123),
|
||||
'class_name': kwargs.get('class_name', 'fake_class_name'),
|
||||
'resource': kwargs.get('resource', 'container'),
|
||||
'hard_limit': kwargs.get('hard_limit', 20)
|
||||
}
|
||||
|
||||
return quota_values
|
||||
|
||||
|
||||
def get_test_quota_class(**kwargs):
|
||||
quota_class_values = get_test_quota_class_value(**kwargs)
|
||||
fake_quota_class = FakeObject()
|
||||
for k, v in quota_class_values.items():
|
||||
setattr(fake_quota_class, k, v)
|
||||
|
||||
return fake_quota_class
|
||||
|
@ -358,6 +358,8 @@ object_data = {
|
||||
'ComputeNode': '1.11-08be22db017745f4f0bc8f873eca7db0',
|
||||
'PciDevicePool': '1.0-3f5ddc3ff7bfa14da7f6c7e9904cc000',
|
||||
'PciDevicePoolList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||
'Quota': '1.0-4daf54427ac19cb23182cad82fcde751',
|
||||
'QuotaClass': '1.0-4739583a70891fbc145031228fb8001e',
|
||||
'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0',
|
||||
'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4',
|
||||
'ContainerAction': '1.1-b0c721f9e10c6c0d1e41e512c49eb877',
|
||||
|
96
zun/tests/unit/objects/test_quota.py
Normal file
96
zun/tests/unit/objects/test_quota.py
Normal file
@ -0,0 +1,96 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from zun import objects
|
||||
from zun.tests.unit.db import base
|
||||
from zun.tests.unit.db import utils
|
||||
|
||||
|
||||
class TestQuotaObject(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestQuotaObject, self).setUp()
|
||||
self.fake_quota = utils.get_test_quota()
|
||||
|
||||
def test_get_quota(self):
|
||||
project_id = self.fake_quota['project_id']
|
||||
resource = self.fake_quota['resource']
|
||||
with mock.patch.object(self.dbapi, 'quota_get',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_quota
|
||||
quota = objects.Quota.get(self.context, project_id, resource)
|
||||
mock_get.assert_called_once_with(
|
||||
self.context, project_id, resource)
|
||||
self.assertEqual(self.context, quota._context)
|
||||
|
||||
def test_get_all_quotas_by_project(self):
|
||||
project_id = self.fake_quota['project_id']
|
||||
with mock.patch.object(self.dbapi, 'quota_get_all_by_project',
|
||||
autospec=True) as mock_get_all:
|
||||
mock_get_all.return_value = {
|
||||
'project_id': project_id,
|
||||
'resource_1': 10,
|
||||
'resource_2': 20
|
||||
}
|
||||
quotas_dict = objects.Quota.get_all(self.context, project_id)
|
||||
mock_get_all.assert_called_once_with(self.context, project_id)
|
||||
self.assertEqual(project_id, quotas_dict['project_id'])
|
||||
|
||||
def test_create_quota(self):
|
||||
project_id = self.fake_quota['project_id']
|
||||
resource = self.fake_quota['resource']
|
||||
hard_limit = self.fake_quota['hard_limit']
|
||||
with mock.patch.object(self.dbapi, 'quota_create',
|
||||
autospec=True) as mock_create:
|
||||
mock_create.return_value = self.fake_quota
|
||||
quota = objects.Quota(
|
||||
self.context, **utils.get_test_quota_value())
|
||||
quota.create(self.context)
|
||||
mock_create.assert_called_once_with(
|
||||
self.context, project_id, resource, hard_limit)
|
||||
|
||||
def test_destroy_quota(self):
|
||||
project_id = self.fake_quota['project_id']
|
||||
resource = self.fake_quota['resource']
|
||||
with mock.patch.object(self.dbapi, 'quota_get',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_quota
|
||||
with mock.patch.object(self.dbapi, 'quota_destroy',
|
||||
autospec=True) as mock_destroy:
|
||||
quota = objects.Quota.get(self.context, project_id,
|
||||
resource)
|
||||
quota.destroy()
|
||||
mock_destroy.assert_called_once_with(
|
||||
None, project_id, resource)
|
||||
self.assertEqual(self.context, quota._context)
|
||||
|
||||
def test_update_quota(self):
|
||||
project_id = self.fake_quota['project_id']
|
||||
resource = self.fake_quota['resource']
|
||||
with mock.patch.object(self.dbapi, 'quota_get',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_quota
|
||||
with mock.patch.object(self.dbapi, 'quota_update',
|
||||
autospec=True) as mock_update:
|
||||
quota = objects.Quota.get(self.context, project_id,
|
||||
resource)
|
||||
quota.hard_limit = 100
|
||||
quota.update()
|
||||
|
||||
mock_get.assert_called_once_with(self.context, project_id,
|
||||
resource)
|
||||
mock_update.assert_called_once_with(
|
||||
None, project_id, resource, 100)
|
||||
self.assertEqual(self.context, quota._context)
|
||||
self.assertEqual(100, quota.hard_limit)
|
97
zun/tests/unit/objects/test_quota_class.py
Normal file
97
zun/tests/unit/objects/test_quota_class.py
Normal file
@ -0,0 +1,97 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from zun.db.sqlalchemy import api
|
||||
from zun import objects
|
||||
from zun.tests.unit.db import base
|
||||
from zun.tests.unit.db import utils
|
||||
|
||||
|
||||
class TestQuotaClassObject(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestQuotaClassObject, self).setUp()
|
||||
self.fake_quota_class = utils.get_test_quota_class()
|
||||
|
||||
def test_get_quota_class(self):
|
||||
class_name = self.fake_quota_class['class_name']
|
||||
resource = self.fake_quota_class['resource']
|
||||
with mock.patch.object(self.dbapi, 'quota_class_get',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_quota_class
|
||||
quota_class = objects.QuotaClass.get(
|
||||
self.context, class_name, resource)
|
||||
mock_get.assert_called_once_with(
|
||||
self.context, class_name, resource)
|
||||
self.assertEqual(self.context, quota_class._context)
|
||||
|
||||
def test_get_all_with_default(self):
|
||||
class_name = api._DEFAULT_QUOTA_NAME
|
||||
with mock.patch.object(self.dbapi, 'quota_class_get_default',
|
||||
autospec=True) as mock_get_all:
|
||||
mock_get_all.return_value = {
|
||||
'class_name': class_name,
|
||||
'resource_1': 10,
|
||||
'resource_2': 20
|
||||
}
|
||||
quota_class_dict = objects.QuotaClass.get_all(self.context)
|
||||
mock_get_all.assert_called_once_with(self.context)
|
||||
self.assertEqual(class_name, quota_class_dict['class_name'])
|
||||
|
||||
def test_get_all_with_class_name(self):
|
||||
class_name = self.fake_quota_class['class_name']
|
||||
with mock.patch.object(self.dbapi, 'quota_class_get_all_by_name',
|
||||
autospec=True) as mock_get_all:
|
||||
mock_get_all.return_value = {
|
||||
'class_name': class_name,
|
||||
'resource_1': 10,
|
||||
'resource_2': 20
|
||||
}
|
||||
quota_class_dict = objects.QuotaClass.get_all(
|
||||
self.context, class_name)
|
||||
mock_get_all.assert_called_once_with(self.context, class_name)
|
||||
self.assertEqual(class_name, quota_class_dict['class_name'])
|
||||
|
||||
def test_create_quota_class(self):
|
||||
class_name = self.fake_quota_class['class_name']
|
||||
resource = self.fake_quota_class['resource']
|
||||
hard_limit = self.fake_quota_class['hard_limit']
|
||||
with mock.patch.object(self.dbapi, 'quota_class_create',
|
||||
autospec=True) as mock_create:
|
||||
mock_create.return_value = self.fake_quota_class
|
||||
quota_class = objects.QuotaClass(
|
||||
self.context, **utils.get_test_quota_class_value())
|
||||
quota_class.create(self.context)
|
||||
mock_create.assert_called_once_with(
|
||||
self.context, class_name, resource, hard_limit)
|
||||
|
||||
def test_update_quota(self):
|
||||
class_name = self.fake_quota_class['class_name']
|
||||
resource = self.fake_quota_class['resource']
|
||||
with mock.patch.object(self.dbapi, 'quota_class_get',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_quota_class
|
||||
with mock.patch.object(self.dbapi, 'quota_class_update',
|
||||
autospec=True) as mock_update:
|
||||
quota_class = objects.QuotaClass.get(
|
||||
self.context, class_name, resource)
|
||||
quota_class.hard_limit = 100
|
||||
quota_class.update()
|
||||
|
||||
mock_get.assert_called_once_with(
|
||||
self.context, class_name, resource)
|
||||
mock_update.assert_called_once_with(
|
||||
None, class_name, resource, 100)
|
||||
self.assertEqual(self.context, quota_class._context)
|
||||
self.assertEqual(100, quota_class.hard_limit)
|
Loading…
Reference in New Issue
Block a user