Merge "DHCP notification optimization" into stable/train

This commit is contained in:
Zuul 2021-04-26 12:01:39 +00:00 committed by Gerrit Code Review
commit 0fb62faaea
4 changed files with 41 additions and 19 deletions

View File

@ -143,11 +143,11 @@ class DhcpAgentNotifyAPI(object):
network['id']) network['id'])
return new_agents + existing_agents return new_agents + existing_agents
def _get_enabled_agents(self, context, network, agents, method, payload): def _get_enabled_agents(
self, context, network_id, network, agents, method, payload):
"""Get the list of agents who can provide services.""" """Get the list of agents who can provide services."""
if not agents: if not agents:
return [] return []
network_id = network['id']
enabled_agents = agents enabled_agents = agents
if not cfg.CONF.enable_services_on_agents_with_admin_state_down: if not cfg.CONF.enable_services_on_agents_with_admin_state_down:
enabled_agents = [x for x in agents if x.admin_state_up] enabled_agents = [x for x in agents if x.admin_state_up]
@ -165,6 +165,10 @@ class DhcpAgentNotifyAPI(object):
if not enabled_agents: if not enabled_agents:
num_ports = self.plugin.get_ports_count( num_ports = self.plugin.get_ports_count(
context, {'network_id': [network_id]}) context, {'network_id': [network_id]})
if not network:
admin_ctx = (context if context.is_admin else
context.elevated())
network = self.plugin.get_network(admin_ctx, network_id)
notification_required = ( notification_required = (
num_ports > 0 and len(network['subnets']) >= 1) num_ports > 0 and len(network['subnets']) >= 1)
if notification_required: if notification_required:
@ -179,7 +183,8 @@ class DhcpAgentNotifyAPI(object):
def _is_reserved_dhcp_port(self, port): def _is_reserved_dhcp_port(self, port):
return port.get('device_id') == constants.DEVICE_ID_RESERVED_DHCP_PORT return port.get('device_id') == constants.DEVICE_ID_RESERVED_DHCP_PORT
def _notify_agents(self, context, method, payload, network_id): def _notify_agents(
self, context, method, payload, network_id, network=None):
"""Notify all the agents that are hosting the network.""" """Notify all the agents that are hosting the network."""
payload['priority'] = METHOD_PRIORITY_MAP.get(method) payload['priority'] = METHOD_PRIORITY_MAP.get(method)
# fanout is required as we do not know who is "listening" # fanout is required as we do not know who is "listening"
@ -194,31 +199,35 @@ class DhcpAgentNotifyAPI(object):
if fanout_required: if fanout_required:
self._fanout_message(context, method, payload) self._fanout_message(context, method, payload)
elif cast_required: elif cast_required:
admin_ctx = (context if context.is_admin else context.elevated()) candidate_hosts = None
network = self.plugin.get_network(admin_ctx, network_id)
if 'subnet' in payload and payload['subnet'].get('segment_id'): if 'subnet' in payload and payload['subnet'].get('segment_id'):
# if segment_id exists then the segment service plugin # if segment_id exists then the segment service plugin
# must be loaded # must be loaded
segment_plugin = directory.get_plugin('segments') segment_plugin = directory.get_plugin('segments')
segment = segment_plugin.get_segment( segment = segment_plugin.get_segment(
context, payload['subnet']['segment_id']) context, payload['subnet']['segment_id'])
network['candidate_hosts'] = segment['hosts'] candidate_hosts = segment['hosts']
agents = self.plugin.get_dhcp_agents_hosting_networks( agents = self.plugin.get_dhcp_agents_hosting_networks(
context, [network_id], hosts=network.get('candidate_hosts')) context, [network_id], hosts=candidate_hosts)
# schedule the network first, if needed # schedule the network first, if needed
schedule_required = ( schedule_required = (
method == 'subnet_create_end' or method == 'subnet_create_end' or
method == 'port_create_end' and method == 'port_create_end' and
not self._is_reserved_dhcp_port(payload['port'])) not self._is_reserved_dhcp_port(payload['port']))
if schedule_required: if schedule_required:
admin_ctx = context if context.is_admin else context.elevated()
network = network or self.plugin.get_network(
admin_ctx, network_id)
if candidate_hosts:
network['candidate_hosts'] = candidate_hosts
agents = self._schedule_network(admin_ctx, network, agents) agents = self._schedule_network(admin_ctx, network, agents)
if not agents: if not agents:
LOG.debug("Network %s is not hosted by any dhcp agent", LOG.debug("Network %s is not hosted by any dhcp agent",
network_id) network_id)
return return
enabled_agents = self._get_enabled_agents( enabled_agents = self._get_enabled_agents(
context, network, agents, method, payload) context, network_id, network, agents, method, payload)
if method == 'port_create_end': if method == 'port_create_end':
high_agent = enabled_agents.pop( high_agent = enabled_agents.pop(
@ -346,6 +355,8 @@ class DhcpAgentNotifyAPI(object):
payload['network_id'] = network_id payload['network_id'] = network_id
if obj_type == 'port': if obj_type == 'port':
payload['fixed_ips'] = obj_value['fixed_ips'] payload['fixed_ips'] = obj_value['fixed_ips']
self._notify_agents(context, method_name, payload, network_id) self._notify_agents(context, method_name, payload, network_id,
obj_value.get('network'))
else: else:
self._notify_agents(context, method_name, data, network_id) self._notify_agents(context, method_name, data, network_id,
obj_value.get('network'))

View File

@ -1267,6 +1267,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# db base plugin post commit ops # db base plugin post commit ops
self._create_subnet_postcommit(context, result) self._create_subnet_postcommit(context, result)
# add network to subnet dict to save a DB call on dhcp notification
result['network'] = mech_context.network.current
kwargs = {'context': context, 'subnet': result} kwargs = {'context': context, 'subnet': result}
registry.notify(resources.SUBNET, events.AFTER_CREATE, self, **kwargs) registry.notify(resources.SUBNET, events.AFTER_CREATE, self, **kwargs)
try: try:
@ -1427,6 +1429,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return self._after_create_port(context, result, mech_context) return self._after_create_port(context, result, mech_context)
def _after_create_port(self, context, result, mech_context): def _after_create_port(self, context, result, mech_context):
# add network to port dict to save a DB call on dhcp notification
result['network'] = mech_context.network.current
# notify any plugin that is interested in port create events # notify any plugin that is interested in port create events
kwargs = {'context': context, 'port': result} kwargs = {'context': context, 'port': result}
registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs) registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs)

