Add consumer_types migration, database and object changes

In advance of a microversion 1.38, add the necessary database and
object code to support saving consumer types when creating consumers
and reporting those types when requesting usage information.

This patch adds a new table called ``consumer_types`` and also
add a new column called ``consumer_type_id`` that references the id
in the consumer_types table.

The consumer_type_id is nullable and records with a NULL
consumer_type_id are considered as 'unknown'.

In future changes we may wish to AttributeCache consumer types in
the same way that traits and resource classes are cached.

Usage object can now be retrieved by consumer type and include
consumer type information when retrieved. Until 1.38 that info
will not be used in results.

Allocations are written and retrieved with a consumer type but
the data is not yet presented in HTTP requests.

Co-Authored-By: Chris Dent <cdent@anticdent.org>
Co-Authored-By: melanie witt <melwittt@gmail.com>

Story: 2005473
Task: 35683
Task: 36420

Change-Id: Ic74c3d1b8aa272e9c7d1070fe7658b928e693889
This commit is contained in:
Surya Seetharaman 2019-07-04 14:41:02 +02:00 committed by melanie witt
parent c4d785cc35
commit b1f3dd39c3
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,