Merge "Add consumer_types migration, database and object changes"

This commit is contained in:
Zuul 2021-08-25 22:18:14 +00:00 committed by Gerrit Code Review
commit 76fc7d6c2e
12 changed files with 512 additions and 8 deletions

View File

@ -0,0 +1,57 @@
# 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.
"""Add consumer_types table
Revision ID: 422ece571366
Revises: b5c396305c25
Create Date: 2019-07-02 13:47:04.165692
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '422ece571366'
down_revision = 'b5c396305c25'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'consumer_types',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.Unicode(length=255), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name', name='uniq_consumer_types0name'),
)
with op.batch_alter_table('consumers') as batch_op:
batch_op.add_column(
sa.Column(
'consumer_type_id', sa.Integer(),
sa.ForeignKey('consumer_types.id',
name='consumers_consumer_type_id_fkey'),
nullable=True
)
)
op.create_index(
'consumers_consumer_type_id_idx',
'consumers',
['consumer_type_id'],
unique=False
)

View File

@ -221,6 +221,7 @@ class Consumer(BASE):
Index('consumers_project_id_uuid_idx', 'project_id', 'uuid'),
Index('consumers_project_id_user_id_uuid_idx', 'project_id', 'user_id',
'uuid'),
Index('consumers_consumer_type_id_idx', 'consumer_type_id'),
schema.UniqueConstraint('uuid', name='uniq_consumers0uuid'),
)
@ -229,3 +230,17 @@ class Consumer(BASE):
project_id = Column(Integer, nullable=False)
user_id = Column(Integer, nullable=False)
generation = Column(Integer, nullable=False, server_default="0", default=0)
consumer_type_id = Column(
Integer, ForeignKey('consumer_types.id'), nullable=True)
class ConsumerType(BASE):
"""Represents a consumer's type."""
__tablename__ = 'consumer_types'
__table_args__ = (
schema.UniqueConstraint('name', name='uniq_consumer_types0name'),
)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
name = Column(Unicode(255), nullable=False)

View File

@ -201,3 +201,11 @@ class ConsumerNotFound(NotFound):
class ConsumerExists(Exists):
msg_fmt = "The consumer %(uuid)s already exists."
class ConsumerTypeNotFound(NotFound):
msg_fmt = "No such consumer type: %(name)s."
class ConsumerTypeExists(Exists):
msg_fmt = "The consumer type %(name)s already exists."

View File

@ -290,6 +290,7 @@ def _get_allocations_by_consumer_uuid(ctx, consumer_uuid):
allocs.c.used,
consumer.c.id.label("consumer_id"),
consumer.c.generation.label("consumer_generation"),
consumer.c.consumer_type_id,
sql.func.coalesce(
consumer.c.uuid, allocs.c.consumer_id).label("consumer_uuid"),
project.c.id.label("project_id"),
@ -445,6 +446,7 @@ def get_all_by_consumer_id(context, consumer_id):
context, id=db_first['consumer_id'],
uuid=db_first['consumer_uuid'],
generation=db_first['consumer_generation'],
consumer_type_id=db_first['consumer_type_id'],
project=project_obj.Project(
context, id=db_first['project_id'],
external_id=db_first['project_external_id']),

View File

