Rename "manager" to "conductor"

This rename to "conductor" more clearly communicates that this service
has a many-to-many relationship. One or more service instances
coordinate between each other to conduct actions on a set of nodes,
using guarded locks to prevent conflicting simultaneous actions on any
given node. The old name "manager" suggested a more one-to-many relationship,
which is not the design pattern which we use here.

Rename ironic/manager to ironic/conductor
Rename ironic.manager.manager.ManagerService
    to ironic.conductor.manager.ConductorManager
Rename ironic-manager to ironic-conductor
Update docs too

Change-Id: I3191be72a44bdaf14c763ce7519a7ae9066b2bc5
This commit is contained in:
Devananda van der Veen 2013-06-22 12:10:21 -07:00
parent 81cf262d21
commit a360cb8771
7 changed files with 2 additions and 391 deletions

View File

@ -32,8 +32,8 @@ from ironic.common import exception
from ironic.common import paths
from ironic.common import states
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.manager import task_manager
from ironic.openstack.common import excutils
from ironic.openstack.common import jsonutils as json
from ironic.openstack.common import log as logging

View File

@ -1,17 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.

View File

@ -1,82 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""Handles all activity related to bare-metal deployments.
A single instance of :py:class:`ironic.manager.manager.ManagerService` is
created within the *ironic-manager* process, and is responsible for performing
all actions on bare metal resources (Chassis, Nodes, and Ports). Commands are
received via RPC calls. The manager service also performs periodic tasks, eg.
to monitor the status of active deployments.
Drivers are loaded via entrypoints, by the
:py:class:`ironic.manager.resource_manager.NodeManager` class. Each driver is
instantiated once and a ref to that singleton is included in each resource
manager, depending on the node's configuration. In this way, a single
ManagerService may use multiple drivers, and manage heterogeneous hardware.
When multiple :py:class:`ManagerService` are run on different hosts, they are
all active and cooperatively manage all nodes in the deplyment. Nodes are
locked by each manager when performing actions which change the state of that
node; these locks are represented by the
:py:class:`ironic.manager.task_manager.TaskManager` class.
"""
from ironic.common import service
from ironic.db import api as dbapi
from ironic.manager import task_manager
from ironic.openstack.common import log
MANAGER_TOPIC = 'ironic.manager'
LOG = log.getLogger(__name__)
class ManagerService(service.PeriodicService):
"""Ironic Manager service main class."""
RPC_API_VERSION = '1.0'
def __init__(self, host, topic):
super(ManagerService, self).__init__(host, topic)
def start(self):
super(ManagerService, self).start()
self.dbapi = dbapi.get_instance()
def initialize_service_hook(self, service):
pass
def process_notification(self, notification):
LOG.debug(_('Received notification: %r') %
notification.get('event_type'))
# TODO(deva)
def periodic_tasks(self, context):
# TODO(deva)
pass
def get_node_power_state(self, context, node_id):
"""Get and return the power state for a single node."""
with task_manager.acquire([node_id], shared=True) as task:
node = task.resources[0].node
driver = task.resources[0].driver
state = driver.power.get_power_state(task, node)
return state
# TODO(deva)

View File

@ -1,108 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Hold the data and drivers for a distinct node within a given context.
Each :py:class:`ironic.manager.resource_manager.NodeManager` instance is a
semi-singleton, keyed by the node id. It contains references to all
:py:class:`ironic.manager.task_manager.TaskManager` which called it. When no
more TaskManagers reference a given NodeManager, it is automatically destroyed.
Do not request a NodeManager directly; instead, you should use a TaskManager to
manage the resource in a given context. See the documentation on TaskManager
for an example.
"""
from stevedore import dispatch
from ironic.openstack.common import lockutils
from ironic.openstack.common import log
from ironic.common import exception
from ironic.db import api as dbapi
LOG = log.getLogger(__name__)
RESOURCE_MANAGER_SEMAPHORE = "node_resource"
class NodeManager(object):
"""The data model, state, and drivers to manage a Node."""
_nodes = {}
_driver_factory = dispatch.NameDispatchExtensionManager(
namespace='ironic.drivers',
check_func=lambda x: True,
invoke_on_load=True)
def __init__(self, id, t):
db = dbapi.get_instance()
self.id = id
self.task_refs = [t]
self.node = db.get_node(id)
self.ports = db.get_ports_by_node(id)
def _get_instance(ext, *args, **kwds):
return ext.obj
# NOTE(deva): Driver loading here may get refactored, depend on:
# https://github.com/dreamhost/stevedore/issues/15
try:
ref = NodeManager._driver_factory.map(
[self.node.get('driver')], _get_instance)
self.driver = ref[0]
except KeyError:
raise exception.IronicException(_(
"Failed to load driver %s.") %
self.node.get('driver'))
@classmethod
@lockutils.synchronized(RESOURCE_MANAGER_SEMAPHORE, 'ironic-')
def acquire(cls, id, t):
"""Acquire a NodeManager and associate to a TaskManager."""
n = cls._nodes.get(id)
if n:
n.task_refs.append(t)
else:
n = cls(id, t)
cls._nodes[id] = n
return n
@classmethod
@lockutils.synchronized(RESOURCE_MANAGER_SEMAPHORE, 'ironic-')
def release(cls, id, t):
"""Release a NodeManager previously acquired."""
n = cls._nodes.get(id)
if not n:
raise exception.IronicException(_(
"Release called on node %s for which no lock "
"has been acquired.") % id)
try:
n.task_refs.remove(t)
except ValueError:
raise exception.IronicException(_(
"Can not release node %s because it was not "
"reserved by this tracker.") % id)
# Delete the resource when no TaskManager references it.
if len(n.task_refs) == 0:
del(cls._nodes[id])

