Remove node object from Magnum

The node object represents either a bare metal or virtual machine
node that is provisioned with an OS to run the containers, or
alternatively, run kubernetes. Magnum use Heat to deploy the nodes,
so it is unnecessary to maintain node object in Magnum. Heat can do
the work for us. The code about node object is useless now, so let's
remove it from Magnum.

Closes-Bug: #1540790
Change-Id: If8761b06a364127683099afb4dc51ea551be6f89
This commit is contained in:
Hua Wang 2016-02-02 16:39:31 +08:00
parent b74fa9ac09
commit 342e83f033
21 changed files with 29 additions and 1345 deletions

View File

@ -35,7 +35,6 @@ There are several different types of objects in the magnum system:
* **Bay:** A collection of node objects where work is scheduled * **Bay:** A collection of node objects where work is scheduled
* **BayModel:** An object stores template information about the bay which is * **BayModel:** An object stores template information about the bay which is
used to create new bays consistently used to create new bays consistently
* **Node:** A baremetal or virtual machine where work executes
* **Pod:** A collection of containers running on one physical or virtual * **Pod:** A collection of containers running on one physical or virtual
machine machine
* **Service:** An abstraction which defines a logical set of pods and a policy * **Service:** An abstraction which defines a logical set of pods and a policy

View File

@ -86,7 +86,6 @@ magnum/tests/unit/objects/test_objects.py::
'BayModel': '1.0-06863f04ab4b98307e3d1b736d3137bf', 'BayModel': '1.0-06863f04ab4b98307e3d1b736d3137bf',
'Container': '1.1-22b40e8eed0414561ca921906b189820', 'Container': '1.1-22b40e8eed0414561ca921906b189820',
'MyObj': '1.0-b43567e512438205e32f4e95ca616697', 'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
'Node': '1.0-30943e6e3387a2fae7490b57c4239a17',
'Pod': '1.0-69b579203c6d726be7878c606626e438', 'Pod': '1.0-69b579203c6d726be7878c606626e438',
'ReplicationController': '1.0-782b7deb9307b2807101541b7e58b8a2', 'ReplicationController': '1.0-782b7deb9307b2807101541b7e58b8a2',
'Service': '1.0-d4b8c0f3a234aec35d273196e18f7ed1', 'Service': '1.0-d4b8c0f3a234aec35d273196e18f7ed1',

View File

@ -19,13 +19,6 @@
"baymodel:update": "rule:default", "baymodel:update": "rule:default",
"baymodel:publish": "rule:admin_or_owner", "baymodel:publish": "rule:admin_or_owner",
"node:create": "rule:default",
"node:delete": "rule:default",
"node:detail": "rule:default",
"node:get": "rule:default",
"node:get_all": "rule:default",
"node:update": "rule:default",
"pod:create": "rule:default", "pod:create": "rule:default",
"pod:delete": "rule:default", "pod:delete": "rule:default",
"pod:detail": "rule:default", "pod:detail": "rule:default",

View File

@ -31,7 +31,6 @@ from magnum.api.controllers.v1 import baymodel
from magnum.api.controllers.v1 import certificate from magnum.api.controllers.v1 import certificate
from magnum.api.controllers.v1 import container from magnum.api.controllers.v1 import container
from magnum.api.controllers.v1 import magnum_services from magnum.api.controllers.v1 import magnum_services
from magnum.api.controllers.v1 import node
from magnum.api.controllers.v1 import pod from magnum.api.controllers.v1 import pod
from magnum.api.controllers.v1 import replicationcontroller as rc from magnum.api.controllers.v1 import replicationcontroller as rc
from magnum.api.controllers.v1 import service from magnum.api.controllers.v1 import service
@ -192,7 +191,6 @@ class Controller(rest.RestController):
bays = bay.BaysController() bays = bay.BaysController()
baymodels = baymodel.BayModelsController() baymodels = baymodel.BayModelsController()
containers = container.ContainersController() containers = container.ContainersController()
nodes = node.NodesController()
pods = pod.PodsController() pods = pod.PodsController()
rcs = rc.ReplicationControllersController() rcs = rc.ReplicationControllersController()
services = service.ServicesController() services = service.ServicesController()

View File

