Initial skeleton for an RPC layer.

This patch implements the a framework for RPC between the API service
and the Manager service. It implements only the 'get_node_power_state'
method, and adds a unit test.

Change-Id: I19e4b761ef212de4c8fd0e600e98368e520a44aa
This commit is contained in:
Devananda van der Veen 2013-05-25 23:57:37 -07:00
parent 378dfac26b
commit e64f64cbc6
8 changed files with 183 additions and 12 deletions

@ -41,7 +41,9 @@ def get_pecan_config():
def setup_app(pecan_config=None, extra_hooks=None):
app_hooks = [hooks.ConfigHook(),
hooks.DBHook()]
hooks.DBHook(),
hooks.ContextHook(),
hooks.RPCHook()]
if extra_hooks:
app_hooks.extend(extra_hooks)

@ -140,6 +140,16 @@ class NodeIfaceController(rest.RestController):
for r in pecan.request.dbapi.get_ifaces_for_node(node_id)]
class NodePowerController(rest.RestController):
"""Initial mock of an API for /node/<id>/power."""
@wsme_pecan.wsexpose(unicode, unicode)
def get_one(self, node_id):
return pecan.request.rpcapi.get_node_power_state(
pecan.request.context,
node_id)
class NodesController(rest.RestController):
"""REST controller for Nodes."""
@ -177,6 +187,7 @@ class NodesController(rest.RestController):
pass
ifaces = NodeIfaceController()
power = NodePowerController()
class Controller(object):

@ -20,6 +20,8 @@ from oslo.config import cfg
from pecan import hooks
from ironic.db import api as dbapi
from ironic.manager import rpcapi
from ironic.openstack.common import context
class ConfigHook(hooks.PecanHook):
@ -35,3 +37,17 @@ class DBHook(hooks.PecanHook):
def before(self, state):
state.request.dbapi = dbapi.get_instance()
class ContextHook(hooks.PecanHook):
def before(self, state):
# TODO(deva): Making all requests have admin context for early
# development. This needs to be fixed later!
state.request.context = context.get_admin_context()
class RPCHook(hooks.PecanHook):
def before(self, state):
state.request.rpcapi = rpcapi.ManagerAPI()

@ -38,7 +38,6 @@ def main():
# Pase config file and command line options, then start logging
ironic_service.prepare_service(sys.argv)
topic = 'ironic.manager'
mgr = manager.ManagerService(CONF.host, topic)
mgr = manager.ManagerService(CONF.host, manager.MANAGER_TOPIC)
launcher = service.launch(mgr)
launcher.wait()

@ -17,9 +17,12 @@
# under the License.
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__)
@ -35,16 +38,17 @@ class ManagerService(service.PeriodicService):
which is also used to coordinate locks between ManagerServices.
"""
RPC_API_VERSION = '1.0'
def __init__(self, host, topic):
super(ManagerService, self).__init__(host, topic)
def start(self):
super(ManagerService, self).start()
# TODO(deva): connect with storage driver
self.dbapi = dbapi.get_instance()
def initialize(self, service):
LOG.debug(_('Manager initializing service hooks'))
# TODO(deva)
def initialize_service_hook(self, service):
pass
def process_notification(self, notification):
LOG.debug(_('Received notification: %r') %
@ -55,10 +59,10 @@ class ManagerService(service.PeriodicService):
# TODO(deva)
pass
def get_node_power_state(self, id):
def get_node_power_state(self, context, node_id):
"""Get and return the power state for a single node."""
with task_manager.acquire([id], shared=True) as task:
with task_manager.acquire([node_id], shared=True) as task:
node = task.resources[0].node
driver = task.resources[0].controller
state = driver.get_power_state(task, node)

54
ironic/manager/rpcapi.py Normal file

@ -0,0 +1,54 @@
# 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))

@ -23,6 +23,7 @@ import mox
from ironic.common import states
from ironic.db import api as dbapi
from ironic.manager import manager
from ironic.openstack.common import context
from ironic.tests.db import base
from ironic.tests.db import utils
from ironic.tests.manager import utils as mgr_utils
@ -33,6 +34,7 @@ class ManagerTestCase(base.DbTestCase):
def setUp(self):
super(ManagerTestCase, self).setUp()
self.service = manager.ManagerService('test-host', 'test-topic')
self.context = context.get_admin_context()
self.dbapi = dbapi.get_instance()
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager()
@ -43,7 +45,7 @@ class ManagerTestCase(base.DbTestCase):
# FakeControlDriver.get_power_state will "pass"
# and states.NOSTATE is None, so this test should pass.
state = self.service.get_node_power_state(n['uuid'])
state = self.service.get_node_power_state(self.context, n['uuid'])
self.assertEqual(state, states.NOSTATE)
def test_get_power_state_with_mock(self):
@ -59,9 +61,9 @@ class ManagerTestCase(base.DbTestCase):
AndReturn(states.POWER_ON)
self.mox.ReplayAll()
state = self.service.get_node_power_state(n['uuid'])
state = self.service.get_node_power_state(self.context, n['uuid'])
self.assertEqual(state, states.POWER_OFF)
state = self.service.get_node_power_state(n['uuid'])
state = self.service.get_node_power_state(self.context, n['uuid'])
self.assertEqual(state, states.POWER_ON)
self.mox.VerifyAll()

@ -0,0 +1,83 @@
# 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.
"""
Unit Tests for :py:class:`ironic.manager.rpcapi.ManagerAPI`.
"""
from oslo.config import cfg
from ironic.db import api as dbapi
from ironic.manager import rpcapi as manager_rpcapi
from ironic.openstack.common import context
from ironic.openstack.common import jsonutils as json
from ironic.openstack.common import rpc
from ironic.tests.db import base
from ironic.tests.db import utils as dbutils
CONF = cfg.CONF
class ManagerRpcAPITestCase(base.DbTestCase):
def setUp(self):
super(ManagerRpcAPITestCase, self).setUp()
self.context = context.get_admin_context()
self.dbapi = dbapi.get_instance()
self.fake_node = json.to_primitive(dbutils.get_test_node(
control_driver='fake',
deploy_driver='fake'))
def test_serialized_instance_has_uuid(self):
self.assertTrue('uuid' in self.fake_node)
def _test_rpcapi(self, method, rpc_method, **kwargs):
ctxt = context.get_admin_context()
rpcapi = manager_rpcapi.ManagerAPI(topic='fake-topic')
expected_retval = 'hello world' if method == 'call' else None
expected_version = kwargs.pop('version', rpcapi.RPC_API_VERSION)
expected_msg = rpcapi.make_msg(method, **kwargs)
expected_msg['version'] = expected_version
expected_topic = 'fake-topic'
if 'host' in kwargs:
expected_topic += ".%s" % kwargs['host']
self.fake_args = None
self.fake_kwargs = None
def _fake_rpc_method(*args, **kwargs):
self.fake_args = args
self.fake_kwargs = kwargs
if expected_retval:
return expected_retval
self.stubs.Set(rpc, rpc_method, _fake_rpc_method)
retval = getattr(rpcapi, method)(ctxt, **kwargs)
self.assertEqual(retval, expected_retval)
expected_args = [ctxt, expected_topic, expected_msg]
for arg, expected_arg in zip(self.fake_args, expected_args):
self.assertEqual(arg, expected_arg)
def test_get_node_power_state(self):
self._test_rpcapi('get_node_power_state',
'call',
node_id=123)