Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions neutron/common/ovn/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
'HAChassisGroupInfo', ['group_name', 'chassis_list', 'az_hints',
'ignore_chassis', 'external_ids'])

_OVS_PERSIST_UUID = _SENTINEL = object()


class OvsdbClientCommand:
_CONNECTION = 0
Expand Down Expand Up @@ -903,6 +905,16 @@ def get_chassis_without_azs(chassis_list):
get_chassis_availability_zones(ch)}


def get_chassis_priority(chassis_list):
"""Given a chassis list, returns a dictionary with chassis name and prio

The chassis list is ordered according to the priority: the first one is the
highest priority chassis, the last one is the least priority chassis.
"""
return {chassis: prio + 1 for prio, chassis
in enumerate(reversed(chassis_list))}


def parse_ovn_lb_port_forwarding(ovn_rtr_lb_pfs):
"""Return a dictionary compatible with port forwarding from OVN lb."""
result = {}
Expand Down Expand Up @@ -1377,3 +1389,41 @@ def validate_port_forwarding_configuration():
if any(net_type in provider_network_types
for net_type in cfg.CONF.ml2.tenant_network_types):
raise ovn_exc.InvalidPortForwardingConfiguration()


def ovs_persist_uuid_supported(nb_idl):
# OVS 3.1+ contain the persist_uuid feature that allows choosing the UUID
# that will be stored in the DB. It was broken prior to 3.1.5/3.2.3/3.3.1
# so this will return True only for the fixed version. As actually testing
# the fix requires committing a transaction, an implementation detail is
# tested. This can be removed once a fixed version is required.
global _OVS_PERSIST_UUID
if _OVS_PERSIST_UUID is _SENTINEL:
_OVS_PERSIST_UUID = isinstance(
next(iter(nb_idl.tables["NB_Global"].rows.data.values())), list)
LOG.debug(f"OVS persist_uuid supported={_OVS_PERSIST_UUID}")
return _OVS_PERSIST_UUID


def get_logical_router_port_ha_chassis(nb_idl, lrp, priorities=None):
"""Get the list of chassis hosting this Logical_Router_Port.

:param nb_idl: (``OvsdbNbOvnIdl``) OVN Northbound IDL
:param lrp: Logical_Router_Port
:param priorities: (list of int) a list of HA_Chassis chassis priorities
to search for
:return: List of tuples (chassis_name, priority) sorted by priority. If
``priorities`` is set then only chassis matching of these
priorities are returned.
"""
chassis = []
lrp = nb_idl.lookup('Logical_Router_Port', lrp.name, default=None)
if not lrp or not lrp.ha_chassis_group:
return chassis

for hc in lrp.ha_chassis_group[0].ha_chassis:
if priorities and hc.priority not in priorities:
continue
chassis.append((hc.chassis_name, hc.priority))

return chassis
4 changes: 4 additions & 0 deletions neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovs_fixes
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
from neutron import service
from neutron.services.logapi.drivers.ovn import driver as log_driver
Expand Down Expand Up @@ -419,6 +420,9 @@ def post_fork_initialize(self, resource, event, trigger, payload=None):
self._post_fork_event.clear()
self._ovn_client_inst = None

# Patch python-ovs for fixes not yet released
ovs_fixes.apply_ovs_fixes()

if worker_class == wsgi.WorkerService:
self._setup_hash_ring()

Expand Down
157 changes: 137 additions & 20 deletions neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
# under the License.

import abc
import copy
import uuid

from oslo_utils import timeutils
from ovs.db import idl as ovs_idl
from ovsdbapp.backend.ovs_idl import command
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import rowview
from ovsdbapp.schema.ovn_northbound import commands as ovn_nb_commands
from ovsdbapp import utils as ovsdbapp_utils

Expand Down Expand Up @@ -100,6 +104,49 @@ def _add_gateway_chassis(api, txn, lrp_name, val):
return 'gateway_chassis', uuid_list


def _sync_ha_chassis_group(txn, nb_api, name, chassis_priority,
may_exist=False, table_name='HA_Chassis_Group',
**columns):
result = None
hcg = nb_api.lookup(table_name, name, default=None)
if hcg:
if not may_exist:
raise RuntimeError(_('HA_Chassis_Group %s exists' % name))
else:
hcg = txn.insert(nb_api._tables[table_name])
hcg.name = name
command.BaseCommand.set_columns(hcg, **columns)
result = hcg.uuid

