TAAS tap-mirror OVN driver
Depends-On: https://review.opendev.org/c/openstack/ovsdbapp/+/890328 Related-Bug: #2015471 Change-Id: I6007a2a1644bbe432d7a8abaeaa8194a4eb2bfed
This commit is contained in:
parent
faa98ef97c
commit
8c1b33d50a
|
@ -0,0 +1,125 @@
|
|||
# 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 queue
|
||||
import threading
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import helpers as log_helpers
|
||||
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib.callbacks import resources
|
||||
from oslo_log import log as logging
|
||||
from ovs.stream import Stream
|
||||
|
||||
from neutron_taas.services.taas.service_drivers.ovn.ovsdb import impl_idl_taas
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TaasOvnProviderHelper(object):
|
||||
|
||||
def __init__(self):
|
||||
ovn_conf.register_opts()
|
||||
self._requests = queue.Queue()
|
||||
self._helper_thread = threading.Thread(target=self._request_handler)
|
||||
self._helper_thread.daemon = True
|
||||
self._check_and_set_ssl_files()
|
||||
self._taas_mirror_func_map = {
|
||||
'mirror_del': self.mirror_del,
|
||||
'mirror_add': self.mirror_add,
|
||||
}
|
||||
self._subscribe()
|
||||
self._helper_thread.start()
|
||||
|
||||
def _subscribe(self):
|
||||
registry.subscribe(self._post_fork_initialize,
|
||||
resources.PROCESS,
|
||||
events.AFTER_INIT)
|
||||
|
||||
def _post_fork_initialize(self, resource, event, trigger, payload=None):
|
||||
self.ovn_nbdb = impl_idl_taas.OvnNbIdlForTaas()
|
||||
self.ovn_nbdb_api = self.ovn_nbdb.start()
|
||||
|
||||
def _check_and_set_ssl_files(self):
|
||||
priv_key_file = CONF.ovn.ovn_nb_private_key
|
||||
cert_file = CONF.ovn.ovn_nb_certificate
|
||||
ca_cert_file = CONF.ovn.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)
|
||||
|
||||
def _request_handler(self):
|
||||
while True:
|
||||
request = self._requests.get()
|
||||
request_type = request['type']
|
||||
if request_type == 'exit':
|
||||
break
|
||||
|
||||
request_handler = self._taas_mirror_func_map.get(request_type)
|
||||
try:
|
||||
if request_handler:
|
||||
request_handler(request['info'])
|
||||
self._requests.task_done()
|
||||
except Exception:
|
||||
# If any unexpected exception happens we don't want the
|
||||
# notify_loop to exit.
|
||||
LOG.exception('Unexpected exception in request_handler')
|
||||
|
||||
def _execute_commands(self, commands):
|
||||
with self.ovn_nbdb_api.transaction(check_error=True) as txn:
|
||||
for command in commands:
|
||||
txn.add(command)
|
||||
|
||||
def shutdown(self):
|
||||
self._requests.put({'type': 'exit'})
|
||||
self._helper_thread.join()
|
||||
self.ovn_nbdb.stop()
|
||||
del self.ovn_nbdb_api
|
||||
|
||||
def add_request(self, req):
|
||||
self._requests.put(req)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def mirror_del(self, request):
|
||||
port_id = request.pop('port_id')
|
||||
ovn_port = self.ovn_nbdb_api.lookup('Logical_Switch_Port', port_id)
|
||||
mirror = self.ovn_nbdb_api.mirror_get(
|
||||
request['name']).execute(check_error=True)
|
||||
self.ovn_nbdb_api.lsp_detach_mirror(
|
||||
ovn_port.name, mirror.uuid,
|
||||
if_exist=True).execute(check_error=True)
|
||||
self.ovn_nbdb_api.mirror_del(
|
||||
mirror.uuid).execute(check_error=True)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def mirror_add(self, request):
|
||||
port_id = request.pop('port_id')
|
||||
ovn_port = self.ovn_nbdb_api.lookup('Logical_Switch_Port', port_id)
|
||||
|
||||
mirror = self.ovn_nbdb_api.mirror_add(
|
||||
**request).execute(check_error=True)
|
||||
self.ovn_nbdb_api.lsp_attach_mirror(
|
||||
ovn_port.name, mirror.uuid,
|
||||
may_exist=True).execute(check_error=True)
|
||||
|
||||
return mirror
|
|
@ -0,0 +1,154 @@
|
|||
# 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 atexit
|
||||
import contextlib
|
||||
import tenacity
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from neutron.common.ovn import exceptions as ovn_exceptions
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron_lib import exceptions as n_exc
|
||||
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.schema.ovn_northbound import impl_idl as nb_impl_idl
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Backend(ovs_idl.Backend):
|
||||
lookup_table = {}
|
||||
ovsdb_connection = None
|
||||
|
||||
def __init__(self, connection):
|
||||
ovn_conf.register_opts()
|
||||
self.ovsdb_connection = connection
|
||||
super().__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 from e
|
||||
|
||||
@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 as e:
|
||||
msg = (_("%(match)s does not exist in %(column)s of %(table)s")
|
||||
% {'match': match, 'column': column, 'table': table})
|
||||
raise RuntimeError(msg) from e
|
||||
|
||||
|
||||
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.")
|
||||
|
||||
|
||||
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
super().__init__(connection)
|
||||
self.idl._session.reconnect.set_probe_interval(
|
||||
ovn_conf.get_ovn_ovsdb_probe_interval())
|
||||
|
||||
@contextlib.contextmanager
|
||||
def transaction(self, check_error=False, log_errors=True, nested=True,
|
||||
**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().transaction(check_error, log_errors, nested,
|
||||
**kwargs) as t:
|
||||
yield t
|
||||
except ovn_exceptions.RevisionConflict as e:
|
||||
LOG.info('Transaction aborted. Reason: %s', e)
|
||||
|
||||
|
||||
class OvnNbIdlForTaas(connection.OvsdbIdl):
|
||||
|
||||
SCHEMA = "OVN_Northbound"
|
||||
TABLES = ('Logical_Switch_Port', 'Mirror')
|
||||
|
||||
def __init__(self):
|
||||
ovn_conf.register_opts()
|
||||
self.conn_string = ovn_conf.get_ovn_nb_connection()
|
||||
helper = self._get_ovsdb_helper(self.conn_string)
|
||||
for table in OvnNbIdlForTaas.TABLES:
|
||||
helper.register_table(table)
|
||||
super().__init__(self.conn_string, helper)
|
||||
atexit.register(self.stop)
|
||||
|
||||
@tenacity.retry(
|
||||
wait=tenacity.wait_exponential(18),
|
||||
reraise=True)
|
||||
def _get_ovsdb_helper(self, connection_string):
|
||||
return idlutils.get_schema_helper(connection_string, self.SCHEMA)
|
||||
|
||||
def start(self):
|
||||
self.conn = connection.Connection(self, timeout=180)
|
||||
return OvsdbNbOvnIdl(self.conn)
|
||||
|
||||
def stop(self):
|
||||
# Close the running connection if it has been initalized
|
||||
if hasattr(self, 'conn'):
|
||||
if not self.conn.stop(timeout=180):
|
||||
LOG.debug("Connection terminated to OvnNb "
|
||||
"but a thread is still alive")
|
||||
del self.conn
|
||||
# Close the idl session
|
||||
self.close()
|
|
@ -0,0 +1,111 @@
|
|||
# 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.
|
||||
|
||||
from oslo_log import helpers as log_helpers
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron_taas.extensions import _tap_mirror
|
||||
from neutron_taas.services.taas import service_drivers
|
||||
from neutron_taas.services.taas.service_drivers.ovn import helper
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaasOvnDriver(service_drivers.TaasBaseDriver):
|
||||
"""Taas OVN Service Driver class"""
|
||||
|
||||
more_supported_extension_aliases = [_tap_mirror.ALIAS]
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
LOG.debug("Loading Taas OVN Driver.")
|
||||
super().__init__(service_plugin)
|
||||
self._ovn_helper = helper.TaasOvnProviderHelper()
|
||||
|
||||
def __del__(self):
|
||||
self._ovn_helper.shutdown()
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_service_precommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_service_postcommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_service_precommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_service_postcommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_flow_precommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_flow_postcommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_flow_precommit(self, context):
|
||||
"""Send tap flow deletion RPC message to agent."""
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_flow_postcommit(self, context):
|
||||
LOG.warning("Not implemented")
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_mirror_precommit(self, context):
|
||||
pass
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_tap_mirror_postcommit(self, context):
|
||||
LOG.info('create_tap_mirror_postcommit %s', context.tap_mirror)
|
||||
t_m = context.tap_mirror
|
||||
type = 'erspan' if 'erspan' in t_m['mirror_type'] else 'gre'
|
||||
directions = t_m['directions']
|
||||
for direction, tunnel_id in directions.items():
|
||||
mirror_port_name = 'tm_%s_%s' % (direction.lower(), t_m['id'][0:6])
|
||||
ovn_direction = ('from-lport' if direction == 'OUT'
|
||||
else 'to-lport')
|
||||
request = {'type': 'mirror_add',
|
||||
'info': {'name': mirror_port_name,
|
||||
'direction_filter': ovn_direction,
|
||||
'dest': t_m['remote_ip'],
|
||||
'mirror_type': type,
|
||||
'index': int(tunnel_id),
|
||||
'port_id': t_m['port_id']}}
|
||||
self._ovn_helper.add_request(request)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_mirror_precommit(self, context):
|
||||
LOG.info('delete_tap_mirror_precommit %s', context.tap_mirror)
|
||||
t_m = context.tap_mirror
|
||||
directions = t_m['directions']
|
||||
for direction, tunnel_id in directions.items():
|
||||
mirror_port_name = 'tm_%s_%s' % (direction.lower(), t_m['id'][0:6])
|
||||
request = {
|
||||
'type': 'mirror_del',
|
||||
'info': {'id': t_m['id'],
|
||||
'name': mirror_port_name,
|
||||
'sink': t_m['remote_ip'],
|
||||
'port_id': t_m['port_id']}
|
||||
}
|
||||
self._ovn_helper.add_request(request)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_mirror_postcommit(self, context):
|
||||
pass
|
|
@ -0,0 +1,665 @@
|
|||
{
|
||||
"name": "OVN_Northbound",
|
||||
"version": "7.1.0",
|
||||
"cksum": "217362582 33949",
|
||||
"tables": {
|
||||
"NB_Global": {
|
||||
"columns": {
|
||||
"name": {"type": "string"},
|
||||
"nb_cfg": {"type": {"key": "integer"}},
|
||||
"nb_cfg_timestamp": {"type": {"key": "integer"}},
|
||||
"sb_cfg": {"type": {"key": "integer"}},
|
||||
"sb_cfg_timestamp": {"type": {"key": "integer"}},
|
||||
"hv_cfg": {"type": {"key": "integer"}},
|
||||
"hv_cfg_timestamp": {"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},
|
||||
"Copp": {
|
||||
"columns": {
|
||||
"name": {"type": "string"},
|
||||
"meters": {
|
||||
"type": {"key": "string",
|
||||
"value": "string",
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"indexes": [["name"]],
|
||||
"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"}},
|
||||
"load_balancer_group": {
|
||||
"type": {"key": {"type": "uuid",
|
||||
"refTable": "Load_Balancer_Group"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"dns_records": {"type": {"key": {"type": "uuid",
|
||||
"refTable": "DNS",
|
||||
"refType": "weak"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
|
||||
"refType": "weak"},
|
||||
"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"}},
|
||||
"forwarding_groups": {
|
||||
"type": {"key": {"type": "uuid",
|
||||
"refTable": "Forwarding_Group",
|
||||
"refType": "strong"},
|
||||
"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}},
|
||||
"mirror_rules": {"type": {"key": {"type": "uuid",
|
||||
"refTable": "Mirror",
|
||||
"refType": "weak"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"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},
|
||||
"Forwarding_Group": {
|
||||
"columns": {
|
||||
"name": {"type": "string"},
|
||||
"vip": {"type": "string"},
|
||||
"vmac": {"type": "string"},
|
||||
"liveness": {"type": "boolean"},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"child_port": {"type": {"key": "string",
|
||||
"min": 1, "max": "unlimited"}}},
|
||||
"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", "sctp"]]},
|
||||
"min": 0, "max": 1}},
|
||||
"health_check": {"type": {
|
||||
"key": {"type": "uuid",
|
||||
"refTable": "Load_Balancer_Health_Check",
|
||||
"refType": "strong"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"ip_port_mappings": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"selection_fields": {
|
||||
"type": {"key": {"type": "string",
|
||||
"enum": ["set",
|
||||
["eth_src", "eth_dst", "ip_src", "ip_dst",
|
||||
"tp_src", "tp_dst"]]},
|
||||
"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},
|
||||
"Load_Balancer_Group": {
|
||||
"columns": {
|
||||
"name": {"type": "string"},
|
||||
"load_balancer": {"type": {"key": {"type": "uuid",
|
||||
"refTable": "Load_Balancer",
|
||||
"refType": "weak"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}}},
|
||||
"indexes": [["name"]],
|
||||
"isRoot": true},
|
||||
"Load_Balancer_Health_Check": {
|
||||
"columns": {
|
||||
"vip": {"type": "string"},
|
||||
"options": {
|
||||
"type": {"key": "string",
|
||||
"value": "string",
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"isRoot": false},
|
||||
"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",
|
||||
"allow-stateless", "drop",
|
||||
"reject", "pass"]]}}},
|
||||
"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}},
|
||||
"label": {"type": {"key": {"type": "integer",
|
||||
"minInteger": 0,
|
||||
"maxInteger": 4294967295}}},
|
||||
"tier": {"type": {"key": {"type": "integer",
|
||||
"minInteger": 0,
|
||||
"maxInteger": 3}}},
|
||||
"options": {
|
||||
"type": {"key": "string",
|
||||
"value": "string",
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"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},
|
||||
"Mirror": {
|
||||
"columns": {
|
||||
"name": {"type": "string"},
|
||||
"filter": {"type": {"key": {"type": "string",
|
||||
"enum": ["set", ["from-lport",
|
||||
"to-lport",
|
||||
"both"]]}}},
|
||||
"sink":{"type": "string"},
|
||||
"type": {"type": {"key": {"type": "string",
|
||||
"enum": ["set", ["gre",
|
||||
"erspan",
|
||||
"local"]]}}},
|
||||
"index": {"type": "integer"},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"indexes": [["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"}},
|
||||
"fair": {"type": {"key": "boolean", "min": 0, "max": 1}},
|
||||
"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"}},
|
||||
"load_balancer_group": {
|
||||
"type": {"key": {"type": "uuid",
|
||||
"refTable": "Load_Balancer_Group"},
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
|
||||
"refType": "weak"},
|
||||
"min": 0, "max": 1}},
|
||||
"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"}},
|
||||
"ipv6_prefix": {"type": {"key": "string",
|
||||
"min": 0,
|
||||
"max": "unlimited"}},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"status": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"indexes": [["name"]],
|
||||
"isRoot": false},
|
||||
"Logical_Router_Static_Route": {
|
||||
"columns": {
|
||||
"route_table": {"type": "string"},
|
||||
"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}},
|
||||
"bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD",
|
||||
"refType": "weak"},
|
||||
"min": 0,
|
||||
"max": 1}},
|
||||
"options": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"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}},
|
||||
"nexthops": {"type": {
|
||||
"key": "string", "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": false},
|
||||
"NAT": {
|
||||
"columns": {
|
||||
"external_ip": {"type": "string"},
|
||||
"external_mac": {"type": {"key": "string",
|
||||
"min": 0, "max": 1}},
|
||||
"external_port_range": {"type": "string"},
|
||||
"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"
|
||||
]]}}},
|
||||
"allowed_ext_ips": {"type": {
|
||||
"key": {"type": "uuid", "refTable": "Address_Set",
|
||||
"refType": "strong"},
|
||||
"min": 0,
|
||||
"max": 1}},
|
||||
"exempted_ext_ips": {"type": {
|
||||
"key": {"type": "uuid", "refTable": "Address_Set",
|
||||
"refType": "strong"},
|
||||
"min": 0,
|
||||
"max": 1}},
|
||||
"gateway_port": {
|
||||
"type": {"key": {"type": "uuid",
|
||||
"refTable": "Logical_Router_Port",
|
||||
"refType": "weak"},
|
||||
"min": 0,
|
||||
"max": 1}},
|
||||
"options": {"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"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},
|
||||
"BFD": {
|
||||
"columns": {
|
||||
"logical_port": {"type": "string"},
|
||||
"dst_ip": {"type": "string"},
|
||||
"min_tx": {"type": {"key": {"type": "integer",
|
||||
"minInteger": 1},
|
||||
"min": 0, "max": 1}},
|
||||
"min_rx": {"type": {"key": {"type": "integer"},
|
||||
"min": 0, "max": 1}},
|
||||
"detect_mult": {"type": {"key": {"type": "integer",
|
||||
"minInteger": 1},
|
||||
"min": 0, "max": 1}},
|
||||
"status": {
|
||||
"type": {"key": {"type": "string",
|
||||
"enum": ["set", ["down", "init", "up",
|
||||
"admin_down"]]},
|
||||
"min": 0, "max": 1}},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"options": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"indexes": [["logical_port", "dst_ip"]],
|
||||
"isRoot": true},
|
||||
"Static_MAC_Binding": {
|
||||
"columns": {
|
||||
"logical_port": {"type": "string"},
|
||||
"ip": {"type": "string"},
|
||||
"mac": {"type": "string"},
|
||||
"override_dynamic_mac": {"type": "boolean"}},
|
||||
"indexes": [["logical_port", "ip"]],
|
||||
"isRoot": true},
|
||||
"Chassis_Template_Var": {
|
||||
"columns": {
|
||||
"chassis": {"type": "string"},
|
||||
"variables": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"indexes": [["chassis"]],
|
||||
"isRoot": true}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
# 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 os
|
||||
from unittest import mock
|
||||
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron.tests import base
|
||||
from ovs.db import idl as ovs_idl
|
||||
from ovsdbapp.backend import ovs_idl as real_ovs_idl
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
|
||||
from neutron_taas.services.taas.service_drivers.ovn.ovsdb import impl_idl_taas
|
||||
|
||||
|
||||
basedir = os.path.dirname(os.path.abspath(__file__))
|
||||
schema_files = {
|
||||
'OVN_Northbound': os.path.join(basedir,
|
||||
'schema_files', 'ovn-nb.ovsschema'),
|
||||
}
|
||||
|
||||
|
||||
class TestOvnNbIdlForTaas(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
ovn_conf.register_opts()
|
||||
self.mock_gsh = mock.patch.object(
|
||||
idlutils, 'get_schema_helper',
|
||||
side_effect=lambda x, y: ovs_idl.SchemaHelper(
|
||||
location=schema_files['OVN_Northbound'])).start()
|
||||
self.idl_taas = impl_idl_taas.OvnNbIdlForTaas()
|
||||
|
||||
def test__get_ovsdb_helper(self):
|
||||
self.mock_gsh.reset_mock()
|
||||
self.idl_taas._get_ovsdb_helper('foo')
|
||||
self.mock_gsh.assert_called_once_with('foo', 'OVN_Northbound')
|
||||
|
||||
@mock.patch.object(real_ovs_idl.Backend, 'autocreate_indices', mock.Mock(),
|
||||
create=True)
|
||||
def test_start(self):
|
||||
with mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection',
|
||||
side_effect=lambda x, timeout: mock.Mock()):
|
||||
idl_taas_1 = impl_idl_taas.OvnNbIdlForTaas()
|
||||
ret_taas_1 = idl_taas_1.start()
|
||||
id1 = id(ret_taas_1.ovsdb_connection)
|
||||
idl_taas_2 = impl_idl_taas.OvnNbIdlForTaas()
|
||||
ret_taas_2 = idl_taas_2.start()
|
||||
id2 = id(ret_taas_2.ovsdb_connection)
|
||||
self.assertNotEqual(id1, id2)
|
||||
|
||||
@mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection')
|
||||
def test_stop(self, mock_conn):
|
||||
mock_conn.stop.return_value = False
|
||||
with mock.patch.object(self.idl_taas, 'close') as mock_close:
|
||||
self.idl_taas.start()
|
||||
self.idl_taas.stop()
|
||||
mock_close.assert_called_once_with()
|
||||
|
||||
@mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection')
|
||||
def test_stop_no_connection(self, mock_conn):
|
||||
mock_conn.stop.return_value = False
|
||||
with mock.patch.object(self.idl_taas, 'close') as mock_close:
|
||||
self.idl_taas.stop()
|
||||
mock_close.assert_called_once_with()
|
|
@ -0,0 +1,96 @@
|
|||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import resources
|
||||
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron.tests import base
|
||||
|
||||
from neutron_taas.services.taas.service_drivers.ovn import helper
|
||||
|
||||
|
||||
class TestTaasOvnProviderHelper(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
ovn_conf.register_opts()
|
||||
|
||||
ovn_nb_idl = mock.patch(
|
||||
'neutron_taas.services.taas.service_drivers.ovn.ovsdb.'
|
||||
'impl_idl_taas.OvnNbIdlForTaas')
|
||||
self.mock_ovn_nb_idl = ovn_nb_idl.start()
|
||||
mock.patch(
|
||||
'ovsdbapp.backend.ovs_idl.idlutils.get_schema_helper').start()
|
||||
self.helper = helper.TaasOvnProviderHelper()
|
||||
self.helper._post_fork_initialize(
|
||||
resources.PROCESS, events.AFTER_INIT, None)
|
||||
|
||||
self.ovn_nbdb_api = mock.patch.object(self.helper, 'ovn_nbdb_api')
|
||||
self.ovn_nbdb_api.start()
|
||||
add_req_thread = mock.patch.object(helper.TaasOvnProviderHelper,
|
||||
'add_request')
|
||||
self.mock_add_request = add_req_thread.start()
|
||||
|
||||
def test_mirror_add(self):
|
||||
port_id = '1234'
|
||||
name = 'foo_mirror'
|
||||
dest_ip = '10.92.10.5'
|
||||
type = 'gre'
|
||||
tunnel_id = 101
|
||||
direction = 'to-lport'
|
||||
|
||||
self.helper.mirror_add({
|
||||
'name': name,
|
||||
'direction_filter': direction,
|
||||
'dest': dest_ip,
|
||||
'mirror_type': type,
|
||||
'index': tunnel_id,
|
||||
'port_id': port_id
|
||||
})
|
||||
|
||||
self.helper.ovn_nbdb_api.lookup.assert_called_once_with(
|
||||
'Logical_Switch_Port', port_id)
|
||||
self.helper.ovn_nbdb_api.mirror_add.assert_called_once_with(
|
||||
name=name,
|
||||
direction_filter=direction,
|
||||
dest=dest_ip,
|
||||
mirror_type=type,
|
||||
index=tunnel_id
|
||||
)
|
||||
self.helper.ovn_nbdb_api.lsp_attach_mirror.assert_called_once()
|
||||
|
||||
def test_mirror_del(self):
|
||||
port_id = '1234'
|
||||
name = 'foo_mirror'
|
||||
dest_ip = '10.92.10.5'
|
||||
type = 'gre'
|
||||
tunnel_id = 101
|
||||
direction = 'to-lport'
|
||||
|
||||
self.helper.mirror_del({
|
||||
'port_id': port_id,
|
||||
'name': name,
|
||||
'direction_filter': direction,
|
||||
'dest': dest_ip,
|
||||
'mirror_type': type,
|
||||
'index': tunnel_id,
|
||||
'port_id': port_id
|
||||
})
|
||||
|
||||
self.helper.ovn_nbdb_api.lookup.assert_called_once_with(
|
||||
'Logical_Switch_Port', port_id)
|
||||
self.helper.ovn_nbdb_api.mirror_get.assert_called_once_with(name)
|
||||
self.helper.ovn_nbdb_api.lsp_detach_mirror.assert_called_once()
|
||||
self.helper.ovn_nbdb_api.mirror_del.assert_called_once()
|
|
@ -0,0 +1,131 @@
|
|||
# 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 copy
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.tests import base
|
||||
|
||||
from neutron_taas.services.taas.service_drivers.ovn import helper
|
||||
from neutron_taas.services.taas.service_drivers.ovn import taas_ovn
|
||||
|
||||
|
||||
class FakeMirrorContext():
|
||||
def __init__(self, tap_mirror):
|
||||
self._tap_mirror = tap_mirror
|
||||
|
||||
@property
|
||||
def tap_mirror(self):
|
||||
return self._tap_mirror
|
||||
|
||||
|
||||
class TestTaasOvnDriver(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.driver = taas_ovn.TaasOvnDriver('tapmirror')
|
||||
add_req_thread = mock.patch.object(helper.TaasOvnProviderHelper,
|
||||
'add_request')
|
||||
self.mock_add_request = add_req_thread.start()
|
||||
helper_mock = mock.patch.object(helper.TaasOvnProviderHelper,
|
||||
'shutdown')
|
||||
helper_mock.start()
|
||||
|
||||
self.tap_mirror_dict = {
|
||||
'mirror_type': 'gre',
|
||||
'directions': {'IN': 101},
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'remote_ip': '10.92.10.5',
|
||||
'port_id': uuidutils.generate_uuid()
|
||||
}
|
||||
self.multi_dir_t_mirror = copy.deepcopy(self.tap_mirror_dict)
|
||||
self.multi_dir_t_mirror['directions'] = {'IN': 101, 'OUT': 102}
|
||||
|
||||
def test_create_tap_mirror_postcommit(self):
|
||||
ctx = FakeMirrorContext(self.tap_mirror_dict)
|
||||
self.driver.create_tap_mirror_postcommit(ctx)
|
||||
expected_dict = {
|
||||
'type': 'mirror_add',
|
||||
'info': {
|
||||
'name': mock.ANY,
|
||||
'direction_filter': 'to-lport',
|
||||
'dest': self.tap_mirror_dict['remote_ip'],
|
||||
'mirror_type': self.tap_mirror_dict['mirror_type'],
|
||||
'index': self.tap_mirror_dict['directions']['IN'],
|
||||
'port_id': self.tap_mirror_dict['port_id'],
|
||||
}
|
||||
}
|
||||
self.mock_add_request.assert_called_once_with(expected_dict)
|
||||
|
||||
def test_create_tap_mirror_postcommit_multi_dir(self):
|
||||
ctx = FakeMirrorContext(self.multi_dir_t_mirror)
|
||||
self.driver.create_tap_mirror_postcommit(ctx)
|
||||
|
||||
expected_in_call = {
|
||||
'type': 'mirror_add',
|
||||
'info': {
|
||||
'name': mock.ANY,
|
||||
'direction_filter': 'to-lport',
|
||||
'dest': self.tap_mirror_dict['remote_ip'],
|
||||
'mirror_type': self.tap_mirror_dict['mirror_type'],
|
||||
'index': self.tap_mirror_dict['directions']['IN'],
|
||||
'port_id': self.tap_mirror_dict['port_id'],
|
||||
}
|
||||
}
|
||||
expected_out_call = copy.deepcopy(expected_in_call)
|
||||
expected_out_call['info']['direction_filter'] = 'from-lport'
|
||||
out_dir_tun_id = self.multi_dir_t_mirror['directions']['OUT']
|
||||
expected_out_call['info']['index'] = out_dir_tun_id
|
||||
|
||||
expected_calls = [
|
||||
mock.call(expected_in_call),
|
||||
mock.call(expected_out_call)
|
||||
]
|
||||
|
||||
self.mock_add_request.assert_has_calls(expected_calls)
|
||||
|
||||
def test_delete_tap_mirror_precommit(self):
|
||||
ctx = FakeMirrorContext(self.tap_mirror_dict)
|
||||
self.driver.delete_tap_mirror_precommit(ctx)
|
||||
|
||||
expected_dict = {
|
||||
'type': 'mirror_del',
|
||||
'info': {
|
||||
'id': self.tap_mirror_dict['id'],
|
||||
'name': mock.ANY,
|
||||
'sink': self.tap_mirror_dict['remote_ip'],
|
||||
'port_id': self.tap_mirror_dict['port_id']}
|
||||
}
|
||||
self.mock_add_request.assert_called_once_with(expected_dict)
|
||||
|
||||
def test_delete_tap_mirror_precommit_multi_dir(self):
|
||||
ctx = FakeMirrorContext(self.multi_dir_t_mirror)
|
||||
self.driver.delete_tap_mirror_precommit(ctx)
|
||||
|
||||
expected_call = {
|
||||
'type': 'mirror_del',
|
||||
'info': {
|
||||
'id': self.tap_mirror_dict['id'],
|
||||
'name': mock.ANY,
|
||||
'sink': self.tap_mirror_dict['remote_ip'],
|
||||
'port_id': self.tap_mirror_dict['port_id'],
|
||||
}
|
||||
}
|
||||
|
||||
expected_calls = [
|
||||
mock.call(expected_call),
|
||||
mock.call(expected_call)
|
||||
]
|
||||
|
||||
self.mock_add_request.assert_has_calls(expected_calls)
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Add possibility to create (CRUD) ``tap_mirrors`` with ``OVN`` backend.
|
||||
At least ``OVN`` ``v22.12.0`` is necessary to create mirrors.
|
||||
Other tap-as-a-service APIs (tap-service and tap-flow) are not part
|
||||
of this effort
|
||||
(https://specs.openstack.org/openstack/neutron-specs/specs/2023.2/erspan-for-tap-as-a-service.html)
|
Loading…
Reference in New Issue