Add Quota objects

Change-Id: I9f8d906a08267a50d628e060f9a05d8694c150b5
Partial-Implements: blueprint quota-support
This commit is contained in:
Kien Nguyen 2018-04-11 17:01:28 +07:00 committed by Kien Nguyen
parent 697412bdb8
commit cae313d166
7 changed files with 458 additions and 0 deletions

View File

@ -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
View 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
View 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)

View File

@ -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

View File

@ -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',

View 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)

View 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)