# HA_Chassis registers handling.
# Remove the non-existing chassis in ``self.chassis_priority``
hc_to_remove = []
for hc in getattr(hcg, 'ha_chassis', []):
if hc.chassis_name not in chassis_priority:
hc_to_remove.append(hc)

for hc in hc_to_remove:
hcg.delvalue('ha_chassis', hc)
hc.delete()

# Update the priority of the existing chassis.
for hc in getattr(hcg, 'ha_chassis', []):
hc_priority = chassis_priority.pop(hc.chassis_name)
hc.priority = hc_priority

# Add the non-existing HA_Chassis registers.
for hc_name, priority in chassis_priority.items():
hc = txn.insert(nb_api.tables['HA_Chassis'])
hc.chassis_name = hc_name
hc.priority = priority
hcg.addvalue('ha_chassis', hc)

if not result:
result = rowview.RowView(hcg)

return result


class CheckLivenessCommand(command.BaseCommand):
def run_idl(self, txn):
# txn.pre_commit responsible for updating nb_global.nb_cfg, but
Expand All @@ -110,21 +157,64 @@ def run_idl(self, txn):
self.result = self.api.nb_global.nb_cfg


class AddNetworkCommand(command.AddCommand):
table_name = 'Logical_Switch'

def __init__(self, api, network_id, may_exist=False, **columns):
super().__init__(api)
self.network_uuid = uuid.UUID(str(network_id))
self.may_exist = may_exist
self.columns = columns

def run_idl(self, txn):
table = self.api.tables[self.table_name]
try:
ls = table.rows[self.network_uuid]
if self.may_exist:
self.result = rowview.RowView(ls)
return
msg = _("Switch %s already exists") % self.network_uuid
raise RuntimeError(msg)
except KeyError:
# Adding a new LS
if utils.ovs_persist_uuid_supported(txn.idl):
ls = txn.insert(table, new_uuid=self.network_uuid,
persist_uuid=True)
else:
ls = txn.insert(table)
self.set_columns(ls, **self.columns)
ls.name = utils.ovn_name(self.network_uuid)
self.result = ls.uuid


class AddLSwitchPortCommand(command.BaseCommand):
def __init__(self, api, lport, lswitch, may_exist, **columns):
def __init__(self, api, lport, lswitch, may_exist, network_id=None,
**columns):
super().__init__(api)
self.lport = lport
self.lswitch = lswitch
self.may_exist = may_exist
self.network_uuid = uuid.UUID(str(network_id)) if network_id else None
self.columns = columns

def run_idl(self, txn):
try:
# We must look in the local cache first, because the LS may have
# been created as part of the current transaction. or in the case
# of adding an LSP to a LS that was created before persist_uuid
lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch',
'name', self.lswitch)
except idlutils.RowNotFound:
msg = _("Logical Switch %s does not exist") % self.lswitch
raise RuntimeError(msg)
if self.network_uuid and utils.ovs_persist_uuid_supported(txn.idl):
# Create a "fake" row with the right UUID so python-ovs creates
# a transaction referencing the Row, even though we might not
# have received the update for the row ourselves.
lswitch = ovs_idl.Row(self.api.idl,
self.api.tables['Logical_Switch'],
uuid=self.network_uuid, data={})
else:
msg = _("Logical Switch %s does not exist") % self.lswitch
raise RuntimeError(msg)
if self.may_exist:
port = idlutils.row_by_value(self.api.idl,
'Logical_Switch_Port', 'name',
Expand Down Expand Up @@ -210,8 +300,8 @@ def run_idl(self, txn):
else:
new_port_dhcp_opts.add(dhcpv6_options.result)
port.dhcpv6_options = [dhcpv6_options.result]
for uuid in cur_port_dhcp_opts - new_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid].delete()
for uuid_ in cur_port_dhcp_opts - new_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid_].delete()

external_ids_update = self.external_ids_update or {}
external_ids = getattr(port, 'external_ids', {})
Expand Down Expand Up @@ -293,8 +383,8 @@ def run_idl(self, txn):
# Delete DHCP_Options records no longer referred by this port.
cur_port_dhcp_opts = get_lsp_dhcp_options_uuids(
lport, self.lport)
for uuid in cur_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid].delete()
for uuid_ in cur_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid_].delete()