@ -91,7 +91,7 @@ def delete_consumers_if_no_allocations(ctx, consumer_uuids):
def _get_consumer_by_uuid(ctx, uuid):
# The SQL for this looks like the following:
# SELECT
# c.id, c.uuid,
# c.id, c.uuid, c.consumer_type_id,
# p.id AS project_id, p.external_id AS project_external_id,
# u.id AS user_id, u.external_id AS user_external_id,
# c.updated_at, c.created_at
@ -107,6 +107,7 @@ def _get_consumer_by_uuid(ctx, uuid):
cols = [
consumers.c.id,
consumers.c.uuid,
consumers.c.consumer_type_id,
projects.c.id.label("project_id"),
projects.c.external_id.label("project_external_id"),
users.c.id.label("user_id"),
@ -143,13 +144,15 @@ def _delete_consumer(ctx, consumer):
class Consumer(object):
def __init__(self, context, id=None, uuid=None, project=None, user=None,
generation=None, updated_at=None, created_at=None):
generation=None, consumer_type_id=None, updated_at=None,
created_at=None):
self._context = context
self.id = id
self.uuid = uuid
self.project = project
self.user = user
self.generation = generation
self.consumer_type_id = consumer_type_id
self.updated_at = updated_at
self.created_at = created_at
@ -158,6 +161,7 @@ class Consumer(object):
target.id = source['id']
target.uuid = source['uuid']
target.generation = source['generation']
target.consumer_type_id = source['consumer_type_id']
target.created_at = source['created_at']
target.updated_at = source['updated_at']
@ -181,7 +185,7 @@ class Consumer(object):
def _create_in_db(ctx):
db_obj = models.Consumer(
uuid=self.uuid, project_id=self.project.id,
user_id=self.user.id)
user_id=self.user.id, consumer_type_id=self.consumer_type_id)
try:
db_obj.save(ctx.session)
# NOTE(jaypipes): We don't do the normal _from_db_object()
@ -200,7 +204,8 @@ class Consumer(object):
@db_api.placement_context_manager.writer
def _update_in_db(ctx):
upd_stmt = CONSUMER_TBL.update().values(
project_id=self.project.id, user_id=self.user.id)
project_id=self.project.id, user_id=self.user.id,
consumer_type_id=self.consumer_type_id)
# NOTE(jaypipes): We add the generation check to the WHERE clause
# above just for safety. We don't need to check that the statement
# actually updated a single row. If it did not, then the

View File

@ -0,0 +1,114 @@
# 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_db import exception as db_exc
import sqlalchemy as sa
from placement.db.sqlalchemy import models
from placement import db_api
from placement import exception
CONSUMER_TYPE_TBL = models.ConsumerType.__table__
_CONSUMER_TYPES_LOCK = 'consumer_types_sync'
_CONSUMER_TYPES_SYNCED = False
NULL_CONSUMER_TYPE_ALIAS = 'unknown'
@db_api.placement_context_manager.reader
def _get_consumer_type_by_id(ctx, id):
# The SQL for this looks like the following:
# SELECT
# c.id, c.name,
# c.updated_at, c.created_at
# FROM consumer_types c
# WHERE c.id = $id
consumer_types = sa.alias(CONSUMER_TYPE_TBL, name="c")
cols = [
consumer_types.c.id,
consumer_types.c.name,
consumer_types.c.updated_at,
consumer_types.c.created_at
]
sel = sa.select(cols).where(consumer_types.c.id == id)
res = ctx.session.execute(sel).fetchone()
if not res:
raise exception.ConsumerTypeNotFound(name=id)
return dict(res)
@db_api.placement_context_manager.reader
def _get_consumer_type_by_name(ctx, name):
# The SQL for this looks like the following:
# SELECT
# c.id, c.name,
# c.updated_at, c.created_at
# FROM consumer_types c
# WHERE c.name = $name
consumer_types = sa.alias(CONSUMER_TYPE_TBL, name="c")
cols = [
consumer_types.c.id,
consumer_types.c.name,
consumer_types.c.updated_at,
consumer_types.c.created_at
]
sel = sa.select(cols).where(consumer_types.c.name == name)
res = ctx.session.execute(sel).fetchone()
if not res:
raise exception.ConsumerTypeNotFound(name=name)
return dict(res)
@db_api.placement_context_manager.writer
def _create_in_db(ctx, name):
db_obj = models.ConsumerType(name=name)
try:
db_obj.save(ctx.session)
return db_obj
except db_exc.DBDuplicateEntry:
raise exception.ConsumerTypeExists(name=name)
class ConsumerType(object):
def __init__(self, context, id=None, name=None,
updated_at=None, created_at=None):
self._context = context
self.id = id
self.name = name
self.updated_at = updated_at
self.created_at = created_at
@staticmethod
def _from_db_object(ctx, target, source):
target.id = source['id']
target.name = source['name']
target.created_at = source['created_at']
target.updated_at = source['updated_at']
target._context = ctx
return target
@classmethod
def get_by_id(cls, ctx, id):
res = _get_consumer_type_by_id(ctx, id)
return cls._from_db_object(ctx, cls(ctx), res)
@classmethod
def get_by_name(cls, ctx, name):
res = _get_consumer_type_by_name(ctx, name)
return cls._from_db_object(ctx, cls(ctx), res)
def create(self):
ct = _create_in_db(self._context, self.name)
return self._from_db_object(self._context, self, ct)

