[OVN] Use the Chassis_Private table for agents healthcheck

The core OVN team has introduced a new table called Chassis_Private to
avoid nb_cfg flooding when checking for the Chassis' status. The OVN
driver does rely on that mechanism for the agent liveness mechanism.

This patch makes use of this new table but it's also backward
compatible.

For more information, check the core OVN changes at:
https://patchwork.ozlabs.org/patch/1254394.

Closes-Bug: #1892477
Change-Id: Iea4263b852d1e3f81eb2557918ea3cbb7adb8016
Signed-off-by: Lucas Alvares Gomes <lucasagomes@gmail.com>
(cherry picked from commit 1dddbbfc92)
This commit is contained in:
Lucas Alvares Gomes 2020-02-12 14:36:56 +00:00
parent 4a2cc2beec
commit c13d6f527c
8 changed files with 89 additions and 47 deletions

View File

@ -111,7 +111,7 @@ class PortBindingChassisDeletedEvent(PortBindingChassisEvent):
return False
class ChassisCreateEvent(row_event.RowEvent):
class ChassisCreateEventBase(row_event.RowEvent):
"""Row create event - Chassis name == our_chassis.
On connection, we get a dump of all chassis so if we catch a creation
@ -119,14 +119,14 @@ class ChassisCreateEvent(row_event.RowEvent):
to do a full sync to make sure that we capture all changes while the
connection to OVSDB was down.
"""
table = None
def __init__(self, metadata_agent):
self.agent = metadata_agent
self.first_time = True
table = 'Chassis'
events = (self.ROW_CREATE,)
super(ChassisCreateEvent, self).__init__(
events, table, (('name', '=', self.agent.chassis),))
super(ChassisCreateEventBase, self).__init__(
events, self.table, (('name', '=', self.agent.chassis),))
self.event_name = self.__class__.__name__
def run(self, event, row, old):
@ -141,6 +141,14 @@ class ChassisCreateEvent(row_event.RowEvent):
self.agent.sync()
class ChassisCreateEvent(ChassisCreateEventBase):
table = 'Chassis'
class ChassisPrivateCreateEvent(ChassisCreateEventBase):
table = 'Chassis_Private'
class SbGlobalUpdateEvent(row_event.RowEvent):
"""Row update event on SB_Global table."""
@ -152,8 +160,12 @@ class SbGlobalUpdateEvent(row_event.RowEvent):
self.event_name = self.__class__.__name__
def run(self, event, row, old):
self.agent.sb_idl.update_metadata_health_status(
self.agent.chassis, row.nb_cfg).execute()
table = ('Chassis_Private' if self.agent.has_chassis_private
else 'Chassis')
self.agent.sb_idl.db_set(
table, self.agent.chassis, ('external_ids', {
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY:
str(row.nb_cfg)})).execute()
class MetadataAgent(object):
@ -189,13 +201,26 @@ class MetadataAgent(object):
proxy = metadata_server.UnixDomainMetadataProxy(self.conf)
proxy.run()
tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global',
'Chassis')
events = (PortBindingChassisCreatedEvent(self),
PortBindingChassisDeletedEvent(self),
SbGlobalUpdateEvent(self))
# TODO(lucasagomes): Remove this in the future. Try to register
# the Chassis_Private table, if not present, fallback to the normal
# Chassis table.
# Open the connection to OVN SB database.
self.sb_idl = ovsdb.MetadataAgentOvnSbIdl(
chassis=self.chassis,
events=[PortBindingChassisCreatedEvent(self),
PortBindingChassisDeletedEvent(self),
ChassisCreateEvent(self),
SbGlobalUpdateEvent(self)]).start()
self.has_chassis_private = False
try:
self.sb_idl = ovsdb.MetadataAgentOvnSbIdl(
chassis=self.chassis, tables=tables + ('Chassis_Private', ),
events=events + (ChassisPrivateCreateEvent(self), )).start()
self.has_chassis_private = True
except AssertionError:
self.sb_idl = ovsdb.MetadataAgentOvnSbIdl(
chassis=self.chassis, tables=tables,
events=events + (ChassisCreateEvent(self), )).start()
# Do the initial sync.
self.sync()
@ -208,9 +233,10 @@ class MetadataAgent(object):
def register_metadata_agent(self):
# NOTE(lucasagomes): db_add() will not overwrite the UUID if
# it's already set.
table = ('Chassis_Private' if self.has_chassis_private else 'Chassis')
ext_ids = {
ovn_const.OVN_AGENT_METADATA_ID_KEY: uuidutils.generate_uuid()}
self.sb_idl.db_add('Chassis', self.chassis, 'external_ids',
self.sb_idl.db_add(table, self.chassis, 'external_ids',
ext_ids).execute(check_error=True)
def _get_own_chassis_name(self):

View File

@ -38,8 +38,10 @@ class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl):
helper.register_table(table)
super(MetadataAgentOvnSbIdl, self).__init__(
None, connection_string, helper)
if chassis and 'Chassis' in tables:
self.tables['Chassis'].condition = [['name', '==', chassis]]
if chassis:
table = ('Chassis_Private' if 'Chassis_Private' in tables
else 'Chassis')
self.tables[table].condition = [['name', '==', chassis]]
if events:
self.notify_handler.watch_events(events)