_delvalue_from_list(lswitch, 'ports', lport)
self.api._tables['Logical_Switch_Port'].rows[lport.uuid].delete()
Expand Down Expand Up @@ -347,8 +437,7 @@ def run_idl(self, txn):
az_hints = self.api.get_gateway_chassis_az_hints(self.g_name)
filtered_existing_chassis = (
self.scheduler.filter_existing_chassis(
nb_idl=self.api, gw_chassis=self.all_gw_chassis,
physnet=physnet,
gw_chassis=self.all_gw_chassis, physnet=physnet,
chassis_physnets=self.chassis_with_physnets,
existing_chassis=existing_chassis, az_hints=az_hints,
chassis_with_azs=self.chassis_with_azs))
Expand Down Expand Up @@ -388,9 +477,12 @@ def run_idl(self, txn):
# the top.
index = chassis.index(primary)
chassis[0], chassis[index] = chassis[index], chassis[0]
setattr(
lrouter_port,
*_add_gateway_chassis(self.api, txn, self.g_name, chassis))
chassis_priority = utils.get_chassis_priority(chassis)
lrouter_name = lrouter_port.external_ids[
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY]
hcg = _sync_ha_chassis_group(txn, self.api, lrouter_name,
chassis_priority, may_exist=True)
setattr(lrouter_port, 'ha_chassis_group', ovsdbapp_utils.get_uuid(hcg))


class ScheduleNewGatewayCommand(command.BaseCommand):
Expand All @@ -415,8 +507,11 @@ def run_idl(self, txn):
self.api, self.sb_api, self.g_name, candidates=candidates,
target_lrouter=lrouter)
if chassis:
setattr(lrouter_port,
*_add_gateway_chassis(self.api, txn, self.g_name, chassis))
chassis_priority = utils.get_chassis_priority(chassis)
hcg = _sync_ha_chassis_group(txn, self.api, self.lrouter_name,
chassis_priority, may_exist=True)
setattr(lrouter_port, 'ha_chassis_group',
ovsdbapp_utils.get_uuid(hcg))


class LrDelCommand(ovn_nb_commands.LrDelCommand):
Expand Down Expand Up @@ -465,8 +560,9 @@ def run_idl(self, txn):
if col == 'gateway_chassis':
col, val = _add_gateway_chassis(self.api, txn, self.name,
val)
setattr(lrouter_port, col, val)
self.set_column(lrouter_port, col, val)
_addvalue_to_list(lrouter, 'ports', lrouter_port)
self.result = lrouter_port.uuid


class UpdateLRouterPortCommand(command.BaseCommand):
Expand All @@ -491,7 +587,7 @@ def run_idl(self, txn):
if col == 'gateway_chassis':
col, val = _add_gateway_chassis(self.api, txn, self.name,
val)
setattr(lrouter_port, col, val)
self.set_column(lrouter_port, col, val)


class DelLRouterPortCommand(command.BaseCommand):
Expand Down Expand Up @@ -1008,15 +1104,17 @@ def run_idl(self, txn):
# Remove the router pinning to a chassis (if any).
lrouter.delkey('options', 'chassis')

# Remove the HA_Chassis_Group of the router (if any).
for gw_port in self.api.get_lrouter_gw_ports(lrouter.name):
gw_port.ha_chassis_group = []
lrouter.delvalue('ports', gw_port)

# Remove the HA_Chassis_Group of the router (if any), after
# removing it from the gateway Logical_Router_Ports.
hcg = self.api.lookup('HA_Chassis_Group',
lrouter.name, default=None)
if hcg:
hcg.delete()

for gw_port in self.api.get_lrouter_gw_ports(lrouter.name):
lrouter.delvalue('ports', gw_port)


class SetLSwitchPortToVirtualTypeCommand(command.BaseCommand):
def __init__(self, api, lport, vip, parent, if_exists):
Expand Down Expand Up @@ -1089,3 +1187,22 @@ def run_idl(self, txn):
virtual_parents)

setattr(lsp, 'options', options)


class HAChassisGroupWithHCAddCommand(command.AddCommand):
table_name = 'HA_Chassis_Group'

def __init__(self, api, name, chassis_priority, may_exist=False,
**columns):
super().__init__(api)
self.name = name
self.chassis_priority = copy.deepcopy(chassis_priority)
self.may_exist = may_exist
self.columns = columns

def run_idl(self, txn):
# HA_Chassis_Group register creation.
self.result = _sync_ha_chassis_group(
txn, self.api, self.name, self.chassis_priority,
may_exist=self.may_exist, table_name=self.table_name,
**self.columns)
Loading