View File

@ -10,18 +10,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import distinct
from sqlalchemy import func
from sqlalchemy import sql
from placement.db.sqlalchemy import models
from placement import db_api
from placement.objects import consumer_type as consumer_type_obj
class Usage(object):
def __init__(self, resource_class=None, usage=0):
def __init__(self, resource_class=None, usage=0, consumer_type=None,
consumer_count=0):
self.resource_class = resource_class
self.usage = int(usage)
self.consumer_type = (consumer_type or
consumer_type_obj.NULL_CONSUMER_TYPE_ALIAS)
self.consumer_count = int(consumer_count)
def get_all_by_resource_provider_uuid(context, rp_uuid):
@ -30,6 +36,14 @@ def get_all_by_resource_provider_uuid(context, rp_uuid):
return [Usage(**db_item) for db_item in usage_list]
def get_by_consumer_type(context, project_id, user_id=None,
consumer_type=None):
"""Get a list of Usage objects by consumer type."""
usage_list = _get_by_consumer_type(context, project_id, user_id=user_id,
consumer_type=consumer_type)
return [Usage(**db_item) for db_item in usage_list]
def get_all_by_project_user(context, project_id, user_id=None):
"""Get a list of Usage objects filtered by project and (optional) user."""
usage_list = _get_all_by_project_user(context, project_id,
@ -58,7 +72,8 @@ def _get_all_by_resource_provider_uuid(context, rp_uuid):
@db_api.placement_context_manager.reader
def _get_all_by_project_user(context, project_id, user_id=None):
def _get_all_by_project_user(context, project_id, user_id=None,
consumer_type=False):
query = (context.session.query(models.Allocation.resource_class_id,
func.coalesce(func.sum(models.Allocation.used), 0))
.join(models.Consumer,
@ -71,7 +86,78 @@ def _get_all_by_project_user(context, project_id, user_id=None):
models.Consumer.user_id == models.User.id)
query = query.filter(models.User.external_id == user_id)
query = query.group_by(models.Allocation.resource_class_id)
if consumer_type:
# NOTE(melwitt): We have to count separately in order to get a count of
# unique consumers. If we count after grouping by resource class, we
# will count duplicate consumers for any unique consumer that consumes
# more than one resource class simultaneously (example: an instance
# consuming both VCPU and MEMORY_MB).
count_query = (context.session.query(
func.count(distinct(models.Allocation.consumer_id)))
.join(models.Consumer,
models.Allocation.consumer_id == models.Consumer.uuid)
.join(models.Project,
models.Consumer.project_id == models.Project.id)
.filter(models.Project.external_id == project_id))
if user_id:
count_query = count_query.join(
models.User, models.Consumer.user_id == models.User.id)
count_query = count_query.filter(
models.User.external_id == user_id)
unique_consumer_count = count_query.scalar()
result = [dict(resource_class=context.rc_cache.string_from_id(item[0]),
usage=item[1],
consumer_type="all",
consumer_count=unique_consumer_count)
for item in query.all()]
else:
result = [dict(resource_class=context.rc_cache.string_from_id(item[0]),
usage=item[1])
for item in query.all()]
return result
@db_api.placement_context_manager.reader
def _get_by_consumer_type(context, project_id, user_id=None,
consumer_type=None):
if consumer_type == 'all':
return _get_all_by_project_user(context, project_id, user_id,
consumer_type=True)
query = (context.session.query(
models.Allocation.resource_class_id,
func.coalesce(func.sum(models.Allocation.used), 0),
func.count(distinct(models.Allocation.consumer_id)),
models.ConsumerType.name)
.join(models.Consumer,
models.Allocation.consumer_id == models.Consumer.uuid)
.outerjoin(models.ConsumerType,
models.Consumer.consumer_type_id ==
models.ConsumerType.id)
.join(models.Project,
models.Consumer.project_id == models.Project.id)
.filter(models.Project.external_id == project_id))
if user_id:
query = query.join(models.User,
models.Consumer.user_id == models.User.id)
query = query.filter(models.User.external_id == user_id)
if consumer_type:
query = query.filter(models.ConsumerType.name == consumer_type)
# NOTE(melwitt): We have to count grouped by only consumer type first in
# order to get a count of unique consumers for a given consumer type. If we
# only count after grouping by resource class, we will count duplicate
# consumers for any unique consumer that consumes more than one resource
# class simultaneously (example: an instance consuming both VCPU and
# MEMORY_MB).
unique_consumer_counts = {item[3]: item[2] for item in
query.group_by(models.ConsumerType.name).all()}
query = query.group_by(models.Allocation.resource_class_id,
models.Consumer.consumer_type_id)
result = [dict(resource_class=context.rc_cache.string_from_id(item[0]),
usage=item[1])
usage=item[1],
consumer_count=unique_consumer_counts[item[3]],
consumer_type=item[3])
for item in query.all()]
return result

View File

@ -18,6 +18,7 @@ from oslo_utils.fixture import uuidsentinel
from placement import exception
from placement.objects import allocation as alloc_obj
from placement.objects import consumer as consumer_obj
from placement.objects import consumer_type as ct_obj
from placement.objects import inventory as inv_obj
from placement.objects import usage as usage_obj
from placement.tests.functional.db import test_base as tb
@ -93,10 +94,16 @@ class TestAllocation(tb.PlacementDbBaseTestCase):
step_size=64,
allocation_ratio=1.5)
# Create an INSTANCE consumer type
ct = ct_obj.ConsumerType(self.ctx, name='INSTANCE')
ct.create()
# Save consumer type id for later confirmation.
ct_id = ct.id
# Create a consumer representing the instance
inst_consumer = consumer_obj.Consumer(
self.ctx, uuid=uuidsentinel.instance, user=self.user_obj,
project=self.project_obj)
project=self.project_obj, consumer_type_id=ct_id)
inst_consumer.create()
# Now create an allocation that represents a move operation where the
@ -175,6 +182,10 @@ class TestAllocation(tb.PlacementDbBaseTestCase):
self.assertEqual(2, len(consumer_allocs))
# check the allocations have the expected INSTANCE consumer type
self.assertEqual(ct_id, consumer_allocs[0].consumer.consumer_type_id)
self.assertEqual(ct_id, consumer_allocs[1].consumer.consumer_type_id)
def test_get_all_by_resource_provider(self):
rp, allocation = self._make_allocation(tb.DISK_INVENTORY,
tb.DISK_ALLOCATION)

