[OVN] Import ovsdb related code (part 2)

This patch imports ovsdb related code from networking_ovn.

Previous paths in networking-ovn tree:
./networking_ovn/ovsdb/ovsdb_monitor.py ->
  ./neutron/ovsdb/ovn/ovsdb_monitor.py
./networking_ovn/ovsdb/impl_idl_ovn.py ->
  ./neutron/ovsdb/ovn/impl_idl_ovn.py

Change-Id: I5fe40a3b3e62e8c2adeb6660d1673dee49fe4965
Related-Blueprint: neutron-ovn-merge
Signed-off-by: Lucas Alvares Gomes <lucasagomes@gmail.com>
Co-Authored-By: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
This commit is contained in:
Lucas Alvares Gomes 2019-11-28 15:36:58 +00:00
parent f77c1037e4
commit fdb3f05055
6 changed files with 3225 additions and 0 deletions

View File

@ -0,0 +1,840 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import uuid
from neutron_lib import exceptions as n_exc
from neutron_lib.utils import helpers
from oslo_log import log
from oslo_utils import uuidutils
from ovsdbapp.backend import ovs_idl
from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import transaction as idl_trans
from ovsdbapp.backend.ovs_idl import vlog
from ovsdbapp.schema.ovn_northbound import impl_idl as nb_impl_idl
from ovsdbapp.schema.ovn_southbound import impl_idl as sb_impl_idl
import tenacity
from neutron._i18n import _
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import exceptions as ovn_exc
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as cfg
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import commands as cmd
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor
LOG = log.getLogger(__name__)
class OvnNbTransaction(idl_trans.Transaction):
def __init__(self, *args, **kwargs):
# NOTE(lucasagomes): The bump_nb_cfg parameter is only used by
# the agents health status check
self.bump_nb_cfg = kwargs.pop('bump_nb_cfg', False)
super(OvnNbTransaction, self).__init__(*args, **kwargs)
def pre_commit(self, txn):
if not self.bump_nb_cfg:
return
self.api.nb_global.increment('nb_cfg')
# This version of Backend doesn't use a class variable for ovsdb_connection
# and therefor allows networking-ovn to manage connection scope on its own
class Backend(ovs_idl.Backend):
lookup_table = {}
def __init__(self, connection):
self.ovsdb_connection = connection
super(Backend, self).__init__(connection)
def start_connection(self, connection):
try:
self.ovsdb_connection.start()
except Exception as e:
connection_exception = OvsdbConnectionUnavailable(
db_schema=self.schema, error=e)
LOG.exception(connection_exception)
raise connection_exception
@property
def idl(self):
return self.ovsdb_connection.idl
@property
def tables(self):
return self.idl.tables
_tables = tables
def is_table_present(self, table_name):
return table_name in self._tables
def is_col_present(self, table_name, col_name):
return self.is_table_present(table_name) and (
col_name in self._tables[table_name].columns)
def create_transaction(self, check_error=False, log_errors=True):
return idl_trans.Transaction(
self, self.ovsdb_connection, self.ovsdb_connection.timeout,
check_error, log_errors)
# Check for a column match in the table. If not found do a retry with
# a stop delay of 10 secs. This function would be useful if the caller
# wants to verify for the presence of a particular row in the table
# with the column match before doing any transaction.
# Eg. We can check if Logical_Switch row is present before adding a
# logical switch port to it.
@tenacity.retry(retry=tenacity.retry_if_exception_type(RuntimeError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_delay(10),
reraise=True)
def check_for_row_by_value_and_retry(self, table, column, match):
try:
idlutils.row_by_value(self.idl, table, column, match)
except idlutils.RowNotFound:
msg = (_("%(match)s does not exist in %(column)s of %(table)s")
% {'match': match, 'column': column, 'table': table})
raise RuntimeError(msg)
class OvsdbConnectionUnavailable(n_exc.ServiceUnavailable):
message = _("OVS database connection to %(db_schema)s failed with error: "
"'%(error)s'. Verify that the OVS and OVN services are "
"available and that the 'ovn_nb_connection' and "
"'ovn_sb_connection' configuration options are correct.")
# Retry forever to get the OVN NB and SB IDLs. Wait 2^x * 1 seconds between
# each retry, up to 'max_interval' seconds, then interval will be fixed
# to 'max_interval' seconds afterwards. The default 'max_interval' is 180.
def get_ovn_idls(driver, trigger, binding_events=False):
@tenacity.retry(
wait=tenacity.wait_exponential(
max=cfg.get_ovn_ovsdb_retry_max_interval()),
reraise=True)
def get_ovn_idl_retry(cls):
trigger_class = utils.get_method_class(trigger)
LOG.info('Getting %(cls)s for %(trigger)s with retry',
{'cls': cls.__name__, 'trigger': trigger_class.__name__})
return cls(get_connection(cls, trigger, driver, binding_events))
vlog.use_python_logger(max_level=cfg.get_ovn_ovsdb_log_level())
return tuple(get_ovn_idl_retry(c) for c in (OvsdbNbOvnIdl, OvsdbSbOvnIdl))
def get_connection(db_class, trigger=None, driver=None, binding_events=False):
if db_class == OvsdbNbOvnIdl:
args = (cfg.get_ovn_nb_connection(), 'OVN_Northbound')
elif db_class == OvsdbSbOvnIdl:
args = (cfg.get_ovn_sb_connection(), 'OVN_Southbound')
if binding_events:
if db_class == OvsdbNbOvnIdl:
idl_ = ovsdb_monitor.OvnNbIdl.from_server(*args, driver=driver)
else:
idl_ = ovsdb_monitor.OvnSbIdl.from_server(*args, driver=driver)
else:
if db_class == OvsdbNbOvnIdl:
idl_ = ovsdb_monitor.BaseOvnIdl.from_server(*args)
else:
idl_ = ovsdb_monitor.BaseOvnSbIdl.from_server(*args)
return connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout())
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
def __init__(self, connection):
super(OvsdbNbOvnIdl, self).__init__(connection)
self.idl._session.reconnect.set_probe_interval(
cfg.get_ovn_ovsdb_probe_interval())
@property
def nb_global(self):
return next(iter(self.tables['NB_Global'].rows.values()))
def create_transaction(self, check_error=False, log_errors=True,
bump_nb_cfg=False):
return OvnNbTransaction(
self, self.ovsdb_connection, self.ovsdb_connection.timeout,
check_error, log_errors, bump_nb_cfg=bump_nb_cfg)
@contextlib.contextmanager
def transaction(self, *args, **kwargs):
"""A wrapper on the ovsdbapp transaction to work with revisions.
This method is just a wrapper around the ovsdbapp transaction
to handle revision conflicts correctly.
"""
try:
with super(OvsdbNbOvnIdl, self).transaction(*args, **kwargs) as t:
yield t
except ovn_exc.RevisionConflict as e:
LOG.info('Transaction aborted. Reason: %s', e)
def set_lswitch_ext_ids(self, lswitch_id, ext_ids, if_exists=True):
return cmd.LSwitchSetExternalIdsCommand(self, lswitch_id, ext_ids,
if_exists)
def create_lswitch_port(self, lport_name, lswitch_name, may_exist=True,
**columns):
return cmd.AddLSwitchPortCommand(self, lport_name, lswitch_name,
may_exist, **columns)
def set_lswitch_port(self, lport_name, if_exists=True, **columns):
return cmd.SetLSwitchPortCommand(self, lport_name,
if_exists, **columns)
def delete_lswitch_port(self, lport_name=None, lswitch_name=None,
ext_id=None, if_exists=True):
if lport_name is not None:
return cmd.DelLSwitchPortCommand(self, lport_name,
lswitch_name, if_exists)
else:
raise RuntimeError(_("Currently only supports "
"delete by lport-name"))
def get_all_logical_switches_with_ports(self):
result = []
for lswitch in self._tables['Logical_Switch'].rows.values():
if ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY not in (
lswitch.external_ids):
continue
ports = []
provnet_port = None
for lport in getattr(lswitch, 'ports', []):
if ovn_const.OVN_PORT_NAME_EXT_ID_KEY in lport.external_ids:
ports.append(lport.name)
# Handle provider network port
elif lport.name.startswith(
ovn_const.OVN_PROVNET_PORT_NAME_PREFIX):
provnet_port = lport.name
result.append({'name': lswitch.name,
'ports': ports,
'provnet_port': provnet_port})
return result
def get_all_logical_routers_with_rports(self):
"""Get logical Router ports associated with all logical Routers
@return: list of dict, each dict has key-value:
- 'name': string router_id in neutron.
- 'static_routes': list of static routes dict.
- 'ports': dict of port_id in neutron (key) and networks on
port (value).
- 'snats': list of snats dict
- 'dnat_and_snats': list of dnat_and_snats dict
"""
result = []
for lrouter in self._tables['Logical_Router'].rows.values():
if ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY not in (
lrouter.external_ids):
continue
lrports = {lrport.name.replace('lrp-', ''): lrport.networks
for lrport in getattr(lrouter, 'ports', [])}
sroutes = [{'destination': sroute.ip_prefix,
'nexthop': sroute.nexthop}
for sroute in getattr(lrouter, 'static_routes', [])]
dnat_and_snats = []
snat = []
for nat in getattr(lrouter, 'nat', []):
columns = {'logical_ip': nat.logical_ip,
'external_ip': nat.external_ip,
'type': nat.type}
if nat.type == 'dnat_and_snat':
if nat.external_mac:
columns['external_mac'] = nat.external_mac[0]
if nat.logical_port:
columns['logical_port'] = nat.logical_port[0]
dnat_and_snats.append(columns)
elif nat.type == 'snat':
snat.append(columns)
result.append({'name': lrouter.name.replace('neutron-', ''),
'static_routes': sroutes,
'ports': lrports,
'snats': snat,
'dnat_and_snats': dnat_and_snats})
return result
def get_acl_by_id(self, acl_id):
try:
return self.lookup('ACL', uuid.UUID(acl_id))
except idlutils.RowNotFound:
return
def get_acls_for_lswitches(self, lswitch_names):
"""Get the existing set of acls that belong to the logical switches
@param lswitch_names: List of logical switch names
@type lswitch_names: []
@var acl_values_dict: A dictionary indexed by port_id containing the
list of acl values in string format that belong
to that port
@var acl_obj_dict: A dictionary indexed by acl value containing the
corresponding acl idl object.
@var lswitch_ovsdb_dict: A dictionary mapping from logical switch
name to lswitch idl object
@return: (acl_values_dict, acl_obj_dict, lswitch_ovsdb_dict)
"""
acl_values_dict = {}
acl_obj_dict = {}
lswitch_ovsdb_dict = {}
for lswitch_name in lswitch_names:
try:
lswitch = idlutils.row_by_value(self.idl,
'Logical_Switch',
'name',
utils.ovn_name(lswitch_name))
except idlutils.RowNotFound:
# It is possible for the logical switch to be deleted
# while we are searching for it by name in idl.
continue
lswitch_ovsdb_dict[lswitch_name] = lswitch
acls = getattr(lswitch, 'acls', [])
# Iterate over each acl in a lswitch and store the acl in
# a key:value representation for e.g. acl_string. This
# key:value representation can invoke the code -
# self._ovn.add_acl(**acl_string)
for acl in acls:
ext_ids = getattr(acl, 'external_ids', {})
port_id = ext_ids.get('neutron:lport')
acl_list = acl_values_dict.setdefault(port_id, [])
acl_string = {'lport': port_id,
'lswitch': utils.ovn_name(lswitch_name)}
for acl_key in getattr(acl, "_data", {}):
try:
acl_string[acl_key] = getattr(acl, acl_key)
except AttributeError:
pass
acl_obj_dict[str(acl_string)] = acl
acl_list.append(acl_string)
return acl_values_dict, acl_obj_dict, lswitch_ovsdb_dict
def create_lrouter(self, name, may_exist=True, **columns):
return cmd.AddLRouterCommand(self, name,
may_exist, **columns)
def update_lrouter(self, name, if_exists=True, **columns):
return cmd.UpdateLRouterCommand(self, name,
if_exists, **columns)
def delete_lrouter(self, name, if_exists=True):
return cmd.DelLRouterCommand(self, name, if_exists)
def add_lrouter_port(self, name, lrouter, may_exist=False, **columns):
return cmd.AddLRouterPortCommand(self, name, lrouter,
may_exist, **columns)
def update_lrouter_port(self, name, if_exists=True, **columns):
return cmd.UpdateLRouterPortCommand(self, name, if_exists, **columns)
def delete_lrouter_port(self, name, lrouter, if_exists=True):
return cmd.DelLRouterPortCommand(self, name, lrouter,
if_exists)
def set_lrouter_port_in_lswitch_port(
self, lswitch_port, lrouter_port, is_gw_port=False, if_exists=True,
lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER):
return cmd.SetLRouterPortInLSwitchPortCommand(self, lswitch_port,
lrouter_port, is_gw_port,
if_exists,
lsp_address)
def add_acl(self, lswitch, lport, **columns):
return cmd.AddACLCommand(self, lswitch, lport, **columns)
def delete_acl(self, lswitch, lport, if_exists=True):
return cmd.DelACLCommand(self, lswitch, lport, if_exists)
def update_acls(self, lswitch_names, port_list, acl_new_values_dict,
need_compare=True, is_add_acl=True):
return cmd.UpdateACLsCommand(self, lswitch_names,
port_list, acl_new_values_dict,
need_compare=need_compare,
is_add_acl=is_add_acl)
def add_static_route(self, lrouter, **columns):
return cmd.AddStaticRouteCommand(self, lrouter, **columns)
def delete_static_route(self, lrouter, ip_prefix, nexthop, if_exists=True):
return cmd.DelStaticRouteCommand(self, lrouter, ip_prefix, nexthop,
if_exists)
def create_address_set(self, name, may_exist=True, **columns):
return cmd.AddAddrSetCommand(self, name, may_exist, **columns)
def delete_address_set(self, name, if_exists=True, **columns):
return cmd.DelAddrSetCommand(self, name, if_exists)
def update_address_set(self, name, addrs_add, addrs_remove,
if_exists=True):
return cmd.UpdateAddrSetCommand(self, name, addrs_add, addrs_remove,
if_exists)
def update_address_set_ext_ids(self, name, external_ids, if_exists=True):
return cmd.UpdateAddrSetExtIdsCommand(self, name, external_ids,
if_exists)
def _get_logical_router_port_gateway_chassis(self, lrp):
"""Get the list of chassis hosting this gateway port.
@param lrp: logical router port
@type lrp: Logical_Router_Port row
@return: List of tuples (chassis_name, priority) sorted by priority
"""
# Try retrieving gateway_chassis with new schema. If new schema is not
# supported or user is using old schema, then use old schema for
# getting gateway_chassis
chassis = []
if self._tables.get('Gateway_Chassis'):
for gwc in lrp.gateway_chassis:
chassis.append((gwc.chassis_name, gwc.priority))
else:
rc = lrp.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
if rc:
chassis.append((rc, 0))
# make sure that chassis are sorted by priority
return sorted(chassis, reverse=True, key=lambda x: x[1])
def get_all_chassis_gateway_bindings(self,
chassis_candidate_list=None):
chassis_bindings = {}
for chassis_name in chassis_candidate_list or []:
chassis_bindings.setdefault(chassis_name, [])
for lrp in self._tables['Logical_Router_Port'].rows.values():
if not lrp.name.startswith('lrp-'):
continue
chassis = self._get_logical_router_port_gateway_chassis(lrp)
for chassis_name, prio in chassis:
if (not chassis_candidate_list or
chassis_name in chassis_candidate_list):
routers_hosted = chassis_bindings.setdefault(chassis_name,
[])
routers_hosted.append((lrp.name, prio))
return chassis_bindings
def get_gateway_chassis_binding(self, gateway_name):
try:
lrp = idlutils.row_by_value(
self.idl, 'Logical_Router_Port', 'name', gateway_name)
chassis_list = self._get_logical_router_port_gateway_chassis(lrp)
return [chassis for chassis, prio in chassis_list]
except idlutils.RowNotFound:
return []
def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
gw_chassis):
unhosted_gateways = []
for lrp in self._tables['Logical_Router_Port'].rows.values():
if not lrp.name.startswith('lrp-'):
continue
physnet = port_physnet_dict.get(lrp.name[len('lrp-'):])
chassis_list = self._get_logical_router_port_gateway_chassis(lrp)
is_max_gw_reached = len(chassis_list) < ovn_const.MAX_GW_CHASSIS
for chassis_name, prio in chassis_list:
# TODO(azbiswas): Handle the case when a chassis is no
# longer valid. This may involve moving conntrack states,
# so it needs to discussed in the OVN community first.
if is_max_gw_reached or utils.is_gateway_chassis_invalid(
chassis_name, gw_chassis, physnet, chassis_physnets):
unhosted_gateways.append(lrp.name)
return unhosted_gateways
def add_dhcp_options(self, subnet_id, port_id=None, may_exist=True,
**columns):
return cmd.AddDHCPOptionsCommand(self, subnet_id, port_id=port_id,
may_exist=may_exist, **columns)
def delete_dhcp_options(self, row_uuid, if_exists=True):
return cmd.DelDHCPOptionsCommand(self, row_uuid, if_exists=if_exists)
def _format_dhcp_row(self, row):
ext_ids = dict(getattr(row, 'external_ids', {}))
return {'cidr': row.cidr, 'options': dict(row.options),
'external_ids': ext_ids, 'uuid': row.uuid}
def get_subnet_dhcp_options(self, subnet_id, with_ports=False):
subnet = None
ports = []
for row in self._tables['DHCP_Options'].rows.values():
external_ids = getattr(row, 'external_ids', {})
if subnet_id == external_ids.get('subnet_id'):
port_id = external_ids.get('port_id')
if with_ports and port_id:
ports.append(self._format_dhcp_row(row))
elif not port_id:
subnet = self._format_dhcp_row(row)
if not with_ports:
break
return {'subnet': subnet, 'ports': ports}
def get_subnets_dhcp_options(self, subnet_ids):
ret_opts = []
for row in self._tables['DHCP_Options'].rows.values():
external_ids = getattr(row, 'external_ids', {})
if (external_ids.get('subnet_id') in subnet_ids and not
external_ids.get('port_id')):
ret_opts.append(self._format_dhcp_row(row))
if len(ret_opts) == len(subnet_ids):
break
return ret_opts
def get_all_dhcp_options(self):
dhcp_options = {'subnets': {}, 'ports_v4': {}, 'ports_v6': {}}
for row in self._tables['DHCP_Options'].rows.values():
external_ids = getattr(row, 'external_ids', {})
if not external_ids.get('subnet_id'):
# This row is not created by OVN ML2 driver. Ignore it.
continue
if not external_ids.get('port_id'):
dhcp_options['subnets'][external_ids['subnet_id']] = (
self._format_dhcp_row(row))
else:
port_dict = 'ports_v6' if ':' in row.cidr else 'ports_v4'
dhcp_options[port_dict][external_ids['port_id']] = (
self._format_dhcp_row(row))
return dhcp_options
def get_address_sets(self):
address_sets = {}
for row in self._tables['Address_Set'].rows.values():
# TODO(lucasagomes): Remove OVN_SG_NAME_EXT_ID_KEY in the
# Rocky release
if not (ovn_const.OVN_SG_EXT_ID_KEY in row.external_ids or
ovn_const.OVN_SG_NAME_EXT_ID_KEY in row.external_ids):
continue
name = getattr(row, 'name')
data = {}
for row_key in getattr(row, "_data", {}):
data[row_key] = getattr(row, row_key)
address_sets[name] = data
return address_sets
def get_router_port_options(self, lsp_name):
try:
lsp = idlutils.row_by_value(self.idl, 'Logical_Switch_Port',
'name', lsp_name)
options = getattr(lsp, 'options')
for key in list(options.keys()):
if key not in ovn_const.OVN_ROUTER_PORT_OPTION_KEYS:
del(options[key])
return options
except idlutils.RowNotFound:
return {}
def add_nat_rule_in_lrouter(self, lrouter, **columns):
return cmd.AddNATRuleInLRouterCommand(self, lrouter, **columns)
def delete_nat_rule_in_lrouter(self, lrouter, type, logical_ip,
external_ip, if_exists=True):
return cmd.DeleteNATRuleInLRouterCommand(self, lrouter, type,
logical_ip, external_ip,
if_exists)
def get_lrouter_nat_rules(self, lrouter_name):
try:
lrouter = idlutils.row_by_value(self.idl, 'Logical_Router',
'name', lrouter_name)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % lrouter_name
raise RuntimeError(msg)
nat_rules = []
for nat_rule in getattr(lrouter, 'nat', []):
ext_ids = {}
# TODO(dalvarez): remove this check once the minimum OVS required
# version contains the column (when OVS 2.8.2 is released).
if self.is_col_present('NAT', 'external_ids'):
ext_ids = dict(getattr(nat_rule, 'external_ids', {}))
nat_rules.append({'external_ip': nat_rule.external_ip,
'logical_ip': nat_rule.logical_ip,
'type': nat_rule.type,
'uuid': nat_rule.uuid,
'external_ids': ext_ids})
return nat_rules
def set_nat_rule_in_lrouter(self, lrouter, nat_rule_uuid, **columns):
return cmd.SetNATRuleInLRouterCommand(self, lrouter, nat_rule_uuid,
**columns)
def get_lswitch_port(self, lsp_name):
try:
return self.lookup('Logical_Switch_Port', lsp_name)
except idlutils.RowNotFound:
return None
def get_parent_port(self, lsp_name):
lsp = self.get_lswitch_port(lsp_name)
if not lsp:
return ''
return lsp.parent_name
def get_lswitch(self, lswitch_name):
# FIXME(lucasagomes): We should refactor those get_*()
# methods. Some of 'em require the name, others IDs etc... It can
# be confusing.
if uuidutils.is_uuid_like(lswitch_name):
lswitch_name = utils.ovn_name(lswitch_name)
try:
return self.lookup('Logical_Switch', lswitch_name)
except idlutils.RowNotFound:
return None
def get_ls_and_dns_record(self, lswitch_name):
ls = self.get_lswitch(lswitch_name)
if not ls:
return (None, None)
if not hasattr(ls, 'dns_records'):
return (ls, None)
for dns_row in ls.dns_records:
if dns_row.external_ids.get('ls_name') == lswitch_name:
return (ls, dns_row)
return (ls, None)
def get_floatingip(self, fip_id):
# TODO(dalvarez): remove this check once the minimum OVS required
# version contains the column (when OVS 2.8.2 is released).
if not self.is_col_present('NAT', 'external_ids'):
return
fip = self.db_find('NAT', ('external_ids', '=',
{ovn_const.OVN_FIP_EXT_ID_KEY: fip_id}))
result = fip.execute(check_error=True)
return result[0] if result else None
def get_floatingip_by_ips(self, router_id, logical_ip, external_ip):
if not all([router_id, logical_ip, external_ip]):
return
for nat in self.get_lrouter_nat_rules(utils.ovn_name(router_id)):
if (nat['type'] == 'dnat_and_snat' and
nat['logical_ip'] == logical_ip and
nat['external_ip'] == external_ip):
return nat
def get_address_set(self, addrset_id, ip_version='ip4'):
addr_name = utils.ovn_addrset_name(addrset_id, ip_version)
try:
return idlutils.row_by_value(self.idl, 'Address_Set',
'name', addr_name)
except idlutils.RowNotFound:
return None
def check_revision_number(self, name, resource, resource_type,
if_exists=True):
return cmd.CheckRevisionNumberCommand(
self, name, resource, resource_type, if_exists)
def get_lrouter(self, lrouter_name):
if uuidutils.is_uuid_like(lrouter_name):
lrouter_name = utils.ovn_name(lrouter_name)
# TODO(lucasagomes): Use lr_get() once we start refactoring this
# API to use methods from ovsdbapp.
lr = self.db_find_rows('Logical_Router', ('name', '=', lrouter_name))
result = lr.execute(check_error=True)
return result[0] if result else None
def get_lrouter_port(self, lrp_name):
# TODO(mangelajo): Implement lrp_get() ovsdbapp and use from here
if uuidutils.is_uuid_like(lrp_name):
lrp_name = utils.ovn_lrouter_port_name(lrp_name)
lrp = self.db_find_rows('Logical_Router_Port', ('name', '=', lrp_name))
result = lrp.execute(check_error=True)
return result[0] if result else None
def delete_lrouter_ext_gw(self, lrouter_name, if_exists=True):
return cmd.DeleteLRouterExtGwCommand(self, lrouter_name, if_exists)
def is_port_groups_supported(self):
return self.is_table_present('Port_Group')
def get_port_group(self, pg_name):
if uuidutils.is_uuid_like(pg_name):
pg_name = utils.ovn_port_group_name(pg_name)
try:
for pg in self._tables['Port_Group'].rows.values():
if pg.name == pg_name:
return pg
except KeyError:
# TODO(dalvarez): This except block is added for backwards compat
# with old OVN schemas (<=2.9) where Port Groups are not present.
# This (and other conditional code around this feature) shall be
# removed at some point.
return
def get_port_groups(self):
port_groups = {}
try:
for row in self._tables['Port_Group'].rows.values():
name = getattr(row, 'name')
if not (ovn_const.OVN_SG_EXT_ID_KEY in row.external_ids or
name == ovn_const.OVN_DROP_PORT_GROUP_NAME):
continue
data = {}
for row_key in getattr(row, "_data", {}):
data[row_key] = getattr(row, row_key)
port_groups[name] = data
except KeyError:
# TODO(dalvarez): This except block is added for backwards compat
# with old OVN schemas (<=2.9) where Port Groups are not present.
# This (and other conditional code around this feature) shall be
# removed at some point.
pass
return port_groups
def check_liveness(self):
return cmd.CheckLivenessCommand(self)
def set_lswitch_port_to_virtual_type(self, lport_name, vip,
virtual_parent, if_exists=True):
return cmd.SetLSwitchPortToVirtualTypeCommand(
self, lport_name, vip, virtual_parent, if_exists)
def unset_lswitch_port_to_virtual_type(self, lport_name,
virtual_parent, if_exists=True):
return cmd.UnsetLSwitchPortToVirtualTypeCommand(
self, lport_name, virtual_parent, if_exists)
class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
def __init__(self, connection):
super(OvsdbSbOvnIdl, self).__init__(connection)
# TODO(twilson) This direct access of the idl should be removed in
# favor of a backend-agnostic method
self.idl._session.reconnect.set_probe_interval(
cfg.get_ovn_ovsdb_probe_interval())
def _get_chassis_physnets(self, chassis):
bridge_mappings = chassis.external_ids.get('ovn-bridge-mappings', '')
mapping_dict = helpers.parse_mappings(bridge_mappings.split(','),
unique_values=False)
return list(mapping_dict.keys())
def chassis_exists(self, hostname):
cmd = self.db_find('Chassis', ('hostname', '=', hostname))
return bool(cmd.execute(check_error=True))
def get_chassis_hostname_and_physnets(self):
chassis_info_dict = {}
for ch in self.chassis_list().execute(check_error=True):
chassis_info_dict[ch.hostname] = self._get_chassis_physnets(ch)
return chassis_info_dict
def get_gateway_chassis_from_cms_options(self):
gw_chassis = []
for ch in self.chassis_list().execute(check_error=True):
cms_options = ch.external_ids.get('ovn-cms-options', '')
if 'enable-chassis-as-gw' in cms_options.split(','):
gw_chassis.append(ch.name)
return gw_chassis
def get_chassis_and_physnets(self):
chassis_info_dict = {}
for ch in self.chassis_list().execute(check_error=True):
chassis_info_dict[ch.name] = self._get_chassis_physnets(ch)
return chassis_info_dict
def get_all_chassis(self, chassis_type=None):
# TODO(azbiswas): Use chassis_type as input once the compute type
# 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)
return (chassis.external_ids.get('datapath-type', ''),
chassis.external_ids.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:
dp = self.lookup('Datapath_Binding', uuid.UUID(network))
except idlutils.RowNotFound:
return None
cmd = self.db_find_rows('Port_Binding', ('datapath', '=', dp),
('type', '=', 'localport'))
return next(iter(cmd.execute(check_error=True)), None)
def get_chassis_metadata_networks(self, chassis_name):
"""Return a list with the metadata networks the chassis is hosting."""
chassis = self.lookup('Chassis', chassis_name)
proxy_networks = chassis.external_ids.get(
'neutron-metadata-proxy-networks', None)
return proxy_networks.split(',') if proxy_networks else []
def set_chassis_metadata_networks(self, chassis, networks):
nets = ','.join(networks) if networks else ''
# TODO(twilson) This could just use DbSetCommand
return cmd.UpdateChassisExtIdsCommand(
self, chassis, {'neutron-metadata-proxy-networks': nets},
if_exists=True)
def set_chassis_neutron_description(self, chassis, description,
agent_type):
desc_key = (ovn_const.OVN_AGENT_METADATA_DESC_KEY
if agent_type == ovn_const.OVN_METADATA_AGENT else
ovn_const.OVN_AGENT_DESC_KEY)
return cmd.UpdateChassisExtIdsCommand(
self, chassis, {desc_key: description}, if_exists=False)
def get_network_port_bindings_by_ip(self, network, ip_address):
rows = self.db_list_rows('Port_Binding').execute(check_error=True)
# TODO(twilson) It would be useful to have a db_find that takes a
# comparison function
return [r for r in rows
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',
{'neutron-port-cidrs': cidrs})
def get_ports_on_chassis(self, chassis):
# TODO(twilson) Some day it would be nice to stop passing names around
# and just start using chassis objects so db_find_rows could be used
rows = self.db_list_rows('Port_Binding').execute(check_error=True)
return [r for r in rows if r.chassis and r.chassis[0].name == chassis]
def get_logical_port_chassis_and_datapath(self, name):
for port in self._tables['Port_Binding'].rows.values():
if port.logical_port == name:
datapath = str(port.datapath.uuid)
chassis = port.chassis[0].name if port.chassis else None
return chassis, datapath

View File

@ -0,0 +1,465 @@
# Copyright 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import datetime
from neutron_lib import context as neutron_context
from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
from ovs.stream import Stream
from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import event as row_event
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp import event
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import exceptions
from neutron.common.ovn import hash_ring_manager
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_hash_ring_db
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class BaseEvent(row_event.RowEvent):
table = None
events = tuple()
def __init__(self):
self.event_name = self.__class__.__name__
super(BaseEvent, self).__init__(self.events, self.table, None)
@abc.abstractmethod
def match_fn(self, event, row, old=None):
"""Define match criteria other than table/event"""
def matches(self, event, row, old=None):
if row._table.name != self.table or event not in self.events:
return False
if not self.match_fn(event, row, old):
return False
LOG.debug("%s : Matched %s, %s, %s %s", self.event_name, self.table,
event, self.conditions, self.old_conditions)
return True
class ChassisEvent(row_event.RowEvent):
"""Chassis create update delete event."""
def __init__(self, driver):
self.driver = driver
self.l3_plugin = directory.get_plugin(constants.L3)
table = 'Chassis'
events = (self.ROW_CREATE, self.ROW_UPDATE, self.ROW_DELETE)
super(ChassisEvent, self).__init__(events, table, None)
self.event_name = 'ChassisEvent'
def run(self, event, row, old):
host = row.hostname
phy_nets = []
if event != self.ROW_DELETE:
bridge_mappings = row.external_ids.get('ovn-bridge-mappings', '')
mapping_dict = helpers.parse_mappings(bridge_mappings.split(','),
unique_values=False)
phy_nets = list(mapping_dict)
self.driver.update_segment_host_mapping(host, phy_nets)
if utils.is_ovn_l3(self.l3_plugin):
self.l3_plugin.schedule_unhosted_gateways()
class PortBindingChassisUpdateEvent(row_event.RowEvent):
"""Event for matching a port moving chassis
If the LSP is up and the Port_Binding chassis has just changed,
there is a good chance the host died without cleaning up the chassis
column on the Port_Binding. The port never goes down, so we won't
see update the driver with the LogicalSwitchPortUpdateUpEvent which
only monitors for transitions from DOWN to UP.
"""
def __init__(self, driver):
self.driver = driver
table = 'Port_Binding'
events = (self.ROW_UPDATE,)
super(PortBindingChassisUpdateEvent, self).__init__(
events, table, None)
self.event_name = self.__class__.__name__
def match_fn(self, event, row, old=None):
# NOTE(twilson) ROW_UPDATE events always pass old, but chassis will
# only be set if chassis has changed
old_chassis = getattr(old, 'chassis', None)
if not (row.chassis and old_chassis) or row.chassis == old_chassis:
return False
if row.type == ovn_const.OVN_CHASSIS_REDIRECT:
return False
try:
lsp = self.driver._nb_ovn.lookup('Logical_Switch_Port',
row.logical_port)
except idlutils.RowNotFound:
LOG.warning("Logical Switch Port %(port)s not found for "
"Port_Binding %(binding)s",
{'port': row.logical_port, 'binding': row.uuid})
return False
return bool(lsp.up)
def run(self, event, row, old=None):
self.driver.set_port_status_up(row.logical_port)
class PortBindingChassisEvent(row_event.RowEvent):
"""Port_Binding update event - set chassis for chassisredirect port.
When a chassisredirect port is updated with chassis, this event get
generated. We will update corresponding router's gateway port with
the chassis's host_id. Later, users can check router's gateway port
host_id to find the location of master HA router.
"""
def __init__(self, driver):
self.driver = driver
self.l3_plugin = directory.get_plugin(constants.L3)
table = 'Port_Binding'
events = (self.ROW_UPDATE,)
super(PortBindingChassisEvent, self).__init__(
events, table, (('type', '=', ovn_const.OVN_CHASSIS_REDIRECT),))
self.event_name = 'PortBindingChassisEvent'
def run(self, event, row, old):
if not utils.is_ovn_l3(self.l3_plugin):
return
router = host = None
chassis = getattr(row, 'chassis', None)
if chassis:
router = row.datapath.external_ids.get('name', '').replace(
'neutron-', '')
host = chassis[0].hostname
LOG.info("Router %(router)s is bound to host %(host)s",
{'router': router, 'host': host})
self.l3_plugin.update_router_gateway_port_bindings(
router, host)
class LogicalSwitchPortCreateUpEvent(row_event.RowEvent):
"""Row create event - Logical_Switch_Port 'up' = True.
On connection, we get a dump of all ports, so if there is a neutron
port that is down that has since been activated, we'll catch it here.
This event will not be generated for new ports getting created.
"""
def __init__(self, driver):
self.driver = driver
table = 'Logical_Switch_Port'
events = (self.ROW_CREATE,)
super(LogicalSwitchPortCreateUpEvent, self).__init__(
events, table, (('up', '=', True),))
self.event_name = 'LogicalSwitchPortCreateUpEvent'
def run(self, event, row, old):
self.driver.set_port_status_up(row.name)
class LogicalSwitchPortCreateDownEvent(row_event.RowEvent):
"""Row create event - Logical_Switch_Port 'up' = False
On connection, we get a dump of all ports, so if there is a neutron
port that is up that has since been deactivated, we'll catch it here.
This event will not be generated for new ports getting created.
"""
def __init__(self, driver):
self.driver = driver
table = 'Logical_Switch_Port'
events = (self.ROW_CREATE,)
super(LogicalSwitchPortCreateDownEvent, self).__init__(
events, table, (('up', '=', False),))
self.event_name = 'LogicalSwitchPortCreateDownEvent'
def run(self, event, row, old):
self.driver.set_port_status_down(row.name)
class LogicalSwitchPortUpdateUpEvent(row_event.RowEvent):
"""Row update event - Logical_Switch_Port 'up' going from False to True
This happens when the VM goes up.
New value of Logical_Switch_Port 'up' will be True and the old value will
be False.
"""
def __init__(self, driver):
self.driver = driver
table = 'Logical_Switch_Port'
events = (self.ROW_UPDATE,)
super(LogicalSwitchPortUpdateUpEvent, self).__init__(
events, table, (('up', '=', True),),
old_conditions=(('up', '=', False),))
self.event_name = 'LogicalSwitchPortUpdateUpEvent'
def run(self, event, row, old):
self.driver.set_port_status_up(row.name)
class LogicalSwitchPortUpdateDownEvent(row_event.RowEvent):
"""Row update event - Logical_Switch_Port 'up' going from True to False
This happens when the VM goes down.
New value of Logical_Switch_Port 'up' will be False and the old value will
be True.
"""
def __init__(self, driver):
self.driver = driver
table = 'Logical_Switch_Port'
events = (self.ROW_UPDATE,)
super(LogicalSwitchPortUpdateDownEvent, self).__init__(
events, table, (('up', '=', False),),
old_conditions=(('up', '=', True),))
self.event_name = 'LogicalSwitchPortUpdateDownEvent'
def run(self, event, row, old):
self.driver.set_port_status_down(row.name)
class FIPAddDeleteEvent(row_event.RowEvent):
"""Row event - NAT 'dnat_and_snat' entry added or deleted
This happens when a FIP is created or removed.
"""
def __init__(self, driver):
self.driver = driver
table = 'NAT'
events = (self.ROW_CREATE, self.ROW_DELETE)
super(FIPAddDeleteEvent, self).__init__(
events, table, (('type', '=', 'dnat_and_snat'),))
self.event_name = 'FIPAddDeleteEvent'
def run(self, event, row, old):
# When a FIP is added or deleted, we will delete all entries in the
# MAC_Binding table of SB OVSDB corresponding to that IP Address.
# TODO(dalvarez): Remove this workaround once fixed in core OVN:
# https://mail.openvswitch.org/pipermail/ovs-discuss/2018-October/047604.html
self.driver.delete_mac_binding_entries(row.external_ip)
class OvnDbNotifyHandler(event.RowEventHandler):
def __init__(self, driver):
super(OvnDbNotifyHandler, self).__init__()
self.driver = driver
class BaseOvnIdl(connection.OvsdbIdl):
@classmethod
def from_server(cls, connection_string, schema_name):
_check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, schema_name)
helper.register_all()
return cls(connection_string, helper)
class BaseOvnSbIdl(connection.OvsdbIdl):
@classmethod
def from_server(cls, connection_string, schema_name):
_check_and_set_ssl_files(schema_name)
helper = idlutils.get_schema_helper(connection_string, schema_name)
helper.register_table('Chassis')
helper.register_table('Encap')
helper.register_table('Port_Binding')
helper.register_table('Datapath_Binding')
return cls(connection_string, helper)
class OvnIdl(BaseOvnIdl):
def __init__(self, driver, remote, schema):
super(OvnIdl, self).__init__(remote, schema)
self.driver = driver
self.notify_handler = OvnDbNotifyHandler(driver)
# ovsdb lock name to acquire.
# This event lock is used to handle the notify events sent by idl.Idl
# idl.Idl will call notify function for the "update" rpc method it
# receives from the ovsdb-server.
# This event lock is required for the following reasons
# - If there are multiple neutron servers running, OvnWorkers of
# these neutron servers would receive the notify events from
# idl.Idl
#
# - we do not want all the neutron servers to handle these events
#
# - only the neutron server which has the lock will handle the
# notify events.
#
# - In case the neutron server which owns this lock goes down,
# ovsdb server would assign the lock to one of the other neutron
# servers.
self.event_lock_name = "neutron_ovn_event_lock"
def notify(self, event, row, updates=None):
# Do not handle the notification if the event lock is requested,
# but not granted by the ovsdb-server.
if self.is_lock_contended:
return
self.notify_handler.notify(event, row, updates)
@abc.abstractmethod
def post_connect(self):
"""Should be called after the idl has been initialized"""
class OvnIdlDistributedLock(BaseOvnIdl):
def __init__(self, driver, remote, schema):
super(OvnIdlDistributedLock, self).__init__(remote, schema)
self.driver = driver
self.notify_handler = OvnDbNotifyHandler(driver)
self._node_uuid = self.driver.node_uuid
self._hash_ring = hash_ring_manager.HashRingManager(
self.driver.hash_ring_group)
self._last_touch = None
def notify(self, event, row, updates=None):
try:
target_node = self._hash_ring.get_node(str(row.uuid))
except exceptions.HashRingIsEmpty as e:
LOG.error('HashRing is empty, error: %s', e)
return
if target_node != self._node_uuid:
return
# If the worker hasn't been health checked by the maintenance
# thread (see bug #1834498), indicate that it's alive here
time_now = timeutils.utcnow()
touch_timeout = time_now - datetime.timedelta(
seconds=ovn_const.HASH_RING_TOUCH_INTERVAL)
if not self._last_touch or touch_timeout >= self._last_touch:
# NOTE(lucasagomes): Guard the db operation with an exception
# handler. If heartbeating fails for whatever reason, log
# the error and continue with processing the event
try:
ctx = neutron_context.get_admin_context()
ovn_hash_ring_db.touch_node(ctx, self._node_uuid)
self._last_touch = time_now
except Exception:
LOG.exception('Hash Ring node %s failed to heartbeat',
self._node_uuid)
LOG.debug('Hash Ring: Node %(node)s (host: %(hostname)s) '
'handling event "%(event)s" for row %(row)s '
'(table: %(table)s)',
{'node': self._node_uuid, 'hostname': CONF.host,
'event': event, 'row': row.uuid, 'table': row._table.name})
self.notify_handler.notify(event, row, updates)
@abc.abstractmethod
def post_connect(self):
"""Should be called after the idl has been initialized"""
class OvnNbIdl(OvnIdlDistributedLock):
def __init__(self, driver, remote, schema):
super(OvnNbIdl, self).__init__(driver, remote, schema)
self._lsp_update_up_event = LogicalSwitchPortUpdateUpEvent(driver)
self._lsp_update_down_event = LogicalSwitchPortUpdateDownEvent(driver)
self._lsp_create_up_event = LogicalSwitchPortCreateUpEvent(driver)
self._lsp_create_down_event = LogicalSwitchPortCreateDownEvent(driver)
self._fip_create_delete_event = FIPAddDeleteEvent(driver)
self.notify_handler.watch_events([self._lsp_create_up_event,
self._lsp_create_down_event,
self._lsp_update_up_event,
self._lsp_update_down_event,
self._fip_create_delete_event])
@classmethod
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)
helper.register_all()
return cls(driver, connection_string, helper)
def unwatch_logical_switch_port_create_events(self):
"""Unwatch the logical switch port create events.
When the ovs idl client connects to the ovsdb-server, it gets
a dump of all logical switch ports as events and we need to process
them at start up.
After the startup, there is no need to watch these events.
So unwatch these events.
"""
self.notify_handler.unwatch_events([self._lsp_create_up_event,
self._lsp_create_down_event])
self._lsp_create_up_event = None
self._lsp_create_down_event = None
def post_connect(self):
self.unwatch_logical_switch_port_create_events()
class OvnSbIdl(OvnIdlDistributedLock):
@classmethod
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)
helper.register_table('Chassis')
helper.register_table('Encap')
helper.register_table('Port_Binding')
helper.register_table('Datapath_Binding')
helper.register_table('MAC_Binding')
return cls(driver, connection_string, helper)
def post_connect(self):
"""Watch Chassis events.
When the ovs idl client connects to the ovsdb-server, it gets
a dump of all Chassis create event. We don't need to process them
because there will be sync up at startup. After that, we will watch
the events to make notify work.
"""
self._chassis_event = ChassisEvent(self.driver)
self._portbinding_event = PortBindingChassisEvent(self.driver)
self.notify_handler.watch_events(
[self._chassis_event, self._portbinding_event,
PortBindingChassisUpdateEvent(self.driver)])
def _check_and_set_ssl_files(schema_name):
if schema_name == 'OVN_Southbound':
priv_key_file = ovn_conf.get_ovn_sb_private_key()
cert_file = ovn_conf.get_ovn_sb_certificate()
ca_cert_file = ovn_conf.get_ovn_sb_ca_cert()
else:
priv_key_file = ovn_conf.get_ovn_nb_private_key()
cert_file = ovn_conf.get_ovn_nb_certificate()
ca_cert_file = ovn_conf.get_ovn_nb_ca_cert()
if priv_key_file:
Stream.ssl_set_private_key_file(priv_key_file)
if cert_file:
Stream.ssl_set_certificate_file(cert_file)
if ca_cert_file:
Stream.ssl_set_ca_cert_file(ca_cert_file)

View File

@ -0,0 +1,449 @@
{
"name": "OVN_Northbound",
"version": "5.16.0",
"cksum": "923459061 23095",
"tables": {
"NB_Global": {
"columns": {
"nb_cfg": {"type": {"key": "integer"}},
"sb_cfg": {"type": {"key": "integer"}},
"hv_cfg": {"type": {"key": "integer"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"connections": {
"type": {"key": {"type": "uuid",
"refTable": "Connection"},
"min": 0,
"max": "unlimited"}},
"ssl": {
"type": {"key": {"type": "uuid",
"refTable": "SSL"},
"min": 0, "max": 1}},
"options": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"ipsec": {"type": "boolean"}},
"maxRows": 1,
"isRoot": true},
"Logical_Switch": {
"columns": {
"name": {"type": "string"},
"ports": {"type": {"key": {"type": "uuid",
"refTable": "Logical_Switch_Port",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"acls": {"type": {"key": {"type": "uuid",
"refTable": "ACL",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"qos_rules": {"type": {"key": {"type": "uuid",
"refTable": "QoS",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"load_balancer": {"type": {"key": {"type": "uuid",
"refTable": "Load_Balancer",
"refType": "weak"},
"min": 0,
"max": "unlimited"}},
"dns_records": {"type": {"key": {"type": "uuid",
"refTable": "DNS",
"refType": "weak"},
"min": 0,
"max": "unlimited"}},
"other_config": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"Logical_Switch_Port": {
"columns": {
"name": {"type": "string"},
"type": {"type": "string"},
"options": {
"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"parent_name": {"type": {"key": "string", "min": 0, "max": 1}},
"tag_request": {
"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 4095},
"min": 0, "max": 1}},
"tag": {
"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 4095},
"min": 0, "max": 1}},
"addresses": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"dynamic_addresses": {"type": {"key": "string",
"min": 0,
"max": 1}},
"port_security": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"up": {"type": {"key": "boolean", "min": 0, "max": 1}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
"dhcpv4_options": {"type": {"key": {"type": "uuid",
"refTable": "DHCP_Options",
"refType": "weak"},
"min": 0,
"max": 1}},
"dhcpv6_options": {"type": {"key": {"type": "uuid",
"refTable": "DHCP_Options",
"refType": "weak"},
"min": 0,
"max": 1}},
"ha_chassis_group": {
"type": {"key": {"type": "uuid",
"refTable": "HA_Chassis_Group",
"refType": "strong"},
"min": 0,
"max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": false},
"Address_Set": {
"columns": {
"name": {"type": "string"},
"addresses": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Port_Group": {
"columns": {
"name": {"type": "string"},
"ports": {"type": {"key": {"type": "uuid",
"refTable": "Logical_Switch_Port",
"refType": "weak"},
"min": 0,
"max": "unlimited"}},
"acls": {"type": {"key": {"type": "uuid",
"refTable": "ACL",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Load_Balancer": {
"columns": {
"name": {"type": "string"},
"vips": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"protocol": {
"type": {"key": {"type": "string",
"enum": ["set", ["tcp", "udp"]]},
"min": 0, "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"ACL": {
"columns": {
"name": {"type": {"key": {"type": "string",
"maxLength": 63},
"min": 0, "max": 1}},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"direction": {"type": {"key": {"type": "string",
"enum": ["set", ["from-lport", "to-lport"]]}}},
"match": {"type": "string"},
"action": {"type": {"key": {"type": "string",
"enum": ["set", ["allow", "allow-related", "drop", "reject"]]}}},
"log": {"type": "boolean"},
"severity": {"type": {"key": {"type": "string",
"enum": ["set",
["alert", "warning",
"notice", "info",
"debug"]]},
"min": 0, "max": 1}},
"meter": {"type": {"key": "string", "min": 0, "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"QoS": {
"columns": {
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"direction": {"type": {"key": {"type": "string",
"enum": ["set", ["from-lport", "to-lport"]]}}},
"match": {"type": "string"},
"action": {"type": {"key": {"type": "string",
"enum": ["set", ["dscp"]]},
"value": {"type": "integer",
"minInteger": 0,
"maxInteger": 63},
"min": 0, "max": "unlimited"}},
"bandwidth": {"type": {"key": {"type": "string",
"enum": ["set", ["rate",
"burst"]]},
"value": {"type": "integer",
"minInteger": 1,
"maxInteger": 4294967295},
"min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"Meter": {
"columns": {
"name": {"type": "string"},
"unit": {"type": {"key": {"type": "string",
"enum": ["set", ["kbps", "pktps"]]}}},
"bands": {"type": {"key": {"type": "uuid",
"refTable": "Meter_Band",
"refType": "strong"},
"min": 1,
"max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Meter_Band": {
"columns": {
"action": {"type": {"key": {"type": "string",
"enum": ["set", ["drop"]]}}},
"rate": {"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 4294967295}}},
"burst_size": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 4294967295}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"Logical_Router": {
"columns": {
"name": {"type": "string"},
"ports": {"type": {"key": {"type": "uuid",
"refTable": "Logical_Router_Port",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"static_routes": {"type": {"key": {"type": "uuid",
"refTable": "Logical_Router_Static_Route",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"policies": {
"type": {"key": {"type": "uuid",
"refTable": "Logical_Router_Policy",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
"nat": {"type": {"key": {"type": "uuid",
"refTable": "NAT",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"load_balancer": {"type": {"key": {"type": "uuid",
"refTable": "Load_Balancer",
"refType": "weak"},
"min": 0,
"max": "unlimited"}},
"options": {
"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"Logical_Router_Port": {
"columns": {
"name": {"type": "string"},
"gateway_chassis": {
"type": {"key": {"type": "uuid",
"refTable": "Gateway_Chassis",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"ha_chassis_group": {
"type": {"key": {"type": "uuid",
"refTable": "HA_Chassis_Group",
"refType": "strong"},
"min": 0,
"max": 1}},
"options": {
"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"networks": {"type": {"key": "string",
"min": 1,
"max": "unlimited"}},
"mac": {"type": "string"},
"peer": {"type": {"key": "string", "min": 0, "max": 1}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
"ipv6_ra_configs": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": false},
"Logical_Router_Static_Route": {
"columns": {
"ip_prefix": {"type": "string"},
"policy": {"type": {"key": {"type": "string",
"enum": ["set", ["src-ip",
"dst-ip"]]},
"min": 0, "max": 1}},
"nexthop": {"type": "string"},
"output_port": {"type": {"key": "string", "min": 0, "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"Logical_Router_Policy": {
"columns": {
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"match": {"type": "string"},
"action": {"type": {
"key": {"type": "string",
"enum": ["set", ["allow", "drop", "reroute"]]}}},
"nexthop": {"type": {"key": "string", "min": 0, "max": 1}}},
"isRoot": false},
"NAT": {
"columns": {
"external_ip": {"type": "string"},
"external_mac": {"type": {"key": "string",
"min": 0, "max": 1}},
"logical_ip": {"type": "string"},
"logical_port": {"type": {"key": "string",
"min": 0, "max": 1}},
"type": {"type": {"key": {"type": "string",
"enum": ["set", ["dnat",
"snat",
"dnat_and_snat"
]]}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"DHCP_Options": {
"columns": {
"cidr": {"type": "string"},
"options": {"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"Connection": {
"columns": {
"target": {"type": "string"},
"max_backoff": {"type": {"key": {"type": "integer",
"minInteger": 1000},
"min": 0,
"max": 1}},
"inactivity_probe": {"type": {"key": "integer",
"min": 0,
"max": 1}},
"other_config": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"is_connected": {"type": "boolean", "ephemeral": true},
"status": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"},
"ephemeral": true}},
"indexes": [["target"]]},
"DNS": {
"columns": {
"records": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}}},
"isRoot": true},
"SSL": {
"columns": {
"private_key": {"type": "string"},
"certificate": {"type": "string"},
"ca_cert": {"type": "string"},
"bootstrap_ca_cert": {"type": "boolean"},
"ssl_protocols": {"type": "string"},
"ssl_ciphers": {"type": "string"},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}}},
"maxRows": 1},
"Gateway_Chassis": {
"columns": {
"name": {"type": "string"},
"chassis_name": {"type": "string"},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"options": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": false},
"HA_Chassis": {
"columns": {
"chassis_name": {"type": "string"},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"HA_Chassis_Group": {
"columns": {
"name": {"type": "string"},
"ha_chassis": {
"type": {"key": {"type": "uuid",
"refTable": "HA_Chassis",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true}}
}

View File

@ -0,0 +1,404 @@
{
"name": "OVN_Southbound",
"version": "2.4.0",
"cksum": "3059284885 20260",
"tables": {
"SB_Global": {
"columns": {
"nb_cfg": {"type": {"key": "integer"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"connections": {
"type": {"key": {"type": "uuid",
"refTable": "Connection"},
"min": 0,
"max": "unlimited"}},
"ssl": {
"type": {"key": {"type": "uuid",
"refTable": "SSL"},
"min": 0, "max": 1}},
"options": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"ipsec": {"type": "boolean"}},
"maxRows": 1,
"isRoot": true},
"Chassis": {
"columns": {
"name": {"type": "string"},
"hostname": {"type": "string"},
"encaps": {"type": {"key": {"type": "uuid",
"refTable": "Encap"},
"min": 1, "max": "unlimited"}},
"vtep_logical_switches" : {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"nb_cfg": {"type": {"key": "integer"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"transport_zones" : {"type": {"key": "string",
"min": 0,
"max": "unlimited"}}},
"isRoot": true,
"indexes": [["name"]]},
"Encap": {
"columns": {
"type": {"type": {"key": {
"type": "string",
"enum": ["set", ["geneve", "stt", "vxlan"]]}}},
"options": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"ip": {"type": "string"},
"chassis_name": {"type": "string"}},
"indexes": [["type", "ip"]]},
"Address_Set": {
"columns": {
"name": {"type": "string"},
"addresses": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Port_Group": {
"columns": {
"name": {"type": "string"},
"ports": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Logical_Flow": {
"columns": {
"logical_datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding"}}},
"pipeline": {"type": {"key": {"type": "string",
"enum": ["set", ["ingress",
"egress"]]}}},
"table_id": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 23}}},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 65535}}},
"match": {"type": "string"},
"actions": {"type": "string"},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"Multicast_Group": {
"columns": {
"datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding"}}},
"name": {"type": "string"},
"tunnel_key": {
"type": {"key": {"type": "integer",
"minInteger": 32768,
"maxInteger": 65535}}},
"ports": {"type": {"key": {"type": "uuid",
"refTable": "Port_Binding",
"refType": "weak"},
"min": 1, "max": "unlimited"}}},
"indexes": [["datapath", "tunnel_key"],
["datapath", "name"]],
"isRoot": true},
"Meter": {
"columns": {
"name": {"type": "string"},
"unit": {"type": {"key": {"type": "string",
"enum": ["set", ["kbps", "pktps"]]}}},
"bands": {"type": {"key": {"type": "uuid",
"refTable": "Meter_Band",
"refType": "strong"},
"min": 1,
"max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Meter_Band": {
"columns": {
"action": {"type": {"key": {"type": "string",
"enum": ["set", ["drop"]]}}},
"rate": {"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 4294967295}}},
"burst_size": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 4294967295}}}},
"isRoot": false},
"Datapath_Binding": {
"columns": {
"tunnel_key": {
"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 16777215}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["tunnel_key"]],
"isRoot": true},
"Port_Binding": {
"columns": {
"logical_port": {"type": "string"},
"type": {"type": "string"},
"gateway_chassis": {
"type": {"key": {"type": "uuid",
"refTable": "Gateway_Chassis",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"ha_chassis_group": {
"type": {"key": {"type": "uuid",
"refTable": "HA_Chassis_Group",
"refType": "strong"},
"min": 0,
"max": 1}},
"options": {
"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding"}}},
"tunnel_key": {
"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 32767}}},
"parent_port": {"type": {"key": "string", "min": 0, "max": 1}},
"tag": {
"type": {"key": {"type": "integer",
"minInteger": 1,
"maxInteger": 4095},
"min": 0, "max": 1}},
"chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0, "max": 1}},
"encap": {"type": {"key": {"type": "uuid",
"refTable": "Encap",
"refType": "weak"},
"min": 0, "max": 1}},
"mac": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"nat_addresses": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}}},
"indexes": [["datapath", "tunnel_key"], ["logical_port"]],
"isRoot": true},
"MAC_Binding": {
"columns": {
"logical_port": {"type": "string"},
"ip": {"type": "string"},
"mac": {"type": "string"},
"datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding"}}}},
"indexes": [["logical_port", "ip"]],
"isRoot": true},
"DHCP_Options": {
"columns": {
"name": {"type": "string"},
"code": {
"type": {"key": {"type": "integer",
"minInteger": 0, "maxInteger": 254}}},
"type": {
"type": {"key": {
"type": "string",
"enum": ["set", ["bool", "uint8", "uint16", "uint32",
"ipv4", "static_routes", "str"]]}}}},
"isRoot": true},
"DHCPv6_Options": {
"columns": {
"name": {"type": "string"},
"code": {
"type": {"key": {"type": "integer",
"minInteger": 0, "maxInteger": 254}}},
"type": {
"type": {"key": {
"type": "string",
"enum": ["set", ["ipv6", "str", "mac"]]}}}},
"isRoot": true},
"Connection": {
"columns": {
"target": {"type": "string"},
"max_backoff": {"type": {"key": {"type": "integer",
"minInteger": 1000},
"min": 0,
"max": 1}},
"inactivity_probe": {"type": {"key": "integer",
"min": 0,
"max": 1}},
"read_only": {"type": "boolean"},
"role": {"type": "string"},
"other_config": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"is_connected": {"type": "boolean", "ephemeral": true},
"status": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"},
"ephemeral": true}},
"indexes": [["target"]]},
"SSL": {
"columns": {
"private_key": {"type": "string"},
"certificate": {"type": "string"},
"ca_cert": {"type": "string"},
"bootstrap_ca_cert": {"type": "boolean"},
"ssl_protocols": {"type": "string"},
"ssl_ciphers": {"type": "string"},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}}},
"maxRows": 1},
"DNS": {
"columns": {
"records": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}},
"datapaths": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding"},
"min": 1,
"max": "unlimited"}},
"external_ids": {"type": {"key": "string",
"value": "string",
"min": 0,
"max": "unlimited"}}},
"isRoot": true},
"RBAC_Role": {
"columns": {
"name": {"type": "string"},
"permissions": {
"type": {"key": {"type": "string"},
"value": {"type": "uuid",
"refTable": "RBAC_Permission",
"refType": "weak"},
"min": 0, "max": "unlimited"}}},
"isRoot": true},
"RBAC_Permission": {
"columns": {
"table": {"type": "string"},
"authorization": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"insert_delete": {"type": "boolean"},
"update" : {"type": {"key": "string",
"min": 0,
"max": "unlimited"}}},
"isRoot": true},
"Gateway_Chassis": {
"columns": {
"name": {"type": "string"},
"chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0, "max": 1}},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"options": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": false},
"HA_Chassis": {
"columns": {
"chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0, "max": 1}},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
"HA_Chassis_Group": {
"columns": {
"name": {"type": "string"},
"ha_chassis": {
"type": {"key": {"type": "uuid",
"refTable": "HA_Chassis",
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
"ref_chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"indexes": [["name"]],
"isRoot": true},
"Controller_Event": {
"columns": {
"event_type": {"type": {"key": {"type": "string",
"enum": ["set", ["empty_lb_backends"]]}}},
"event_info": {"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0, "max": 1}},
"seq_num": {"type": {"key": "integer"}}
},
"isRoot": true},
"IP_Multicast": {
"columns": {
"datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding",
"refType": "weak"}}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
"querier": {"type": {"key": "boolean", "min": 0, "max": 1}},
"eth_src": {"type": "string"},
"ip4_src": {"type": "string"},
"table_size": {"type": {"key": "integer",
"min": 0, "max": 1}},
"idle_timeout": {"type": {"key": "integer",
"min": 0, "max": 1}},
"query_interval": {"type": {"key": "integer",
"min": 0, "max": 1}},
"query_max_resp": {"type": {"key": "integer",
"min": 0, "max": 1}},
"seq_no": {"type": "integer"}},
"indexes": [["datapath"]],
"isRoot": true},
"IGMP_Group": {
"columns": {
"address": {"type": "string"},
"datapath": {"type": {"key": {"type": "uuid",
"refTable": "Datapath_Binding",
"refType": "weak"},
"min": 0,
"max": 1}},
"chassis": {"type": {"key": {"type": "uuid",
"refTable": "Chassis",
"refType": "weak"},
"min": 0,
"max": 1}},
"ports": {"type": {"key": {"type": "uuid",
"refTable": "Port_Binding",
"refType": "weak"},
"min": 0, "max": "unlimited"}}},
"indexes": [["address", "datapath", "chassis"]],
"isRoot": true}}}

View File

@ -0,0 +1,773 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import collections
import copy
import uuid
import mock
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn
from neutron.tests import base
from neutron.tests.unit import fake_resources as fakes
class TestDBImplIdlOvn(base.BaseTestCase):
def _load_ovsdb_fake_rows(self, table, fake_attrs):
for fake_attr in fake_attrs:
fake_row = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs=fake_attr)
# Pre-populate ovs idl "._data"
fake_data = copy.deepcopy(fake_attr)
try:
del fake_data["unit_test_id"]
except KeyError:
pass
setattr(fake_row, "_data", fake_data)
table.rows[fake_row.uuid] = fake_row
def _find_ovsdb_fake_row(self, table, key, value):
for fake_row in table.rows.values():
if getattr(fake_row, key) == value:
return fake_row
return None
def _construct_ovsdb_references(self, fake_associations,
parent_table, child_table,
parent_key, child_key,
reference_column_name):
for p_name, c_names in fake_associations.items():
p_row = self._find_ovsdb_fake_row(parent_table, parent_key, p_name)
c_uuids = []
for c_name in c_names:
c_row = self._find_ovsdb_fake_row(child_table, child_key,
c_name)
if not c_row:
continue
# Fake IDL processing (uuid -> row)
c_uuids.append(c_row)
setattr(p_row, reference_column_name, c_uuids)
class TestNBImplIdlOvn(TestDBImplIdlOvn):
fake_set = {
'lswitches': [
{'name': utils.ovn_name('ls-id-1'),
'external_ids': {ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
'ls-name-1'}},
{'name': utils.ovn_name('ls-id-2'),
'external_ids': {ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
'ls-name-2'}},
{'name': utils.ovn_name('ls-id-3'),
'external_ids': {ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
'ls-name-3'}},
{'name': 'ls-id-4',
'external_ids': {'not-neutron:network_name': 'ls-name-4'}},
{'name': utils.ovn_name('ls-id-5'),
'external_ids': {ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
'ls-name-5'}}],
'lswitch_ports': [
{'name': 'lsp-id-11', 'addresses': ['10.0.1.1'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-11'}},
{'name': 'lsp-id-12', 'addresses': ['10.0.1.2'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-12'}},
{'name': 'lsp-rp-id-1', 'addresses': ['10.0.1.254'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-rp-name-1'},
'options': {'router-port':
utils.ovn_lrouter_port_name('orp-id-a1')}},
{'name': 'provnet-ls-id-1', 'addresses': ['unknown'],
'external_ids': {},
'options': {'network_name': 'physnet1'}},
{'name': 'lsp-id-21', 'addresses': ['10.0.2.1'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-21'}},
{'name': 'lsp-id-22', 'addresses': ['10.0.2.2'],
'external_ids': {}},
{'name': 'lsp-id-23', 'addresses': ['10.0.2.3'],
'external_ids': {'not-neutron:port_name': 'lsp-name-23'}},
{'name': 'lsp-rp-id-2', 'addresses': ['10.0.2.254'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-rp-name-2'},
'options': {'router-port':
utils.ovn_lrouter_port_name('orp-id-a2')}},
{'name': 'provnet-ls-id-2', 'addresses': ['unknown'],
'external_ids': {},
'options': {'network_name': 'physnet2'}},
{'name': 'lsp-id-31', 'addresses': ['10.0.3.1'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-31'}},
{'name': 'lsp-id-32', 'addresses': ['10.0.3.2'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-32'}},
{'name': 'lsp-rp-id-3', 'addresses': ['10.0.3.254'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-rp-name-3'},
'options': {'router-port':
utils.ovn_lrouter_port_name('orp-id-a3')}},
{'name': 'lsp-vpn-id-3', 'addresses': ['10.0.3.253'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-vpn-name-3'}},
{'name': 'lsp-id-41', 'addresses': ['20.0.1.1'],
'external_ids': {'not-neutron:port_name': 'lsp-name-41'}},
{'name': 'lsp-rp-id-4', 'addresses': ['20.0.1.254'],
'external_ids': {},
'options': {'router-port': 'xrp-id-b1'}},
{'name': 'lsp-id-51', 'addresses': ['20.0.2.1'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-51'}},
{'name': 'lsp-id-52', 'addresses': ['20.0.2.2'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-name-52'}},
{'name': 'lsp-rp-id-5', 'addresses': ['20.0.2.254'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-rp-name-5'},
'options': {'router-port':
utils.ovn_lrouter_port_name('orp-id-b2')}},
{'name': 'lsp-vpn-id-5', 'addresses': ['20.0.2.253'],
'external_ids': {ovn_const.OVN_PORT_NAME_EXT_ID_KEY:
'lsp-vpn-name-5'}}],
'lrouters': [
{'name': utils.ovn_name('lr-id-a'),
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
'lr-name-a'}},
{'name': utils.ovn_name('lr-id-b'),
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
'lr-name-b'}},
{'name': utils.ovn_name('lr-id-c'),
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
'lr-name-c'}},
{'name': utils.ovn_name('lr-id-d'),
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
'lr-name-d'}},
{'name': utils.ovn_name('lr-id-e'),
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
'lr-name-e'}}],
'lrouter_ports': [
{'name': utils.ovn_lrouter_port_name('orp-id-a1'),
'external_ids': {}, 'networks': ['10.0.1.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-1'}},
{'name': utils.ovn_lrouter_port_name('orp-id-a2'),
'external_ids': {}, 'networks': ['10.0.2.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-1'}},
{'name': utils.ovn_lrouter_port_name('orp-id-a3'),
'external_ids': {}, 'networks': ['10.0.3.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY:
ovn_const.OVN_GATEWAY_INVALID_CHASSIS}},
{'name': 'xrp-id-b1',
'external_ids': {}, 'networks': ['20.0.1.0/24']},
{'name': utils.ovn_lrouter_port_name('orp-id-b2'),
'external_ids': {}, 'networks': ['20.0.2.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'}},
{'name': utils.ovn_lrouter_port_name('orp-id-b3'),
'external_ids': {}, 'networks': ['20.0.3.0/24'],
'options': {}}],
'static_routes': [{'ip_prefix': '20.0.0.0/16',
'nexthop': '10.0.3.253'},
{'ip_prefix': '10.0.0.0/16',
'nexthop': '20.0.2.253'}],
'nats': [{'external_ip': '10.0.3.1', 'logical_ip': '20.0.0.0/16',
'type': 'snat'},
{'external_ip': '20.0.2.1', 'logical_ip': '10.0.0.0/24',
'type': 'snat'},
{'external_ip': '20.0.2.4', 'logical_ip': '10.0.0.4',
'type': 'dnat_and_snat', 'external_mac': [],
'logical_port': []},
{'external_ip': '20.0.2.5', 'logical_ip': '10.0.0.5',
'type': 'dnat_and_snat',
'external_mac': ['00:01:02:03:04:05'],
'logical_port': ['lsp-id-001']}],
'acls': [
{'unit_test_id': 1,
'action': 'allow-related', 'direction': 'from-lport',
'external_ids': {'neutron:lport': 'lsp-id-11'},
'match': 'inport == "lsp-id-11" && ip4'},
{'unit_test_id': 2,
'action': 'allow-related', 'direction': 'to-lport',
'external_ids': {'neutron:lport': 'lsp-id-11'},
'match': 'outport == "lsp-id-11" && ip4.src == $as_ip4_id_1'},
{'unit_test_id': 3,
'action': 'allow-related', 'direction': 'from-lport',
'external_ids': {'neutron:lport': 'lsp-id-12'},
'match': 'inport == "lsp-id-12" && ip4'},
{'unit_test_id': 4,
'action': 'allow-related', 'direction': 'to-lport',
'external_ids': {'neutron:lport': 'lsp-id-12'},
'match': 'outport == "lsp-id-12" && ip4.src == $as_ip4_id_1'},
{'unit_test_id': 5,
'action': 'allow-related', 'direction': 'from-lport',
'external_ids': {'neutron:lport': 'lsp-id-21'},
'match': 'inport == "lsp-id-21" && ip4'},
{'unit_test_id': 6,
'action': 'allow-related', 'direction': 'to-lport',
'external_ids': {'neutron:lport': 'lsp-id-21'},
'match': 'outport == "lsp-id-21" && ip4.src == $as_ip4_id_2'},
{'unit_test_id': 7,
'action': 'allow-related', 'direction': 'from-lport',
'external_ids': {'neutron:lport': 'lsp-id-41'},
'match': 'inport == "lsp-id-41" && ip4'},
{'unit_test_id': 8,
'action': 'allow-related', 'direction': 'to-lport',
'external_ids': {'neutron:lport': 'lsp-id-41'},
'match': 'outport == "lsp-id-41" && ip4.src == $as_ip4_id_4'},
{'unit_test_id': 9,
'action': 'allow-related', 'direction': 'from-lport',
'external_ids': {'neutron:lport': 'lsp-id-52'},
'match': 'inport == "lsp-id-52" && ip4'},
{'unit_test_id': 10,
'action': 'allow-related', 'direction': 'to-lport',
'external_ids': {'neutron:lport': 'lsp-id-52'},
'match': 'outport == "lsp-id-52" && ip4.src == $as_ip4_id_5'}],
'dhcp_options': [
{'cidr': '10.0.1.0/24',
'external_ids': {'subnet_id': 'subnet-id-10-0-1-0'},
'options': {'mtu': '1442', 'router': '10.0.1.254'}},
{'cidr': '10.0.2.0/24',
'external_ids': {'subnet_id': 'subnet-id-10-0-2-0'},
'options': {'mtu': '1442', 'router': '10.0.2.254'}},
{'cidr': '10.0.1.0/26',
'external_ids': {'subnet_id': 'subnet-id-10-0-1-0',
'port_id': 'lsp-vpn-id-3'},
'options': {'mtu': '1442', 'router': '10.0.1.1'}},
{'cidr': '20.0.1.0/24',
'external_ids': {'subnet_id': 'subnet-id-20-0-1-0'},
'options': {'mtu': '1442', 'router': '20.0.1.254'}},
{'cidr': '20.0.2.0/24',
'external_ids': {'subnet_id': 'subnet-id-20-0-2-0',
'port_id': 'lsp-vpn-id-5'},
'options': {'mtu': '1442', 'router': '20.0.2.254'}},
{'cidr': '2001:dba::/64',
'external_ids': {'subnet_id': 'subnet-id-2001-dba',
'port_id': 'lsp-vpn-id-5'},
'options': {'server_id': '12:34:56:78:9a:bc'}},
{'cidr': '30.0.1.0/24',
'external_ids': {'port_id': 'port-id-30-0-1-0'},
'options': {'mtu': '1442', 'router': '30.0.2.254'}},
{'cidr': '30.0.2.0/24', 'external_ids': {}, 'options': {}}],
'address_sets': [
{'name': '$as_ip4_id_1',
'addresses': ['10.0.1.1', '10.0.1.2'],
'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'id_1'}},
{'name': '$as_ip4_id_2',
'addresses': ['10.0.2.1'],
'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'id_2'}},
{'name': '$as_ip4_id_3',
'addresses': ['10.0.3.1', '10.0.3.2'],
'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'id_3'}},
{'name': '$as_ip4_id_4',
'addresses': ['20.0.1.1', '20.0.1.2'],
'external_ids': {}},
{'name': '$as_ip4_id_5',
'addresses': ['20.0.2.1', '20.0.2.2'],
'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'id_5'}}],
}
fake_associations = {
'lstolsp': {
utils.ovn_name('ls-id-1'): [
'lsp-id-11', 'lsp-id-12', 'lsp-rp-id-1', 'provnet-ls-id-1'],
utils.ovn_name('ls-id-2'): [
'lsp-id-21', 'lsp-id-22', 'lsp-id-23', 'lsp-rp-id-2',
'provnet-ls-id-2'],
utils.ovn_name('ls-id-3'): [
'lsp-id-31', 'lsp-id-32', 'lsp-rp-id-3', 'lsp-vpn-id-3'],
'ls-id-4': [
'lsp-id-41', 'lsp-rp-id-4'],
utils.ovn_name('ls-id-5'): [
'lsp-id-51', 'lsp-id-52', 'lsp-rp-id-5', 'lsp-vpn-id-5']},
'lrtolrp': {
utils.ovn_name('lr-id-a'): [
utils.ovn_lrouter_port_name('orp-id-a1'),
utils.ovn_lrouter_port_name('orp-id-a2'),
utils.ovn_lrouter_port_name('orp-id-a3')],
utils.ovn_name('lr-id-b'): [
'xrp-id-b1',
utils.ovn_lrouter_port_name('orp-id-b2')]},
'lrtosroute': {
utils.ovn_name('lr-id-a'): ['20.0.0.0/16'],
utils.ovn_name('lr-id-b'): ['10.0.0.0/16']
},
'lrtonat': {
utils.ovn_name('lr-id-a'): ['10.0.3.1'],
utils.ovn_name('lr-id-b'): ['20.0.2.1', '20.0.2.4', '20.0.2.5'],
},
'lstoacl': {
utils.ovn_name('ls-id-1'): [1, 2, 3, 4],
utils.ovn_name('ls-id-2'): [5, 6],
'ls-id-4': [7, 8],
utils.ovn_name('ls-id-5'): [9, 10]}
}
def setUp(self):
super(TestNBImplIdlOvn, self).setUp()
self.lswitch_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.lsp_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.lrouter_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.lrp_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.sroute_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.nat_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.acl_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.dhcp_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self.address_set_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self._tables = {}
self._tables['Logical_Switch'] = self.lswitch_table
self._tables['Logical_Switch_Port'] = self.lsp_table
self._tables['Logical_Router'] = self.lrouter_table
self._tables['Logical_Router_Port'] = self.lrp_table
self._tables['Logical_Router_Static_Route'] = self.sroute_table
self._tables['ACL'] = self.acl_table
self._tables['DHCP_Options'] = self.dhcp_table
self._tables['Address_Set'] = self.address_set_table
with mock.patch.object(impl_idl_ovn, 'get_connection',
return_value=mock.Mock()):
impl_idl_ovn.OvsdbNbOvnIdl.ovsdb_connection = None
self.nb_ovn_idl = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock())
self.nb_ovn_idl.idl.tables = self._tables
def _load_nb_db(self):
# Load Switches and Switch Ports
fake_lswitches = TestNBImplIdlOvn.fake_set['lswitches']
self._load_ovsdb_fake_rows(self.lswitch_table, fake_lswitches)
fake_lsps = TestNBImplIdlOvn.fake_set['lswitch_ports']
self._load_ovsdb_fake_rows(self.lsp_table, fake_lsps)
# Associate switches and ports
self._construct_ovsdb_references(
TestNBImplIdlOvn.fake_associations['lstolsp'],
self.lswitch_table, self.lsp_table,
'name', 'name', 'ports')
# Load Routers and Router Ports
fake_lrouters = TestNBImplIdlOvn.fake_set['lrouters']
self._load_ovsdb_fake_rows(self.lrouter_table, fake_lrouters)
fake_lrps = TestNBImplIdlOvn.fake_set['lrouter_ports']
self._load_ovsdb_fake_rows(self.lrp_table, fake_lrps)
# Associate routers and router ports
self._construct_ovsdb_references(
TestNBImplIdlOvn.fake_associations['lrtolrp'],
self.lrouter_table, self.lrp_table,
'name', 'name', 'ports')
# Load static routes
fake_sroutes = TestNBImplIdlOvn.fake_set['static_routes']
self._load_ovsdb_fake_rows(self.sroute_table, fake_sroutes)
# Associate routers and static routes
self._construct_ovsdb_references(
TestNBImplIdlOvn.fake_associations['lrtosroute'],
self.lrouter_table, self.sroute_table,
'name', 'ip_prefix', 'static_routes')
# Load nats
fake_nats = TestNBImplIdlOvn.fake_set['nats']
self._load_ovsdb_fake_rows(self.nat_table, fake_nats)
# Associate routers and nats
self._construct_ovsdb_references(
TestNBImplIdlOvn.fake_associations['lrtonat'],
self.lrouter_table, self.nat_table,
'name', 'external_ip', 'nat')
# Load acls
fake_acls = TestNBImplIdlOvn.fake_set['acls']
self._load_ovsdb_fake_rows(self.acl_table, fake_acls)
# Associate switches and acls
self._construct_ovsdb_references(
TestNBImplIdlOvn.fake_associations['lstoacl'],
self.lswitch_table, self.acl_table,
'name', 'unit_test_id', 'acls')
# Load dhcp options
fake_dhcp_options = TestNBImplIdlOvn.fake_set['dhcp_options']
self._load_ovsdb_fake_rows(self.dhcp_table, fake_dhcp_options)
# Load address sets
fake_address_sets = TestNBImplIdlOvn.fake_set['address_sets']
self._load_ovsdb_fake_rows(self.address_set_table, fake_address_sets)
@mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'ovsdb_connection', None)
@mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock())
def test_setting_ovsdb_probe_timeout_default_value(self):
inst = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock())
inst.idl._session.reconnect.set_probe_interval.assert_called_with(
60000)
@mock.patch.object(impl_idl_ovn.OvsdbNbOvnIdl, 'ovsdb_connection', None)
@mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock())
@mock.patch.object(ovn_conf, 'get_ovn_ovsdb_probe_interval')
def test_setting_ovsdb_probe_timeout(self, mock_get_probe_interval):
mock_get_probe_interval.return_value = 5000
inst = impl_idl_ovn.OvsdbNbOvnIdl(mock.Mock())
inst.idl._session.reconnect.set_probe_interval.assert_called_with(5000)
def test_get_all_logical_switches_with_ports(self):
# Test empty
mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
self.assertItemsEqual(mapping, {})
# Test loaded values
self._load_nb_db()
mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
expected = [{'name': utils.ovn_name('ls-id-1'),
'ports': ['lsp-id-11', 'lsp-id-12', 'lsp-rp-id-1'],
'provnet_port': 'provnet-ls-id-1'},
{'name': utils.ovn_name('ls-id-2'),
'ports': ['lsp-id-21', 'lsp-rp-id-2'],
'provnet_port': 'provnet-ls-id-2'},
{'name': utils.ovn_name('ls-id-3'),
'ports': ['lsp-id-31', 'lsp-id-32', 'lsp-rp-id-3',
'lsp-vpn-id-3'],
'provnet_port': None},
{'name': utils.ovn_name('ls-id-5'),
'ports': ['lsp-id-51', 'lsp-id-52', 'lsp-rp-id-5',
'lsp-vpn-id-5'],
'provnet_port': None}]
self.assertItemsEqual(mapping, expected)
def test_get_all_logical_routers_with_rports(self):
# Test empty
mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
self.assertItemsEqual(mapping, {})
# Test loaded values
self._load_nb_db()
mapping = self.nb_ovn_idl.get_all_logical_routers_with_rports()
expected = [{'name': 'lr-id-a',
'ports': {'orp-id-a1': ['10.0.1.0/24'],
'orp-id-a2': ['10.0.2.0/24'],
'orp-id-a3': ['10.0.3.0/24']},
'static_routes': [{'destination': '20.0.0.0/16',
'nexthop': '10.0.3.253'}],
'snats': [{'external_ip': '10.0.3.1',
'logical_ip': '20.0.0.0/16',
'type': 'snat'}],
'dnat_and_snats': []},
{'name': 'lr-id-b',
'ports': {'xrp-id-b1': ['20.0.1.0/24'],
'orp-id-b2': ['20.0.2.0/24']},
'static_routes': [{'destination': '10.0.0.0/16',
'nexthop': '20.0.2.253'}],
'snats': [{'external_ip': '20.0.2.1',
'logical_ip': '10.0.0.0/24',
'type': 'snat'}],
'dnat_and_snats': [{'external_ip': '20.0.2.4',
'logical_ip': '10.0.0.4',
'type': 'dnat_and_snat'},
{'external_ip': '20.0.2.5',
'logical_ip': '10.0.0.5',
'type': 'dnat_and_snat',
'external_mac': '00:01:02:03:04:05',
'logical_port': 'lsp-id-001'}]},
{'name': 'lr-id-c', 'ports': {}, 'static_routes': [],
'snats': [], 'dnat_and_snats': []},
{'name': 'lr-id-d', 'ports': {}, 'static_routes': [],
'snats': [], 'dnat_and_snats': []},
{'name': 'lr-id-e', 'ports': {}, 'static_routes': [],
'snats': [], 'dnat_and_snats': []}]
self.assertItemsEqual(mapping, expected)
def test_get_acls_for_lswitches(self):
self._load_nb_db()
# Test neutron switches
lswitches = ['ls-id-1', 'ls-id-2', 'ls-id-3', 'ls-id-5']
acl_values, acl_objs, lswitch_ovsdb_dict = \
self.nb_ovn_idl.get_acls_for_lswitches(lswitches)
excepted_acl_values = {
'lsp-id-11': [
{'action': 'allow-related', 'lport': 'lsp-id-11',
'lswitch': 'neutron-ls-id-1',
'external_ids': {'neutron:lport': 'lsp-id-11'},
'direction': 'from-lport',
'match': 'inport == "lsp-id-11" && ip4'},
{'action': 'allow-related', 'lport': 'lsp-id-11',
'lswitch': 'neutron-ls-id-1',
'external_ids': {'neutron:lport': 'lsp-id-11'},
'direction': 'to-lport',
'match': 'outport == "lsp-id-11" && ip4.src == $as_ip4_id_1'}
],
'lsp-id-12': [
{'action': 'allow-related', 'lport': 'lsp-id-12',
'lswitch': 'neutron-ls-id-1',
'external_ids': {'neutron:lport': 'lsp-id-12'},
'direction': 'from-lport',
'match': 'inport == "lsp-id-12" && ip4'},
{'action': 'allow-related', 'lport': 'lsp-id-12',
'lswitch': 'neutron-ls-id-1',
'external_ids': {'neutron:lport': 'lsp-id-12'},
'direction': 'to-lport',
'match': 'outport == "lsp-id-12" && ip4.src == $as_ip4_id_1'}
],
'lsp-id-21': [
{'action': 'allow-related', 'lport': 'lsp-id-21',
'lswitch': 'neutron-ls-id-2',
'external_ids': {'neutron:lport': 'lsp-id-21'},
'direction': 'from-lport',
'match': 'inport == "lsp-id-21" && ip4'},
{'action': 'allow-related', 'lport': 'lsp-id-21',
'lswitch': 'neutron-ls-id-2',
'external_ids': {'neutron:lport': 'lsp-id-21'},
'direction': 'to-lport',
'match': 'outport == "lsp-id-21" && ip4.src == $as_ip4_id_2'}
],
'lsp-id-52': [
{'action': 'allow-related', 'lport': 'lsp-id-52',
'lswitch': 'neutron-ls-id-5',
'external_ids': {'neutron:lport': 'lsp-id-52'},
'direction': 'from-lport',
'match': 'inport == "lsp-id-52" && ip4'},
{'action': 'allow-related', 'lport': 'lsp-id-52',
'lswitch': 'neutron-ls-id-5',
'external_ids': {'neutron:lport': 'lsp-id-52'},
'direction': 'to-lport',
'match': 'outport == "lsp-id-52" && ip4.src == $as_ip4_id_5'}
]}
self.assertItemsEqual(acl_values, excepted_acl_values)
self.assertEqual(len(acl_objs), 8)
self.assertEqual(len(lswitch_ovsdb_dict), len(lswitches))
# Test non-neutron switches
lswitches = ['ls-id-4']
acl_values, acl_objs, lswitch_ovsdb_dict = \
self.nb_ovn_idl.get_acls_for_lswitches(lswitches)
self.assertItemsEqual(acl_values, {})
self.assertEqual(len(acl_objs), 0)
self.assertEqual(len(lswitch_ovsdb_dict), 0)
def test_get_all_chassis_gateway_bindings(self):
self._load_nb_db()
bindings = self.nb_ovn_idl.get_all_chassis_gateway_bindings()
expected = {'host-1': [utils.ovn_lrouter_port_name('orp-id-a1'),
utils.ovn_lrouter_port_name('orp-id-a2')],
'host-2': [utils.ovn_lrouter_port_name('orp-id-b2')],
ovn_const.OVN_GATEWAY_INVALID_CHASSIS: [
utils.ovn_name('orp-id-a3')]}
self.assertItemsEqual(bindings, expected)
bindings = self.nb_ovn_idl.get_all_chassis_gateway_bindings([])
self.assertItemsEqual(bindings, expected)
bindings = self.nb_ovn_idl.get_all_chassis_gateway_bindings(['host-1'])
expected = {'host-1': [utils.ovn_lrouter_port_name('orp-id-a1'),
utils.ovn_lrouter_port_name('orp-id-a2')]}
self.assertItemsEqual(bindings, expected)
def test_get_gateway_chassis_binding(self):
self._load_nb_db()
chassis = self.nb_ovn_idl.get_gateway_chassis_binding(
utils.ovn_lrouter_port_name('orp-id-a1'))
self.assertEqual(chassis, ['host-1'])
chassis = self.nb_ovn_idl.get_gateway_chassis_binding(
utils.ovn_lrouter_port_name('orp-id-b2'))
self.assertEqual(chassis, ['host-2'])
chassis = self.nb_ovn_idl.get_gateway_chassis_binding(
utils.ovn_lrouter_port_name('orp-id-a3'))
self.assertEqual(chassis, ['neutron-ovn-invalid-chassis'])
chassis = self.nb_ovn_idl.get_gateway_chassis_binding(
utils.ovn_lrouter_port_name('orp-id-b3'))
self.assertEqual([], chassis)
chassis = self.nb_ovn_idl.get_gateway_chassis_binding('bad')
self.assertEqual([], chassis)
def test_get_unhosted_gateways(self):
self._load_nb_db()
# Test only host-1 in the valid list
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1'}, [])
expected = ['lrp-orp-id-a1', 'lrp-orp-id-a2',
'lrp-orp-id-a3', 'lrp-orp-id-b2']
self.assertItemsEqual(unhosted_gateways, expected)
# Test both host-1, host-2 in valid list
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1', 'host-2': 'physnet2'}, [])
self.assertItemsEqual(unhosted_gateways, expected)
# Schedule unhosted_gateways on host-2
for unhosted_gateway in unhosted_gateways:
router_row = self._find_ovsdb_fake_row(self.lrp_table,
'name', unhosted_gateway)
setattr(router_row, 'options', {
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'})
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1', 'host-2': 'physnet2'}, [])
self.assertItemsEqual(unhosted_gateways, expected)
def test_unhosted_gateway_max_chassis(self):
gw_chassis_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self._tables['Gateway_Chassis'] = gw_chassis_table
gw_chassis = collections.namedtuple('gw_chassis',
'chassis_name priority')
TestNBImplIdlOvn.fake_set['lrouter_ports'][0]['gateway_chassis'] = [
gw_chassis(chassis_name='host-%s' % x,
priority=x) for x in range(1, 6)]
for port in TestNBImplIdlOvn.fake_set['lrouter_ports'][1:]:
port['gateway_chassis'] = []
self._load_nb_db()
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1', 'host-2': 'physnet2',
'host-3': 'physnet1', 'host-4': 'physnet2',
'host-5': 'physnet1', 'host-6': 'physnet2'}, [])
expected = []
self.assertItemsEqual(unhosted_gateways, expected)
def test_get_subnet_dhcp_options(self):
self._load_nb_db()
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-10-0-2-0')
expected_row = self._find_ovsdb_fake_row(self.dhcp_table,
'cidr', '10.0.2.0/24')
self.assertEqual({
'subnet': {'cidr': expected_row.cidr,
'external_ids': expected_row.external_ids,
'options': expected_row.options,
'uuid': expected_row.uuid},
'ports': []}, subnet_options)
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-11-0-2-0')['subnet']
self.assertIsNone(subnet_options)
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'port-id-30-0-1-0')['subnet']
self.assertIsNone(subnet_options)
def test_get_subnet_dhcp_options_with_ports(self):
# Test empty
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-10-0-1-0', with_ports=True)
self.assertItemsEqual({'subnet': None, 'ports': []}, subnet_options)
# Test loaded values
self._load_nb_db()
# Test getting both subnet and port dhcp options
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-10-0-1-0', with_ports=True)
dhcp_rows = [
self._find_ovsdb_fake_row(self.dhcp_table, 'cidr', '10.0.1.0/24'),
self._find_ovsdb_fake_row(self.dhcp_table, 'cidr', '10.0.1.0/26')]
expected_rows = [{'cidr': dhcp_row.cidr,
'external_ids': dhcp_row.external_ids,
'options': dhcp_row.options,
'uuid': dhcp_row.uuid} for dhcp_row in dhcp_rows]
self.assertItemsEqual(expected_rows, [
subnet_options['subnet']] + subnet_options['ports'])
# Test getting only subnet dhcp options
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-10-0-2-0', with_ports=True)
dhcp_rows = [
self._find_ovsdb_fake_row(self.dhcp_table, 'cidr', '10.0.2.0/24')]
expected_rows = [{'cidr': dhcp_row.cidr,
'external_ids': dhcp_row.external_ids,
'options': dhcp_row.options,
'uuid': dhcp_row.uuid} for dhcp_row in dhcp_rows]
self.assertItemsEqual(expected_rows, [
subnet_options['subnet']] + subnet_options['ports'])
# Test getting no dhcp options
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
'subnet-id-11-0-2-0', with_ports=True)
self.assertItemsEqual({'subnet': None, 'ports': []}, subnet_options)
def test_get_subnets_dhcp_options(self):
self._load_nb_db()
def get_row_dict(row):
return {'cidr': row.cidr, 'external_ids': row.external_ids,
'options': row.options, 'uuid': row.uuid}
subnets_options = self.nb_ovn_idl.get_subnets_dhcp_options(
['subnet-id-10-0-1-0', 'subnet-id-10-0-2-0'])
expected_rows = [
get_row_dict(
self._find_ovsdb_fake_row(self.dhcp_table, 'cidr', cidr))
for cidr in ('10.0.1.0/24', '10.0.2.0/24')]
self.assertItemsEqual(expected_rows, subnets_options)
subnets_options = self.nb_ovn_idl.get_subnets_dhcp_options(
['subnet-id-11-0-2-0', 'subnet-id-20-0-1-0'])
expected_row = get_row_dict(
self._find_ovsdb_fake_row(self.dhcp_table, 'cidr', '20.0.1.0/24'))
self.assertItemsEqual([expected_row], subnets_options)
subnets_options = self.nb_ovn_idl.get_subnets_dhcp_options(
['port-id-30-0-1-0', 'fake-not-exist'])
self.assertEqual([], subnets_options)
def test_get_all_dhcp_options(self):
self._load_nb_db()
dhcp_options = self.nb_ovn_idl.get_all_dhcp_options()
self.assertEqual(len(dhcp_options['subnets']), 3)
self.assertEqual(len(dhcp_options['ports_v4']), 2)
def test_get_address_sets(self):
self._load_nb_db()
address_sets = self.nb_ovn_idl.get_address_sets()
self.assertEqual(len(address_sets), 4)
def test_get_port_group_not_supported(self):
self._load_nb_db()
# Make sure that PG tables doesn't exist in fake db.
self._tables.pop('Port_Group', None)
port_group = self.nb_ovn_idl.get_port_group(str(uuid.uuid4()))
self.assertIsNone(port_group)
def test_get_port_groups_not_supported(self):
self._load_nb_db()
# Make sure that PG tables doesn't exist in fake db.
self._tables.pop('Port_Group', None)
port_groups = self.nb_ovn_idl.get_port_groups()
self.assertEqual({}, port_groups)
class TestSBImplIdlOvn(TestDBImplIdlOvn):
fake_set = {
'chassis': [
{'name': 'host-1', 'hostname': 'host-1.localdomain.com',
'external_ids': {'ovn-bridge-mappings':
'public:br-ex,private:br-0'}},
{'name': 'host-2', 'hostname': 'host-2.localdomain.com',
'external_ids': {'ovn-bridge-mappings':
'public:br-ex,public2:br-ex'}},
{'name': 'host-3', 'hostname': 'host-3.localdomain.com',
'external_ids': {'ovn-bridge-mappings':
'public:br-ex'}}],
}
def setUp(self):
super(TestSBImplIdlOvn, self).setUp()
self.chassis_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
self._tables = {}
self._tables['Chassis'] = self.chassis_table
with mock.patch.object(impl_idl_ovn, 'get_connection',
return_value=mock.Mock()):
impl_idl_ovn.OvsdbSbOvnIdl.ovsdb_connection = None
self.sb_ovn_idl = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock())
self.sb_ovn_idl.idl.tables = self._tables
def _load_sb_db(self):
# Load Chassis
fake_chassis = TestSBImplIdlOvn.fake_set['chassis']
self._load_ovsdb_fake_rows(self.chassis_table, fake_chassis)
@mock.patch.object(impl_idl_ovn.OvsdbSbOvnIdl, 'ovsdb_connection', None)
@mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock())
def test_setting_ovsdb_probe_timeout_default_value(self):
inst = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock())
inst.idl._session.reconnect.set_probe_interval.assert_called_with(
60000)
@mock.patch.object(impl_idl_ovn.OvsdbSbOvnIdl, 'ovsdb_connection', None)
@mock.patch.object(impl_idl_ovn, 'get_connection', mock.Mock())
@mock.patch.object(ovn_conf, 'get_ovn_ovsdb_probe_interval')
def test_setting_ovsdb_probe_timeout(self, mock_get_probe_interval):
mock_get_probe_interval.return_value = 5000
inst = impl_idl_ovn.OvsdbSbOvnIdl(mock.Mock())
inst.idl._session.reconnect.set_probe_interval.assert_called_with(5000)

View File

@ -0,0 +1,294 @@
# Copyright 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import os
import mock
from oslo_utils import timeutils
from oslo_utils import uuidutils
from ovs.db import idl as ovs_idl
from ovs import poller
from ovs.stream import Stream
from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import idlutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import hash_ring_manager
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_hash_ring_db
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor
from neutron.tests import base
from neutron.tests.unit import fake_resources as fakes
basedir = os.path.dirname(os.path.abspath(__file__))
schema_files = {
'OVN_Northbound': os.path.join(basedir, 'schemas', 'ovn-nb.ovsschema'),
'OVN_Southbound': os.path.join(basedir, 'schemas', 'ovn-sb.ovsschema'),
}
OVN_NB_SCHEMA = {
"name": "OVN_Northbound", "version": "3.0.0",
"tables": {
"Logical_Switch_Port": {
"columns": {
"name": {"type": "string"},
"type": {"type": "string"},
"addresses": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"port_security": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
"up": {"type": {"key": "boolean", "min": 0, "max": 1}}},
"indexes": [["name"]],
"isRoot": False,
},
"Logical_Switch": {
"columns": {"name": {"type": "string"}},
"indexes": [["name"]],
"isRoot": True,
}
}
}
OVN_SB_SCHEMA = {
"name": "OVN_Southbound", "version": "1.3.0",
"tables": {
"Chassis": {
"columns": {
"name": {"type": "string"},
"hostname": {"type": "string"},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": True,
"indexes": [["name"]]
}
}
}
ROW_CREATE = ovsdb_monitor.BaseEvent.ROW_CREATE
ROW_UPDATE = ovsdb_monitor.BaseEvent.ROW_UPDATE
class TestOvnDbNotifyHandler(base.BaseTestCase):
def setUp(self):
super(TestOvnDbNotifyHandler, self).setUp()
self.handler = ovsdb_monitor.OvnDbNotifyHandler(mock.ANY)
self.watched_events = self.handler._RowEventHandler__watched_events
def test_watch_and_unwatch_events(self):
expected_events = set()
networking_event = mock.Mock()
ovn_event = mock.Mock()
unknown_event = mock.Mock()
self.assertItemsEqual(set(), self.watched_events)
expected_events.add(networking_event)
self.handler.watch_event(networking_event)
self.assertItemsEqual(expected_events, self.watched_events)
expected_events.add(ovn_event)
self.handler.watch_events([ovn_event])
self.assertItemsEqual(expected_events, self.watched_events)
self.handler.unwatch_events([networking_event, ovn_event])
self.handler.unwatch_event(unknown_event)
self.handler.unwatch_events([unknown_event])
self.assertItemsEqual(set(), self.watched_events)
def test_shutdown(self):
self.handler.shutdown()
# class TestOvnBaseConnection(base.TestCase):
#
# Each test is being deleted, but for reviewers sake I wanted to exaplain why:
#
# @mock.patch.object(idlutils, 'get_schema_helper')
# def testget_schema_helper_success(self, mock_gsh):
#
# 1. OvnBaseConnection and OvnConnection no longer exist
# 2. get_schema_helper is no longer a part of the Connection class
#
# @mock.patch.object(idlutils, 'get_schema_helper')
# def testget_schema_helper_initial_exception(self, mock_gsh):
#
# @mock.patch.object(idlutils, 'get_schema_helper')
# def testget_schema_helper_all_exception(self, mock_gsh):
#
# 3. The only reason get_schema_helper had a retry loop was for Neutron's
# use case of trying to set the Manager to listen on ptcp:127.0.0.1:6640
# if it wasn't already set up. Since that code being removed was the whole
# reason to re-implement get_schema_helper here,the exception retry is not
# needed and therefor is not a part of ovsdbapp's implementation of
# idlutils.get_schema_helper which we now use directly in from_server()
# 4. These tests now would be testing the various from_server() calls, but
# there is almost nothing to test in those except maybe SSL being set up
# but that was done below.
class TestOvnConnection(base.BaseTestCase):
def setUp(self):
super(TestOvnConnection, self).setUp()
@mock.patch.object(idlutils, 'get_schema_helper')
@mock.patch.object(idlutils, 'wait_for_change')
def _test_connection_start(self, mock_wfc, mock_gsh,
idl_class, schema):
mock_gsh.return_value = ovs_idl.SchemaHelper(
location=schema_files[schema])
_idl = idl_class.from_server('punix:/tmp/fake', schema, mock.Mock())
self.ovn_connection = connection.Connection(_idl, mock.Mock())
with mock.patch.object(poller, 'Poller'), \
mock.patch('threading.Thread'):
self.ovn_connection.start()
# A second start attempt shouldn't re-register.
self.ovn_connection.start()
self.ovn_connection.thread.start.assert_called_once_with()
def test_connection_nb_start(self):
ovn_conf.cfg.CONF.set_override('ovn_nb_private_key', 'foo-key', 'ovn')
Stream.ssl_set_private_key_file = mock.Mock()
Stream.ssl_set_certificate_file = mock.Mock()
Stream.ssl_set_ca_cert_file = mock.Mock()
self._test_connection_start(idl_class=ovsdb_monitor.OvnNbIdl,
schema='OVN_Northbound')
Stream.ssl_set_private_key_file.assert_called_once_with('foo-key')
Stream.ssl_set_certificate_file.assert_not_called()
Stream.ssl_set_ca_cert_file.assert_not_called()
def test_connection_sb_start(self):
self._test_connection_start(idl_class=ovsdb_monitor.OvnSbIdl,
schema='OVN_Southbound')
class TestOvnIdlDistributedLock(base.BaseTestCase):
def setUp(self):
super(TestOvnIdlDistributedLock, self).setUp()
self.node_uuid = uuidutils.generate_uuid()
self.fake_driver = mock.Mock()
self.fake_driver.node_uuid = self.node_uuid
self.fake_event = 'fake-event'
self.fake_row = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'_table': mock.Mock(name='FakeTable')})
helper = ovs_idl.SchemaHelper(schema_json=OVN_NB_SCHEMA)
helper.register_all()
with mock.patch.object(ovsdb_monitor, 'OvnDbNotifyHandler'):
self.idl = ovsdb_monitor.OvnIdlDistributedLock(
self.fake_driver, 'punix:/tmp/fake', helper)
self.mock_get_node = mock.patch.object(
hash_ring_manager.HashRingManager,
'get_node', return_value=self.node_uuid).start()
@mock.patch.object(ovn_hash_ring_db, 'touch_node')
def test_notify(self, mock_touch_node):
self.idl.notify(self.fake_event, self.fake_row)
mock_touch_node.assert_called_once_with(mock.ANY, self.node_uuid)
self.idl.notify_handler.notify.assert_called_once_with(
self.fake_event, self.fake_row, None)
@mock.patch.object(ovn_hash_ring_db, 'touch_node')
def test_notify_skip_touch_node(self, mock_touch_node):
# Set a time for last touch
self.idl._last_touch = timeutils.utcnow()
self.idl.notify(self.fake_event, self.fake_row)
# Assert that touch_node() wasn't called
self.assertFalse(mock_touch_node.called)
self.idl.notify_handler.notify.assert_called_once_with(
self.fake_event, self.fake_row, None)
@mock.patch.object(ovn_hash_ring_db, 'touch_node')
def test_notify_last_touch_expired(self, mock_touch_node):
# Set a time for last touch
self.idl._last_touch = timeutils.utcnow()
# Let's expire the touch node interval for the next utcnow()
with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
mock_utcnow.return_value = (
self.idl._last_touch + datetime.timedelta(
seconds=ovn_const.HASH_RING_TOUCH_INTERVAL + 1))
self.idl.notify(self.fake_event, self.fake_row)
# Assert that touch_node() was invoked
mock_touch_node.assert_called_once_with(mock.ANY, self.node_uuid)
self.idl.notify_handler.notify.assert_called_once_with(
self.fake_event, self.fake_row, None)
@mock.patch.object(ovsdb_monitor.LOG, 'exception')
@mock.patch.object(ovn_hash_ring_db, 'touch_node')
def test_notify_touch_node_exception(self, mock_touch_node, mock_log):
mock_touch_node.side_effect = Exception('BoOooOmmMmmMm')
self.idl.notify(self.fake_event, self.fake_row)
# Assert that in an eventual failure on touch_node() the event
# will continue to be processed by notify_handler.notify()
mock_touch_node.assert_called_once_with(mock.ANY, self.node_uuid)
# Assert we are logging the exception
self.assertTrue(mock_log.called)
self.idl.notify_handler.notify.assert_called_once_with(
self.fake_event, self.fake_row, None)
def test_notify_different_node(self):
self.mock_get_node.return_value = 'different-node-uuid'
self.idl.notify('fake-event', self.fake_row)
# Assert that notify() wasn't called for a different node uuid
self.assertFalse(self.idl.notify_handler.notify.called)
class TestPortBindingChassisUpdateEvent(base.BaseTestCase):
def setUp(self):
super(TestPortBindingChassisUpdateEvent, self).setUp()
self.driver = mock.Mock()
self.event = ovsdb_monitor.PortBindingChassisUpdateEvent(self.driver)
def _test_event(self, event, row, old):
if self.event.matches(event, row, old):
self.event.run(event, row, old)
self.driver.set_port_status_up.assert_called()
else:
self.driver.set_port_status_up.assert_not_called()
def test_event_matches(self):
# NOTE(twilson) This primarily tests implementation details. If a
# scenario test is written that handles shutting down a compute
# node uncleanly and performing a 'host-evacuate', this can be removed
pbtable = fakes.FakeOvsdbTable.create_one_ovsdb_table(
attrs={'name': 'Port_Binding'})
ovsdb_row = fakes.FakeOvsdbRow.create_one_ovsdb_row
self.driver._nb_ovn.lookup.return_value = ovsdb_row(attrs={'up': True})
self._test_event(
self.event.ROW_UPDATE,
ovsdb_row(attrs={'_table': pbtable, 'chassis': 'one',
'type': '_fake_', 'logical_port': 'foo'}),
ovsdb_row(attrs={'_table': pbtable, 'chassis': 'two',
'type': '_fake_'}))
# NOTE(ralonsoh): once the OVN mech driver is implemented, we'll be able to
# test OvnNbIdl and OvnSbIdl properly.