[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>
This commit is contained in:
Lucas Alvares Gomes 2020-02-12 14:36:56 +00:00
parent b4dad5bb3d
commit 1dddbbfc92
8 changed files with 89 additions and 47 deletions

View File

@ -129,7 +129,7 @@ class PortBindingChassisDeletedEvent(PortBindingChassisEvent):
return False return False
class ChassisCreateEvent(row_event.RowEvent): class ChassisCreateEventBase(row_event.RowEvent):
"""Row create event - Chassis name == our_chassis. """Row create event - Chassis name == our_chassis.
On connection, we get a dump of all chassis so if we catch a creation On connection, we get a dump of all chassis so if we catch a creation
@ -137,14 +137,14 @@ class ChassisCreateEvent(row_event.RowEvent):
to do a full sync to make sure that we capture all changes while the to do a full sync to make sure that we capture all changes while the
connection to OVSDB was down. connection to OVSDB was down.
""" """
table = None
def __init__(self, metadata_agent): def __init__(self, metadata_agent):
self.agent = metadata_agent self.agent = metadata_agent
self.first_time = True self.first_time = True
table = 'Chassis'
events = (self.ROW_CREATE,) events = (self.ROW_CREATE,)
super(ChassisCreateEvent, self).__init__( super(ChassisCreateEventBase, self).__init__(
events, table, (('name', '=', self.agent.chassis),)) events, self.table, (('name', '=', self.agent.chassis),))
self.event_name = self.__class__.__name__ self.event_name = self.__class__.__name__
def run(self, event, row, old): def run(self, event, row, old):
@ -159,6 +159,14 @@ class ChassisCreateEvent(row_event.RowEvent):
self.agent.sync() self.agent.sync()
class ChassisCreateEvent(ChassisCreateEventBase):
table = 'Chassis'
class ChassisPrivateCreateEvent(ChassisCreateEventBase):
table = 'Chassis_Private'
class SbGlobalUpdateEvent(row_event.RowEvent): class SbGlobalUpdateEvent(row_event.RowEvent):
"""Row update event on SB_Global table.""" """Row update event on SB_Global table."""
@ -170,8 +178,12 @@ class SbGlobalUpdateEvent(row_event.RowEvent):
self.event_name = self.__class__.__name__ self.event_name = self.__class__.__name__
def run(self, event, row, old): def run(self, event, row, old):
self.agent.sb_idl.update_metadata_health_status( table = ('Chassis_Private' if self.agent.has_chassis_private
self.agent.chassis, row.nb_cfg).execute() 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): class MetadataAgent(object):
@ -207,13 +219,26 @@ class MetadataAgent(object):
proxy = metadata_server.UnixDomainMetadataProxy(self.conf) proxy = metadata_server.UnixDomainMetadataProxy(self.conf)
proxy.run() 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. # Open the connection to OVN SB database.
self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( self.has_chassis_private = False
chassis=self.chassis, try:
events=[PortBindingChassisCreatedEvent(self), self.sb_idl = ovsdb.MetadataAgentOvnSbIdl(
PortBindingChassisDeletedEvent(self), chassis=self.chassis, tables=tables + ('Chassis_Private', ),
ChassisCreateEvent(self), events=events + (ChassisPrivateCreateEvent(self), )).start()
SbGlobalUpdateEvent(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. # Do the initial sync.
self.sync() self.sync()
@ -226,9 +251,10 @@ class MetadataAgent(object):
def register_metadata_agent(self): def register_metadata_agent(self):
# NOTE(lucasagomes): db_add() will not overwrite the UUID if # NOTE(lucasagomes): db_add() will not overwrite the UUID if
# it's already set. # it's already set.
table = ('Chassis_Private' if self.has_chassis_private else 'Chassis')
ext_ids = { ext_ids = {
ovn_const.OVN_AGENT_METADATA_ID_KEY: uuidutils.generate_uuid()} 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) ext_ids).execute(check_error=True)
def _get_own_chassis_name(self): def _get_own_chassis_name(self):

View File

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

View File

@ -28,8 +28,13 @@ class NeutronAgent(abc.ABC):
for _type in cls.types: for _type in cls.types:
NeutronAgent.types[_type] = cls NeutronAgent.types[_type] = cls
def __init__(self, chassis): def __init__(self, chassis_private):
self.chassis = chassis 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 @property
def updated_at(self): def updated_at(self):
@ -58,8 +63,8 @@ class NeutronAgent(abc.ABC):
'admin_state_up': True} 'admin_state_up': True}
@classmethod @classmethod
def from_type(cls, _type, chassis): def from_type(cls, _type, chassis_private):
return cls.types[_type](chassis) return cls.types[_type](chassis_private)
@staticmethod @staticmethod
def agent_types(): def agent_types():
@ -85,15 +90,15 @@ class ControllerAgent(NeutronAgent):
@property @property
def nb_cfg(self): def nb_cfg(self):
return self.chassis.nb_cfg return self.chassis_private.nb_cfg
@property @property
def agent_id(self): def agent_id(self):
return self.chassis.name return self.chassis_private.name
@property @property
def description(self): def description(self):
return self.chassis.external_ids.get( return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_DESC_KEY, '') ovn_const.OVN_AGENT_DESC_KEY, '')
@ -105,15 +110,15 @@ class MetadataAgent(NeutronAgent):
@property @property
def nb_cfg(self): 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)) ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0))
@property @property
def agent_id(self): def agent_id(self):
return self.chassis.external_ids.get( return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_ID_KEY) ovn_const.OVN_AGENT_METADATA_ID_KEY)
@property @property
def description(self): def description(self):
return self.chassis.external_ids.get( return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_DESC_KEY, '') ovn_const.OVN_AGENT_METADATA_DESC_KEY, '')