View File

@ -0,0 +1,47 @@
# 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 placement import exception
from placement.objects import consumer_type as ct_obj
from placement.tests.functional.db import test_base as tb
class ConsumerTypeTestCase(tb.PlacementDbBaseTestCase):
def test_get_by_name_and_id(self):
ct = ct_obj.ConsumerType(self.context, name='MIGRATION')
ct.create()
named_ct = ct_obj.ConsumerType.get_by_name(self.context, 'MIGRATION')
self.assertEqual(ct.id, named_ct.id)
id_ct = ct_obj.ConsumerType.get_by_id(self.context, ct.id)
self.assertEqual(ct.name, id_ct.name)
def test_id_not_found(self):
self.assertRaises(
exception.ConsumerTypeNotFound, ct_obj.ConsumerType.get_by_id,
self.context, 999999)
def test_name_not_found(self):
self.assertRaises(
exception.ConsumerTypeNotFound, ct_obj.ConsumerType.get_by_name,
self.context, 'LOSTPONY')
def test_duplicate_create(self):
ct = ct_obj.ConsumerType(self.context, name='MIGRATION')
ct.create()
ct2 = ct_obj.ConsumerType(self.context, name='MIGRATION')
self.assertRaises(exception.ConsumerTypeExists, ct2.create)