View File

@ -1,54 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Client side of the manager RPC API.
"""
import ironic.openstack.common.rpc.proxy
MANAGER_TOPIC = 'ironic.manager'
class ManagerAPI(ironic.openstack.common.rpc.proxy.RpcProxy):
"""Client side of the manager RPC API.
API version history:
1.0 - Initial version.
"""
RPC_API_VERSION = '1.0'
def __init__(self, topic=None):
if topic is None:
topic = MANAGER_TOPIC
super(ManagerAPI, self).__init__(
topic=topic,
default_version=self.RPC_API_VERSION)
def get_node_power_state(self, context, node_id):
"""Ask a manager for the node power state.
:param context: request context.
:param node_id: node id or uuid.
:returns: power status.
"""
return self.call(context,
self.make_msg('get_node_power_state',
node_id=node_id))

View File

@ -1,128 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
A context manager to peform a series of tasks on a set of resources.
:class:`TaskManager` is a context manager, created on-demand to synchronize
locking and simplify operations across a set of
:class:`ironic.manager.resource_manager.NodeManager` instances. Each
NodeManager holds the data model for a node, as well as references to the
driver singleton appropriate for that node.
The :class:`TaskManager` will acquire either a shared or exclusive lock, as
indicated. Multiple shared locks for the same resource may coexist with an
exclusive lock, but only one exclusive lock will be granted across a
deployment; attempting to allocate another will raise an exception. An
exclusive lock is represented in the database to coordinate between
:class:`ironic.manager.manager` instances, even when deployed on
different hosts.
:class:`TaskManager` methods, as well as driver methods, may be decorated to
determine whether their invocation requires an exclusive lock. For example::
from ironic.manager import task_manager
node_ids = [1, 2, 3]
try:
# Get an exclusive lock so we can manage power state.
# This is the default behaviour of acquire_nodes.
with task_manager.acquire(node_ids) as task:
task.power_on()
states = task.get_power_state()
except exception.NodeLocked:
LOG.info(_("Unable to power nodes on."))
# Get a shared lock, just to check the power state.
with task_manager.acquire(node_ids, shared=True) as task:
states = nodes.get_power_state()
In case :class:`TaskManager` does not provide a method wrapping a particular
driver function, you can access the drivers directly in this way::
with task_manager.acquire(node_ids) as task:
states = []
for node, driver in [r.node, r.driver
for r in task.resources]:
# the driver is loaded based on that node's configuration.
states.append(driver.power.get_power_state(task, node)
"""
import contextlib
from oslo.config import cfg
from ironic.common import exception
from ironic.db import api as dbapi
from ironic.manager import resource_manager
CONF = cfg.CONF
def require_exclusive_lock(f):
"""Decorator to require an exclusive lock.
Decorated functions must take a :class:`TaskManager` as the first
parameter. Decorated class methods should take a :class:`TaskManager`
as the first parameter after "self".
"""
def wrapper(*args, **kwargs):
task = args[0] if isinstance(args[0], TaskManager) else args[1]
if task.shared:
raise exception.ExclusiveLockRequired()
return f(*args, **kwargs)
return wrapper
@contextlib.contextmanager
def acquire(node_ids, shared=False):
"""Context manager for acquiring a lock on one or more Nodes.
Acquire a lock atomically on a non-empty set of nodes. The lock
can be either shared or exclusive. Shared locks may be used for
read-only or non-disruptive actions only, and must be considerate
to what other threads may be doing on the nodes at the same time.
:param node_ids: A list of ids or uuids of nodes to lock.
:param shared: Boolean indicating whether to take a shared or exclusive
lock. Default: False.
:returns: An instance of :class:`TaskManager`.
"""
t = TaskManager(shared)
try:
if not shared:
t.dbapi.reserve_nodes(CONF.host, node_ids)
for id in node_ids:
t.resources.append(resource_manager.NodeManager.acquire(id, t))
yield t
finally:
for id in [r.id for r in t.resources]:
resource_manager.NodeManager.release(id, t)
if not shared:
t.dbapi.release_nodes(CONF.host, node_ids)
class TaskManager(object):
"""Context manager for tasks."""
def __init__(self, shared):
self.shared = shared
self.resources = []
self.dbapi = dbapi.get_instance()

View File

@ -30,7 +30,7 @@ packages =
console_scripts =
ironic-api = ironic.cmd.api:main
ironic-dbsync = ironic.cmd.dbsync:main
ironic-manager = ironic.cmd.manager:main
ironic-conductor = ironic.cmd.conductor:main
ironic-rootwrap = ironic.openstack.common.rootwrap.cmd:main
ironic.drivers =