View File

@ -85,12 +85,12 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
new_agents=None, existing_agents=[], new_agents=None, existing_agents=[],
expected_casts=0, expected_warnings=1) expected_casts=0, expected_warnings=1)
def _test__get_enabled_agents(self, network, def _test__get_enabled_agents(self, network_id,
agents=None, port_count=0, agents=None, port_count=0,
expected_warnings=0, expected_errors=0): expected_warnings=0, expected_errors=0):
self.notifier.plugin.get_ports_count.return_value = port_count self.notifier.plugin.get_ports_count.return_value = port_count
enabled_agents = self.notifier._get_enabled_agents( enabled_agents = self.notifier._get_enabled_agents(
mock.ANY, network, agents, mock.ANY, mock.ANY) mock.Mock(), network_id, None, agents, mock.ANY, mock.ANY)
if not cfg.CONF.enable_services_on_agents_with_admin_state_down: if not cfg.CONF.enable_services_on_agents_with_admin_state_down:
agents = [x for x in agents if x.admin_state_up] agents = [x for x in agents if x.admin_state_up]
self.assertEqual(agents, enabled_agents) self.assertEqual(agents, enabled_agents)
@ -104,8 +104,8 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid())
agent2.admin_state_up = False agent2.admin_state_up = False
agent2.heartbeat_timestamp = timeutils.utcnow() agent2.heartbeat_timestamp = timeutils.utcnow()
network = {'id': 'foo_network_id'} self._test__get_enabled_agents(network_id='foo_network_id',
self._test__get_enabled_agents(network, agents=[agent1]) agents=[agent1])
def test__get_enabled_agents_with_inactive_ones(self): def test__get_enabled_agents_with_inactive_ones(self):
agent1 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent1 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid())
@ -115,17 +115,18 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
agent2.admin_state_up = True agent2.admin_state_up = True
# This is effectively an inactive agent # This is effectively an inactive agent
agent2.heartbeat_timestamp = datetime.datetime(2000, 1, 1, 0, 0) agent2.heartbeat_timestamp = datetime.datetime(2000, 1, 1, 0, 0)
network = {'id': 'foo_network_id'} self._test__get_enabled_agents(network_id='foo_network_id',
self._test__get_enabled_agents(network,
agents=[agent1, agent2], agents=[agent1, agent2],
expected_warnings=1, expected_errors=0) expected_warnings=1, expected_errors=0)
def test__get_enabled_agents_with_notification_required(self): def test__get_enabled_agents_with_notification_required(self):
network = {'id': 'foo_network_id', 'subnets': ['foo_subnet_id']} network = {'id': 'foo_network_id', 'subnets': ['foo_subnet_id']}
self.notifier.plugin.get_network.return_value = network
agent = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid())
agent.admin_state_up = False agent.admin_state_up = False
agent.heartbeat_timestamp = timeutils.utcnow() agent.heartbeat_timestamp = timeutils.utcnow()
self._test__get_enabled_agents(network, [agent], port_count=20, self._test__get_enabled_agents('foo_network_id',
[agent], port_count=20,
expected_warnings=0, expected_errors=1) expected_warnings=0, expected_errors=1)
def test__get_enabled_agents_with_admin_state_down(self): def test__get_enabled_agents_with_admin_state_down(self):
@ -137,8 +138,8 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid()) agent2 = agent_obj.Agent(mock.ANY, id=uuidutils.generate_uuid())
agent2.admin_state_up = False agent2.admin_state_up = False
agent2.heartbeat_timestamp = timeutils.utcnow() agent2.heartbeat_timestamp = timeutils.utcnow()
network = {'id': 'foo_network_id'} self._test__get_enabled_agents(network_id='foo_network_id',
self._test__get_enabled_agents(network, agents=[agent1, agent2]) agents=[agent1, agent2])
def test__notify_agents_allocate_priority(self): def test__notify_agents_allocate_priority(self):
mock_context = mock.MagicMock() mock_context = mock.MagicMock()

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import datetime import datetime
import mock import mock
@ -1494,6 +1495,11 @@ class OvsDhcpAgentNotifierTestCase(test_agent.AgentDBTestMixIn,
return net, sub, port return net, sub, port
def _notification_mocks(self, hosts, net, subnet, port, port_priority): def _notification_mocks(self, hosts, net, subnet, port, port_priority):
subnet['subnet']['network'] = copy.deepcopy(net['network'])
# 'availability_zones' is empty at the time subnet_create_end
# notification is sent
subnet['subnet']['network']['availability_zones'] = []
port['port']['network'] = net['network']
host_calls = {} host_calls = {}
for host in hosts: for host in hosts:
expected_calls = [ expected_calls = [