Merge "Implement indirection_api"
This commit is contained in:
@@ -28,6 +28,7 @@ from six.moves import socketserver
|
||||
from ironic.api import app
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common import service as ironic_service
|
||||
from ironic.objects import base
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -42,6 +43,9 @@ def main():
|
||||
# Parse config file and command line options, then start logging
|
||||
ironic_service.prepare_service(sys.argv)
|
||||
|
||||
# Enable object backporting via the conductor
|
||||
base.IronicObject.indirection_api = base.IronicObjectIndirectionAPI()
|
||||
|
||||
# Build and start the WSGI app
|
||||
host = CONF.api.host_ip
|
||||
port = CONF.api.port
|
||||
|
||||
@@ -350,10 +350,6 @@ class UnsupportedDriverExtension(Invalid):
|
||||
'(disabled or not implemented).')
|
||||
|
||||
|
||||
class IncompatibleObjectVersion(IronicException):
|
||||
message = _('Version %(objver)s of %(objname)s is not supported')
|
||||
|
||||
|
||||
class GlanceConnectionFailed(IronicException):
|
||||
message = _("Connection to glance host %(host)s:%(port)s failed: "
|
||||
"%(reason)s")
|
||||
|
||||
@@ -77,6 +77,7 @@ from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils
|
||||
from ironic.db import api as dbapi
|
||||
from ironic import objects
|
||||
from ironic.objects import base as objects_base
|
||||
|
||||
MANAGER_TOPIC = 'ironic.conductor_manager'
|
||||
WORKER_SPAWN_lOCK = "conductor_worker_spawn"
|
||||
@@ -211,7 +212,7 @@ class ConductorManager(periodic_task.PeriodicTasks):
|
||||
"""Ironic Conductor manager main class."""
|
||||
|
||||
# NOTE(rloo): This must be in sync with rpcapi.ConductorAPI's.
|
||||
RPC_API_VERSION = '1.30'
|
||||
RPC_API_VERSION = '1.31'
|
||||
|
||||
target = messaging.Target(version=RPC_API_VERSION)
|
||||
|
||||
@@ -2137,6 +2138,108 @@ class ConductorManager(periodic_task.PeriodicTasks):
|
||||
|
||||
return driver.raid.get_logical_disk_properties()
|
||||
|
||||
def _object_dispatch(self, target, method, context, args, kwargs):
|
||||
"""Dispatch a call to an object method.
|
||||
|
||||
This ensures that object methods get called and any exception
|
||||
that is raised gets wrapped in an ExpectedException for forwarding
|
||||
back to the caller (without spamming the conductor logs).
|
||||
"""
|
||||
try:
|
||||
# NOTE(danms): Keep the getattr inside the try block since
|
||||
# a missing method is really a client problem
|
||||
return getattr(target, method)(context, *args, **kwargs)
|
||||
except Exception:
|
||||
# NOTE(danms): This is oslo.messaging fu. ExpectedException()
|
||||
# grabs sys.exc_info here and forwards it along. This allows the
|
||||
# caller to see the exception information, but causes us *not* to
|
||||
# log it as such in this service. This is something that is quite
|
||||
# critical so that things that conductor does on behalf of another
|
||||
# node are not logged as exceptions in conductor logs. Otherwise,
|
||||
# you'd have the same thing logged in both places, even though an
|
||||
# exception here *always* means that the caller screwed up, so
|
||||
# there's no reason to log it here.
|
||||
raise messaging.ExpectedException()
|
||||
|
||||
def object_class_action_versions(self, context, objname, objmethod,
|
||||
object_versions, args, kwargs):
|
||||
"""Perform an action on a VersionedObject class.
|
||||
|
||||
:param context: The context within which to perform the action
|
||||
:param objname: The registry name of the object
|
||||
:param objmethod: The name of the action method to call
|
||||
:param object_versions: A dict of {objname: version} mappings
|
||||
:param args: The positional arguments to the action method
|
||||
:param kwargs: The keyword arguments to the action method
|
||||
:returns: The result of the action method, which may (or may not)
|
||||
be an instance of the implementing VersionedObject class.
|
||||
"""
|
||||
objclass = objects_base.IronicObject.obj_class_from_name(
|
||||
objname, object_versions[objname])
|
||||
result = self._object_dispatch(objclass, objmethod, context,
|
||||
args, kwargs)
|
||||
# NOTE(danms): The RPC layer will convert to primitives for us,
|
||||
# but in this case, we need to honor the version the client is
|
||||
# asking for, so we do it before returning here.
|
||||
if isinstance(result, objects_base.IronicObject):
|
||||
result = result.obj_to_primitive(
|
||||
target_version=object_versions[objname],
|
||||
version_manifest=object_versions)
|
||||
return result
|
||||
|
||||
def object_action(self, context, objinst, objmethod, args, kwargs):
|
||||
"""Perform an action on a VersionedObject instance.
|
||||
|
||||
:param context: The context within which to perform the action
|
||||
:param objinst: The object instance on which to perform the action
|
||||
:param objmethod: The name of the action method to call
|
||||
:param args: The positional arguments to the action method
|
||||
:param kwargs: The keyword arguments to the action method
|
||||
:returns: A tuple with the updates made to the object and
|
||||
the result of the action method
|
||||
"""
|
||||
|
||||
oldobj = objinst.obj_clone()
|
||||
result = self._object_dispatch(objinst, objmethod, context,
|
||||
args, kwargs)
|
||||
updates = dict()
|
||||
# NOTE(danms): Diff the object with the one passed to us and
|
||||
# generate a list of changes to forward back
|
||||
for name, field in objinst.fields.items():
|
||||
if not objinst.obj_attr_is_set(name):
|
||||
# Avoid demand-loading anything
|
||||
continue
|
||||
if (not oldobj.obj_attr_is_set(name) or
|
||||
getattr(oldobj, name) != getattr(objinst, name)):
|
||||
updates[name] = field.to_primitive(objinst, name,
|
||||
getattr(objinst, name))
|
||||
# This is safe since a field named this would conflict with the
|
||||
# method anyway
|
||||
updates['obj_what_changed'] = objinst.obj_what_changed()
|
||||
return updates, result
|
||||
|
||||
def object_backport_versions(self, context, objinst, object_versions):
|
||||
"""Perform a backport of an object instance.
|
||||
|
||||
The default behavior of the base VersionedObjectSerializer, upon
|
||||
receiving an object with a version newer than what is in the local
|
||||
registry, is to call this method to request a backport of the object.
|
||||
|
||||
:param context: The context within which to perform the backport
|
||||
:param objinst: An instance of a VersionedObject to be backported
|
||||
:param object_versions: A dict of {objname: version} mappings
|
||||
:returns: The downgraded instance of objinst
|
||||
"""
|
||||
target = object_versions[objinst.obj_name()]
|
||||
LOG.debug('Backporting %(obj)s to %(ver)s with versions %(manifest)s',
|
||||
{'obj': objinst.obj_name(),
|
||||
'ver': target,
|
||||
'manifest': ','.join(
|
||||
['%s=%s' % (name, ver)
|
||||
for name, ver in object_versions.items()])})
|
||||
return objinst.obj_to_primitive(target_version=target,
|
||||
version_manifest=object_versions)
|
||||
|
||||
|
||||
def get_vendor_passthru_metadata(route_dict):
|
||||
d = {}
|
||||
|
||||
@@ -75,11 +75,14 @@ class ConductorAPI(object):
|
||||
| driver_vendor_passthru to a dictionary
|
||||
| 1.30 - Added set_target_raid_config and
|
||||
| get_raid_logical_disk_properties
|
||||
| 1.31 - Added Versioned Objects indirection API methods:
|
||||
| object_class_action_versions, object_action and
|
||||
| object_backport_versions
|
||||
|
||||
"""
|
||||
|
||||
# NOTE(rloo): This must be in sync with manager.ConductorManager's.
|
||||
RPC_API_VERSION = '1.30'
|
||||
RPC_API_VERSION = '1.31'
|
||||
|
||||
def __init__(self, topic=None):
|
||||
super(ConductorAPI, self).__init__()
|
||||
@@ -589,3 +592,74 @@ class ConductorAPI(object):
|
||||
cctxt = self.client.prepare(topic=topic or self.topic, version='1.30')
|
||||
return cctxt.call(context, 'get_raid_logical_disk_properties',
|
||||
driver_name=driver_name)
|
||||
|
||||
def object_class_action_versions(self, context, objname, objmethod,
|
||||
object_versions, args, kwargs):
|
||||
"""Perform an action on a VersionedObject class.
|
||||
|
||||
We want any conductor to handle this, so it is intentional that there
|
||||
is no topic argument for this method.
|
||||
|
||||
:param context: The context within which to perform the action
|
||||
:param objname: The registry name of the object
|
||||
:param objmethod: The name of the action method to call
|
||||
:param object_versions: A dict of {objname: version} mappings
|
||||
:param args: The positional arguments to the action method
|
||||
:param kwargs: The keyword arguments to the action method
|
||||
:raises: NotImplemented when an operator makes an error during upgrade
|
||||
:returns: The result of the action method, which may (or may not)
|
||||
be an instance of the implementing VersionedObject class.
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
raise NotImplemented(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor first'))
|
||||
cctxt = self.client.prepare(topic=self.topic, version='1.31')
|
||||
return cctxt.call(context, 'object_class_action_versions',
|
||||
objname=objname, objmethod=objmethod,
|
||||
object_versions=object_versions,
|
||||
args=args, kwargs=kwargs)
|
||||
|
||||
def object_action(self, context, objinst, objmethod, args, kwargs):
|
||||
"""Perform an action on a VersionedObject instance.
|
||||
|
||||
We want any conductor to handle this, so it is intentional that there
|
||||
is no topic argument for this method.
|
||||
|
||||
:param context: The context within which to perform the action
|
||||
:param objinst: The object instance on which to perform the action
|
||||
:param objmethod: The name of the action method to call
|
||||
:param args: The positional arguments to the action method
|
||||
:param kwargs: The keyword arguments to the action method
|
||||
:raises: NotImplemented when an operator makes an error during upgrade
|
||||
:returns: A tuple with the updates made to the object and
|
||||
the result of the action method
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
raise NotImplemented(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor first'))
|
||||
cctxt = self.client.prepare(topic=self.topic, version='1.31')
|
||||
return cctxt.call(context, 'object_action', objinst=objinst,
|
||||
objmethod=objmethod, args=args, kwargs=kwargs)
|
||||
|
||||
def object_backport_versions(self, context, objinst, object_versions):
|
||||
"""Perform a backport of an object instance.
|
||||
|
||||
The default behavior of the base VersionedObjectSerializer, upon
|
||||
receiving an object with a version newer than what is in the local
|
||||
registry, is to call this method to request a backport of the object.
|
||||
|
||||
We want any conductor to handle this, so it is intentional that there
|
||||
is no topic argument for this method.
|
||||
|
||||
:param context: The context within which to perform the backport
|
||||
:param objinst: An instance of a VersionedObject to be backported
|
||||
:param object_versions: A dict of {objname: version} mappings
|
||||
:raises: NotImplemented when an operator makes an error during upgrade
|
||||
:returns: The downgraded instance of objinst
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
raise NotImplemented(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor first'))
|
||||
cctxt = self.client.prepare(topic=self.topic, version='1.31')
|
||||
return cctxt.call(context, 'object_backport_versions', objinst=objinst,
|
||||
object_versions=object_versions)
|
||||
|
||||
@@ -65,6 +65,36 @@ class IronicObject(object_base.VersionedObject):
|
||||
self[field] = loaded_object[field]
|
||||
|
||||
|
||||
class IronicObjectIndirectionAPI(object_base.VersionedObjectIndirectionAPI):
|
||||
def __init__(self):
|
||||
super(IronicObjectIndirectionAPI, self).__init__()
|
||||
# FIXME(xek): importing here due to a cyclical import error
|
||||
from ironic.conductor import rpcapi as conductor_api
|
||||
self._conductor = conductor_api.ConductorAPI()
|
||||
|
||||
def object_action(self, context, objinst, objmethod, args, kwargs):
|
||||
return self._conductor.object_action(context, objinst, objmethod,
|
||||
args, kwargs)
|
||||
|
||||
def object_class_action(self, context, objname, objmethod, objver,
|
||||
args, kwargs):
|
||||
# NOTE(xek): This method is implemented for compatibility with
|
||||
# oslo.versionedobjects 0.10.0 and older. It will be replaced by
|
||||
# object_class_action_versions.
|
||||
versions = object_base.obj_tree_get_versions(objname)
|
||||
return self.object_class_action_versions(
|
||||
context, objname, objmethod, versions, args, kwargs)
|
||||
|
||||
def object_class_action_versions(self, context, objname, objmethod,
|
||||
object_versions, args, kwargs):
|
||||
return self._conductor.object_class_action_versions(
|
||||
context, objname, objmethod, object_versions, args, kwargs)
|
||||
|
||||
def object_backport_versions(self, context, objinst, object_versions):
|
||||
return self._conductor.object_backport_versions(context, objinst,
|
||||
object_versions)
|
||||
|
||||
|
||||
class IronicObjectSerializer(object_base.VersionedObjectSerializer):
|
||||
# Base class to use for object hydration
|
||||
OBJ_BASE_CLASS = IronicObject
|
||||
|
||||
@@ -55,7 +55,11 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
chassis.obj_reset_changes()
|
||||
return chassis
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get(cls, context, chassis_id):
|
||||
"""Find a chassis based on its id or uuid and return a Chassis object.
|
||||
|
||||
@@ -69,7 +73,11 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=chassis_id)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_id(cls, context, chassis_id):
|
||||
"""Find a chassis based on its integer id and return a Chassis object.
|
||||
|
||||
@@ -80,7 +88,11 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
chassis = Chassis._from_db_object(cls(context), db_chassis)
|
||||
return chassis
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a chassis based on uuid and return a :class:`Chassis` object.
|
||||
|
||||
@@ -92,7 +104,11 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
chassis = Chassis._from_db_object(cls(context), db_chassis)
|
||||
return chassis
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Chassis objects.
|
||||
@@ -112,7 +128,10 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
return [Chassis._from_db_object(cls(context), obj)
|
||||
for obj in db_chassis]
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a Chassis record in the DB.
|
||||
|
||||
@@ -133,7 +152,10 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
db_chassis = self.dbapi.create_chassis(values)
|
||||
self._from_db_object(self, db_chassis)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Chassis from the DB.
|
||||
|
||||
@@ -147,7 +169,10 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
self.dbapi.destroy_chassis(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this Chassis.
|
||||
|
||||
@@ -165,7 +190,10 @@ class Chassis(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
updated_chassis = self.dbapi.update_chassis(self.uuid, updates)
|
||||
self._from_db_object(self, updated_chassis)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads and applies updates for this Chassis.
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
conductor.obj_reset_changes()
|
||||
return conductor
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_hostname(cls, context, hostname):
|
||||
"""Get a Conductor record by its hostname.
|
||||
|
||||
@@ -58,7 +62,10 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
raise NotImplementedError(
|
||||
_('Cannot update a conductor record directly.'))
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads and applies updates for this Conductor.
|
||||
|
||||
@@ -77,7 +84,10 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
hostname=self.hostname)
|
||||
self.obj_refresh(current)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def touch(self, context):
|
||||
"""Touch this conductor's DB record, marking it as up-to-date."""
|
||||
self.dbapi.touch_conductor(self.hostname)
|
||||
|
||||
@@ -106,7 +106,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node.obj_reset_changes()
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get(cls, context, node_id):
|
||||
"""Find a node based on its id or uuid and return a Node object.
|
||||
|
||||
@@ -120,7 +124,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=node_id)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_id(cls, context, node_id):
|
||||
"""Find a node based on its integer id and return a Node object.
|
||||
|
||||
@@ -131,7 +139,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node = Node._from_db_object(cls(context), db_node)
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a node based on uuid and return a Node object.
|
||||
|
||||
@@ -142,7 +154,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node = Node._from_db_object(cls(context), db_node)
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find a node based on name and return a Node object.
|
||||
|
||||
@@ -153,7 +169,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node = Node._from_db_object(cls(context), db_node)
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_instance_uuid(cls, context, instance_uuid):
|
||||
"""Find a node based on the instance uuid and return a Node object.
|
||||
|
||||
@@ -164,7 +184,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node = Node._from_db_object(cls(context), db_node)
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, filters=None):
|
||||
"""Return a list of Node objects.
|
||||
@@ -183,7 +207,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
sort_dir=sort_dir)
|
||||
return [Node._from_db_object(cls(context), obj) for obj in db_nodes]
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def reserve(cls, context, tag, node_id):
|
||||
"""Get and reserve a node.
|
||||
|
||||
@@ -201,7 +229,11 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
node = Node._from_db_object(cls(context), db_node)
|
||||
return node
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def release(cls, context, tag, node_id):
|
||||
"""Release the reservation on a node.
|
||||
|
||||
@@ -213,7 +245,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
"""
|
||||
cls.dbapi.release_node(tag, node_id)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a Node record in the DB.
|
||||
|
||||
@@ -234,7 +269,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
db_node = self.dbapi.create_node(values)
|
||||
self._from_db_object(self, db_node)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Node from the DB.
|
||||
|
||||
@@ -248,7 +286,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
self.dbapi.destroy_node(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this Node.
|
||||
|
||||
@@ -272,7 +313,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
self.dbapi.update_node(self.uuid, updates)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Refresh the object by re-fetching from the DB.
|
||||
|
||||
@@ -286,7 +330,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
current = self.__class__.get_by_uuid(self._context, self.uuid)
|
||||
self.obj_refresh(current)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def touch_provisioning(self, context=None):
|
||||
"""Touch the database record to mark the provisioning as alive."""
|
||||
self.dbapi.touch_node_provisioning(self.id)
|
||||
|
||||
@@ -58,7 +58,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [Port._from_db_object(cls(context), obj) for obj in db_objects]
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get(cls, context, port_id):
|
||||
"""Find a port based on its id or uuid and return a Port object.
|
||||
|
||||
@@ -76,7 +80,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=port_id)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_id(cls, context, port_id):
|
||||
"""Find a port based on its integer id and return a Port object.
|
||||
|
||||
@@ -89,7 +97,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a port based on uuid and return a :class:`Port` object.
|
||||
|
||||
@@ -103,7 +115,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_address(cls, context, address):
|
||||
"""Find a port based on address and return a :class:`Port` object.
|
||||
|
||||
@@ -117,7 +133,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Port objects.
|
||||
@@ -137,7 +157,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
sort_dir=sort_dir)
|
||||
return Port._from_db_object_list(db_ports, cls, context)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list_by_node_id(cls, context, node_id, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Port objects associated with a given node ID.
|
||||
@@ -157,7 +181,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
sort_dir=sort_dir)
|
||||
return Port._from_db_object_list(db_ports, cls, context)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a Port record in the DB.
|
||||
|
||||
@@ -175,7 +202,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
db_port = self.dbapi.create_port(values)
|
||||
self._from_db_object(self, db_port)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Port from the DB.
|
||||
|
||||
@@ -191,7 +221,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
self.dbapi.destroy_port(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this Port.
|
||||
|
||||
@@ -212,7 +245,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
updated_port = self.dbapi.update_port(self.uuid, updates)
|
||||
self._from_db_object(self, updated_port)
|
||||
|
||||
@object_base.remotable
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
# methods can be used in the future to replace current explicit RPC calls.
|
||||
# Implications of calling new remote procedures should be thought through.
|
||||
# @object_base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads updates for this Port.
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ from oslo_db import exception as db_exception
|
||||
import oslo_messaging as messaging
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
from oslo_versionedobjects import base as ovo_base
|
||||
from oslo_versionedobjects import fields
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import driver_factory
|
||||
@@ -41,6 +43,7 @@ from ironic.db import api as dbapi
|
||||
from ironic.drivers import base as drivers_base
|
||||
from ironic.drivers.modules import fake
|
||||
from ironic import objects
|
||||
from ironic.objects import base as obj_base
|
||||
from ironic.tests import base as tests_base
|
||||
from ironic.tests.conductor import utils as mgr_utils
|
||||
from ironic.tests.db import base as tests_db_base
|
||||
@@ -4466,3 +4469,105 @@ class ManagerCheckDeployingStatusTestCase(_ServiceSetUpMixin,
|
||||
'provision_updated_at',
|
||||
callback_method=conductor_utils.cleanup_after_timeout,
|
||||
err_handler=manager.provisioning_error_handler)
|
||||
|
||||
|
||||
class TestIndirectionApiConductor(tests_db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIndirectionApiConductor, self).setUp()
|
||||
self.conductor = manager.ConductorManager('test-host', 'test-topic')
|
||||
|
||||
def _test_object_action(self, is_classmethod, raise_exception,
|
||||
return_object=False):
|
||||
@obj_base.IronicObjectRegistry.register
|
||||
class TestObject(obj_base.IronicObject):
|
||||
context = self.context
|
||||
|
||||
def foo(self, context, raise_exception=False, return_object=False):
|
||||
if raise_exception:
|
||||
raise Exception('test')
|
||||
elif return_object:
|
||||
return obj
|
||||
else:
|
||||
return 'test'
|
||||
|
||||
@classmethod
|
||||
def bar(cls, context, raise_exception=False, return_object=False):
|
||||
if raise_exception:
|
||||
raise Exception('test')
|
||||
elif return_object:
|
||||
return obj
|
||||
else:
|
||||
return 'test'
|
||||
|
||||
obj = TestObject(self.context)
|
||||
if is_classmethod:
|
||||
versions = ovo_base.obj_tree_get_versions(TestObject.obj_name())
|
||||
result = self.conductor.object_class_action_versions(
|
||||
self.context, TestObject.obj_name(), 'bar', versions,
|
||||
tuple(), {'raise_exception': raise_exception,
|
||||
'return_object': return_object})
|
||||
else:
|
||||
updates, result = self.conductor.object_action(
|
||||
self.context, obj, 'foo', tuple(),
|
||||
{'raise_exception': raise_exception,
|
||||
'return_object': return_object})
|
||||
if return_object:
|
||||
self.assertEqual(obj, result)
|
||||
else:
|
||||
self.assertEqual('test', result)
|
||||
|
||||
def test_object_action(self):
|
||||
self._test_object_action(False, False)
|
||||
|
||||
def test_object_action_on_raise(self):
|
||||
self.assertRaises(messaging.ExpectedException,
|
||||
self._test_object_action, False, True)
|
||||
|
||||
def test_object_action_on_object(self):
|
||||
self._test_object_action(False, False, True)
|
||||
|
||||
def test_object_class_action(self):
|
||||
self._test_object_action(True, False)
|
||||
|
||||
def test_object_class_action_on_raise(self):
|
||||
self.assertRaises(messaging.ExpectedException,
|
||||
self._test_object_action, True, True)
|
||||
|
||||
def test_object_class_action_on_object(self):
|
||||
self._test_object_action(True, False, False)
|
||||
|
||||
def test_object_action_copies_object(self):
|
||||
@obj_base.IronicObjectRegistry.register
|
||||
class TestObject(obj_base.IronicObject):
|
||||
fields = {'dict': fields.DictOfStringsField()}
|
||||
|
||||
def touch_dict(self, context):
|
||||
self.dict['foo'] = 'bar'
|
||||
self.obj_reset_changes()
|
||||
|
||||
obj = TestObject(self.context)
|
||||
obj.dict = {}
|
||||
obj.obj_reset_changes()
|
||||
updates, result = self.conductor.object_action(
|
||||
self.context, obj, 'touch_dict', tuple(), {})
|
||||
# NOTE(danms): If conductor did not properly copy the object, then
|
||||
# the new and reference copies of the nested dict object will be
|
||||
# the same, and thus 'dict' will not be reported as changed
|
||||
self.assertIn('dict', updates)
|
||||
self.assertEqual({'foo': 'bar'}, updates['dict'])
|
||||
|
||||
def test_object_backport_versions(self):
|
||||
fake_backported_obj = 'fake-backported-obj'
|
||||
obj_name = 'fake-obj'
|
||||
test_obj = mock.Mock()
|
||||
test_obj.obj_name.return_value = obj_name
|
||||
test_obj.obj_to_primitive.return_value = fake_backported_obj
|
||||
fake_version_manifest = {obj_name: '1.0'}
|
||||
|
||||
result = self.conductor.object_backport_versions(
|
||||
self.context, test_obj, fake_version_manifest)
|
||||
|
||||
self.assertEqual(result, fake_backported_obj)
|
||||
test_obj.obj_to_primitive.assert_called_once_with(
|
||||
target_version='1.0', version_manifest=fake_version_manifest)
|
||||
|
||||
@@ -22,6 +22,7 @@ import copy
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_messaging import _utils as messaging_utils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
@@ -145,6 +146,10 @@ class RPCAPITestCase(base.DbTestCase):
|
||||
self.fake_args = None
|
||||
self.fake_kwargs = None
|
||||
|
||||
def _fake_can_send_version_method(version):
|
||||
return messaging_utils.version_is_compatible(
|
||||
rpcapi.RPC_API_VERSION, version)
|
||||
|
||||
def _fake_prepare_method(*args, **kwargs):
|
||||
for kwd in kwargs:
|
||||
self.assertEqual(kwargs[kwd], target[kwd])
|
||||
@@ -156,16 +161,21 @@ class RPCAPITestCase(base.DbTestCase):
|
||||
if expected_retval:
|
||||
return expected_retval
|
||||
|
||||
with mock.patch.object(rpcapi.client, "prepare") as mock_prepared:
|
||||
mock_prepared.side_effect = _fake_prepare_method
|
||||
with mock.patch.object(rpcapi.client,
|
||||
"can_send_version") as mock_can_send_version:
|
||||
mock_can_send_version.side_effect = _fake_can_send_version_method
|
||||
with mock.patch.object(rpcapi.client, "prepare") as mock_prepared:
|
||||
mock_prepared.side_effect = _fake_prepare_method
|
||||
|
||||
with mock.patch.object(rpcapi.client, rpc_method) as mock_method:
|
||||
mock_method.side_effect = _fake_rpc_method
|
||||
retval = getattr(rpcapi, method)(self.context, **kwargs)
|
||||
self.assertEqual(retval, expected_retval)
|
||||
expected_args = [self.context, method, expected_msg]
|
||||
for arg, expected_arg in zip(self.fake_args, expected_args):
|
||||
self.assertEqual(arg, expected_arg)
|
||||
with mock.patch.object(rpcapi.client,
|
||||
rpc_method) as mock_method:
|
||||
mock_method.side_effect = _fake_rpc_method
|
||||
retval = getattr(rpcapi, method)(self.context, **kwargs)
|
||||
self.assertEqual(retval, expected_retval)
|
||||
expected_args = [self.context, method, expected_msg]
|
||||
for arg, expected_arg in zip(self.fake_args,
|
||||
expected_args):
|
||||
self.assertEqual(arg, expected_arg)
|
||||
|
||||
def test_update_node(self):
|
||||
self._test_rpcapi('update_node',
|
||||
@@ -306,3 +316,29 @@ class RPCAPITestCase(base.DbTestCase):
|
||||
version='1.30',
|
||||
node_id=self.fake_node['uuid'],
|
||||
target_raid_config='config')
|
||||
|
||||
def test_object_action(self):
|
||||
self._test_rpcapi('object_action',
|
||||
'call',
|
||||
version='1.31',
|
||||
objinst='fake-object',
|
||||
objmethod='foo',
|
||||
args=tuple(),
|
||||
kwargs=dict())
|
||||
|
||||
def test_object_class_action_versions(self):
|
||||
self._test_rpcapi('object_class_action_versions',
|
||||
'call',
|
||||
version='1.31',
|
||||
objname='fake-object',
|
||||
objmethod='foo',
|
||||
object_versions={'fake-object': '1.0'},
|
||||
args=tuple(),
|
||||
kwargs=dict())
|
||||
|
||||
def test_object_backport_versions(self):
|
||||
self._test_rpcapi('object_backport_versions',
|
||||
'call',
|
||||
version='1.31',
|
||||
objinst='fake-object',
|
||||
object_versions={'fake-object': '1.0'})
|
||||
|
||||
@@ -17,6 +17,7 @@ import datetime
|
||||
import gettext
|
||||
import iso8601
|
||||
|
||||
import mock
|
||||
from oslo_context import context
|
||||
from oslo_versionedobjects import base as object_base
|
||||
from oslo_versionedobjects import exception as object_exception
|
||||
@@ -439,3 +440,48 @@ class TestObjectSerializer(test_base.TestCase):
|
||||
self.assertEqual(1, len(thing2))
|
||||
for item in thing2:
|
||||
self.assertIsInstance(item, MyObj)
|
||||
|
||||
@mock.patch('ironic.objects.base.IronicObject.indirection_api')
|
||||
def _test_deserialize_entity_newer(self, obj_version, backported_to,
|
||||
mock_indirection_api,
|
||||
my_version='1.6'):
|
||||
ser = base.IronicObjectSerializer()
|
||||
mock_indirection_api.object_backport_versions.return_value \
|
||||
= 'backported'
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class MyTestObj(MyObj):
|
||||
VERSION = my_version
|
||||
|
||||
obj = MyTestObj(self.context)
|
||||
obj.VERSION = obj_version
|
||||
primitive = obj.obj_to_primitive()
|
||||
result = ser.deserialize_entity(self.context, primitive)
|
||||
if backported_to is None:
|
||||
self.assertFalse(
|
||||
mock_indirection_api.object_backport_versions.called)
|
||||
else:
|
||||
self.assertEqual('backported', result)
|
||||
versions = object_base.obj_tree_get_versions('MyTestObj')
|
||||
mock_indirection_api.object_backport_versions.assert_called_with(
|
||||
self.context, primitive, versions)
|
||||
|
||||
def test_deserialize_entity_newer_version_backports(self):
|
||||
"Test object with unsupported (newer) version"
|
||||
self._test_deserialize_entity_newer('1.25', '1.6')
|
||||
|
||||
def test_deserialize_entity_same_revision_does_not_backport(self):
|
||||
"Test object with supported revision"
|
||||
self._test_deserialize_entity_newer('1.6', None)
|
||||
|
||||
def test_deserialize_entity_newer_revision_does_not_backport_zero(self):
|
||||
"Test object with supported revision"
|
||||
self._test_deserialize_entity_newer('1.6.0', None)
|
||||
|
||||
def test_deserialize_entity_newer_revision_does_not_backport(self):
|
||||
"Test object with supported (newer) revision"
|
||||
self._test_deserialize_entity_newer('1.6.1', None)
|
||||
|
||||
def test_deserialize_entity_newer_version_passes_revision(self):
|
||||
"Test object with unsupported (newer) version and revision"
|
||||
self._test_deserialize_entity_newer('1.7', '1.6.1', my_version='1.6.1')
|
||||
|
||||
Reference in New Issue
Block a user