Merge "Add consumer_types migration, database and object changes"
This commit is contained in:
commit
76fc7d6c2e
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue