[ovn]Refusing to bind port to dead agent

Closes-bug: #1958501

Conflicts:
    neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py
    neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py
    neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
    neutron/tests/unit/fake_resources.py
    neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Change-Id: Ia84410675d28002afc74368349c9b54f048f4f4d
(cherry picked from commit 8a55f09192)
(cherry picked from commit 267631e8fb)
This commit is contained in:
zhouhenglc
2022-01-20 15:09:32 +08:00
committed by Slawek Kaplonski
parent 86aa8a3a10
commit 461d30debc
8 changed files with 97 additions and 91 deletions

View File

@@ -262,3 +262,12 @@ class AgentCache:
for cls in NeutronAgent.types.values()}
# Return the cached agents of agent_ids whose keys are in the cache
return (agent for agent in self if agent.agent_id in agent_ids)
def get_agents(self, filters=None):
filters = filters or {}
agent_list = []
for agent in self:
agent_dict = agent.as_dict()
if all(agent_dict[k] in v for k, v in filters.items()):
agent_list.append(agent)
return agent_list

View File

@@ -950,18 +950,26 @@ class OVNMechanismDriver(api.MechanismDriver):
# OVN chassis information is needed to ensure a valid port bind.
# Collect port binding data and refuse binding if the OVN chassis
# cannot be found.
chassis_physnets = []
try:
datapath_type, iface_types, chassis_physnets = (
self.sb_ovn.get_chassis_data_for_ml2_bind_port(context.host))
iface_types = iface_types.split(',') if iface_types else []
except RuntimeError:
LOG.debug('Refusing to bind port %(port_id)s due to '
'no OVN chassis for host: %(host)s',
# cannot be found or is dead.
agents = n_agent.AgentCache().get_agents({'host': context.host})
if not agents:
LOG.warning('Refusing to bind port %(port_id)s due to '
'no OVN chassis for host: %(host)s',
{'port_id': port['id'], 'host': context.host})
return
agent = agents[0]
if not agent.alive:
LOG.warning("Refusing to bind port %(pid)s to dead agent: "
"%(agent)s", {'pid': context.current['id'],
'agent': agent})
return
chassis = agent.chassis
datapath_type = ovn_utils.get_ovn_chassis_other_config(
chassis).get('datapath-type', '')
iface_types = ovn_utils.get_ovn_chassis_other_config(
chassis).get('iface-types', '')
iface_types = iface_types.split(',') if iface_types else []
chassis_physnets = self.sb_ovn._get_chassis_physnets(chassis)
for segment_to_bind in context.segments_to_bind:
network_type = segment_to_bind['network_type']
segmentation_id = segment_to_bind['segmentation_id']
@@ -1276,12 +1284,8 @@ class OVNMechanismDriver(api.MechanismDriver):
def get_agents(self, context, filters=None, fields=None, _driver=None):
_driver.ping_all_chassis()
filters = filters or {}
agent_list = []
for agent in n_agent.AgentCache():
agent_dict = agent.as_dict()
if all(agent_dict[k] in v for k, v in filters.items()):
agent_list.append(agent_dict)
return agent_list
agent_list = n_agent.AgentCache().get_agents(filters)
return [agent.as_dict() for agent in agent_list]
def get_agent(self, context, id, fields=None, _driver=None):

View File

@@ -664,16 +664,3 @@ class SbAPI(api.API, metaclass=abc.ABCMeta):
:param chassis_type: The type of chassis
:type chassis_type: string
"""
@abc.abstractmethod
def get_chassis_data_for_ml2_bind_port(self, hostname):
"""Return chassis data for ML2 port binding.
@param hostname: The hostname of the chassis
@type hostname: string
:returns: Tuple containing the chassis datapath type,
iface types and physical networks for the
OVN bridge mappings.
:raises: RuntimeError exception if an OVN chassis
does not exist.
"""

View File

@@ -878,18 +878,6 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
# preference patch (as part of external ids) merges.
return [c.name for c in self.chassis_list().execute(check_error=True)]
def get_chassis_data_for_ml2_bind_port(self, hostname):
try:
cmd = self.db_find_rows('Chassis', ('hostname', '=', hostname))
chassis = next(c for c in cmd.execute(check_error=True))
except StopIteration:
msg = _('Chassis with hostname %s does not exist') % hostname
raise RuntimeError(msg)
other_config = utils.get_ovn_chassis_other_config(chassis)
return (other_config.get('datapath-type', ''),
other_config.get('iface-types', ''),
self._get_chassis_physnets(chassis))
def get_metadata_port_network(self, network):
# TODO(twilson) This function should really just take a Row/RowView
try:

View File

@@ -83,13 +83,6 @@ class TestSbApi(BaseOvnIdlTest):
our_chassis = {c['name'] for c in self.data['chassis']}
self.assertLessEqual(our_chassis, chassis_list)
def test_get_chassis_data_for_ml2_bind_port(self):
host = self.data['chassis'][0]['hostname']
dp, iface, phys = self.api.get_chassis_data_for_ml2_bind_port(host)
self.assertEqual('', dp)
self.assertEqual('', iface)
self.assertCountEqual(phys, ['private', 'public'])
def test_chassis_exists(self):
self.assertTrue(self.api.chassis_exists(
self.data['chassis'][0]['hostname']))

View File

@@ -170,9 +170,8 @@ class FakeOvsdbSbOvnIdl(object):
self.get_chassis_hostname_and_physnets = mock.Mock()
self.get_chassis_hostname_and_physnets.return_value = {}
self.get_all_chassis = mock.Mock()
self.get_chassis_data_for_ml2_bind_port = mock.Mock()
self.get_chassis_data_for_ml2_bind_port.return_value = \
('fake', '', ['fake-physnet'])
self._get_chassis_physnets = mock.Mock()
self._get_chassis_physnets.return_value = ['fake-physnet']
self.get_logical_port_chassis_and_datapath = mock.Mock()
self.get_logical_port_chassis_and_datapath.return_value = \
('fake', 'fake-dp')

View File

@@ -86,6 +86,53 @@ class MechDriverSetupBase:
self._virtual_port_parents = mock.patch.object(
ovn_utils, 'get_virtual_port_parents', return_value=[])
self.virtual_port_parents = self._virtual_port_parents.start()
neutron_agent.AgentCache(self.mech_driver)
# Because AgentCache is a singleton and we get a new mech_driver each
# setUp(), override the AgentCache driver.
neutron_agent.AgentCache().driver = self.mech_driver
agent1 = self._add_agent('agent1')
neutron_agent.AgentCache().get_agents = mock.Mock()
neutron_agent.AgentCache().get_agents.return_value = [agent1]
def _add_chassis(self, nb_cfg, name=None):
chassis_private = mock.Mock()
chassis_private.nb_cfg = nb_cfg
chassis_private.uuid = uuid.uuid4()
chassis_private.name = name if name else str(uuid.uuid4())
return chassis_private
def _add_chassis_agent(self, nb_cfg, agent_type, chassis_private=None,
updated_at=None):
chassis_private = chassis_private or self._add_chassis(nb_cfg)
if hasattr(chassis_private, 'nb_cfg_timestamp') and isinstance(
chassis_private.nb_cfg_timestamp, mock.Mock):
del chassis_private.nb_cfg_timestamp
chassis_private.other_config = {}
chassis_private.external_ids = {}
if updated_at:
chassis_private.other_config = {
ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)}
if agent_type == ovn_const.OVN_METADATA_AGENT:
chassis_private.external_ids.update({
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg,
ovn_const.OVN_AGENT_METADATA_ID_KEY: str(uuid.uuid4())})
chassis_private.chassis = [chassis_private]
return neutron_agent.AgentCache().update(agent_type, chassis_private,
updated_at)
def _add_agent(self, name, alive=True):
nb_cfg = 5
now = timeutils.utcnow(with_timezone=True)
if not alive:
updated_at = now - datetime.timedelta(cfg.CONF.agent_down_time + 1)
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg
else:
updated_at = now
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2
chassis = self._add_chassis(nb_cfg, name=name)
return self._add_chassis_agent(
nb_cfg, ovn_const.OVN_CONTROLLER_AGENT, chassis, updated_at)
class TestOVNMechanismDriverBase(MechDriverSetupBase,
@@ -116,10 +163,6 @@ class TestOVNMechanismDriverBase(MechDriverSetupBase,
cfg.CONF.set_override('ovsdb_connection_timeout', 30, group='ovn')
mock.patch.object(impl_idl_ovn.Backend, 'schema_helper').start()
super().setUp()
neutron_agent.AgentCache(self.mech_driver)
# Because AgentCache is a singleton and we get a new mech_driver each
# setUp(), override the AgentCache driver.
neutron_agent.AgentCache().driver = self.mech_driver
self.nb_ovn = self.mech_driver.nb_ovn
self.sb_ovn = self.mech_driver.sb_ovn
@@ -1141,7 +1184,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
attrs={'binding:vnic_type': 'unknown'}).info()
fake_port_context = fakes.FakePortContext(fake_port, 'host', [])
self.mech_driver.bind_port(fake_port_context)
self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_not_called()
neutron_agent.AgentCache().get_agents.assert_not_called()
fake_port_context.set_binding.assert_not_called()
def _test_bind_port_failed(self, fake_segments):
@@ -1150,13 +1193,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
fake_port_context = fakes.FakePortContext(
fake_port, fake_host, fake_segments)
self.mech_driver.bind_port(fake_port_context)
self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
fake_host)
neutron_agent.AgentCache().get_agents.assert_called_once_with(
{'host': fake_host})
fake_port_context.set_binding.assert_not_called()
def test_bind_port_host_not_found(self):
self.sb_ovn.get_chassis_data_for_ml2_bind_port.side_effect = \
RuntimeError
neutron_agent.AgentCache().get_agents.return_value = []
self._test_bind_port_failed([])
def test_bind_port_no_segments_to_bind(self):
@@ -1170,14 +1212,19 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
[fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
self._test_bind_port_failed(fake_segments)
def test_bind_port_host_not_alive(self):
agent = self._add_agent('agent_no_alive', False)
neutron_agent.AgentCache().get_agents.return_value = [agent]
self._test_bind_port_failed([])
def _test_bind_port(self, fake_segments):
fake_port = fakes.FakePort.create_one_port().info()
fake_host = 'host'
fake_port_context = fakes.FakePortContext(
fake_port, fake_host, fake_segments)
self.mech_driver.bind_port(fake_port_context)
self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
fake_host)
neutron_agent.AgentCache().get_agents.assert_called_once_with(
{'host': fake_host})
fake_port_context.set_binding.assert_called_once_with(
fake_segments[0]['id'],
portbindings.VIF_TYPE_OVS,
@@ -1191,8 +1238,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
fake_port_context = fakes.FakePortContext(
fake_port, fake_host, fake_segments)
self.mech_driver.bind_port(fake_port_context)
self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
fake_host)
neutron_agent.AgentCache().get_agents.assert_called_once_with(
{'host': fake_host})
fake_port_context.set_binding.assert_called_once_with(
fake_segments[0]['id'],
portbindings.VIF_TYPE_OVS,
@@ -1212,8 +1259,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
fake_port_context = fakes.FakePortContext(
fake_port, fake_host, fake_segments)
self.mech_driver.bind_port(fake_port_context)
self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
fake_host)
neutron_agent.AgentCache().get_agents.assert_called_once_with(
{'host': fake_host})
fake_port_context.set_binding.assert_called_once_with(
fake_segments[0]['id'],
portbindings.VIF_TYPE_OVS,
@@ -2133,31 +2180,6 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.assertEqual(1, mock_update_port.call_count)
mock_notify_dhcp.assert_called_with(fake_port['id'])
def _add_chassis(self, nb_cfg):
chassis_private = mock.Mock()
chassis_private.nb_cfg = nb_cfg
chassis_private.uuid = uuid.uuid4()
chassis_private.name = str(uuid.uuid4())
return chassis_private
def _add_chassis_agent(self, nb_cfg, agent_type, chassis_private=None,
updated_at=None):
updated_at = updated_at or timeutils.utcnow(with_timezone=True)
chassis_private = chassis_private or self._add_chassis(nb_cfg)
if hasattr(chassis_private, 'nb_cfg_timestamp') and isinstance(
chassis_private.nb_cfg_timestamp, mock.Mock):
del chassis_private.nb_cfg_timestamp
chassis_private.external_ids = {}
chassis_private.other_config = {}
if agent_type == ovn_const.OVN_METADATA_AGENT:
chassis_private.external_ids.update({
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg,
ovn_const.OVN_AGENT_METADATA_ID_KEY: str(uuid.uuid4())})
chassis_private.chassis = [chassis_private]
return neutron_agent.AgentCache().update(agent_type, chassis_private,
updated_at)
def test_agent_alive_true(self):
chassis_private = self._add_chassis(5)
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,

View File

@@ -0,0 +1,4 @@
---
features:
- |
OVN mechanism driver refuses to bind a port to a dead agent.