diff --git a/heat/api/cfn/v1/stacks.py b/heat/api/cfn/v1/stacks.py index 99f1a7222d..65476e1db5 100644 --- a/heat/api/cfn/v1/stacks.py +++ b/heat/api/cfn/v1/stacks.py @@ -25,9 +25,9 @@ import errno from heat.api.aws import exception from heat.api.aws import utils as api_utils from heat.common import wsgi -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import client as rpc_client from heat.common import template_format -import heat.engine.api as engine_api +from heat.rpc import api as engine_api from heat.common import identifier import heat.openstack.common.rpc.common as rpc_common @@ -47,7 +47,7 @@ class StackController(object): def __init__(self, options): self.options = options - self.engine_rpcapi = engine_rpcapi.EngineAPI() + self.engine_rpcapi = rpc_client.EngineClient() @staticmethod def _id_format(resp): diff --git a/heat/api/cfn/v1/waitcondition.py b/heat/api/cfn/v1/waitcondition.py index f8bd2c479f..9f93e1cde4 100644 --- a/heat/api/cfn/v1/waitcondition.py +++ b/heat/api/cfn/v1/waitcondition.py @@ -19,7 +19,7 @@ from webob.exc import Response from heat.common import wsgi from heat.common import context -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import client as rpc_client from heat.openstack.common import rpc @@ -40,11 +40,11 @@ def json_error(http_status, message): class WaitConditionController: def __init__(self, options): self.options = options - self.engine_rpcapi = engine_rpcapi.EngineAPI() + self.engine = rpc_client.EngineClient() def update_waitcondition(self, req, body, stack_id, resource_name): con = req.context - [error, metadata] = self.engine_rpcapi.metadata_update(con, + [error, metadata] = self.engine.metadata_update(con, stack_id=stack_id, resource_name=resource_name, metadata=body) diff --git a/heat/api/cloudwatch/watch.py b/heat/api/cloudwatch/watch.py index 58a2fcd334..0020ec1942 100644 --- a/heat/api/cloudwatch/watch.py +++ b/heat/api/cloudwatch/watch.py @@ -19,8 +19,8 @@ endpoint for heat AWS-compatible CloudWatch API from heat.api.aws import exception from heat.api.aws import utils as api_utils from heat.common import wsgi -from heat.engine import rpcapi as engine_rpcapi -import heat.engine.api as engine_api +from heat.rpc import client as rpc_client +from heat.rpc import api as engine_api import heat.openstack.common.rpc.common as rpc_common from heat.openstack.common import log as logging @@ -37,7 +37,7 @@ class WatchController(object): def __init__(self, options): self.options = options - self.engine_rpcapi = engine_rpcapi.EngineAPI() + self.engine_rpcapi = rpc_client.EngineClient() @staticmethod def _reformat_dimensions(dims): diff --git a/heat/api/openstack/v1/events.py b/heat/api/openstack/v1/events.py index b2b0e38579..20c7658123 100644 --- a/heat/api/openstack/v1/events.py +++ b/heat/api/openstack/v1/events.py @@ -18,9 +18,9 @@ from webob import exc from heat.api.openstack.v1 import util from heat.common import wsgi -from heat.engine import api as engine_api +from heat.rpc import api as engine_api from heat.common import identifier -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import client as rpc_client import heat.openstack.common.rpc.common as rpc_common from heat.openstack.common.gettextutils import _ @@ -68,7 +68,7 @@ class EventController(object): def __init__(self, options): self.options = options - self.engine = engine_rpcapi.EngineAPI() + self.engine = rpc_client.EngineClient() def _event_list(self, req, identity, filter_func=lambda e: True, detail=False): diff --git a/heat/api/openstack/v1/resources.py b/heat/api/openstack/v1/resources.py index a63e35f809..ca31516fb7 100644 --- a/heat/api/openstack/v1/resources.py +++ b/heat/api/openstack/v1/resources.py @@ -17,9 +17,9 @@ import itertools from heat.api.openstack.v1 import util from heat.common import wsgi -from heat.engine import api as engine_api +from heat.rpc import api as engine_api from heat.common import identifier -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import client as rpc_client import heat.openstack.common.rpc.common as rpc_common @@ -54,7 +54,7 @@ class ResourceController(object): def __init__(self, options): self.options = options - self.engine = engine_rpcapi.EngineAPI() + self.engine = rpc_client.EngineClient() @util.identified_stack def index(self, req, identity): diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index e76f02e882..2c058e15e0 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -27,8 +27,8 @@ from webob import exc from heat.api.openstack.v1 import util from heat.common import wsgi from heat.common import template_format -from heat.engine import api as engine_api -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import api as engine_api +from heat.rpc import client as rpc_client import heat.openstack.common.rpc.common as rpc_common from heat.openstack.common import log as logging @@ -164,7 +164,7 @@ class StackController(object): def __init__(self, options): self.options = options - self.engine_rpcapi = engine_rpcapi.EngineAPI() + self.engine = rpc_client.EngineClient() def default(self, req, **args): raise exc.HTTPNotFound() @@ -176,7 +176,7 @@ class StackController(object): """ try: - stack_list = self.engine_rpcapi.list_stacks(req.context) + stack_list = self.engine.list_stacks(req.context) except rpc_common.RemoteError as ex: return util.remote_error(ex, True) @@ -202,11 +202,11 @@ class StackController(object): data = InstantiationData(body) try: - result = self.engine_rpcapi.create_stack(req.context, - data.stack_name(), - data.template(), - data.user_params(), - data.args()) + result = self.engine.create_stack(req.context, + data.stack_name(), + data.template(), + data.user_params(), + data.args()) except rpc_common.RemoteError as ex: return util.remote_error(ex, True) @@ -222,8 +222,8 @@ class StackController(object): """ try: - identity = self.engine_rpcapi.identify_stack(req.context, - stack_name) + identity = self.engine.identify_stack(req.context, + stack_name) except rpc_common.RemoteError as ex: return util.remote_error(ex) @@ -240,8 +240,8 @@ class StackController(object): """ try: - stack_list = self.engine_rpcapi.show_stack(req.context, - identity) + stack_list = self.engine.show_stack(req.context, + identity) except rpc_common.RemoteError as ex: return util.remote_error(ex) @@ -259,8 +259,8 @@ class StackController(object): """ try: - templ = self.engine_rpcapi.get_template(req.context, - identity) + templ = self.engine.get_template(req.context, + identity) except rpc_common.RemoteError as ex: return util.remote_error(ex) @@ -278,11 +278,11 @@ class StackController(object): data = InstantiationData(body) try: - res = self.engine_rpcapi.update_stack(req.context, - identity, - data.template(), - data.user_params(), - data.args()) + res = self.engine.update_stack(req.context, + identity, + data.template(), + data.user_params(), + data.args()) except rpc_common.RemoteError as ex: return util.remote_error(ex) @@ -298,9 +298,9 @@ class StackController(object): """ try: - res = self.engine_rpcapi.delete_stack(req.context, - identity, - cast=False) + res = self.engine.delete_stack(req.context, + identity, + cast=False) except rpc_common.RemoteError as ex: return util.remote_error(ex) @@ -320,8 +320,8 @@ class StackController(object): data = InstantiationData(body) try: - result = self.engine_rpcapi.validate_template(req.context, - data.template()) + result = self.engine.validate_template(req.context, + data.template()) except rpc_common.RemoteError as ex: return util.remote_error(ex, True) diff --git a/heat/engine/api.py b/heat/engine/api.py index 62711f799f..9403f4c7b4 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.rpc.api import * from heat.openstack.common import timeutils from heat.engine import parser from heat.engine import template @@ -21,8 +22,6 @@ from heat.openstack.common import log as logging logger = logging.getLogger('heat.engine.manager') -PARAM_KEYS = (PARAM_TIMEOUT, ) = ('timeout_mins', ) - def extract_args(params): ''' @@ -41,33 +40,6 @@ def extract_args(params): return kwargs -STACK_KEYS = ( - STACK_NAME, STACK_ID, - STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME, - STACK_NOTIFICATION_TOPICS, - STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION, - STACK_PARAMETERS, STACK_OUTPUTS, - STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES, - STACK_DISABLE_ROLLBACK, STACK_TIMEOUT, -) = ( - 'stack_name', 'stack_identity', - 'creation_time', 'updated_time', 'deletion_time', - 'notification_topics', - 'description', 'template_description', - 'parameters', 'outputs', - 'stack_status', 'stack_status_reason', 'capabilities', - 'disable_rollback', 'timeout_mins' -) - -STACK_OUTPUT_KEYS = ( - OUTPUT_DESCRIPTION, - OUTPUT_KEY, OUTPUT_VALUE, -) = ( - 'description', - 'output_key', 'output_value', -) - - def format_stack_outputs(stack, outputs): ''' Return a representation of the given output template for the given stack @@ -110,19 +82,6 @@ def format_stack(stack): return info -RES_KEYS = ( - RES_DESCRIPTION, RES_UPDATED_TIME, - RES_NAME, RES_PHYSICAL_ID, RES_METADATA, - RES_STATUS, RES_STATUS_DATA, RES_TYPE, - RES_ID, RES_STACK_ID, RES_STACK_NAME, -) = ( - 'description', 'updated_time', - 'logical_resource_id', 'physical_resource_id', 'metadata', - 'resource_status', 'resource_status_reason', 'resource_type', - 'resource_identity', STACK_ID, STACK_NAME, -) - - def format_stack_resource(resource, detail=True): ''' Return a representation of the given resource that matches the API output @@ -149,23 +108,6 @@ def format_stack_resource(resource, detail=True): return res -EVENT_KEYS = ( - EVENT_ID, - EVENT_STACK_ID, EVENT_STACK_NAME, - EVENT_TIMESTAMP, - EVENT_RES_NAME, EVENT_RES_PHYSICAL_ID, - EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE, - EVENT_RES_PROPERTIES, -) = ( - 'event_identity', - STACK_ID, STACK_NAME, - "event_time", - RES_NAME, RES_PHYSICAL_ID, - RES_STATUS, RES_STATUS_DATA, RES_TYPE, - 'resource_properties', -) - - def format_event(event): stack_identifier = event.stack.identifier() @@ -185,56 +127,6 @@ def format_event(event): return result -# This is the representation of a watch we expose to the API via RPC -WATCH_KEYS = ( - WATCH_ACTIONS_ENABLED, WATCH_ALARM_ACTIONS, WATCH_TOPIC, - WATCH_UPDATED_TIME, WATCH_DESCRIPTION, WATCH_NAME, - WATCH_COMPARISON, WATCH_DIMENSIONS, WATCH_PERIODS, - WATCH_INSUFFICIENT_ACTIONS, WATCH_METRIC_NAME, WATCH_NAMESPACE, - WATCH_OK_ACTIONS, WATCH_PERIOD, WATCH_STATE_REASON, - WATCH_STATE_REASON_DATA, WATCH_STATE_UPDATED_TIME, WATCH_STATE_VALUE, - WATCH_STATISTIC, WATCH_THRESHOLD, WATCH_UNIT, WATCH_STACK_ID - ) = ( - 'actions_enabled', 'actions', 'topic', - 'updated_time', 'description', 'name', - 'comparison', 'dimensions', 'periods', - 'insufficient_actions', 'metric_name', 'namespace', - 'ok_actions', 'period', 'state_reason', - 'state_reason_data', 'state_updated_time', 'state_value', - 'statistic', 'threshold', 'unit', 'stack_id') - - -# Alternate representation of a watch rule to align with DB format -# FIXME : These align with AWS naming for compatibility with the -# current cfn-push-stats & metadata server, fix when we've ported -# cfn-push-stats to use the Cloudwatch server and/or moved metric -# collection into ceilometer, these should just be WATCH_KEYS -# or each field should be stored separately in the DB watch_data -# table if we stick to storing watch data in the heat DB -WATCH_RULE_KEYS = ( - RULE_ACTIONS_ENABLED, RULE_ALARM_ACTIONS, RULE_TOPIC, - RULE_UPDATED_TIME, RULE_DESCRIPTION, RULE_NAME, - RULE_COMPARISON, RULE_DIMENSIONS, RULE_PERIODS, - RULE_INSUFFICIENT_ACTIONS, RULE_METRIC_NAME, RULE_NAMESPACE, - RULE_OK_ACTIONS, RULE_PERIOD, RULE_STATE_REASON, - RULE_STATE_REASON_DATA, RULE_STATE_UPDATED_TIME, RULE_STATE_VALUE, - RULE_STATISTIC, RULE_THRESHOLD, RULE_UNIT, RULE_STACK_NAME - ) = ( - 'ActionsEnabled', 'AlarmActions', 'AlarmArn', - 'AlarmConfigurationUpdatedTimestamp', 'AlarmDescription', 'AlarmName', - 'ComparisonOperator', 'Dimensions', 'EvaluationPeriods', - 'InsufficientDataActions', 'MetricName', 'Namespace', - 'OKActions', 'Period', 'StateReason', - 'StateReasonData', 'StateUpdatedTimestamp', 'StateValue', - 'Statistic', 'Threshold', 'Unit', 'StackName') - - -WATCH_STATES = (WATCH_STATE_OK, WATCH_STATE_ALARM, WATCH_STATE_NODATA - ) = (watchrule.WatchRule.NORMAL, - watchrule.WatchRule.ALARM, - watchrule.WatchRule.NODATA) - - def format_watch(watch): result = { @@ -266,14 +158,6 @@ def format_watch(watch): return result -WATCH_DATA_KEYS = ( - WATCH_DATA_ALARM, WATCH_DATA_METRIC, WATCH_DATA_TIME, - WATCH_DATA_NAMESPACE, WATCH_DATA - ) = ( - 'watch_name', 'metric_name', 'timestamp', - 'namespace', 'data') - - def format_watch_data(wd): # Demangle DB format data into something more easily used in the API diff --git a/heat/engine/watchrule.py b/heat/engine/watchrule.py index 09a278e96f..58ff391a10 100644 --- a/heat/engine/watchrule.py +++ b/heat/engine/watchrule.py @@ -20,6 +20,7 @@ from heat.openstack.common import timeutils from heat.engine import timestamp from heat.db import api as db_api from heat.engine import parser +from heat.rpc import api as rpc_api from heat.common import context as ctxtlib import eventlet @@ -28,9 +29,15 @@ greenpool = eventlet.GreenPool() class WatchRule(object): - WATCH_STATES = (ALARM, NORMAL, NODATA - ) = ('ALARM', 'NORMAL', 'NODATA') - + WATCH_STATES = ( + ALARM, + NORMAL, + NODATA + ) = ( + rpc_api.WATCH_STATE_ALARM, + rpc_api.WATCH_STATE_OK, + rpc_api.WATCH_STATE_NODATA + ) ACTION_MAP = {ALARM: 'AlarmActions', NORMAL: 'OKActions', NODATA: 'InsufficientDataActions'} diff --git a/heat/rpc/__init__.py b/heat/rpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/heat/rpc/api.py b/heat/rpc/api.py new file mode 100644 index 0000000000..ec673057eb --- /dev/null +++ b/heat/rpc/api.py @@ -0,0 +1,125 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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. + +PARAM_KEYS = (PARAM_TIMEOUT, ) = ('timeout_mins', ) + +STACK_KEYS = ( + STACK_NAME, STACK_ID, + STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME, + STACK_NOTIFICATION_TOPICS, + STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION, + STACK_PARAMETERS, STACK_OUTPUTS, + STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES, + STACK_DISABLE_ROLLBACK, STACK_TIMEOUT, +) = ( + 'stack_name', 'stack_identity', + 'creation_time', 'updated_time', 'deletion_time', + 'notification_topics', + 'description', 'template_description', + 'parameters', 'outputs', + 'stack_status', 'stack_status_reason', 'capabilities', + 'disable_rollback', 'timeout_mins' +) + +STACK_OUTPUT_KEYS = ( + OUTPUT_DESCRIPTION, + OUTPUT_KEY, OUTPUT_VALUE, +) = ( + 'description', + 'output_key', 'output_value', +) + +RES_KEYS = ( + RES_DESCRIPTION, RES_UPDATED_TIME, + RES_NAME, RES_PHYSICAL_ID, RES_METADATA, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + RES_ID, RES_STACK_ID, RES_STACK_NAME, +) = ( + 'description', 'updated_time', + 'logical_resource_id', 'physical_resource_id', 'metadata', + 'resource_status', 'resource_status_reason', 'resource_type', + 'resource_identity', STACK_ID, STACK_NAME, +) + +EVENT_KEYS = ( + EVENT_ID, + EVENT_STACK_ID, EVENT_STACK_NAME, + EVENT_TIMESTAMP, + EVENT_RES_NAME, EVENT_RES_PHYSICAL_ID, + EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE, + EVENT_RES_PROPERTIES, +) = ( + 'event_identity', + STACK_ID, STACK_NAME, + "event_time", + RES_NAME, RES_PHYSICAL_ID, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + 'resource_properties', +) + +# This is the representation of a watch we expose to the API via RPC +WATCH_KEYS = ( + WATCH_ACTIONS_ENABLED, WATCH_ALARM_ACTIONS, WATCH_TOPIC, + WATCH_UPDATED_TIME, WATCH_DESCRIPTION, WATCH_NAME, + WATCH_COMPARISON, WATCH_DIMENSIONS, WATCH_PERIODS, + WATCH_INSUFFICIENT_ACTIONS, WATCH_METRIC_NAME, WATCH_NAMESPACE, + WATCH_OK_ACTIONS, WATCH_PERIOD, WATCH_STATE_REASON, + WATCH_STATE_REASON_DATA, WATCH_STATE_UPDATED_TIME, WATCH_STATE_VALUE, + WATCH_STATISTIC, WATCH_THRESHOLD, WATCH_UNIT, WATCH_STACK_ID +) = ( + 'actions_enabled', 'actions', 'topic', + 'updated_time', 'description', 'name', + 'comparison', 'dimensions', 'periods', + 'insufficient_actions', 'metric_name', 'namespace', + 'ok_actions', 'period', 'state_reason', + 'state_reason_data', 'state_updated_time', 'state_value', + 'statistic', 'threshold', 'unit', 'stack_id') + +# Alternate representation of a watch rule to align with DB format +# FIXME : These align with AWS naming for compatibility with the +# current cfn-push-stats & metadata server, fix when we've ported +# cfn-push-stats to use the Cloudwatch server and/or moved metric +# collection into ceilometer, these should just be WATCH_KEYS +# or each field should be stored separately in the DB watch_data +# table if we stick to storing watch data in the heat DB +WATCH_RULE_KEYS = ( + RULE_ACTIONS_ENABLED, RULE_ALARM_ACTIONS, RULE_TOPIC, + RULE_UPDATED_TIME, RULE_DESCRIPTION, RULE_NAME, + RULE_COMPARISON, RULE_DIMENSIONS, RULE_PERIODS, + RULE_INSUFFICIENT_ACTIONS, RULE_METRIC_NAME, RULE_NAMESPACE, + RULE_OK_ACTIONS, RULE_PERIOD, RULE_STATE_REASON, + RULE_STATE_REASON_DATA, RULE_STATE_UPDATED_TIME, RULE_STATE_VALUE, + RULE_STATISTIC, RULE_THRESHOLD, RULE_UNIT, RULE_STACK_NAME +) = ( + 'ActionsEnabled', 'AlarmActions', 'AlarmArn', + 'AlarmConfigurationUpdatedTimestamp', 'AlarmDescription', 'AlarmName', + 'ComparisonOperator', 'Dimensions', 'EvaluationPeriods', + 'InsufficientDataActions', 'MetricName', 'Namespace', + 'OKActions', 'Period', 'StateReason', + 'StateReasonData', 'StateUpdatedTimestamp', 'StateValue', + 'Statistic', 'Threshold', 'Unit', 'StackName') + +WATCH_STATES = ( + WATCH_STATE_OK, WATCH_STATE_ALARM, WATCH_STATE_NODATA +) = ( + 'NORMAL', 'ALARM', 'NODATA', +) + +WATCH_DATA_KEYS = ( + WATCH_DATA_ALARM, WATCH_DATA_METRIC, WATCH_DATA_TIME, + WATCH_DATA_NAMESPACE, WATCH_DATA +) = ( + 'watch_name', 'metric_name', 'timestamp', + 'namespace', 'data' +) diff --git a/heat/engine/rpcapi.py b/heat/rpc/client.py similarity index 98% rename from heat/engine/rpcapi.py rename to heat/rpc/client.py index 534c629258..9036ed4efe 100644 --- a/heat/engine/rpcapi.py +++ b/heat/rpc/client.py @@ -40,7 +40,7 @@ def _engine_topic(topic, ctxt, host): return rpc.queue_get_for(ctxt, topic, host) -class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy): +class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy): '''Client side of the heat engine rpc API. API version history: @@ -51,7 +51,7 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy): BASE_RPC_API_VERSION = '1.0' def __init__(self): - super(EngineAPI, self).__init__( + super(EngineClient, self).__init__( topic=FLAGS.engine_topic, default_version=self.BASE_RPC_API_VERSION) diff --git a/heat/tests/test_api_cloudwatch.py b/heat/tests/test_api_cloudwatch.py index a2fe652eff..3f5398e6ef 100644 --- a/heat/tests/test_api_cloudwatch.py +++ b/heat/tests/test_api_cloudwatch.py @@ -33,7 +33,7 @@ import heat.openstack.common.rpc.common as rpc_common from heat.common.wsgi import Request from heat.api.aws import exception import heat.api.cloudwatch.watch as watches -import heat.engine.api as engine_api +from heat.rpc import api as engine_api @attr(tag=['unit', 'api-cloudwatch', 'WatchController']) diff --git a/heat/tests/test_rpcapi.py b/heat/tests/test_rpc_client.py similarity index 97% rename from heat/tests/test_rpcapi.py rename to heat/tests/test_rpc_client.py index 5112c5c9d7..11c0ae2ddf 100644 --- a/heat/tests/test_rpcapi.py +++ b/heat/tests/test_rpc_client.py @@ -15,7 +15,7 @@ # under the License. """ -Unit Tests for heat.engine.rpcapi +Unit Tests for heat.rpc.client """ @@ -25,7 +25,7 @@ import unittest from heat.common import config from heat.common import context -from heat.engine import rpcapi as engine_rpcapi +from heat.rpc import client as rpc_client from heat.openstack.common import cfg from heat.openstack.common import rpc @@ -54,7 +54,7 @@ class EngineRpcAPITestCase(unittest.TestCase): rpcapi_class = kwargs['rpcapi_class'] del kwargs['rpcapi_class'] else: - rpcapi_class = engine_rpcapi.EngineAPI + rpcapi_class = rpc_client.EngineClient rpcapi = rpcapi_class() expected_retval = 'foo' if method == 'call' else None