Add compute node to DB and objects

Change-Id: Ic196736ff263220eb83acce72772f0ebcc462b9f
This commit is contained in:
Hongbin Lu 2017-02-21 18:02:38 -06:00
parent 8053efa4cf
commit 7255e44328
13 changed files with 729 additions and 17 deletions

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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': ''})

View File

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

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

View File

@ -362,6 +362,7 @@ object_data = {
'ResourceClass': '1.1-d661c7675b3cd5b8c3618b68ba64324e',
'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24',
'ZunService': '1.0-2a19ab9987a746621b2ada02d8aadf22',
'ComputeNode': '1.0-790d40d5af2d05dc6302bd2e4aa4d80b',
}