View File

@ -28,8 +28,13 @@ class NeutronAgent(abc.ABC):
for _type in cls.types:
NeutronAgent.types[_type] = cls
def __init__(self, chassis):
self.chassis = chassis
def __init__(self, chassis_private):
self.chassis_private = chassis_private
try:
self.chassis = self.chassis_private.chassis[0]
except (AttributeError, IndexError):
# No Chassis_Private support, just use Chassis
self.chassis = self.chassis_private
@property
def updated_at(self):
@ -58,8 +63,8 @@ class NeutronAgent(abc.ABC):
'admin_state_up': True}
@classmethod
def from_type(cls, _type, chassis):
return cls.types[_type](chassis)
def from_type(cls, _type, chassis_private):
return cls.types[_type](chassis_private)
@staticmethod
def agent_types():
@ -85,15 +90,15 @@ class ControllerAgent(NeutronAgent):
@property
def nb_cfg(self):
return self.chassis.nb_cfg
return self.chassis_private.nb_cfg
@property
def agent_id(self):
return self.chassis.name
return self.chassis_private.name
@property
def description(self):
return self.chassis.external_ids.get(
return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_DESC_KEY, '')
@ -105,15 +110,15 @@ class MetadataAgent(NeutronAgent):
@property
def nb_cfg(self):
return int(self.chassis.external_ids.get(
return int(self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0))
@property
def agent_id(self):
return self.chassis.external_ids.get(
return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_ID_KEY)
@property
def description(self):
return self.chassis.external_ids.get(
return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_DESC_KEY, '')

View File

@ -119,6 +119,9 @@ class OVNMechanismDriver(api.MechanismDriver):
self.subscribe()
self.qos_driver = qos_driver.OVNQosDriver.create(self)
self.trunk_driver = trunk_driver.OVNTrunkDriver.create(self)
# The agent_chassis_table will be changed to Chassis_Private if it
# exists, we need to have a connection in order to check that.
self.agent_chassis_table = 'Chassis'
@property
def _plugin(self):
@ -233,6 +236,9 @@ class OVNMechanismDriver(api.MechanismDriver):
self._nb_ovn, self._sb_ovn = impl_idl_ovn.get_ovn_idls(
self, trigger, binding_events=not is_maintenance)
if self._sb_ovn.is_table_present('Chassis_Private'):
self.agent_chassis_table = 'Chassis_Private'
# AGENTS must be populated after fork so if ovn-controller is stopped
# before a worker handles a get_agents request, we still show agents
populate_agents(self)
@ -1059,14 +1065,15 @@ class OVNMechanismDriver(api.MechanismDriver):
def mark_agent_alive(self, agent):
# Update the time of our successful check
value = timeutils.utcnow(with_timezone=True).isoformat()
self._sb_ovn.db_set('Chassis', agent.chassis.uuid,
('external_ids', {agent.key: value})).execute(
check_error=True)
self._sb_ovn.db_set(
self.agent_chassis_table, agent.chassis_private.uuid,
('external_ids', {agent.key: value})).execute(check_error=True)
def agents_from_chassis(self, chassis, update_db=True):
def agents_from_chassis(self, chassis_private, update_db=True):
agent_dict = {}
# Iterate over each unique Agent subclass
for agent in [a(chassis) for a in n_agent.NeutronAgent.agent_types()]:
for agent in [a(chassis_private)
for a in n_agent.NeutronAgent.agent_types()]:
if not agent.agent_id:
continue
alive = self.agent_alive(agent, update_db)
@ -1140,7 +1147,7 @@ class OVNMechanismDriver(api.MechanismDriver):
def populate_agents(driver):
for ch in driver._sb_ovn.tables['Chassis'].rows.values():
for ch in driver._sb_ovn.tables[driver.agent_chassis_table].rows.values():
# update the cache, rows are hashed on uuid but it is the name that
# stays consistent across ovn-controller restarts
AGENTS.update({ch.name: ch})
@ -1161,11 +1168,12 @@ def get_agents(self, context, filters=None, fields=None, _driver=None):
def get_agent(self, context, id, fields=None, _driver=None):
chassis = None
try:
# look up Chassis by *name*, which the id attribte is
chassis = _driver._sb_ovn.lookup('Chassis', id)
# look up Chassis by *name*, which the id attribute is
chassis = _driver._sb_ovn.lookup(_driver.agent_chassis_table, id)
except idlutils.RowNotFound:
# If the UUID is not found, check for the metadata agent ID
for ch in _driver._sb_ovn.tables['Chassis'].rows.values():
for ch in _driver._sb_ovn.tables[
_driver.agent_chassis_table].rows.values():
metadata_agent_id = ch.external_ids.get(
ovn_const.OVN_AGENT_METADATA_ID_KEY)
if id == metadata_agent_id:

View File

@ -813,12 +813,6 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
if (r.mac and str(r.datapath.uuid) == network) and
ip_address in r.mac[0].split(' ')]
def update_metadata_health_status(self, chassis, nb_cfg):
return cmd.UpdateChassisExtIdsCommand(
self, chassis,
{ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: str(nb_cfg)},
if_exists=True)
def set_port_cidrs(self, name, cidrs):
# TODO(twilson) add if_exists to db commands
return self.db_set('Port_Binding', name, 'external_ids',

View File

@ -516,6 +516,8 @@ class OvnSbIdl(OvnIdlDistributedLock):
def from_server(cls, connection_string, schema_name, driver):
_check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, schema_name)
if 'Chassis_Private' in helper.schema_json['tables']:
helper.register_table('Chassis_Private')
helper.register_table('Chassis')
helper.register_table('Encap')
helper.register_table('Port_Binding')

View File

@ -163,6 +163,8 @@ class FakeOvsdbSbOvnIdl(object):
self.db_set = mock.Mock()
self.lookup = mock.MagicMock()
self.chassis_list = mock.MagicMock()
self.is_table_present = mock.Mock()
self.is_table_present.return_value = False
class FakeOvsdbTransaction(object):

View File

@ -1558,18 +1558,21 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
def _add_chassis_agent(self, nb_cfg, agent_type, updated_at=None):
updated_at = updated_at or datetime.datetime.utcnow()
chassis = mock.Mock()
chassis.nb_cfg = nb_cfg
chassis.uuid = uuid.uuid4()
chassis.external_ids = {ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)}
chassis_private = mock.Mock()
chassis_private.nb_cfg = nb_cfg
chassis_private.uuid = uuid.uuid4()
chassis_private.external_ids = {
ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)}
if agent_type == ovn_const.OVN_METADATA_AGENT:
chassis.external_ids.update({
chassis_private.external_ids.update({
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg,
ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)})
chassis_private.chassis = [chassis_private]
return neutron_agent.NeutronAgent.from_type(agent_type, chassis)
return neutron_agent.NeutronAgent.from_type(
agent_type, chassis_private)
def test_agent_alive_true(self):
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,