View File

@ -33,6 +33,7 @@ from oslo_db.sqlalchemy import utils as db_utils
from oslo_log import log as logging
from oslo_utils.fixture import uuidsentinel as uuids
from oslotest import base as test_base
from sqlalchemy import inspect
import testtools
from placement.db.sqlalchemy import migration
@ -226,6 +227,38 @@ class MigrationCheckersMixin(object):
}).execute().inserted_primary_key[0]
self.migration_api.upgrade('b5c396305c25')
def test_consumer_types_422ece571366(self):
# Upgrade to populate the schema.
self.migration_api.upgrade('422ece571366')
insp = inspect(self.engine)
# Test creation of consumer_types table
con = db_utils.get_table(self.engine, 'consumer_types')
col_names = [column.name for column in con.c]
self.assertIn('created_at', col_names)
self.assertIn('updated_at', col_names)
self.assertIn('id', col_names)
self.assertIn('name', col_names)
# check constraints
pkey = insp.get_pk_constraint("consumer_types")
self.assertEqual(['id'], pkey['constrained_columns'])
ukey = insp.get_unique_constraints("consumer_types")
self.assertEqual('uniq_consumer_types0name', ukey[0]['name'])
def test_consumer_type_id_column_422ece571366(self):
# Upgrade to populate the schema.
self.migration_api.upgrade('422ece571366')
insp = inspect(self.engine)
# Test creation of consumer_types table
consumers = db_utils.get_table(self.engine, 'consumers')
col_names = [column.name for column in consumers.c]
self.assertIn('consumer_type_id', col_names)
# Check index and constraints
fkey = insp.get_foreign_keys("consumers")
self.assertEqual(['consumer_type_id'], fkey[0]['constrained_columns'])
ind = insp.get_indexes('consumers')
names = [r['name'] for r in ind]
self.assertIn('consumers_consumer_type_id_idx', names)
class PlacementOpportunisticFixture(object):
def get_enginefacade(self):

View File

