[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:
parent
f77c1037e4
commit
fdb3f05055
@ -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
|
@ -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)
|
@ -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}}
|
||||
}
|
@ -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}}}
|
@ -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)
|
@ -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.
|
Loading…
Reference in New Issue
Block a user