View File

@ -120,6 +120,9 @@ class OVNMechanismDriver(api.MechanismDriver):
self.subscribe() self.subscribe()
self.qos_driver = qos_driver.OVNQosDriver.create(self) self.qos_driver = qos_driver.OVNQosDriver.create(self)
self.trunk_driver = trunk_driver.OVNTrunkDriver.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 @property
def _plugin(self): def _plugin(self):
@ -262,6 +265,9 @@ class OVNMechanismDriver(api.MechanismDriver):
self._nb_ovn, self._sb_ovn = impl_idl_ovn.get_ovn_idls( self._nb_ovn, self._sb_ovn = impl_idl_ovn.get_ovn_idls(
self, trigger, binding_events=not is_maintenance) 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 # AGENTS must be populated after fork so if ovn-controller is stopped
# before a worker handles a get_agents request, we still show agents # before a worker handles a get_agents request, we still show agents
populate_agents(self) populate_agents(self)
@ -1105,14 +1111,15 @@ class OVNMechanismDriver(api.MechanismDriver):
def mark_agent_alive(self, agent): def mark_agent_alive(self, agent):
# Update the time of our successful check # Update the time of our successful check
value = timeutils.utcnow(with_timezone=True).isoformat() value = timeutils.utcnow(with_timezone=True).isoformat()
self._sb_ovn.db_set('Chassis', agent.chassis.uuid, self._sb_ovn.db_set(
('external_ids', {agent.key: value})).execute( self.agent_chassis_table, agent.chassis_private.uuid,
check_error=True) ('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 = {} agent_dict = {}
# Iterate over each unique Agent subclass # 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: if not agent.agent_id:
continue continue
alive = self.agent_alive(agent, update_db) alive = self.agent_alive(agent, update_db)
@ -1186,7 +1193,7 @@ class OVNMechanismDriver(api.MechanismDriver):
def populate_agents(driver): 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 # update the cache, rows are hashed on uuid but it is the name that
# stays consistent across ovn-controller restarts # stays consistent across ovn-controller restarts
AGENTS.update({ch.name: ch}) AGENTS.update({ch.name: ch})
@ -1207,11 +1214,12 @@ def get_agents(self, context, filters=None, fields=None, _driver=None):
def get_agent(self, context, id, fields=None, _driver=None): def get_agent(self, context, id, fields=None, _driver=None):
chassis = None chassis = None
try: try:
# look up Chassis by *name*, which the id attribte is # look up Chassis by *name*, which the id attribute is
chassis = _driver._sb_ovn.lookup('Chassis', id) chassis = _driver._sb_ovn.lookup(_driver.agent_chassis_table, id)
except idlutils.RowNotFound: except idlutils.RowNotFound:
# If the UUID is not found, check for the metadata agent ID # 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( metadata_agent_id = ch.external_ids.get(
ovn_const.OVN_AGENT_METADATA_ID_KEY) ovn_const.OVN_AGENT_METADATA_ID_KEY)
if id == metadata_agent_id: if id == metadata_agent_id:

View File

@ -836,12 +836,6 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
if (r.mac and str(r.datapath.uuid) == network) and if (r.mac and str(r.datapath.uuid) == network) and
ip_address in r.mac[0].split(' ')] 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): def set_port_cidrs(self, name, cidrs):
# TODO(twilson) add if_exists to db commands # TODO(twilson) add if_exists to db commands
return self.db_set('Port_Binding', name, 'external_ids', return self.db_set('Port_Binding', name, 'external_ids',

View File

@ -528,6 +528,8 @@ class OvnSbIdl(OvnIdlDistributedLock):
def from_server(cls, connection_string, schema_name, driver): def from_server(cls, connection_string, schema_name, driver):
_check_and_set_ssl_files(schema_name) _check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, 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('Chassis')
helper.register_table('Encap') helper.register_table('Encap')
helper.register_table('Port_Binding') helper.register_table('Port_Binding')

View File

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

View File

@ -1615,18 +1615,21 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
def _add_chassis_agent(self, nb_cfg, agent_type, updated_at=None): def _add_chassis_agent(self, nb_cfg, agent_type, updated_at=None):
updated_at = updated_at or datetime.datetime.utcnow() updated_at = updated_at or datetime.datetime.utcnow()
chassis = mock.Mock() chassis_private = mock.Mock()
chassis.nb_cfg = nb_cfg chassis_private.nb_cfg = nb_cfg
chassis.uuid = uuid.uuid4() chassis_private.uuid = uuid.uuid4()
chassis.external_ids = {ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY: chassis_private.external_ids = {
datetime.datetime.isoformat(updated_at)} ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)}
if agent_type == ovn_const.OVN_METADATA_AGENT: 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.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg,
ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY: ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY:
datetime.datetime.isoformat(updated_at)}) 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): def test_agent_alive_true(self):
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,