@ -10,9 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import os_resource_classes as orc
from oslo_utils.fixture import uuidsentinel
from oslo_utils import uuidutils
from placement.objects import consumer as c_obj
from placement.objects import consumer_type as ct_obj
from placement.objects import inventory as inv_obj
from placement.objects import usage as usage_obj
from placement.tests.functional.db import test_base as tb
@ -62,3 +67,123 @@ class UsageListTestCase(tb.PlacementDbBaseTestCase):
usages = usage_obj.get_all_by_resource_provider_uuid(
self.ctx, db_rp.uuid)
self.assertEqual(2, len(usages))
def test_get_by_unspecified_consumer_type(self):
# This will add a consumer with a NULL consumer type and the default
# project and user external_ids
self._make_allocation(tb.DISK_INVENTORY, tb.DISK_ALLOCATION)
# Verify we filter the project external_id correctly. Note: this will
# also work if filtering is broken (if it's not filtering at all)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id)
self.assertEqual(1, len(usages))
usage = usages[0]
self.assertEqual('unknown', usage.consumer_type)
self.assertEqual(1, usage.consumer_count)
self.assertEqual(orc.DISK_GB, usage.resource_class)
self.assertEqual(2, usage.usage)
# Verify we get nothing back if we filter on a different project
# external_id that does not exist (will not work if filtering is
# broken)
usages = usage_obj.get_by_consumer_type(self.ctx, 'BOGUS')
self.assertEqual(0, len(usages))
def test_get_by_specified_consumer_type(self):
ct = ct_obj.ConsumerType(self.ctx, name='INSTANCE')
ct.create()
consumer_id = uuidutils.generate_uuid()
c = c_obj.Consumer(self.ctx, uuid=consumer_id,
project=self.project_obj, user=self.user_obj,
consumer_type_id=ct.id)
c.create()
# This will add a consumer with the consumer type INSTANCE
# and the default project and user external_ids
da = copy.deepcopy(tb.DISK_ALLOCATION)
da['consumer_id'] = c.uuid
self._make_allocation(tb.DISK_INVENTORY, da)
# Verify we filter the INSTANCE type correctly. Note: this will also
# work if filtering is broken (if it's not filtering at all)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id,
consumer_type=ct.name)
self.assertEqual(1, len(usages))
usage = usages[0]
self.assertEqual(ct.name, usage.consumer_type)
self.assertEqual(1, usage.consumer_count)
self.assertEqual(orc.DISK_GB, usage.resource_class)
self.assertEqual(2, usage.usage)
# Verify we get nothing back if we filter on a different consumer
# type that does not exist (will not work if filtering is broken)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id,
consumer_type='BOGUS')
self.assertEqual(0, len(usages))
def test_get_by_specified_consumer_type_with_user(self):
ct = ct_obj.ConsumerType(self.ctx, name='INSTANCE')
ct.create()
consumer_id = uuidutils.generate_uuid()
c = c_obj.Consumer(self.ctx, uuid=consumer_id,
project=self.project_obj, user=self.user_obj,
consumer_type_id=ct.id)
c.create()
# This will add a consumer with the consumer type INSTANCE
# and the default project and user external_ids
da = copy.deepcopy(tb.DISK_ALLOCATION)
da['consumer_id'] = c.uuid
db_rp, _ = self._make_allocation(tb.DISK_INVENTORY, da)
# Verify we filter the user external_id correctly. Note: this will also
# work if filtering is broken (if it's not filtering at all)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id,
user_id=self.user_obj.external_id,
consumer_type=ct.name)
self.assertEqual(1, len(usages))
usage = usages[0]
self.assertEqual(ct.name, usage.consumer_type)
self.assertEqual(1, usage.consumer_count)
self.assertEqual(orc.DISK_GB, usage.resource_class)
self.assertEqual(2, usage.usage)
# Verify we get nothing back if we filter on a different user
# external_id that does not exist (will not work if filtering is
# broken)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id,
user_id='BOGUS',
consumer_type=ct.name)
self.assertEqual(0, len(usages))
def test_get_by_all_consumer_type(self):
# This will add a consumer with the default consumer type UNKNOWN
db_rp, _ = self._make_allocation(tb.DISK_INVENTORY,
tb.DISK_ALLOCATION)
# Make another allocation with a different consumer type
ct = ct_obj.ConsumerType(self.ctx, name='FOO')
ct.create()
consumer_id = uuidutils.generate_uuid()
c = c_obj.Consumer(self.ctx, uuid=consumer_id,
project=self.project_obj, user=self.user_obj,
consumer_type_id=ct.id)
c.create()
self.allocate_from_provider(db_rp, orc.DISK_GB, 2, consumer=c)
# Verify we get usages back for both consumer types with 'all'
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id, consumer_type='all')
self.assertEqual(1, len(usages))
usage = usages[0]
self.assertEqual('all', usage.consumer_type)
self.assertEqual(2, usage.consumer_count)
self.assertEqual(orc.DISK_GB, usage.resource_class)
self.assertEqual(4, usage.usage)
def test_get_by_unused_consumer_type(self):
# This will add a consumer with the default consumer type UNKNOWN
self._make_allocation(tb.DISK_INVENTORY, tb.DISK_ALLOCATION)
usages = usage_obj.get_by_consumer_type(
self.ctx, self.project_obj.external_id, consumer_type='EMPTY')
self.assertEqual(0, len(usages))

View File

@ -48,6 +48,7 @@ _ALLOCATION_BY_CONSUMER_DB = {
'resource_class_id': _RESOURCE_CLASS_ID,
'consumer_uuid': uuids.fake_instance,
'consumer_id': 1,
'consumer_type_id': 1,
'consumer_generation': 0,
'used': 8,
'user_id': 1,