Introduce bulk push to rpc callback mechanism
There are usage patterns which would benefit from having the capability to send a list of resources in bulk instead of using individual fanout messages. From now on, the rpc callback subscriber receives a list of resources (single or multiple), and the pushers must always push a list. Backwards compatibility for QoSPolicy consumers in mitaka is provided by calling push with "resource" parameter for single item lists during one release cycle. That will be dropped when Ocata opens. Partially-implements: blueprint vlan-aware-vms Change-Id: I1117925360a29ecbd1902fa527b2f24f94ce81ec
This commit is contained in:
parent
2a64fabd52
commit
7f617e6a21
@ -211,10 +211,10 @@ The agent code processing port updates may look like::
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
|
||||
|
||||
def process_resource_updates(resource_type, resource, event_type):
|
||||
def process_resource_updates(resource_type, resource_list, event_type):
|
||||
|
||||
# send to the right handler which will update any control plane
|
||||
# details related to the updated resource...
|
||||
# details related to the updated resources...
|
||||
|
||||
|
||||
def subscribe_resources():
|
||||
@ -238,7 +238,7 @@ The relevant function is:
|
||||
The callback function will receive the following arguments:
|
||||
|
||||
* resource_type: the type of resource which is receiving the update.
|
||||
* resource: resource of supported object
|
||||
* resource_list: list of resources which have been pushed by server.
|
||||
* event_type: will be one of CREATED, UPDATED, or DELETED, see
|
||||
neutron.api.rpc.callbacks.events for details.
|
||||
|
||||
@ -263,9 +263,22 @@ Sending resource events
|
||||
-----------------------
|
||||
|
||||
On the server side, resource updates could come from anywhere, a service plugin,
|
||||
an extension, anything that updates, creates, or destroys the resource and that
|
||||
an extension, anything that updates, creates, or destroys the resources and that
|
||||
is of any interest to subscribed agents.
|
||||
|
||||
A callback is expected to receive a list of resources. When resources in the list
|
||||
belong to the same resource type, a single push RPC message is sent; if the list
|
||||
contains objects of different resource types, resources of each type are grouped
|
||||
and sent separately, one push RPC message per type. On the receiver side,
|
||||
resources in a list always belong to the same type. In other words, a server-side
|
||||
push of a list of heterogenous objects will result into N messages on bus and
|
||||
N client-side callback invocations, where N is the number of unique resource
|
||||
types in the given list, e.g. L(A, A, B, C, C, C) would be fragmented into
|
||||
L1(A, A), L2(B), L3(C, C, C), and each list pushed separately.
|
||||
|
||||
Note: there is no guarantee in terms of order in which separate resource lists
|
||||
will be delivered to consumers.
|
||||
|
||||
The server/publisher side may look like::
|
||||
|
||||
from neutron.api.rpc.callbacks.producer import registry
|
||||
@ -274,17 +287,17 @@ The server/publisher side may look like::
|
||||
def create_qos_policy(...):
|
||||
policy = fetch_policy(...)
|
||||
update_the_db(...)
|
||||
registry.push(policy, events.CREATED)
|
||||
registry.push([policy], events.CREATED)
|
||||
|
||||
def update_qos_policy(...):
|
||||
policy = fetch_policy(...)
|
||||
update_the_db(...)
|
||||
registry.push(policy, events.UPDATED)
|
||||
registry.push([policy], events.UPDATED)
|
||||
|
||||
def delete_qos_policy(...):
|
||||
policy = fetch_policy(...)
|
||||
update_the_db(...)
|
||||
registry.push(policy, events.DELETED)
|
||||
registry.push([policy], events.DELETED)
|
||||
|
||||
|
||||
References
|
||||
|
@ -220,12 +220,13 @@ class QosAgentExtension(l2_agent_extension.L2AgentExtension):
|
||||
connection.create_consumer(topic, endpoints, fanout=True)
|
||||
|
||||
@lockutils.synchronized('qos-port')
|
||||
def _handle_notification(self, qos_policy, event_type):
|
||||
def _handle_notification(self, qos_policies, event_type):
|
||||
# server does not allow to remove a policy that is attached to any
|
||||
# port, so we ignore DELETED events. Also, if we receive a CREATED
|
||||
# event for a policy, it means that there are no ports so far that are
|
||||
# attached to it. That's why we are interested in UPDATED events only
|
||||
if event_type == events.UPDATED:
|
||||
for qos_policy in qos_policies:
|
||||
self._process_update_policy(qos_policy)
|
||||
|
||||
@lockutils.synchronized('qos-port')
|
||||
|
@ -27,12 +27,12 @@ def unsubscribe(callback, resource_type):
|
||||
_get_manager().unregister(callback, resource_type)
|
||||
|
||||
|
||||
def push(resource_type, resource, event_type):
|
||||
"""Push resource events into all registered callbacks for the type."""
|
||||
def push(resource_type, resource_list, event_type):
|
||||
"""Push resource list into all registered callbacks for the event type."""
|
||||
|
||||
callbacks = _get_manager().get_callbacks(resource_type)
|
||||
for callback in callbacks:
|
||||
callback(resource, event_type)
|
||||
callback(resource_list, event_type)
|
||||
|
||||
|
||||
def clear():
|
||||
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
from neutron_lib import exceptions
|
||||
from oslo_log import helpers as log_helpers
|
||||
from oslo_log import log as logging
|
||||
@ -179,26 +181,62 @@ class ResourcesPushRpcApi(object):
|
||||
|
||||
def __init__(self):
|
||||
target = oslo_messaging.Target(
|
||||
version='1.0',
|
||||
namespace=constants.RPC_NAMESPACE_RESOURCES)
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def _prepare_object_fanout_context(self, obj, version):
|
||||
def _prepare_object_fanout_context(self, obj, resource_version,
|
||||
rpc_version):
|
||||
"""Prepare fanout context, one topic per object type."""
|
||||
obj_topic = resource_type_versioned_topic(obj.obj_name(), version)
|
||||
return self.client.prepare(fanout=True, topic=obj_topic)
|
||||
obj_topic = resource_type_versioned_topic(obj.obj_name(),
|
||||
resource_version)
|
||||
return self.client.prepare(fanout=True, topic=obj_topic,
|
||||
version=rpc_version)
|
||||
|
||||
@staticmethod
|
||||
def _classify_resources_by_type(resource_list):
|
||||
resources_by_type = collections.defaultdict(list)
|
||||
for resource in resource_list:
|
||||
resource_type = resources.get_resource_type(resource)
|
||||
resources_by_type[resource_type].append(resource)
|
||||
return resources_by_type
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def push(self, context, resource, event_type):
|
||||
resource_type = resources.get_resource_type(resource)
|
||||
def push(self, context, resource_list, event_type):
|
||||
"""Push an event and list of resources to agents, batched per type.
|
||||
When a list of different resource types is passed to this method,
|
||||
the push will be sent as separate individual list pushes, one per
|
||||
resource type.
|
||||
"""
|
||||
|
||||
resources_by_type = self._classify_resources_by_type(resource_list)
|
||||
for resource_type, type_resources in resources_by_type.items():
|
||||
self._push(context, resource_type, type_resources, event_type)
|
||||
|
||||
def _push(self, context, resource_type, resource_list, event_type):
|
||||
"""Push an event and list of resources of the same type to agents."""
|
||||
_validate_resource_type(resource_type)
|
||||
versions = version_manager.get_resource_versions(resource_type)
|
||||
for version in versions:
|
||||
cctxt = self._prepare_object_fanout_context(resource, version)
|
||||
dehydrated_resource = resource.obj_to_primitive(
|
||||
target_version=version)
|
||||
compat_call = len(resource_list) == 1
|
||||
|
||||
for version in version_manager.get_resource_versions(resource_type):
|
||||
cctxt = self._prepare_object_fanout_context(
|
||||
resource_list[0], version,
|
||||
rpc_version='1.0' if compat_call else '1.1')
|
||||
|
||||
dehydrated_resources = [
|
||||
resource.obj_to_primitive(target_version=version)
|
||||
for resource in resource_list]
|
||||
|
||||
if compat_call:
|
||||
#TODO(mangelajo): remove in Ocata, backwards compatibility
|
||||
# for agents expecting a single element as
|
||||
# a single element instead of a list, this
|
||||
# is only relevant to the QoSPolicy topic queue
|
||||
cctxt.cast(context, 'push',
|
||||
resource=dehydrated_resource,
|
||||
resource=dehydrated_resources[0],
|
||||
event_type=event_type)
|
||||
else:
|
||||
cctxt.cast(context, 'push',
|
||||
resource_list=dehydrated_resources,
|
||||
event_type=event_type)
|
||||
|
||||
|
||||
@ -211,14 +249,22 @@ class ResourcesPushRpcCallback(object):
|
||||
"""
|
||||
# History
|
||||
# 1.0 Initial version
|
||||
# 1.1 push method introduces resource_list support
|
||||
|
||||
target = oslo_messaging.Target(version='1.0',
|
||||
target = oslo_messaging.Target(version='1.1',
|
||||
namespace=constants.RPC_NAMESPACE_RESOURCES)
|
||||
|
||||
def push(self, context, resource, event_type):
|
||||
resource_obj = obj_base.NeutronObject.clean_obj_from_primitive(
|
||||
resource)
|
||||
LOG.debug("Resources notification (%(event_type)s): %(resource)s",
|
||||
{'event_type': event_type, 'resource': repr(resource_obj)})
|
||||
resource_type = resources.get_resource_type(resource_obj)
|
||||
cons_registry.push(resource_type, resource_obj, event_type)
|
||||
def push(self, context, **kwargs):
|
||||
"""Push receiver, will always receive resources of the same type."""
|
||||
# TODO(mangelajo): accept single 'resource' parameter for backwards
|
||||
# compatibility during Newton, remove in Ocata
|
||||
resource_list = ([kwargs['resource']] if 'resource' in kwargs else
|
||||
kwargs['resource_list'])
|
||||
event_type = kwargs['event_type']
|
||||
|
||||
resource_objs = [
|
||||
obj_base.NeutronObject.clean_obj_from_primitive(resource)
|
||||
for resource in resource_list]
|
||||
|
||||
resource_type = resources.get_resource_type(resource_objs[0])
|
||||
cons_registry.push(resource_type, resource_objs, event_type)
|
||||
|
@ -53,7 +53,7 @@ class RpcQosServiceNotificationDriver(
|
||||
pass
|
||||
|
||||
def update_policy(self, context, policy):
|
||||
self.notification_api.push(context, policy, events.UPDATED)
|
||||
self.notification_api.push(context, [policy], events.UPDATED)
|
||||
|
||||
def delete_policy(self, context, policy):
|
||||
self.notification_api.push(context, policy, events.DELETED)
|
||||
self.notification_api.push(context, [policy], events.DELETED)
|
||||
|
@ -229,7 +229,7 @@ class TestOVSAgentQosExtension(OVSAgentQoSExtensionTestFramework):
|
||||
policy_copy.rules[0].max_kbps = 500
|
||||
policy_copy.rules[0].max_burst_kbps = 5
|
||||
policy_copy.rules[1].dscp_mark = TEST_DSCP_MARK_2
|
||||
consumer_reg.push(resources.QOS_POLICY, policy_copy, events.UPDATED)
|
||||
consumer_reg.push(resources.QOS_POLICY, [policy_copy], events.UPDATED)
|
||||
self.wait_until_bandwidth_limit_rule_applied(self.ports[0],
|
||||
policy_copy.rules[0])
|
||||
self._assert_bandwidth_limit_rule_is_set(self.ports[0],
|
||||
@ -265,6 +265,6 @@ class TestOVSAgentQosExtension(OVSAgentQoSExtensionTestFramework):
|
||||
|
||||
policy_copy = copy.deepcopy(self.qos_policies[TEST_POLICY_ID1])
|
||||
policy_copy.rules = list()
|
||||
consumer_reg.push(resources.QOS_POLICY, policy_copy, events.UPDATED)
|
||||
consumer_reg.push(resources.QOS_POLICY, [policy_copy], events.UPDATED)
|
||||
|
||||
self.wait_until_bandwidth_limit_rule_applied(port_dict, None)
|
||||
|
@ -238,7 +238,7 @@ class QosExtensionRpcTestCase(QosExtensionBaseTestCase):
|
||||
self.qos_ext, '_process_update_policy') as update_mock:
|
||||
|
||||
policy_obj = mock.Mock()
|
||||
self.qos_ext._handle_notification(policy_obj, events.UPDATED)
|
||||
self.qos_ext._handle_notification([policy_obj], events.UPDATED)
|
||||
update_mock.assert_called_with(policy_obj)
|
||||
|
||||
def test__process_update_policy(self):
|
||||
|
@ -51,6 +51,6 @@ class ConsumerRegistryTestCase(base.BaseTestCase):
|
||||
callback2 = mock.Mock()
|
||||
callbacks = {callback1, callback2}
|
||||
manager_mock().get_callbacks.return_value = callbacks
|
||||
registry.push(resource_type_, resource_, event_type_)
|
||||
registry.push(resource_type_, [resource_], event_type_)
|
||||
for callback in callbacks:
|
||||
callback.assert_called_with(resource_, event_type_)
|
||||
callback.assert_called_with([resource_], event_type_)
|
||||
|
@ -28,30 +28,45 @@ from neutron.objects import base as objects_base
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
TEST_EVENT = 'test_event'
|
||||
TEST_VERSION = '1.0'
|
||||
|
||||
|
||||
def _create_test_dict(uuid=None):
|
||||
return {'id': uuid or uuidutils.generate_uuid(),
|
||||
'field': 'foo'}
|
||||
|
||||
|
||||
def _create_test_resource(context=None):
|
||||
def _create_test_resource(context=None, resource_cls=None):
|
||||
resource_cls = resource_cls or FakeResource
|
||||
resource_dict = _create_test_dict()
|
||||
resource = FakeResource(context, **resource_dict)
|
||||
resource = resource_cls(context, **resource_dict)
|
||||
resource.obj_reset_changes()
|
||||
return resource
|
||||
|
||||
|
||||
class FakeResource(objects_base.NeutronObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
class BaseFakeResource(objects_base.NeutronObject):
|
||||
@classmethod
|
||||
def get_objects(cls, context, **kwargs):
|
||||
return list()
|
||||
|
||||
|
||||
class FakeResource(BaseFakeResource):
|
||||
VERSION = TEST_VERSION
|
||||
|
||||
fields = {
|
||||
'id': obj_fields.UUIDField(),
|
||||
'field': obj_fields.StringField()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_objects(cls, context, **kwargs):
|
||||
return list()
|
||||
|
||||
class FakeResource2(BaseFakeResource):
|
||||
VERSION = TEST_VERSION
|
||||
|
||||
fields = {
|
||||
'id': obj_fields.UUIDField(),
|
||||
'field': obj_fields.StringField()
|
||||
}
|
||||
|
||||
|
||||
class ResourcesRpcBaseTestCase(base.BaseTestCase):
|
||||
@ -63,6 +78,21 @@ class ResourcesRpcBaseTestCase(base.BaseTestCase):
|
||||
fixture.VersionedObjectRegistryFixture())
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
mock.patch.object(resources_rpc.resources,
|
||||
'is_valid_resource_type').start()
|
||||
mock.patch.object(resources_rpc.resources, 'get_resource_cls',
|
||||
side_effect=self._get_resource_cls).start()
|
||||
|
||||
self.resource_objs = [_create_test_resource(self.context)
|
||||
for _ in range(2)]
|
||||
self.resource_objs2 = [_create_test_resource(self.context,
|
||||
FakeResource2)
|
||||
for _ in range(2)]
|
||||
|
||||
@staticmethod
|
||||
def _get_resource_cls(resource_type):
|
||||
return {FakeResource.obj_name(): FakeResource,
|
||||
FakeResource2.obj_name(): FakeResource2}.get(resource_type)
|
||||
|
||||
|
||||
class _ValidateResourceTypeTestCase(base.BaseTestCase):
|
||||
@ -99,9 +129,6 @@ class ResourcesPullRpcApiTestCase(ResourcesRpcBaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ResourcesPullRpcApiTestCase, self).setUp()
|
||||
mock.patch.object(resources_rpc, '_validate_resource_type').start()
|
||||
mock.patch('neutron.api.rpc.callbacks.resources.get_resource_cls',
|
||||
return_value=FakeResource).start()
|
||||
self.rpc = resources_rpc.ResourcesPullRpcApi()
|
||||
mock.patch.object(self.rpc, 'client').start()
|
||||
self.cctxt_mock = self.rpc.client.prepare.return_value
|
||||
@ -120,7 +147,7 @@ class ResourcesPullRpcApiTestCase(ResourcesRpcBaseTestCase):
|
||||
|
||||
self.cctxt_mock.call.assert_called_once_with(
|
||||
self.context, 'pull', resource_type='FakeResource',
|
||||
version=FakeResource.VERSION, resource_id=resource_id)
|
||||
version=TEST_VERSION, resource_id=resource_id)
|
||||
self.assertEqual(expected_obj, result)
|
||||
|
||||
def test_pull_resource_not_found(self):
|
||||
@ -162,7 +189,7 @@ class ResourcesPullRpcCallbackTestCase(ResourcesRpcBaseTestCase):
|
||||
return_value=self.resource_obj) as registry_mock:
|
||||
primitive = self.callbacks.pull(
|
||||
self.context, resource_type=FakeResource.obj_name(),
|
||||
version=FakeResource.VERSION,
|
||||
version=TEST_VERSION,
|
||||
resource_id=self.resource_obj.id)
|
||||
registry_mock.assert_called_once_with(
|
||||
'FakeResource', self.resource_obj.id, context=self.context)
|
||||
@ -182,58 +209,96 @@ class ResourcesPullRpcCallbackTestCase(ResourcesRpcBaseTestCase):
|
||||
|
||||
|
||||
class ResourcesPushRpcApiTestCase(ResourcesRpcBaseTestCase):
|
||||
"""Tests the neutron server side of the RPC interface."""
|
||||
|
||||
def setUp(self):
|
||||
super(ResourcesPushRpcApiTestCase, self).setUp()
|
||||
mock.patch.object(resources_rpc.n_rpc, 'get_client').start()
|
||||
mock.patch.object(resources_rpc, '_validate_resource_type').start()
|
||||
self.rpc = resources_rpc.ResourcesPushRpcApi()
|
||||
self.cctxt_mock = self.rpc.client.prepare.return_value
|
||||
self.resource_obj = _create_test_resource(self.context)
|
||||
mock.patch.object(version_manager, 'get_resource_versions',
|
||||
return_value=set([TEST_VERSION])).start()
|
||||
|
||||
def test__prepare_object_fanout_context(self):
|
||||
expected_topic = topics.RESOURCE_TOPIC_PATTERN % {
|
||||
'resource_type': resources.get_resource_type(self.resource_obj),
|
||||
'version': self.resource_obj.VERSION}
|
||||
'resource_type': resources.get_resource_type(
|
||||
self.resource_objs[0]),
|
||||
'version': TEST_VERSION}
|
||||
|
||||
with mock.patch.object(resources_rpc.resources, 'get_resource_cls',
|
||||
return_value=FakeResource):
|
||||
observed = self.rpc._prepare_object_fanout_context(
|
||||
self.resource_obj, self.resource_obj.VERSION)
|
||||
self.resource_objs[0], self.resource_objs[0].VERSION, '1.0')
|
||||
|
||||
self.rpc.client.prepare.assert_called_once_with(
|
||||
fanout=True, topic=expected_topic)
|
||||
fanout=True, topic=expected_topic, version='1.0')
|
||||
self.assertEqual(self.cctxt_mock, observed)
|
||||
|
||||
def test_pushy(self):
|
||||
with mock.patch.object(resources_rpc.resources, 'get_resource_cls',
|
||||
return_value=FakeResource):
|
||||
with mock.patch.object(version_manager, 'get_resource_versions',
|
||||
return_value=set([FakeResource.VERSION])):
|
||||
def test_push_single_type(self):
|
||||
self.rpc.push(
|
||||
self.context, self.resource_obj, 'TYPE')
|
||||
self.context, self.resource_objs, TEST_EVENT)
|
||||
|
||||
self.cctxt_mock.cast.assert_called_once_with(
|
||||
self.context, 'push',
|
||||
resource=self.resource_obj.obj_to_primitive(),
|
||||
event_type='TYPE')
|
||||
resource_list=[resource.obj_to_primitive()
|
||||
for resource in self.resource_objs],
|
||||
event_type=TEST_EVENT)
|
||||
|
||||
def test_push_mixed(self):
|
||||
self.rpc.push(
|
||||
self.context, self.resource_objs + self.resource_objs2,
|
||||
event_type=TEST_EVENT)
|
||||
|
||||
self.cctxt_mock.cast.assert_any_call(
|
||||
self.context, 'push',
|
||||
resource_list=[resource.obj_to_primitive()
|
||||
for resource in self.resource_objs],
|
||||
event_type=TEST_EVENT)
|
||||
|
||||
self.cctxt_mock.cast.assert_any_call(
|
||||
self.context, 'push',
|
||||
resource_list=[resource.obj_to_primitive()
|
||||
for resource in self.resource_objs2],
|
||||
event_type=TEST_EVENT)
|
||||
|
||||
def test_push_mitaka_backwardscompat(self):
|
||||
#TODO(mangelajo) remove in Ocata, since the 'resource' parameter
|
||||
# is just for backwards compatibility with Mitaka
|
||||
# agents.
|
||||
self.rpc.push(
|
||||
self.context, [self.resource_objs[0]], TEST_EVENT)
|
||||
|
||||
self.cctxt_mock.cast.assert_called_once_with(
|
||||
self.context, 'push',
|
||||
resource=self.resource_objs[0].obj_to_primitive(),
|
||||
event_type=TEST_EVENT)
|
||||
|
||||
|
||||
class ResourcesPushRpcCallbackTestCase(ResourcesRpcBaseTestCase):
|
||||
"""Tests the agent-side of the RPC interface."""
|
||||
|
||||
def setUp(self):
|
||||
super(ResourcesPushRpcCallbackTestCase, self).setUp()
|
||||
mock.patch.object(resources_rpc, '_validate_resource_type').start()
|
||||
mock.patch.object(
|
||||
resources_rpc.resources,
|
||||
'get_resource_cls', return_value=FakeResource).start()
|
||||
self.resource_obj = _create_test_resource(self.context)
|
||||
self.resource_prim = self.resource_obj.obj_to_primitive()
|
||||
self.callbacks = resources_rpc.ResourcesPushRpcCallback()
|
||||
|
||||
@mock.patch.object(resources_rpc.cons_registry, 'push')
|
||||
def test_push(self, reg_push_mock):
|
||||
self.obj_registry.register(FakeResource)
|
||||
self.callbacks.push(self.context, self.resource_prim, 'TYPE')
|
||||
reg_push_mock.assert_called_once_with(self.resource_obj.obj_name(),
|
||||
self.resource_obj, 'TYPE')
|
||||
self.callbacks.push(self.context,
|
||||
resource_list=[resource.obj_to_primitive()
|
||||
for resource in self.resource_objs],
|
||||
event_type=TEST_EVENT)
|
||||
reg_push_mock.assert_called_once_with(self.resource_objs[0].obj_name(),
|
||||
self.resource_objs,
|
||||
TEST_EVENT)
|
||||
|
||||
@mock.patch.object(resources_rpc.cons_registry, 'push')
|
||||
def test_push_mitaka_backwardscompat(self, reg_push_mock):
|
||||
#TODO(mangelajo) remove in Ocata, since the 'resource' parameter
|
||||
# is just for backwards compatibility with Mitaka
|
||||
# agents.
|
||||
self.obj_registry.register(FakeResource)
|
||||
self.callbacks.push(self.context,
|
||||
resource=self.resource_objs[0].obj_to_primitive(),
|
||||
event_type=TEST_EVENT)
|
||||
reg_push_mock.assert_called_once_with(self.resource_objs[0].obj_name(),
|
||||
[self.resource_objs[0]],
|
||||
TEST_EVENT)
|
||||
|
@ -66,7 +66,7 @@ class TestQosDriversManager(TestQosDriversManagerBase):
|
||||
self.driver_manager = driver_mgr.QosServiceNotificationDriverManager()
|
||||
|
||||
def _validate_registry_params(self, event_type, policy):
|
||||
self.rpc_api.push.assert_called_with(self.context, policy,
|
||||
self.rpc_api.push.assert_called_with(self.context, [policy],
|
||||
event_type)
|
||||
|
||||
def test_create_policy_default_configuration(self):
|
||||
|
@ -53,7 +53,7 @@ class TestQosRpcNotificationDriver(base.BaseQosTestCase):
|
||||
**self.rule_data['bandwidth_limit_rule'])
|
||||
|
||||
def _validate_push_params(self, event_type, policy):
|
||||
self.rpc_api.push.assert_called_once_with(self.context, policy,
|
||||
self.rpc_api.push.assert_called_once_with(self.context, [policy],
|
||||
event_type)
|
||||
|
||||
def test_create_policy(self):
|
||||
|
Loading…
Reference in New Issue
Block a user