@ -1,255 +0,0 @@
# Copyright 2013 UnitedStack Inc.
# All Rights Reserved.
#
# 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_utils import timeutils
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import base
from magnum.api.controllers import link
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.common import exception
from magnum.common import policy
from magnum import objects
class NodePatchType(types.JsonPatchType):
pass
class Node(base.APIBase):
"""API representation of a node.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a node.
"""
uuid = types.uuid
"""Unique UUID for this node"""
type = wtypes.text
"""Type of this node"""
image_id = wtypes.text
"""The image name or UUID to use as a base image for this node"""
ironic_node_id = wtypes.text
"""The Ironic node ID associated with this node"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated node links"""
def __init__(self, **kwargs):
self.fields = []
for field in objects.Node.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@staticmethod
def _convert_with_links(node, url, expand=True):
if not expand:
node.unset_fields_except(['uuid', 'name', 'type', 'image_id',
'ironic_node_id'])
node.links = [link.Link.make_link('self', url,
'nodes', node.uuid),
link.Link.make_link('bookmark', url,
'nodes', node.uuid,
bookmark=True)]
return node
@classmethod
def convert_with_links(cls, rpc_node, expand=True):
node = Node(**rpc_node.as_dict())
return cls._convert_with_links(node, pecan.request.host_url, expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
type='virt',
image_id='Fedora-k8s',
ironic_node_id='4b6ec4a9-d412-494a-be77-a2fd16361402',
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
class NodeCollection(collection.Collection):
"""API representation of a collection of nodes."""
nodes = [Node]
"""A list containing nodes objects"""
def __init__(self, **kwargs):
self._type = 'nodes'
@staticmethod
def convert_with_links(rpc_nodes, limit, url=None, expand=False, **kwargs):
collection = NodeCollection()
collection.nodes = [Node.convert_with_links(p, expand)
for p in rpc_nodes]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.nodes = [Node.sample(expand=False)]
return sample
class NodesController(rest.RestController):
"""REST controller for Nodes."""
_custom_actions = {
'detail': ['GET'],
}
def _get_nodes_collection(self, marker, limit,
sort_key, sort_dir, expand=False,
resource_url=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.Node.get_by_uuid(pecan.request.context,
marker)
nodes = objects.Node.list(pecan.request.context, limit,
marker_obj, sort_key=sort_key,
sort_dir=sort_dir)
return NodeCollection.convert_with_links(nodes, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@expose.expose(NodeCollection, types.uuid, int, wtypes.text,
wtypes.text)
@policy.enforce_wsgi("node")
def get_all(self, marker=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of nodes.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
return self._get_nodes_collection(marker, limit, sort_key,
sort_dir)
@expose.expose(NodeCollection, types.uuid, int, wtypes.text,
wtypes.text)
@policy.enforce_wsgi("node")
def detail(self, marker=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of nodes with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
# NOTE(lucasagomes): /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "nodes":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['nodes', 'detail'])
return self._get_nodes_collection(marker, limit,
sort_key, sort_dir, expand,
resource_url)
@expose.expose(Node, types.uuid)
@policy.enforce_wsgi("node", "get")
def get_one(self, node_uuid):
"""Retrieve information about the given node.
:param node_uuid: UUID of a node.
"""
rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
return Node.convert_with_links(rpc_node)
@expose.expose(Node, body=Node, status_code=201)
@policy.enforce_wsgi("node", "create")
def post(self, node):
"""Create a new node.
:param node: a node within the request body.
"""
node_dict = node.as_dict()
context = pecan.request.context
node_dict['project_id'] = context.project_id
node_dict['user_id'] = context.user_id
new_node = objects.Node(context, **node_dict)
new_node.create()
# Set the HTTP Location Header
pecan.response.location = link.build_url('nodes', new_node.uuid)
return Node.convert_with_links(new_node)
@wsme.validate(types.uuid, [NodePatchType])
@expose.expose(Node, types.uuid, body=[NodePatchType])
@policy.enforce_wsgi("node", "update")
def patch(self, node_uuid, patch):
"""Update an existing node.
:param node_uuid: UUID of a node.
:param patch: a json PATCH document to apply to this node.
"""
rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
try:
node_dict = rpc_node.as_dict()
node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
# Update only the fields that have changed
for field in objects.Node.fields:
try:
patch_val = getattr(node, field)
except AttributeError:
# Ignore fields that aren't exposed in the API
continue
if patch_val == wtypes.Unset:
patch_val = None
if rpc_node[field] != patch_val:
rpc_node[field] = patch_val
rpc_node.save()
return Node.convert_with_links(rpc_node)
@expose.expose(None, types.uuid, status_code=204)
@policy.enforce_wsgi("node", "delete")
def delete(self, node_uuid):
"""Delete a node.
:param node_uuid: UUID of a node.
"""
rpc_node = objects.Node.get_by_uuid(pecan.request.context,
node_uuid)
rpc_node.destroy()

View File

@ -314,11 +314,6 @@ class InvalidParameterValue(Invalid):
message = _("%(err)s") message = _("%(err)s")
class InstanceAssociated(Conflict):
message = _("Instance %(instance_uuid)s is already associated with a node,"
" it cannot be associated with this other node %(node)s")
class InstanceNotFound(ResourceNotFound): class InstanceNotFound(ResourceNotFound):
message = _("Instance %(instance)s could not be found.") message = _("Instance %(instance)s could not be found.")
@ -351,18 +346,6 @@ class ConfigInvalid(MagnumException):
message = _("Invalid configuration file. %(error_msg)s") message = _("Invalid configuration file. %(error_msg)s")
class NodeAlreadyExists(Conflict):
message = _("A node with UUID %(uuid)s already exists.")
class NodeNotFound(ResourceNotFound):
message = _("Node %(node)s could not be found.")
class NodeAssociated(InvalidState):
message = _("Node %(node)s is associated with instance %(instance)s.")
class SSHConnectFailed(MagnumException): class SSHConnectFailed(MagnumException):
message = _("Failed to establish SSH connection to host %(host)s.") message = _("Failed to establish SSH connection to host %(host)s.")

View File

@ -289,78 +289,6 @@ class Connection(object):
""" """
@abc.abstractmethod @abc.abstractmethod
def get_node_list(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get for matching nodes.
Return a list of the specified columns for all nodes that match the
specified filters.
:param context: The security context
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of 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.
"""
@abc.abstractmethod
def create_node(self, values):
"""Create a new node.
:param values: A dict containing several items used to identify
and track the node, and several dicts which are passed
into the Drivers when managing this node. For example:
::
{
'uuid': utils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A node.
"""
@abc.abstractmethod
def get_node_by_id(self, context, node_id):
"""Return a node.
:param context: The security context
:param node_id: The id of a node.
:returns: A node.
"""
@abc.abstractmethod
def get_node_by_uuid(self, context, node_uuid):
"""Return a node.
:param context: The security context
:param node_uuid: The uuid of a node.
:returns: A node.
"""
@abc.abstractmethod
def destroy_node(self, node_id):
"""Destroy a node and all associated interfaces.
:param node_id: The id or uuid of a node.
"""
@abc.abstractmethod
def update_node(self, node_id, values):
"""Update properties of a node.
:param node_id: The id or uuid of a node.
:returns: A node.
:raises: NodeAssociated
:raises: NodeNotFound
"""
@abc.abstractmethod
def get_pod_list(self, context, filters=None, limit=None, def get_pod_list(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None): marker=None, sort_key=None, sort_dir=None):
"""Get matching pods. """Get matching pods.

View File

@ -0,0 +1,29 @@
# 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.
"""remove node object
Revision ID: bb42b7cad130
Revises: 05d3e97de9ee
Create Date: 2016-02-02 16:04:36.501547
"""
# revision identifiers, used by Alembic.
revision = 'bb42b7cad130'
down_revision = '05d3e97de9ee'
from alembic import op
def upgrade():
op.drop_table('node')

View File

@ -21,7 +21,6 @@ from oslo_db.sqlalchemy import utils as db_utils
from oslo_utils import timeutils from oslo_utils import timeutils
from sqlalchemy.orm.exc import MultipleResultsFound from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy import sql
from magnum.common import exception from magnum.common import exception
from magnum.common import utils from magnum.common import utils
@ -463,110 +462,6 @@ class Connection(api.Connection):
ref.update(values) ref.update(values)
return ref return ref
def _add_nodes_filters(self, query, filters):
if filters is None:
filters = {}
if 'associated' in filters:
if filters['associated']:
query = query.filter(models.Node.ironic_node_id != sql.null())
else:
query = query.filter(models.Node.ironic_node_id == sql.null())
if 'type' in filters:
query = query.filter_by(type=filters['type'])
if 'image_id' in filters:
query = query.filter_by(image_id=filters['image_id'])
if 'project_id' in filters:
query = query.filter_by(project_id=filters['project_id'])
if 'user_id' in filters:
query = query.filter_by(user_id=filters['user_id'])
return query
def get_node_list(self, context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Node)
query = self._add_tenant_filters(context, query)
query = self._add_nodes_filters(query, filters)
return _paginate_query(models.Node, limit, marker,
sort_key, sort_dir, query)
def create_node(self, values):
# ensure defaults are present for new nodes
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
node = models.Node()
node.update(values)
try:
node.save()
except db_exc.DBDuplicateEntry as exc:
if 'ironic_node_id' in exc.columns:
raise exception.InstanceAssociated(
instance_uuid=values['ironic_node_id'],
node=values['uuid'])
raise exception.NodeAlreadyExists(uuid=values['uuid'])
return node
def get_node_by_id(self, context, node_id):
query = model_query(models.Node)
query = self._add_tenant_filters(context, query)
query = query.filter_by(id=node_id)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
def get_node_by_uuid(self, context, node_uuid):
query = model_query(models.Node)
query = self._add_tenant_filters(context, query)
query = query.filter_by(uuid=node_uuid)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_uuid)
def destroy_node(self, node_id):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
count = query.delete()
if count != 1:
raise exception.NodeNotFound(node_id)
def update_node(self, node_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Node.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_node(node_id, values)
except db_exc.DBDuplicateEntry:
raise exception.InstanceAssociated(
instance_uuid=values['ironic_node_id'],
node=node_id)
def _do_update_node(self, node_id, values):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
# Prevent ironic_node_id overwriting
if values.get("ironic_node_id") and ref.ironic_node_id:
raise exception.NodeAssociated(
node=node_id,
instance=ref.ironic_node_id)
ref.update(values)
return ref
def _add_pods_filters(self, query, filters): def _add_pods_filters(self, query, filters):
if filters is None: if filters is None:
filters = {} filters = {}

View File

@ -190,25 +190,6 @@ class Container(Base):
environment = Column(JSONEncodedDict) environment = Column(JSONEncodedDict)
class Node(Base):
"""Represents a node."""
__tablename__ = 'node'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_node0uuid'),
schema.UniqueConstraint('ironic_node_id',
name='uniq_node0ironic_node_id'),
table_args()
)
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
type = Column(String(20))
project_id = Column(String(255))
user_id = Column(String(255))
image_id = Column(String(255))
ironic_node_id = Column(String(36))
class Pod(Base): class Pod(Base):
"""Represents a pod.""" """Represents a pod."""

View File

@ -17,7 +17,6 @@ from magnum.objects import baymodel
from magnum.objects import certificate from magnum.objects import certificate
from magnum.objects import container from magnum.objects import container
from magnum.objects import magnum_service from magnum.objects import magnum_service
from magnum.objects import node
from magnum.objects import pod from magnum.objects import pod
from magnum.objects import replicationcontroller as rc from magnum.objects import replicationcontroller as rc
from magnum.objects import service from magnum.objects import service
@ -28,7 +27,6 @@ Container = container.Container
Bay = bay.Bay Bay = bay.Bay
BayModel = baymodel.BayModel BayModel = baymodel.BayModel
MagnumService = magnum_service.MagnumService MagnumService = magnum_service.MagnumService
Node = node.Node
Pod = pod.Pod Pod = pod.Pod
ReplicationController = rc.ReplicationController ReplicationController = rc.ReplicationController
Service = service.Service Service = service.Service
@ -38,7 +36,6 @@ __all__ = (Bay,
BayModel, BayModel,
Container, Container,
MagnumService, MagnumService,
Node,
Pod, Pod,
ReplicationController, ReplicationController,
Service, Service,

View File

@ -1,161 +0,0 @@
# 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 magnum.db import api as dbapi
from magnum.objects import base
@base.MagnumObjectRegistry.register
class Node(base.MagnumPersistentObject, base.MagnumObject,
base.MagnumObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': fields.IntegerField(),
'uuid': fields.StringField(nullable=True),
'project_id': fields.StringField(nullable=True),
'user_id': fields.StringField(nullable=True),
'type': fields.StringField(nullable=True),
'image_id': fields.StringField(nullable=True),
'ironic_node_id': fields.StringField(nullable=True)
}
@staticmethod
def _from_db_object(node, db_node):
"""Converts a database entity to a formal object."""
for field in node.fields:
node[field] = db_node[field]
node.obj_reset_changes()
return node
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [Node._from_db_object(cls(context), obj) for obj in db_objects]
@base.remotable_classmethod
def get_by_id(cls, context, node_id):
"""Find a node based on its integer id and return a Node object.
:param node_id: the id of a node.
:param context: Security context
:returns: a :class:`Node` object.
"""
db_node = cls.dbapi.get_node_by_id(context, node_id)
node = Node._from_db_object(cls(context), db_node)
return node
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a node based on uuid and return a :class:`Node` object.
:param uuid: the uuid of a node.
:param context: Security context
:returns: a :class:`Node` object.
"""
db_node = cls.dbapi.get_node_by_uuid(context, uuid)
node = Node._from_db_object(cls(context), db_node)
return node
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Node 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".
:returns: a list of :class:`Node` object.
"""
db_nodes = cls.dbapi.get_node_list(context, limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Node._from_db_object_list(db_nodes, cls, context)
@base.remotable
def create(self, context=None):
"""Create a Node record in the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Node(context)
"""
values = self.obj_get_changes()
db_node = self.dbapi.create_node(values)
self._from_db_object(self, db_node)
@base.remotable
def destroy(self, context=None):
"""Delete the Node from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Node(context)
"""
self.dbapi.destroy_node(self.uuid)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this Node.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Node(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_node(self.uuid, updates)
self.obj_reset_changes()
@base.remotable
def refresh(self, context=None):
"""Loads updates for this Node.
Loads a node with the same uuid from the database and
checks for updated attributes. Updates are applied from
the loaded node column by column, if there are any updates.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Node(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 self[field] != current[field]:
self[field] = current[field]

View File

@ -34,13 +34,6 @@ policy_data = """
"baymodel:get_all": "", "baymodel:get_all": "",
"baymodel:update": "", "baymodel:update": "",
"node:create": "",
"node:delete": "",
"node:detail": "",
"node:get": "",
"node:get_all": "",
"node:update": "",
"pod:create": "", "pod:create": "",
"pod:delete": "", "pod:delete": "",
"pod:detail": "", "pod:detail": "",

View File

@ -23,6 +23,3 @@ class TestListResources(BaseMagnumClient):
def test_containers_list(self): def test_containers_list(self):
self.assertIsNotNone(self.cs.containers.list()) self.assertIsNotNone(self.cs.containers.list())
def test_nodes_list(self):
self.assertIsNotNone(self.cs.nodes.list())

View File

@ -1,341 +0,0 @@
# 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 datetime
import json
import mock
from oslo_config import cfg
from oslo_utils import timeutils
from six.moves.urllib import parse as urlparse
from wsme import types as wtypes
from magnum.api.controllers.v1 import node as api_node
from magnum.common import utils
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.objects import utils as obj_utils
class TestNodeObject(base.TestCase):
def test_node_init(self):
node_dict = apiutils.node_post_data()
del node_dict['image_id']
node = api_node.Node(**node_dict)
self.assertEqual(wtypes.Unset, node.image_id)
class TestListNode(api_base.FunctionalTest):
def test_empty(self):
response = self.get_json('/nodes')
self.assertEqual([], response['nodes'])
def _assert_node_fields(self, node):
node_fields = ['type', 'image_id', 'ironic_node_id']
for field in node_fields:
self.assertIn(field, node)
def test_one(self):
node = obj_utils.create_test_node(self.context)
response = self.get_json('/nodes')
self.assertEqual(node.uuid, response['nodes'][0]["uuid"])
self._assert_node_fields(response['nodes'][0])
def test_get_one(self):
node = obj_utils.create_test_node(self.context)
response = self.get_json('/nodes/%s' % node['uuid'])
self.assertEqual(node.uuid, response['uuid'])
self._assert_node_fields(response)
def test_get_all_with_pagination_marker(self):
node_list = []
for id_ in range(4):
node = obj_utils.create_test_node(self.context, id=id_,
uuid=utils.generate_uuid())
node_list.append(node.uuid)
response = self.get_json('/nodes?limit=3&marker=%s' % node_list[2])
self.assertEqual(1, len(response['nodes']))
self.assertEqual(node_list[-1], response['nodes'][0]['uuid'])
def test_detail(self):
node = obj_utils.create_test_node(self.context)
response = self.get_json('/nodes/detail')
self.assertEqual(node.uuid, response['nodes'][0]["uuid"])
self._assert_node_fields(response['nodes'][0])
def test_detail_with_pagination_marker(self):
node_list = []
for id_ in range(4):
node = obj_utils.create_test_node(self.context, id=id_,
uuid=utils.generate_uuid())
node_list.append(node.uuid)
response = self.get_json('/nodes/detail?limit=3&marker=%s'
% node_list[2])
self.assertEqual(1, len(response['nodes']))
self.assertEqual(node_list[-1], response['nodes'][0]['uuid'])
self._assert_node_fields(response['nodes'][0])
def test_detail_against_single(self):
node = obj_utils.create_test_node(self.context)
response = self.get_json('/nodes/%s/detail' % node['uuid'],
expect_errors=True)
self.assertEqual(404, response.status_int)
def test_many(self):
node_list = []
for id_ in range(5):
node = obj_utils.create_test_node(self.context, id=id_,
uuid=utils.generate_uuid())
node_list.append(node.uuid)
response = self.get_json('/nodes')
self.assertEqual(len(node_list), len(response['nodes']))
uuids = [s['uuid'] for s in response['nodes']]
self.assertEqual(sorted(node_list), sorted(uuids))
def test_links(self):
uuid = utils.generate_uuid()
obj_utils.create_test_node(self.context, id=1, uuid=uuid)
response = self.get_json('/nodes/%s' % uuid)
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
self.assertIn(uuid, response['links'][0]['href'])
for l in response['links']:
bookmark = l['rel'] == 'bookmark'
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
def test_collection_links(self):
for id_ in range(5):
obj_utils.create_test_node(self.context, id=id_,
uuid=utils.generate_uuid())
response = self.get_json('/nodes/?limit=3')
self.assertEqual(3, len(response['nodes']))
next_marker = response['nodes'][-1]['uuid']
self.assertIn(next_marker, response['next'])
def test_collection_links_default_limit(self):
cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5):
obj_utils.create_test_node(self.context, id=id_,
uuid=utils.generate_uuid())
response = self.get_json('/nodes')
self.assertEqual(3, len(response['nodes']))
next_marker = response['nodes'][-1]['uuid']
self.assertIn(next_marker, response['next'])
class TestPatch(api_base.FunctionalTest):
def setUp(self):
super(TestPatch, self).setUp()
self.node = obj_utils.create_test_node(self.context, image_id='Fedora')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
new_image = 'Ubuntu'
response = self.get_json('/nodes/%s' % self.node.uuid)
self.assertNotEqual(new_image, response['image_id'])
response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/image_id', 'value': new_image,
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/nodes/%s' % self.node.uuid)
self.assertEqual(new_image, response['image_id'])
return_updated_at = timeutils.parse_isotime(
response['updated_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_updated_at)
def test_replace_non_existent_node(self):
response = self.patch_json('/nodes/%s' % utils.generate_uuid(),
[{'path': '/image_id', 'value': 'Ubuntu',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_add_non_existent_property(self):
response = self.patch_json(
'/nodes/%s' % self.node.uuid,
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['error_message'])
def test_remove_ok(self):
response = self.get_json('/nodes/%s' % self.node.uuid)
self.assertIsNotNone(response['image_id'])
response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/image_id', 'op': 'remove'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/nodes/%s' % self.node.uuid)
self.assertIsNone(response['image_id'])
def test_remove_uuid(self):
response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/uuid', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_remove_non_existent_property(self):
response = self.patch_json(
'/nodes/%s' % self.node.uuid,
[{'path': '/non-existent', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
class TestPost(api_base.FunctionalTest):
@mock.patch('oslo_utils.timeutils.utcnow')
def test_create_node(self, mock_utcnow):
node_dict = apiutils.node_post_data()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.post_json('/nodes', node_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
# Check location header
self.assertIsNotNone(response.location)
expected_location = '/v1/nodes/%s' % node_dict['uuid']
self.assertEqual(expected_location,
urlparse.urlparse(response.location).path)
self.assertEqual(node_dict['uuid'], response.json['uuid'])
self.assertNotIn('updated_at', response.json.keys)
return_created_at = timeutils.parse_isotime(
response.json['created_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_created_at)
def test_create_node_set_project_id_and_user_id(self):
with mock.patch.object(self.dbapi, 'create_node',
wraps=self.dbapi.create_node) as cc_mock:
node_dict = apiutils.node_post_data()
self.post_json('/nodes', node_dict)
cc_mock.assert_called_once_with(mock.ANY)
self.assertEqual(self.context.project_id,
cc_mock.call_args[0][0]['project_id'])
self.assertEqual(self.context.user_id,
cc_mock.call_args[0][0]['user_id'])
def test_create_node_doesnt_contain_id(self):
with mock.patch.object(self.dbapi, 'create_node',
wraps=self.dbapi.create_node) as cn_mock:
node_dict = apiutils.node_post_data(image_id='Ubuntu')
response = self.post_json('/nodes', node_dict)
self.assertEqual(node_dict['image_id'], response.json['image_id'])
cn_mock.assert_called_once_with(mock.ANY)
# Check that 'id' is not in first arg of positional args
self.assertNotIn('id', cn_mock.call_args[0][0])
def test_create_node_generate_uuid(self):
node_dict = apiutils.node_post_data()
del node_dict['uuid']
response = self.post_json('/nodes', node_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(node_dict['image_id'],
response.json['image_id'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
class TestDelete(api_base.FunctionalTest):
def setUp(self):
super(TestDelete, self).setUp()
self.node = obj_utils.create_test_node(self.context, image_id='Fedora')
def test_delete_node(self):
self.delete('/nodes/%s' % self.node.uuid)
response = self.get_json('/nodes/%s' % self.node.uuid,
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_delete_node_not_found(self):
uuid = utils.generate_uuid()
response = self.delete('/nodes/%s' % uuid, expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
class TestNodePolicyEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: "project:non_fake"})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
json.loads(response.json['error_message'])['faultstring'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
"node:get_all", self.get_json, '/nodes', expect_errors=True)
def test_policy_disallow_get_one(self):
self._common_policy_check(
"node:get", self.get_json,
'/nodes/%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
"node:detail", self.get_json,
'/nodes/%s/detail' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_update(self):
node = obj_utils.create_test_node(self.context,
type='type_A',
uuid=utils.generate_uuid())
self._common_policy_check(
"node:update", self.patch_json,
'/nodes/%s' % node.uuid,
[{'path': '/type', 'value': "new_type", 'op': 'replace'}],
expect_errors=True)
def test_policy_disallow_create(self):
bdict = apiutils.node_post_data(name='node_example_A')
self._common_policy_check(
"node:create", self.post_json, '/nodes', bdict, expect_errors=True)
def test_policy_disallow_delete(self):
node = obj_utils.create_test_node(self.context,
uuid=utils.generate_uuid())
self._common_policy_check(
"node:delete", self.delete,
'/nodes/%s' % node.uuid, expect_errors=True)

View File

@ -17,7 +17,6 @@ import pytz
from magnum.api.controllers.v1 import bay as bay_controller from magnum.api.controllers.v1 import bay as bay_controller
from magnum.api.controllers.v1 import baymodel as baymodel_controller from magnum.api.controllers.v1 import baymodel as baymodel_controller
from magnum.api.controllers.v1 import node as node_controller
from magnum.api.controllers.v1 import pod as pod_controller from magnum.api.controllers.v1 import pod as pod_controller
from magnum.api.controllers.v1 import replicationcontroller as rc_controller from magnum.api.controllers.v1 import replicationcontroller as rc_controller
from magnum.api.controllers.v1 import service as service_controller from magnum.api.controllers.v1 import service as service_controller
@ -138,12 +137,6 @@ def rc_post_data(**kw):
return remove_internal(rc, internal) return remove_internal(rc, internal)
def node_post_data(**kw):
node = utils.get_test_node(**kw)
internal = node_controller.NodePatchType.internal_attrs()
return remove_internal(node, internal)
def x509keypair_post_data(**kw): def x509keypair_post_data(**kw):
x509keypair = utils.get_test_x509keypair(**kw) x509keypair = utils.get_test_x509keypair(**kw)
internal = x509keypair_controller.X509KeyPairPatchType.internal_attrs() internal = x509keypair_controller.X509KeyPairPatchType.internal_attrs()

View File

@ -1,175 +0,0 @@
# Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# 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 Nodes via the DB API"""
import six
from magnum.common import exception
from magnum.common import utils as magnum_utils
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
class DbNodeTestCase(base.DbTestCase):
def test_create_node(self):
utils.create_test_node()
def test_create_node_already_exists(self):
utils.create_test_node()
self.assertRaises(exception.NodeAlreadyExists,
utils.create_test_node)
def test_create_node_instance_already_associated(self):
instance_uuid = magnum_utils.generate_uuid()
utils.create_test_node(uuid=magnum_utils.generate_uuid(),
ironic_node_id=instance_uuid)
self.assertRaises(exception.InstanceAssociated,
utils.create_test_node,
uuid=magnum_utils.generate_uuid(),
ironic_node_id=instance_uuid)
def test_get_node_by_id(self):
node = utils.create_test_node()
res = self.dbapi.get_node_by_id(self.context, node.id)
self.assertEqual(node.id, res.id)
self.assertEqual(node.uuid, res.uuid)
def test_get_node_by_uuid(self):
node = utils.create_test_node()
res = self.dbapi.get_node_by_uuid(self.context, node.uuid)
self.assertEqual(node.id, res.id)
self.assertEqual(node.uuid, res.uuid)
def test_get_node_that_does_not_exist(self):
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_id, self.context, 99)
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_uuid,
self.context,
magnum_utils.generate_uuid())
def test_get_node_list(self):
uuids = []
for i in range(1, 6):
node = utils.create_test_node(uuid=magnum_utils.generate_uuid())
uuids.append(six.text_type(node['uuid']))
res = self.dbapi.get_node_list(self.context)
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), sorted(res_uuids))
def test_get_node_list_sorted(self):
uuids = []
for _ in range(5):
node = utils.create_test_node(uuid=magnum_utils.generate_uuid())
uuids.append(six.text_type(node.uuid))
res = self.dbapi.get_node_list(self.context, sort_key='uuid')
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), res_uuids)
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.get_node_list,
self.context,
sort_key='foo')
def test_get_node_list_with_filters(self):
node1 = utils.create_test_node(
type='virt',
ironic_node_id=magnum_utils.generate_uuid(),
uuid=magnum_utils.generate_uuid())
node2 = utils.create_test_node(
type='bare',
uuid=magnum_utils.generate_uuid())
res = self.dbapi.get_node_list(self.context, filters={'type': 'virt'})
self.assertEqual([node1.id], [r.id for r in res])
res = self.dbapi.get_node_list(self.context,
filters={'type': 'bad-type'})
self.assertEqual([], [r.id for r in res])
res = self.dbapi.get_node_list(self.context,
filters={'associated': True})
self.assertEqual([node1.id], [r.id for r in res])
res = self.dbapi.get_node_list(self.context,
filters={'associated': False})
self.assertEqual([node2.id], [r.id for r in res])
def test_destroy_node(self):
node = utils.create_test_node()
self.dbapi.destroy_node(node.id)
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_id, self.context, node.id)
def test_destroy_node_by_uuid(self):
node = utils.create_test_node()
self.dbapi.destroy_node(node.uuid)
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_uuid,
self.context, node.uuid)
def test_destroy_node_that_does_not_exist(self):
self.assertRaises(exception.NodeNotFound,
self.dbapi.destroy_node,
magnum_utils.generate_uuid())
def test_update_node(self):
node = utils.create_test_node()
old_image = node.image_id
new_image = 'new-image'
self.assertNotEqual(old_image, new_image)
res = self.dbapi.update_node(node.id, {'image_id': new_image})
self.assertEqual(new_image, res.image_id)
def test_update_node_not_found(self):
node_uuid = magnum_utils.generate_uuid()
new_image = 'new-image'
self.assertRaises(exception.NodeNotFound, self.dbapi.update_node,
node_uuid, {'image_id': new_image})
def test_update_node_uuid(self):
node = utils.create_test_node()
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.update_node, node.id,
{'uuid': ''})
def test_update_node_associate_and_disassociate(self):
node = utils.create_test_node()
new_i_uuid = magnum_utils.generate_uuid()
res = self.dbapi.update_node(node.id, {'ironic_node_id': new_i_uuid})
self.assertEqual(new_i_uuid, res.ironic_node_id)
res = self.dbapi.update_node(node.id, {'ironic_node_id': None})
self.assertIsNone(res.ironic_node_id)
def test_update_node_already_associated(self):
node = utils.create_test_node()
new_i_uuid_one = magnum_utils.generate_uuid()
self.dbapi.update_node(node.id, {'ironic_node_id': new_i_uuid_one})
new_i_uuid_two = magnum_utils.generate_uuid()
self.assertRaises(exception.NodeAssociated,
self.dbapi.update_node, node.id,
{'ironic_node_id': new_i_uuid_two})
def test_update_node_instance_already_associated(self):
node1 = utils.create_test_node(uuid=magnum_utils.generate_uuid())
new_i_uuid = magnum_utils.generate_uuid()
self.dbapi.update_node(node1.id, {'ironic_node_id': new_i_uuid})
node2 = utils.create_test_node(uuid=magnum_utils.generate_uuid())
self.assertRaises(exception.InstanceAssociated,
self.dbapi.update_node, node2.id,
{'ironic_node_id': new_i_uuid})

