Add compute node to DB and objects
Change-Id: Ic196736ff263220eb83acce72772f0ebcc462b9f
This commit is contained in:
parent
8053efa4cf
commit
7255e44328
@ -341,6 +341,10 @@ class ContainerNotFound(HTTPNotFound):
|
||||
message = _("Container %(container)s could not be found.")
|
||||
|
||||
|
||||
class ComputeNodeNotFound(HTTPNotFound):
|
||||
message = _("Compute node %(compute_node)s could not be found.")
|
||||
|
||||
|
||||
class ImageNotFound(HTTPNotFound):
|
||||
message = _("Image %(image)s could not be found.")
|
||||
|
||||
@ -369,6 +373,10 @@ class ContainerAlreadyExists(ResourceExists):
|
||||
message = _("A container with %(field)s %(value)s already exists.")
|
||||
|
||||
|
||||
class ComputeNodeAlreadyExists(ResourceExists):
|
||||
message = _("A compute node with %(field)s %(value)s already exists.")
|
||||
|
||||
|
||||
class ImageAlreadyExists(ResourceExists):
|
||||
message = _("An image with tag %(tag)s and repo %(repo)s already exists.")
|
||||
|
||||
|
@ -55,6 +55,7 @@ def list_containers(context, filters=None, limit=None, marker=None,
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
@ -65,6 +66,7 @@ def list_containers(context, filters=None, limit=None, marker=None,
|
||||
def create_container(context, values):
|
||||
"""Create a new container.
|
||||
|
||||
:param context: The security context
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the container, and several dicts which are
|
||||
passed
|
||||
@ -180,7 +182,7 @@ def list_zun_services(context, filters=None, limit=None,
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: direction in which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
@ -242,7 +244,8 @@ def list_images(context, filters=None,
|
||||
:param marker: the last item of the previous page; we
|
||||
return the next
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
(asc, desc)
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
return _get_dbdriver_instance().list_images(
|
||||
@ -291,6 +294,7 @@ def list_resource_providers(context, filters=None, limit=None, marker=None,
|
||||
def create_resource_provider(context, values):
|
||||
"""Create a new resource provider.
|
||||
|
||||
:param context: The security context
|
||||
:param values: A dict containing several items used to identify and
|
||||
track the resource provider, and several dicts which are
|
||||
passed into the Drivers when managing this resource
|
||||
@ -344,7 +348,8 @@ def list_resource_classes(context, limit=None, marker=None, sort_key=None,
|
||||
:param marker: the last item of the previous page; we
|
||||
return the next
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
(asc, desc)
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
return _get_dbdriver_instance().list_resource_classes(
|
||||
@ -354,6 +359,7 @@ def list_resource_classes(context, limit=None, marker=None, sort_key=None,
|
||||
def create_resource_class(context, values):
|
||||
"""Create a new resource class.
|
||||
|
||||
:param context: The security context
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the resource class, and several dicts which are
|
||||
passed into the Drivers when managing this resource class.
|
||||
@ -408,6 +414,7 @@ def list_inventories(context, filters=None, limit=None, marker=None,
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
@ -418,10 +425,11 @@ def list_inventories(context, filters=None, limit=None, marker=None,
|
||||
def create_inventory(context, provider_id, values):
|
||||
"""Create a new inventory.
|
||||
|
||||
:param context: The security context
|
||||
:param provider_id: The id of a resource provider.
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the inventory, and several dicts which are
|
||||
passed into the Drivers when managing this inventory.
|
||||
:param provider_id: The id of a resource provider.
|
||||
:returns: An inventory.
|
||||
"""
|
||||
return _get_dbdriver_instance().create_inventory(
|
||||
@ -473,6 +481,7 @@ def list_allocations(context, filters=None, limit=None, marker=None,
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
@ -483,10 +492,10 @@ def list_allocations(context, filters=None, limit=None, marker=None,
|
||||
def create_allocation(context, values):
|
||||
"""Create a new allocation.
|
||||
|
||||
:param context: The security context
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the allocation, and several dicts which are
|
||||
passed into the Drivers when managing this allocation.
|
||||
:param provider_id: The id of a resource provider.
|
||||
:returns: An allocation.
|
||||
"""
|
||||
return _get_dbdriver_instance().create_allocation(context, values)
|
||||
@ -522,3 +531,78 @@ def update_allocation(context, allocation_id, values):
|
||||
"""
|
||||
return _get_dbdriver_instance().update_allocation(
|
||||
context, allocation_id, values)
|
||||
|
||||
|
||||
def list_compute_nodes(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""List matching compute nodes.
|
||||
|
||||
Return a list of the specified columns for all compute nodes that match
|
||||
the specified filters.
|
||||
:param context: The security context
|
||||
:param filters: Filters to apply. Defaults to None.
|
||||
:param limit: Maximum number of compute nodes to return.
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of tuples of the specified columns.
|
||||
"""
|
||||
return _get_dbdriver_instance().list_compute_nodes(
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
def create_compute_node(context, values):
|
||||
"""Create a new compute node.
|
||||
|
||||
:param context: The security context
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the compute node, and several dicts which are
|
||||
passed into the Drivers when managing this compute node.
|
||||
:returns: A compute node.
|
||||
"""
|
||||
return _get_dbdriver_instance().create_compute_node(context, values)
|
||||
|
||||
|
||||
def get_compute_node(context, node_uuid):
|
||||
"""Return a compute node.
|
||||
|
||||
:param context: The security context
|
||||
:param node_uuid: The uuid of a compute node.
|
||||
:returns: A compute node.
|
||||
"""
|
||||
return _get_dbdriver_instance().get_compute_node(context, node_uuid)
|
||||
|
||||
|
||||
def get_compute_node_by_hostname(context, hostname):
|
||||
"""Return a compute node.
|
||||
|
||||
:param context: The security context
|
||||
:param hostname: The hostname of a compute node.
|
||||
:returns: A compute node.
|
||||
"""
|
||||
return _get_dbdriver_instance().get_compute_node_by_hostname(
|
||||
context, hostname)
|
||||
|
||||
|
||||
def destroy_compute_node(context, node_uuid):
|
||||
"""Destroy a compute node and all associated interfaces.
|
||||
|
||||
:param context: Request context
|
||||
:param node_uuid: The uuid of a compute node.
|
||||
"""
|
||||
return _get_dbdriver_instance().destroy_compute_node(context, node_uuid)
|
||||
|
||||
|
||||
def update_compute_node(context, node_uuid, values):
|
||||
"""Update properties of a compute node.
|
||||
|
||||
:context: Request context
|
||||
:param node_uuid: The uuid of a compute node.
|
||||
:values: The properties to be updated
|
||||
:returns: A compute node.
|
||||
:raises: ComputeNodeNotFound
|
||||
"""
|
||||
return _get_dbdriver_instance().update_compute_node(
|
||||
context, node_uuid, values)
|
||||
|
@ -0,0 +1,42 @@
|
||||
# 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 compute node table
|
||||
|
||||
Revision ID: eeac0d191f5a
|
||||
Revises: 8192905fd835
|
||||
Create Date: 2017-02-28 21:32:58.122924
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'eeac0d191f5a'
|
||||
down_revision = '8192905fd835'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from zun.db.sqlalchemy import models
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'compute_node',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=False),
|
||||
sa.Column('hostname', sa.String(length=255), nullable=False),
|
||||
sa.Column('numa_topology', models.JSONEncodedDict(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('uuid'),
|
||||
)
|
@ -87,10 +87,10 @@ def add_identity_filter(query, value):
|
||||
|
||||
|
||||
def _paginate_query(model, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, query=None):
|
||||
sort_dir=None, query=None, default_sort_key='id'):
|
||||
if not query:
|
||||
query = model_query(model)
|
||||
sort_keys = ['id']
|
||||
sort_keys = [default_sort_key]
|
||||
if sort_key and sort_key not in sort_keys:
|
||||
sort_keys.insert(0, sort_key)
|
||||
try:
|
||||
@ -300,7 +300,7 @@ class Connection(object):
|
||||
return _paginate_query(models.ZunService, query=query)
|
||||
|
||||
def pull_image(self, context, values):
|
||||
# ensure defaults are present for new containers
|
||||
# ensure defaults are present for new images
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = uuidutils.generate_uuid()
|
||||
image = models.Image()
|
||||
@ -652,3 +652,88 @@ class Connection(object):
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def _add_compute_nodes_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = {}
|
||||
|
||||
filter_names = ['hostname']
|
||||
for name in filter_names:
|
||||
if name in filters:
|
||||
query = query.filter_by(**{name: filters[name]})
|
||||
|
||||
return query
|
||||
|
||||
def list_compute_nodes(self, context, filters=None, limit=None,
|
||||
marker=None, sort_key=None, sort_dir=None):
|
||||
query = model_query(models.ComputeNode)
|
||||
query = self._add_compute_nodes_filters(query, filters)
|
||||
return _paginate_query(models.ComputeNode, limit, marker,
|
||||
sort_key, sort_dir, query,
|
||||
default_sort_key='uuid')
|
||||
|
||||
def create_compute_node(self, context, values):
|
||||
# ensure defaults are present for new compute nodes
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = uuidutils.generate_uuid()
|
||||
|
||||
compute_node = models.ComputeNode()
|
||||
compute_node.update(values)
|
||||
try:
|
||||
compute_node.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.ComputeNodeAlreadyExists(
|
||||
field='UUID', value=values['uuid'])
|
||||
return compute_node
|
||||
|
||||
def get_compute_node(self, context, node_uuid):
|
||||
query = model_query(models.ComputeNode)
|
||||
query = query.filter_by(uuid=node_uuid)
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.ComputeNodeNotFound(
|
||||
compute_node=node_uuid)
|
||||
|
||||
def get_compute_node_by_hostname(self, context, hostname):
|
||||
query = model_query(models.ComputeNode)
|
||||
query = query.filter_by(hostname=hostname)
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.ComputeNodeNotFound(
|
||||
compute_node=hostname)
|
||||
except MultipleResultsFound:
|
||||
raise exception.Conflict('Multiple compute nodes exist with same '
|
||||
'hostname. Please use the uuid instead.')
|
||||
|
||||
def destroy_compute_node(self, context, node_uuid):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.ComputeNode, session=session)
|
||||
query = query.filter_by(uuid=node_uuid)
|
||||
count = query.delete()
|
||||
if count != 1:
|
||||
raise exception.ComputeNodeNotFound(
|
||||
compute_node=node_uuid)
|
||||
|
||||
def update_compute_node(self, context, node_uuid, values):
|
||||
if 'uuid' in values:
|
||||
msg = _("Cannot overwrite UUID for an existing ComputeNode.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return self._do_update_compute_node(node_uuid, values)
|
||||
|
||||
def _do_update_compute_node(self, node_uuid, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.ComputeNode, session=session)
|
||||
query = query.filter_by(uuid=node_uuid)
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.ComputeNodeNotFound(
|
||||
compute_node=node_uuid)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
@ -259,3 +259,15 @@ class Allocation(Base):
|
||||
primaryjoin=('and_(Allocation.resource_provider_id == '
|
||||
'ResourceProvider.id)'),
|
||||
foreign_keys=resource_provider_id)
|
||||
|
||||
|
||||
class ComputeNode(Base):
|
||||
"""Represents a compute node. """
|
||||
|
||||
__tablename__ = 'compute_node'
|
||||
__table_args__ = (
|
||||
table_args()
|
||||
)
|
||||
uuid = Column(String(36), primary_key=True, nullable=False)
|
||||
hostname = Column(String(255), nullable=False)
|
||||
numa_topology = Column(JSONEncodedDict, nullable=True)
|
||||
|
@ -10,7 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from zun.objects import compute_node
|
||||
from zun.objects import container
|
||||
from zun.objects import image
|
||||
from zun.objects import numa
|
||||
@ -25,6 +25,7 @@ NUMANode = numa.NUMANode
|
||||
NUMATopology = numa.NUMATopology
|
||||
ResourceProvider = resource_provider.ResourceProvider
|
||||
ResourceClass = resource_class.ResourceClass
|
||||
ComputeNode = compute_node.ComputeNode
|
||||
|
||||
__all__ = (
|
||||
Container,
|
||||
@ -34,4 +35,5 @@ __all__ = (
|
||||
ResourceClass,
|
||||
NUMANode,
|
||||
NUMATopology,
|
||||
ComputeNode,
|
||||
)
|
||||
|
142
zun/objects/compute_node.py
Normal file
142
zun/objects/compute_node.py
Normal file
@ -0,0 +1,142 @@
|
||||
# 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
|
||||
from zun.objects.numa import NUMATopology
|
||||
|
||||
|
||||
@base.ZunObjectRegistry.register
|
||||
class ComputeNode(base.ZunPersistentObject, base.ZunObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'uuid': fields.UUIDField(read_only=True, nullable=False),
|
||||
'numa_topology': fields.ObjectField('NUMATopology', nullable=True),
|
||||
'hostname': fields.StringField(nullable=False),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(context, compute_node, db_compute_node):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in compute_node.fields:
|
||||
if field == 'numa_topology':
|
||||
numa_obj = NUMATopology._from_dict(
|
||||
db_compute_node['numa_topology'])
|
||||
compute_node.numa_topology = numa_obj
|
||||
else:
|
||||
setattr(compute_node, field, db_compute_node[field])
|
||||
|
||||
compute_node.obj_reset_changes(recursive=True)
|
||||
return compute_node
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [ComputeNode._from_db_object(context, cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@base.remotable
|
||||
def create(self, context):
|
||||
"""Create a compute node record in the DB.
|
||||
|
||||
:param context: Security context.
|
||||
|
||||
"""
|
||||
values = self.obj_get_changes()
|
||||
numa_obj = values.pop('numa_topology', None)
|
||||
if numa_obj is not None:
|
||||
values['numa_topology'] = numa_obj._to_dict()
|
||||
|
||||
db_compute_node = dbapi.create_compute_node(context, values)
|
||||
self._from_db_object(context, self, db_compute_node)
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a compute node based on uuid.
|
||||
|
||||
:param uuid: the uuid of a compute node.
|
||||
:param context: Security context
|
||||
:returns: a :class:`ComputeNode` object.
|
||||
"""
|
||||
db_compute_node = dbapi.get_compute_node(context, uuid)
|
||||
compute_node = ComputeNode._from_db_object(
|
||||
context, cls(context), db_compute_node)
|
||||
return compute_node
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_hostname(cls, context, hostname):
|
||||
db_compute_node = dbapi.get_compute_node_by_hostname(
|
||||
context, hostname)
|
||||
return cls._from_db_object(context, cls(), db_compute_node)
|
||||
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None, filters=None):
|
||||
"""Return a list of ComputeNode objects.
|
||||
|
||||
:param context: Security context.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param sort_key: column to sort results by.
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param filters: filters when list resource providers.
|
||||
:returns: a list of :class:`ComputeNode` object.
|
||||
|
||||
"""
|
||||
db_compute_nodes = dbapi.list_compute_nodes(
|
||||
context, limit=limit, marker=marker, sort_key=sort_key,
|
||||
sort_dir=sort_dir, filters=filters)
|
||||
return ComputeNode._from_db_object_list(
|
||||
db_compute_nodes, cls, context)
|
||||
|
||||
@base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the ComputeNode from the DB.
|
||||
|
||||
:param context: Security context.
|
||||
"""
|
||||
dbapi.destroy_compute_node(context, self.uuid)
|
||||
self.obj_reset_changes(recursive=True)
|
||||
|
||||
@base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this ComputeNode.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context.
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
dbapi.update_compute_node(context, self.uuid, updates)
|
||||
self.obj_reset_changes(recursive=True)
|
||||
|
||||
@base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads updates for this ComputeNode.
|
||||
|
||||
Loads a compute node with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded compute node column by column, if there are any
|
||||
updates.
|
||||
|
||||
:param context: Security context.
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if self.obj_attr_is_set(field) and \
|
||||
getattr(self, field) != getattr(current, field):
|
||||
setattr(self, field, getattr(current, field))
|
@ -60,16 +60,16 @@ class NUMANode(base.ZunObject):
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'cpus': self.cpuset,
|
||||
'pinned_cpus': self.pinned_cpus
|
||||
'cpuset': list(self.cpuset),
|
||||
'pinned_cpus': list(self.pinned_cpus)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, data_dict):
|
||||
cpuset = data_dict.get('cpus', '')
|
||||
cell_id = data_dict.get('id')
|
||||
pinned_cpus = data_dict.get('pinned_cpus')
|
||||
return cls(id=cell_id, cpuset=cpuset,
|
||||
cpuset = set(data_dict.get('cpuset', ''))
|
||||
node_id = data_dict.get('id')
|
||||
pinned_cpus = set(data_dict.get('pinned_cpus'))
|
||||
return cls(id=node_id, cpuset=cpuset,
|
||||
pinned_cpus=pinned_cpus)
|
||||
|
||||
|
||||
@ -88,5 +88,10 @@ class NUMATopology(base.ZunObject):
|
||||
NUMANode._from_dict(node_dict)
|
||||
for node_dict in data_dict.get('nodes', [])])
|
||||
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'nodes': [n._to_dict() for n in self.nodes],
|
||||
}
|
||||
|
||||
def to_list(self):
|
||||
return [n._to_dict() for n in self.nodes]
|
||||
|
@ -32,8 +32,8 @@ CONF = conf.CONF
|
||||
|
||||
_numa_node = {
|
||||
'id': 0,
|
||||
'cpus': set([8]),
|
||||
'pinned_cpus': set([])
|
||||
'cpuset': [8],
|
||||
'pinned_cpus': []
|
||||
}
|
||||
|
||||
_numa_topo_spec = [_numa_node]
|
||||
|
161
zun/tests/unit/db/test_compute_host.py
Normal file
161
zun/tests/unit/db/test_compute_host.py
Normal file
@ -0,0 +1,161 @@
|
||||
# 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.
|
||||
|
||||
"""Tests for manipulating compute nodes via the DB API"""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from zun.common import exception
|
||||
import zun.conf
|
||||
from zun.db import api as dbapi
|
||||
from zun.tests.unit.db import base
|
||||
from zun.tests.unit.db import utils
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
|
||||
|
||||
class DbComputeNodeTestCase(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('db_type', 'sql')
|
||||
super(DbComputeNodeTestCase, self).setUp()
|
||||
|
||||
def test_create_compute_node(self):
|
||||
utils.create_test_compute_node(context=self.context)
|
||||
|
||||
def test_create_compute_node_already_exists(self):
|
||||
utils.create_test_compute_node(
|
||||
context=self.context, uuid='123')
|
||||
with self.assertRaisesRegexp(exception.ComputeNodeAlreadyExists,
|
||||
'A compute node with UUID 123.*'):
|
||||
utils.create_test_compute_node(
|
||||
context=self.context, uuid='123')
|
||||
|
||||
def test_get_compute_node_by_uuid(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
res = dbapi.get_compute_node(
|
||||
self.context, node.uuid)
|
||||
self.assertEqual(node.uuid, res.uuid)
|
||||
self.assertEqual(node.hostname, res.hostname)
|
||||
|
||||
def test_get_compute_node_by_hostname(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
res = dbapi.get_compute_node_by_hostname(
|
||||
self.context, node.hostname)
|
||||
self.assertEqual(node.uuid, res.uuid)
|
||||
self.assertEqual(node.hostname, res.hostname)
|
||||
|
||||
def test_get_compute_node_that_does_not_exist(self):
|
||||
self.assertRaises(exception.ComputeNodeNotFound,
|
||||
dbapi.get_compute_node,
|
||||
self.context,
|
||||
uuidutils.generate_uuid())
|
||||
|
||||
def test_list_compute_nodes(self):
|
||||
uuids = []
|
||||
for i in range(1, 6):
|
||||
node = utils.create_test_compute_node(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
context=self.context,
|
||||
hostname='node'+str(i))
|
||||
uuids.append(six.text_type(node['uuid']))
|
||||
res = dbapi.list_compute_nodes(self.context)
|
||||
res_uuids = [r.uuid for r in res]
|
||||
self.assertEqual(sorted(uuids), sorted(res_uuids))
|
||||
|
||||
def test_list_compute_nodes_sorted(self):
|
||||
uuids = []
|
||||
for i in range(5):
|
||||
node = utils.create_test_compute_node(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
context=self.context,
|
||||
hostname='node'+str(i))
|
||||
uuids.append(six.text_type(node.uuid))
|
||||
res = dbapi.list_compute_nodes(self.context, sort_key='uuid')
|
||||
res_uuids = [r.uuid for r in res]
|
||||
self.assertEqual(sorted(uuids), res_uuids)
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
dbapi.list_compute_nodes,
|
||||
self.context,
|
||||
sort_key='foo')
|
||||
|
||||
def test_list_compute_nodes_with_filters(self):
|
||||
node1 = utils.create_test_compute_node(
|
||||
hostname='node-one',
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
context=self.context)
|
||||
node2 = utils.create_test_compute_node(
|
||||
hostname='node-two',
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
context=self.context)
|
||||
|
||||
res = dbapi.list_compute_nodes(
|
||||
self.context, filters={'hostname': 'node-one'})
|
||||
self.assertEqual([node1.uuid], [r.uuid for r in res])
|
||||
|
||||
res = dbapi.list_compute_nodes(
|
||||
self.context, filters={'hostname': 'node-two'})
|
||||
self.assertEqual([node2.uuid], [r.uuid for r in res])
|
||||
|
||||
res = dbapi.list_compute_nodes(
|
||||
self.context, filters={'hostname': 'bad-node'})
|
||||
self.assertEqual([], [r.uuid for r in res])
|
||||
|
||||
res = dbapi.list_compute_nodes(
|
||||
self.context,
|
||||
filters={'hostname': node1.hostname})
|
||||
self.assertEqual([node1.uuid], [r.uuid for r in res])
|
||||
|
||||
def test_destroy_compute_node(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
dbapi.destroy_compute_node(self.context, node.uuid)
|
||||
self.assertRaises(exception.ComputeNodeNotFound,
|
||||
dbapi.get_compute_node,
|
||||
self.context, node.uuid)
|
||||
|
||||
def test_destroy_compute_node_by_uuid(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
dbapi.destroy_compute_node(self.context, node.uuid)
|
||||
self.assertRaises(exception.ComputeNodeNotFound,
|
||||
dbapi.get_compute_node,
|
||||
self.context, node.uuid)
|
||||
|
||||
def test_destroy_compute_node_that_does_not_exist(self):
|
||||
self.assertRaises(exception.ComputeNodeNotFound,
|
||||
dbapi.destroy_compute_node, self.context,
|
||||
uuidutils.generate_uuid())
|
||||
|
||||
def test_update_compute_node(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
old_hostname = node.hostname
|
||||
new_hostname = 'new-hostname'
|
||||
self.assertNotEqual(old_hostname, new_hostname)
|
||||
|
||||
res = dbapi.update_compute_node(
|
||||
self.context, node.uuid, {'hostname': new_hostname})
|
||||
self.assertEqual(new_hostname, res.hostname)
|
||||
|
||||
def test_update_compute_node_not_found(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
new_hostname = 'new-hostname'
|
||||
self.assertRaises(exception.ComputeNodeNotFound,
|
||||
dbapi.update_compute_node, self.context,
|
||||
node_uuid, {'hostname': new_hostname})
|
||||
|
||||
def test_update_compute_node_uuid(self):
|
||||
node = utils.create_test_compute_node(context=self.context)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
dbapi.update_compute_node, self.context,
|
||||
node.uuid, {'uuid': ''})
|
@ -240,6 +240,39 @@ def create_test_allocation(**kw):
|
||||
return dbapi.create_allocation(kw['context'], allocation)
|
||||
|
||||
|
||||
def get_test_numa_topology(**kw):
|
||||
return {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 0,
|
||||
"cpuset": [1, 2],
|
||||
"pinned_cpus": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"cpuset": [3, 4],
|
||||
"pinned_cpus": [3, 4]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def get_test_compute_node(**kw):
|
||||
return {
|
||||
'uuid': kw.get('uuid', '24a5b17a-f2eb-4556-89db-5f4169d13982'),
|
||||
'hostname': kw.get('hostname', 'localhost'),
|
||||
'numa_topology': kw.get('numa_topology', get_test_numa_topology()),
|
||||
'created_at': kw.get('created_at'),
|
||||
'updated_at': kw.get('updated_at'),
|
||||
}
|
||||
|
||||
|
||||
def create_test_compute_node(**kw):
|
||||
compute_host = get_test_compute_node(**kw)
|
||||
dbapi = db_api._get_dbdriver_instance()
|
||||
return dbapi.create_compute_node(kw['context'], compute_host)
|
||||
|
||||
|
||||
class FakeEtcdMultipleResult(object):
|
||||
|
||||
def __init__(self, value):
|
||||
|
137
zun/tests/unit/objects/test_compute_node.py
Normal file
137
zun/tests/unit/objects/test_compute_node.py
Normal file
@ -0,0 +1,137 @@
|
||||
# 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 testtools.matchers import HasLength
|
||||
|
||||
from zun import objects
|
||||
from zun.tests.unit.db import base
|
||||
from zun.tests.unit.db import utils
|
||||
|
||||
|
||||
class TestComputeNodeObject(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestComputeNodeObject, self).setUp()
|
||||
self.fake_numa_topology = utils.get_test_numa_topology()
|
||||
self.fake_compute_node = utils.get_test_compute_node(
|
||||
numa_topology=self.fake_numa_topology)
|
||||
|
||||
def test_get_by_uuid(self):
|
||||
uuid = self.fake_compute_node['uuid']
|
||||
with mock.patch.object(self.dbapi, 'get_compute_node',
|
||||
autospec=True) as mock_get_compute_node:
|
||||
mock_get_compute_node.return_value = self.fake_compute_node
|
||||
compute_node = objects.ComputeNode.get_by_uuid(self.context, uuid)
|
||||
mock_get_compute_node.assert_called_once_with(
|
||||
self.context, uuid)
|
||||
self.assertEqual(self.context, compute_node._context)
|
||||
|
||||
def test_get_by_hostname(self):
|
||||
hostname = self.fake_compute_node['hostname']
|
||||
with mock.patch.object(self.dbapi, 'get_compute_node_by_hostname',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_compute_node
|
||||
compute_node = objects.ComputeNode.get_by_hostname(
|
||||
self.context, hostname)
|
||||
mock_get.assert_called_once_with(self.context, hostname)
|
||||
self.assertEqual(self.context, compute_node._context)
|
||||
|
||||
def test_list(self):
|
||||
with mock.patch.object(self.dbapi, 'list_compute_nodes',
|
||||
autospec=True) as mock_get_list:
|
||||
mock_get_list.return_value = [self.fake_compute_node]
|
||||
compute_nodes = objects.ComputeNode.list(self.context)
|
||||
self.assertEqual(1, mock_get_list.call_count)
|
||||
self.assertThat(compute_nodes, HasLength(1))
|
||||
self.assertIsInstance(compute_nodes[0], objects.ComputeNode)
|
||||
self.assertEqual(self.context, compute_nodes[0]._context)
|
||||
|
||||
def test_list_with_filters(self):
|
||||
with mock.patch.object(self.dbapi, 'list_compute_nodes',
|
||||
autospec=True) as mock_get_list:
|
||||
mock_get_list.return_value = [self.fake_compute_node]
|
||||
filt = {'hostname': 'test'}
|
||||
compute_nodes = objects.ComputeNode.list(
|
||||
self.context, filters=filt)
|
||||
self.assertEqual(1, mock_get_list.call_count)
|
||||
self.assertThat(compute_nodes, HasLength(1))
|
||||
self.assertIsInstance(compute_nodes[0], objects.ComputeNode)
|
||||
self.assertEqual(self.context, compute_nodes[0]._context)
|
||||
mock_get_list.assert_called_once_with(
|
||||
self.context, filters=filt, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None)
|
||||
|
||||
def test_create(self):
|
||||
with mock.patch.object(self.dbapi, 'create_compute_node',
|
||||
autospec=True) as mock_create:
|
||||
mock_create.return_value = self.fake_compute_node
|
||||
compute_node_dict = dict(self.fake_compute_node)
|
||||
compute_node_dict['numa_topology'] = objects.NUMATopology\
|
||||
._from_dict(compute_node_dict['numa_topology'])
|
||||
compute_node = objects.ComputeNode(
|
||||
self.context, **compute_node_dict)
|
||||
compute_node.create(self.context)
|
||||
mock_create.assert_called_once_with(
|
||||
self.context, self.fake_compute_node)
|
||||
self.assertEqual(self.context, compute_node._context)
|
||||
|
||||
def test_destroy(self):
|
||||
uuid = self.fake_compute_node['uuid']
|
||||
with mock.patch.object(self.dbapi, 'get_compute_node',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_compute_node
|
||||
with mock.patch.object(self.dbapi, 'destroy_compute_node',
|
||||
autospec=True) as mock_destroy:
|
||||
compute_node = objects.ComputeNode.get_by_uuid(
|
||||
self.context, uuid)
|
||||
compute_node.destroy()
|
||||
mock_get.assert_called_once_with(self.context, uuid)
|
||||
mock_destroy.assert_called_once_with(None, uuid)
|
||||
self.assertEqual(self.context, compute_node._context)
|
||||
|
||||
def test_save(self):
|
||||
uuid = self.fake_compute_node['uuid']
|
||||
with mock.patch.object(self.dbapi, 'get_compute_node',
|
||||
autospec=True) as mock_get:
|
||||
mock_get.return_value = self.fake_compute_node
|
||||
with mock.patch.object(self.dbapi, 'update_compute_node',
|
||||
autospec=True) as mock_update:
|
||||
compute_node = objects.ComputeNode.get_by_uuid(
|
||||
self.context, uuid)
|
||||
compute_node.hostname = 'myhostname'
|
||||
compute_node.save()
|
||||
|
||||
mock_get.assert_called_once_with(self.context, uuid)
|
||||
mock_update.assert_called_once_with(
|
||||
None, uuid,
|
||||
{'hostname': 'myhostname'})
|
||||
self.assertEqual(self.context, compute_node._context)
|
||||
|
||||
def test_refresh(self):
|
||||
uuid = self.fake_compute_node['uuid']
|
||||
hostname = self.fake_compute_node['hostname']
|
||||
new_hostname = 'myhostname'
|
||||
returns = [dict(self.fake_compute_node, hostname=hostname),
|
||||
dict(self.fake_compute_node, hostname=new_hostname)]
|
||||
expected = [mock.call(self.context, uuid),
|
||||
mock.call(self.context, uuid)]
|
||||
with mock.patch.object(self.dbapi, 'get_compute_node',
|
||||
side_effect=returns,
|
||||
autospec=True) as mock_get:
|
||||
compute_node = objects.ComputeNode.get_by_uuid(self.context, uuid)
|
||||
self.assertEqual(hostname, compute_node.hostname)
|
||||
compute_node.refresh()
|
||||
self.assertEqual(new_hostname, compute_node.hostname)
|
||||
self.assertEqual(expected, mock_get.call_args_list)
|
||||
self.assertEqual(self.context, compute_node._context)
|
@ -362,6 +362,7 @@ object_data = {
|
||||
'ResourceClass': '1.1-d661c7675b3cd5b8c3618b68ba64324e',
|
||||
'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24',
|
||||
'ZunService': '1.0-2a19ab9987a746621b2ada02d8aadf22',
|
||||
'ComputeNode': '1.0-790d40d5af2d05dc6302bd2e4aa4d80b',
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user