View File

@ -183,35 +183,6 @@ def create_test_service(**kw):
return dbapi.create_service(service) return dbapi.create_service(service)
def get_test_node(**kw):
return {
'id': kw.get('id', 42),
'uuid': kw.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
'type': kw.get('type', 'virt'),
'project_id': kw.get('project_id', 'fake_project'),
'user_id': kw.get('user_id', 'fake_user'),
'image_id': kw.get('image_id', 'ubuntu'),
'ironic_node_id': kw.get('ironic_node_id'),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),
}
def create_test_node(**kw):
"""Create test node entry in DB and return Node DB object.
Function to be used to create test Node objects in the database.
:param kw: kwargs with overriding values for node's attributes.
:returns: Test Node DB object.
"""
node = get_test_node(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del node['id']
dbapi = db_api.get_instance()
return dbapi.create_node(node)
def get_test_container(**kw): def get_test_container(**kw):
return { return {
'id': kw.get('id', 42), 'id': kw.get('id', 42),

View File

@ -1,112 +0,0 @@
# Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# 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 magnum.common import utils as magnum_utils
from magnum import objects
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
class TestNodeObject(base.DbTestCase):
def setUp(self):
super(TestNodeObject, self).setUp()
self.fake_node = utils.get_test_node()
def test_get_by_id(self):
node_id = self.fake_node['id']
with mock.patch.object(self.dbapi, 'get_node_by_id',
autospec=True) as mock_get_node:
mock_get_node.return_value = self.fake_node
node = objects.Node.get_by_id(self.context, node_id)
mock_get_node.assert_called_once_with(self.context, node_id)
self.assertEqual(self.context, node._context)
def test_get_by_uuid(self):
uuid = self.fake_node['uuid']
with mock.patch.object(self.dbapi, 'get_node_by_uuid',
autospec=True) as mock_get_node:
mock_get_node.return_value = self.fake_node
node = objects.Node.get_by_uuid(self.context, uuid)
mock_get_node.assert_called_once_with(self.context, uuid)
self.assertEqual(self.context, node._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'get_node_list',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_node]
nodes = objects.Node.list(self.context)
self.assertEqual(1, mock_get_list.call_count)
self.assertThat(nodes, HasLength(1))
self.assertIsInstance(nodes[0], objects.Node)
self.assertEqual(self.context, nodes[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'create_node',
autospec=True) as mock_create_node:
mock_create_node.return_value = self.fake_node
node = objects.Node(self.context, **self.fake_node)
node.create()
mock_create_node.assert_called_once_with(self.fake_node)
self.assertEqual(self.context, node._context)
def test_destroy(self):
uuid = self.fake_node['uuid']
with mock.patch.object(self.dbapi, 'get_node_by_uuid',
autospec=True) as mock_get_node:
mock_get_node.return_value = self.fake_node
with mock.patch.object(self.dbapi, 'destroy_node',
autospec=True) as mock_destroy_node:
node = objects.Node.get_by_uuid(self.context, uuid)
node.destroy()
mock_get_node.assert_called_once_with(self.context, uuid)
mock_destroy_node.assert_called_once_with(uuid)
self.assertEqual(self.context, node._context)
def test_save(self):
uuid = self.fake_node['uuid']
with mock.patch.object(self.dbapi, 'get_node_by_uuid',
autospec=True) as mock_get_node:
mock_get_node.return_value = self.fake_node
with mock.patch.object(self.dbapi, 'update_node',
autospec=True) as mock_update_node:
node = objects.Node.get_by_uuid(self.context, uuid)
node.type = 'bare'
node.save()
mock_get_node.assert_called_once_with(self.context, uuid)
mock_update_node.assert_called_once_with(
uuid, {'type': 'bare'})
self.assertEqual(self.context, node._context)
def test_refresh(self):
uuid = self.fake_node['uuid']
new_uuid = magnum_utils.generate_uuid()
returns = [dict(self.fake_node, uuid=uuid),
dict(self.fake_node, uuid=new_uuid)]
expected = [mock.call(self.context, uuid),
mock.call(self.context, uuid)]
with mock.patch.object(self.dbapi, 'get_node_by_uuid',
side_effect=returns,
autospec=True) as mock_get_node:
node = objects.Node.get_by_uuid(self.context, uuid)
self.assertEqual(uuid, node.uuid)
node.refresh()
self.assertEqual(new_uuid, node.uuid)
self.assertEqual(expected, mock_get_node.call_args_list)
self.assertEqual(self.context, node._context)

View File

@ -428,7 +428,6 @@ object_data = {
'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2', 'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2',
'Container': '1.3-e2d9d2e8a8844d421148cd9fde6c6bd6', 'Container': '1.3-e2d9d2e8a8844d421148cd9fde6c6bd6',
'MyObj': '1.0-b43567e512438205e32f4e95ca616697', 'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
'Node': '1.0-30943e6e3387a2fae7490b57c4239a17',
'Pod': '1.1-39f221ad1dad0eb7f7bee3569d42fa7e', 'Pod': '1.1-39f221ad1dad0eb7f7bee3569d42fa7e',
'ReplicationController': '1.0-a471c2429c212ed91833cfcf0f934eab', 'ReplicationController': '1.0-a471c2429c212ed91833cfcf0f934eab',
'Service': '1.0-f4a1c5a4618708824a553568c1ada0ea', 'Service': '1.0-f4a1c5a4618708824a553568c1ada0ea',

View File

@ -160,33 +160,6 @@ def create_test_rc(context, **kw):
return rc return rc
def get_test_node(context, **kw):
"""Return a Node object with appropriate attributes.
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_node = db_utils.get_test_node(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_node['id']
node = objects.Node(context)
for key in db_node:
setattr(node, key, db_node[key])
return node
def create_test_node(context, **kw):
"""Create and return a test Node object.
Create a node in the DB and return a Node object with appropriate
attributes.
"""
node = get_test_node(context, **kw)
node.create()
return node
def get_test_x509keypair(context, **kw): def get_test_x509keypair(context, **kw):
"""Return a X509KeyPair object with appropriate attributes. """Return a X509KeyPair object with appropriate attributes.