Browse Source

Check for router port correctness

This past checks correctness when adding, removing router
interfaces, updating them, or managing the router gateway port.

For router ports, every resource (port) on the neutron side,
becomes two on the ovn side. This is why we introduce a new
type of resource to be tracked ROUTER_PORTS, and we also track
them as standard L2 PORTS.

The ROUTER_PORT resources handle the creation and update of
Logical_Router_Port and NAT resources (always going on the
same transactions).

Change-Id: I96a7e812427ebbc8e868a0e3ab78754084cdaecf
changes/23/534423/18
Miguel Angel Ajo 4 years ago committed by Daniel Alvarez
parent
commit
d205e0f0ab
  1. 2
      networking_ovn/common/constants.py
  2. 20
      networking_ovn/common/maintenance.py
  3. 101
      networking_ovn/common/ovn_client.py
  4. 1
      networking_ovn/common/utils.py
  5. 2
      networking_ovn/db/migration/alembic_migrations/versions/EXPAND_HEAD
  6. 50
      networking_ovn/db/migration/alembic_migrations/versions/queens/expand/5c198d2723b6_add_ovn_revision_resource_type_as_pk.py
  7. 2
      networking_ovn/db/models.py
  8. 18
      networking_ovn/db/revision.py
  9. 27
      networking_ovn/l3/l3_ovn.py
  10. 19
      networking_ovn/ml2/mech_driver.py
  11. 1
      networking_ovn/ovsdb/commands.py
  12. 6
      networking_ovn/ovsdb/impl_idl_ovn.py
  13. 2
      networking_ovn/tests/unit/db/test_maintenance.py
  14. 2
      networking_ovn/tests/unit/db/test_revision.py
  15. 1
      networking_ovn/tests/unit/fakes.py
  16. 77
      networking_ovn/tests/unit/l3/test_l3_ovn.py
  17. 3
      networking_ovn/tests/unit/ml2/test_mech_driver.py

2
networking_ovn/common/constants.py

@ -99,6 +99,7 @@ TYPE_NETWORKS = 'networks'
TYPE_PORTS = 'ports'
TYPE_SECURITY_GROUP_RULES = 'security_group_rules'
TYPE_ROUTERS = 'routers'
TYPE_ROUTER_PORTS = 'router_ports'
TYPE_SECURITY_GROUPS = 'security_groups'
TYPE_FLOATINGIPS = 'floatingips'
TYPE_SUBNETS = 'subnets'
@ -109,6 +110,7 @@ _TYPES_PRIORITY_ORDER = (
TYPE_SUBNETS,
TYPE_ROUTERS,
TYPE_PORTS,
TYPE_ROUTER_PORTS,
TYPE_FLOATINGIPS,
TYPE_SECURITY_GROUP_RULES)

20
networking_ovn/common/maintenance.py

@ -133,6 +133,14 @@ class DBInconsistenciesPeriodics(object):
'ovn_create': self._ovn_client.create_security_group_rule,
'ovn_delete': self._ovn_client.delete_security_group_rule,
},
ovn_const.TYPE_ROUTER_PORTS: {
'neutron_get':
self._ovn_client._plugin.get_port,
'ovn_get': self._nb_idl.get_lrouter_port,
'ovn_create': self._create_lrouter_port,
'ovn_update': self._update_lrouter_port,
'ovn_delete': self._ovn_client.delete_router_port,
},
}
@property
@ -176,7 +184,7 @@ class DBInconsistenciesPeriodics(object):
res_map = self._resources_func_map[row.resource_type]
ovn_obj = res_map['ovn_get'](row.resource_uuid)
if not ovn_obj:
db_rev.delete_revision(row.resource_uuid)
db_rev.delete_revision(row.resource_uuid, row.resource_type)
else:
res_map['ovn_delete'](row.resource_uuid)
@ -238,3 +246,13 @@ class DBInconsistenciesPeriodics(object):
'(type: %(res_type)s)',
{'res_uuid': row.resource_uuid,
'res_type': row.resource_type})
def _create_lrouter_port(self, port):
admin_context = n_context.get_admin_context()
self._ovn_client._l3_plugin.add_router_interface(
admin_context, port['device_owner'],
{'port_id': port['id']})
def _update_lrouter_port(self, port):
self._ovn_client._l3_plugin.update_router_port(
port['device_owner'], port)

101
networking_ovn/common/ovn_client.py

@ -321,8 +321,7 @@ class OVNClient(object):
if self.is_dns_required_for_port(port):
self.add_txns_to_sync_port_dns_records(txn, port)
if not utils.is_lsp_router_port(port):
db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
# TODO(lucasagomes): Remove this helper method in the Rocky release
def _get_lsp_backward_compat_sgs(self, ovn_port, port_object=None,
@ -486,8 +485,7 @@ class OVNClient(object):
self.add_txns_to_remove_port_dns_records(txn, port_object)
if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
if not utils.is_lsp_router_port(port):
db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
def _delete_port(self, port_id, port_object=None):
ovn_port = self._nb_idl.lookup('Logical_Switch_Port', port_id)
@ -527,7 +525,7 @@ class OVNClient(object):
self._delete_port(port_id, port_object=port_object)
except idlutils.RowNotFound:
pass
db_rev.delete_revision(port_id)
db_rev.delete_revision(port_id, ovn_const.TYPE_PORTS)
def _create_or_update_floatingip(self, floatingip, txn=None):
router_id = floatingip.get('router_id')
@ -691,7 +689,7 @@ class OVNClient(object):
with excutils.save_and_reraise_exception():
LOG.error('Unable to delete floating ip in gateway '
'router. Error: %s', e)
db_rev.delete_revision(fip_id)
db_rev.delete_revision(fip_id, ovn_const.TYPE_FLOATINGIPS)
def disassociate_floatingip(self, floatingip, router_id):
lrouter = utils.ovn_name(router_id)
@ -785,6 +783,7 @@ class OVNClient(object):
# 3. Add snat rules for tenant networks in lrouter if snat is enabled
if utils.is_snat_enabled(router) and networks:
self.update_nat_rules(router, networks, enable_snat=True, txn=txn)
return port
def _check_external_ips_changed(self, context, ovn_snats, ovn_static_route,
router):
@ -869,6 +868,7 @@ class OVNClient(object):
external_ids = self._gen_router_ext_ids(router)
enabled = router.get('admin_state_up')
lrouter_name = utils.ovn_name(router['id'])
added_gw_port = None
with self._nb_idl.transaction(check_error=True) as txn:
txn.add(self._nb_idl.create_lrouter(lrouter_name,
external_ids=external_ids,
@ -881,7 +881,12 @@ class OVNClient(object):
networks = self._get_v4_network_of_all_router_ports(
context, router['id'])
if router.get(l3.EXTERNAL_GW_INFO) and networks is not None:
self._add_router_ext_gw(context, router, networks, txn)
added_gw_port = self._add_router_ext_gw(context, router,
networks, txn)
if added_gw_port:
db_rev.bump_revision(added_gw_port,
ovn_const.TYPE_ROUTER_PORTS)
db_rev.bump_revision(router, ovn_const.TYPE_ROUTERS)
# TODO(lucasagomes): The ``router_object`` parameter was added to
@ -895,6 +900,9 @@ class OVNClient(object):
ovn_router = self._nb_idl.get_lrouter(router_name)
gateway_new = new_router.get(l3.EXTERNAL_GW_INFO)
gateway_old = utils.get_lrouter_ext_gw_static_route(ovn_router)
added_gw_port = None
deleted_gw_port_id = None
if router_object:
gateway_old = gateway_old or router_object.get(l3.EXTERNAL_GW_INFO)
ovn_snats = utils.get_lrouter_snats(ovn_router)
@ -906,7 +914,7 @@ class OVNClient(object):
txn.add(check_rev_cmd)
if gateway_new and not gateway_old:
# Route gateway is set
self._add_router_ext_gw(
added_gw_port = self._add_router_ext_gw(
context, new_router, networks, txn)
elif gateway_old and not gateway_new:
# router gateway is removed
@ -914,6 +922,7 @@ class OVNClient(object):
if router_object:
self._delete_router_ext_gw(context, router_object,
networks, txn)
deleted_gw_port_id = router_object['gw_port_id']
elif gateway_new and gateway_old:
# Check if external gateway has changed, if yes, delete
# the old gateway and add the new gateway
@ -924,7 +933,8 @@ class OVNClient(object):
if router_object:
self._delete_router_ext_gw(context, router_object,
networks, txn)
self._add_router_ext_gw(
deleted_gw_port_id = router_object['gw_port_id']
added_gw_port = self._add_router_ext_gw(
context, new_router, networks, txn)
else:
# Check if snat has been enabled/disabled and update
@ -951,6 +961,14 @@ class OVNClient(object):
if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
db_rev.bump_revision(new_router, ovn_const.TYPE_ROUTERS)
if added_gw_port:
db_rev.bump_revision(added_gw_port,
ovn_const.TYPE_ROUTER_PORTS)
if deleted_gw_port_id:
db_rev.delete_revision(deleted_gw_port_id,
ovn_const.TYPE_ROUTER_PORTS)
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error('Unable to update router %(router)s. '
@ -962,7 +980,7 @@ class OVNClient(object):
lrouter_name = utils.ovn_name(router_id)
with self._nb_idl.transaction(check_error=True) as txn:
txn.add(self._nb_idl.delete_lrouter(lrouter_name))
db_rev.delete_revision(router_id)
db_rev.delete_revision(router_id, ovn_const.TYPE_ROUTERS)
def get_candidates_for_scheduling(self, extnet):
if extnet.get(pnet.NETWORK_TYPE) in [const.TYPE_FLAT,
@ -975,6 +993,11 @@ class OVNClient(object):
if physnet in physnets]
return []
def _gen_router_port_ext_ids(self, port):
return {
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
port, ovn_const.TYPE_ROUTER_PORTS))}
def create_router_port(self, router_id, port, txn=None):
"""Create a logical router port."""
lrouter = utils.ovn_name(router_id)
@ -997,15 +1020,23 @@ class OVNClient(object):
if ipv6_ra_configs:
columns['ipv6_ra_configs'] = ipv6_ra_configs
commands = [self._nb_idl.add_lrouter_port(
name=lrouter_port_name, lrouter=lrouter,
mac=port['mac_address'], networks=networks,
may_exist=True, **columns),
self._nb_idl.set_lrouter_port_in_lswitch_port(
port['id'], lrouter_port_name, is_gw_port=is_gw_port)]
commands = [
self._nb_idl.add_lrouter_port(
name=lrouter_port_name,
lrouter=lrouter,
mac=port['mac_address'],
networks=networks,
may_exist=True,
external_ids=self._gen_router_port_ext_ids(port),
**columns),
self._nb_idl.set_lrouter_port_in_lswitch_port(
port['id'], lrouter_port_name, is_gw_port=is_gw_port)]
self._transaction(commands, txn=txn)
# NOTE(mangelajo): we don't bump the revision here, but we do
# in the higher level add_router_interface function, because
# we can't consider it done until the Nat rules are updated
def update_router_port(self, port, if_exists=False):
def update_router_port(self, port, bump_db_rev=True, if_exists=False):
"""Update a logical router port."""
networks, ipv6_ra_configs = (
self._get_nets_and_ipv6_ra_confs_for_router_port(
@ -1015,19 +1046,34 @@ class OVNClient(object):
update = {'networks': networks, 'ipv6_ra_configs': ipv6_ra_configs}
is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
'device_owner')
check_rev_cmd = self._nb_idl.check_revision_number(
lrouter_port_name, port, ovn_const.TYPE_ROUTER_PORTS)
with self._nb_idl.transaction(check_error=True) as txn:
txn.add(self._nb_idl.update_lrouter_port(name=lrouter_port_name,
if_exists=if_exists,
**update))
txn.add(check_rev_cmd)
txn.add(self._nb_idl.update_lrouter_port(
name=lrouter_port_name,
external_ids=self._gen_router_port_ext_ids(port),
if_exists=if_exists,
**update))
txn.add(self._nb_idl.set_lrouter_port_in_lswitch_port(
port['id'], lrouter_port_name, is_gw_port=is_gw_port))
def delete_router_port(self, port_id, router_id):
if bump_db_rev and check_rev_cmd.result == ovn_const.TXN_COMMITTED:
db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
else:
# NOTE(mangelajo): we don't bump the revision here, but we do
# in the higher level add_router_interface function, because
# we can't consider it done until the Nat rules are updated
pass
def delete_router_port(self, port_id, router_id=None):
"""Delete a logical router port."""
with self._nb_idl.transaction(check_error=True) as txn:
txn.add(self._nb_idl.delete_lrouter_port(
txn.add(self._nb_idl.lrp_del(
utils.ovn_lrouter_port_name(port_id),
utils.ovn_name(router_id), if_exists=True))
utils.ovn_name(router_id) if router_id else None,
if_exists=True))
db_rev.delete_revision(port_id, ovn_const.TYPE_ROUTER_PORTS)
def update_nat_rules(self, router, networks, enable_snat, txn=None):
"""Update the NAT rules in a logical router."""
@ -1094,7 +1140,7 @@ class OVNClient(object):
if_exists=True))
if ls_dns_record:
txn.add(self._nb_idl.dns_del(ls_dns_record.uuid))
db_rev.delete_revision(network_id)
db_rev.delete_revision(network_id, ovn_const.TYPE_NETWORKS)
def _is_qos_update_required(self, network):
# Is qos service enabled
@ -1335,7 +1381,7 @@ class OVNClient(object):
def delete_subnet(self, subnet_id):
with self._nb_idl.transaction(check_error=True) as txn:
self._remove_subnet_dhcp_options(subnet_id, txn)
db_rev.delete_revision(subnet_id)
db_rev.delete_revision(subnet_id, ovn_const.TYPE_FLOATINGIPS)
def create_security_group(self, security_group):
with self._nb_idl.transaction(check_error=True) as txn:
@ -1351,7 +1397,8 @@ class OVNClient(object):
for ip_version in ('ip4', 'ip6'):
name = utils.ovn_addrset_name(security_group_id, ip_version)
txn.add(self._nb_idl.delete_address_set(name=name))
db_rev.delete_revision(security_group_id)
db_rev.delete_revision(security_group_id,
ovn_const.TYPE_SECURITY_GROUPS)
def _process_security_group_rule(self, rule, is_add_acl=True):
admin_context = n_context.get_admin_context()
@ -1365,7 +1412,7 @@ class OVNClient(object):
def delete_security_group_rule(self, rule):
self._process_security_group_rule(rule, is_add_acl=False)
db_rev.delete_revision(rule['id'])
db_rev.delete_revision(rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
def _find_metadata_port(self, context, network_id):
if not config.is_ovn_metadata_enabled():

1
networking_ovn/common/utils.py

@ -207,6 +207,7 @@ def get_revision_number(resource, resource_type):
constants.TYPE_PORTS,
constants.TYPE_SECURITY_GROUP_RULES,
constants.TYPE_ROUTERS,
constants.TYPE_ROUTER_PORTS,
constants.TYPE_SECURITY_GROUPS,
constants.TYPE_FLOATINGIPS, constants.TYPE_SUBNETS):
return resource['revision_number']

2
networking_ovn/db/migration/alembic_migrations/versions/EXPAND_HEAD

@ -1 +1 @@
f48286668608
5c198d2723b6

50
networking_ovn/db/migration/alembic_migrations/versions/queens/expand/5c198d2723b6_add_ovn_revision_resource_type_as_pk.py

@ -0,0 +1,50 @@
# Copyright 2018 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.
#
"""add_ovn_revision_resource_type_as_pk
Revision ID: 5c198d2723b6
Revises: f48286668608
Create Date: 2018-01-17 16:10:20.232123
"""
# revision identifiers, used by Alembic.
revision = '5c198d2723b6'
down_revision = 'f48286668608'
from alembic import op
from sqlalchemy.engine.reflection import Inspector as insp
MYSQL_ENGINE = 'mysql'
OVN_REVISION_NUMBER = 'ovn_revision_numbers'
def upgrade():
bind = op.get_bind()
engine = bind.engine
if (engine.name == MYSQL_ENGINE):
op.execute("ALTER TABLE ovn_revision_numbers DROP PRIMARY KEY,"
"ADD PRIMARY KEY (resource_uuid, resource_type);")
else:
inspector = insp.from_engine(bind)
pk_constraint = inspector.get_pk_constraint(OVN_REVISION_NUMBER)
op.drop_constraint(pk_constraint.get('name'), OVN_REVISION_NUMBER,
type_='primary')
op.create_primary_key(op.f('pk_ovn_revision_numbers'),
OVN_REVISION_NUMBER, ['resource_uuid',
'resource_type'])

2
networking_ovn/db/models.py

@ -28,7 +28,7 @@ class OVNRevisionNumbers(model_base.BASEV2):
sa.ForeignKey('standardattributes.id', ondelete='SET NULL'),
nullable=True)
resource_uuid = sa.Column(sa.String(36), nullable=False, primary_key=True)
resource_type = sa.Column(sa.String(36), nullable=False)
resource_type = sa.Column(sa.String(36), nullable=False, primary_key=True)
revision_number = sa.Column(
sa.BigInteger().with_variant(sa.Integer(), 'sqlite'),
server_default='0', nullable=False)

18
networking_ovn/db/revision.py

@ -28,6 +28,10 @@ LOG = log.getLogger(__name__)
STD_ATTR_MAP = standard_attr.get_standard_attr_resource_model_map()
# 1:2 mapping for OVN, neutron router ports are simple ports, but
# for OVN we handle LSP & LRP objects
STD_ATTR_MAP[ovn_const.TYPE_ROUTER_PORTS] = STD_ATTR_MAP[ovn_const.TYPE_PORTS]
_wrap_db_retry = oslo_db_api.wrap_db_retry(
max_retries=ovn_const.DB_MAX_RETRIES,
retry_interval=ovn_const.DB_INITIAL_RETRY_INTERVAL,
@ -48,6 +52,8 @@ def _get_standard_attr_id(session, resource_uuid, resource_type):
@_wrap_db_retry
def create_initial_revision(resource_uuid, resource_type, session,
revision_number=ovn_const.INITIAL_REV_NUM):
LOG.debug('create_initial_revision uuid=%s, type=%s, rev=%s',
resource_uuid, resource_type, revision_number)
with session.begin(subtransactions=True):
std_attr_id = _get_standard_attr_id(
session, resource_uuid, resource_type)
@ -58,11 +64,13 @@ def create_initial_revision(resource_uuid, resource_type, session,
@_wrap_db_retry
def delete_revision(resource_id):
def delete_revision(resource_id, resource_type):
LOG.debug('delete_revision(%s)', resource_id)
session = db_api.get_writer_session()
with session.begin():
row = session.query(models.OVNRevisionNumbers).filter_by(
resource_uuid=resource_id).one_or_none()
resource_uuid=resource_id,
resource_type=resource_type).one_or_none()
if row:
session.delete(row)
@ -82,7 +90,8 @@ def _ensure_revision_row_exist(session, resource, resource_type):
with session.begin(subtransactions=True):
try:
session.query(models.OVNRevisionNumbers).filter_by(
resource_uuid=resource['id']).one()
resource_uuid=resource['id'],
resource_type=resource_type).one()
except exc.NoResultFound:
LOG.warning(
'No revision row found for %(res_uuid)s (type: '
@ -101,7 +110,8 @@ def bump_revision(resource, resource_type):
std_attr_id = _get_standard_attr_id(
session, resource['id'], resource_type)
row = session.merge(models.OVNRevisionNumbers(
standard_attr_id=std_attr_id, resource_uuid=resource['id']))
standard_attr_id=std_attr_id, resource_uuid=resource['id'],
resource_type=resource_type))
if revision_number < row.revision_number:
LOG.debug(
'Skip bumping the revision number for %(res_uuid)s (type: '

27
networking_ovn/l3/l3_ovn.py

@ -170,13 +170,15 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
len(port['fixed_ips']) > 1):
# NOTE(lizk) It's adding a subnet onto an already existing router
# interface port, try to update lrouter port 'networks' column.
self._ovn_client.update_router_port(port)
self._ovn_client.update_router_port(port,
bump_db_rev=False)
multi_prefix = True
else:
self._ovn_client.create_router_port(router_id, port)
router = self.get_router(context, router_id)
if not router.get(l3.EXTERNAL_GW_INFO):
db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
return router_interface_info
cidr = None
@ -205,6 +207,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
{'subnet': router_interface_info['subnet_id'],
'router': router_id})
db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
return router_interface_info
def remove_router_interface(self, context, router_id, interface_info):
@ -214,17 +217,22 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
router = self.get_router(context, router_id)
port_id = router_interface_info['port_id']
multi_prefix = False
port_removed = False
try:
port = self._plugin.get_port(context, port_id)
# The router interface port still exists, call ovn to update it.
self._ovn_client.update_router_port(port)
self._ovn_client.update_router_port(port,
bump_db_rev=False)
multi_prefix = True
except n_exc.PortNotFound:
# The router interface port doesn't exist any more, call ovn to
# delete it.
self._ovn_client.delete_router_port(port_id, router_id)
# The router interface port doesn't exist any more,
# we will call ovn to delete it once we remove the snat
# rules in the router itself if we have to
port_removed = True
if not router.get(l3.EXTERNAL_GW_INFO):
if port_removed:
self._ovn_client.delete_router_port(port_id, router_id)
return router_interface_info
try:
@ -251,6 +259,15 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
context, router_id, interface_info)
LOG.error('Error is deleting snat')
# NOTE(mangelajo): If the port doesn't exist anymore, we delete the
# router port as the last operation and update the revision database
# to ensure consistency
if port_removed:
self._ovn_client.delete_router_port(port_id, router_id)
else:
# otherwise, we just update the revision database
db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
return router_interface_info
def create_floatingip_precommit(self, resource, event, trigger, context,

19
networking_ovn/ml2/mech_driver.py

@ -383,8 +383,15 @@ class OVNMechanismDriver(api.MechanismDriver):
if self._is_port_provisioning_required(port, context.host):
self._insert_port_provisioning_block(context._plugin_context, port)
if not utils.is_lsp_router_port(port):
db_rev.create_initial_revision(port['id'], ovn_const.TYPE_PORTS,
db_rev.create_initial_revision(port['id'],
ovn_const.TYPE_PORTS,
context._plugin_context.session)
# in the case of router ports we also need to
# track the creation and update of the LRP OVN objects
if utils.is_lsp_router_port(port):
db_rev.create_initial_revision(port['id'],
ovn_const.TYPE_ROUTER_PORTS,
context._plugin_context.session)
def _is_port_provisioning_required(self, port, host, original_host=None):
@ -490,6 +497,14 @@ class OVNMechanismDriver(api.MechanismDriver):
context.original_host):
self._insert_port_provisioning_block(context._plugin_context, port)
if utils.is_lsp_router_port(port):
# handle the case when an existing port is added to a
# logical router so we need to track the creation of the lrp
if not utils.is_lsp_router_port(original_port):
db_rev.create_initial_revision(port['id'],
ovn_const.TYPE_ROUTER_PORTS,
context._plugin_context.session)
def update_port_postcommit(self, context):
"""Update a port.

1
networking_ovn/ovsdb/commands.py

@ -22,6 +22,7 @@ RESOURCE_TYPE_MAP = {
ovn_const.TYPE_NETWORKS: 'Logical_Switch',
ovn_const.TYPE_PORTS: 'Logical_Switch_Port',
ovn_const.TYPE_ROUTERS: 'Logical_Router',
ovn_const.TYPE_ROUTER_PORTS: 'Logical_Router_Port',
ovn_const.TYPE_FLOATINGIPS: 'NAT',
ovn_const.TYPE_SUBNETS: 'DHCP_Options',
}

6
networking_ovn/ovsdb/impl_idl_ovn.py

@ -636,6 +636,12 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
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
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)

2
networking_ovn/tests/unit/db/test_maintenance.py

@ -116,6 +116,8 @@ class TestMaintenance(test_securitygroup.SecurityGroupsTestCase,
fip['id'], constants.TYPE_FLOATINGIPS, self.session)
db_rev.create_initial_revision(
port['id'], constants.TYPE_PORTS, self.session)
db_rev.create_initial_revision(
port['id'], constants.TYPE_ROUTER_PORTS, self.session)
db_rev.create_initial_revision(
sg['id'], constants.TYPE_SECURITY_GROUPS, self.session)
db_rev.create_initial_revision(

2
networking_ovn/tests/unit/db/test_revision.py

@ -61,6 +61,6 @@ class TestRevisionNumber(db_base.DBTestCase, test_plugin.Ml2PluginV2TestCase):
def test_delete_revision(self):
db_rev.create_initial_revision(self.net['id'], constants.TYPE_NETWORKS,
self.session)
db_rev.delete_revision(self.net['id'])
db_rev.delete_revision(self.net['id'], constants.TYPE_NETWORKS)
row = self.get_revision_row(self.net['id'])
self.assertIsNone(row)

1
networking_ovn/tests/unit/fakes.py

@ -56,6 +56,7 @@ class FakeOvsdbNbOvnIdl(object):
self.delete_lswitch_port = mock.Mock()
self.get_acls_for_lswitches = mock.Mock()
self.create_lrouter = mock.Mock()
self.lrp_del = mock.Mock()
self.update_lrouter = mock.Mock()
self.delete_lrouter = mock.Mock()
self.add_lrouter_port = mock.Mock()

77
networking_ovn/tests/unit/l3/test_l3_ovn.py

@ -42,8 +42,9 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
def _start_mock(self, path, return_value, new_callable=None):
patcher = mock.patch(path, return_value=return_value,
new_callable=new_callable)
patcher.start()
patch = patcher.start()
self.addCleanup(patcher.stop)
return patch
def setUp(self):
super(OVNL3RouterPlugin, self).setUp()
@ -57,11 +58,13 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'fixed_ips': [{'ip_address': '10.0.0.100',
'subnet_id': 'subnet-id'}],
'id': 'router-port-id'}
self.fake_router_port_assert = {'lrouter': 'neutron-router-id',
'mac': 'aa:aa:aa:aa:aa:aa',
'name': 'lrp-router-port-id',
'may_exist': True,
'networks': ['10.0.0.100/24']}
self.fake_router_port_assert = {
'lrouter': 'neutron-router-id',
'mac': 'aa:aa:aa:aa:aa:aa',
'name': 'lrp-router-port-id',
'may_exist': True,
'networks': ['10.0.0.100/24'],
'external_ids': {ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}}
self.fake_router_ports = [self.fake_router_port]
self.fake_subnet = {'id': 'subnet-id',
'ip_version': 4,
@ -107,12 +110,14 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'mac_address': '00:00:00:02:04:06',
'network_id': self.fake_network['id'],
'id': 'gw-port-id'}
self.fake_ext_gw_port_assert = {'lrouter': 'neutron-router-id',
'mac': '00:00:00:02:04:06',
'name': 'lrp-gw-port-id',
'networks': ['192.168.1.1/24'],
'may_exist': True,
'gateway_chassis': ['hv1']}
self.fake_ext_gw_port_assert = {
'lrouter': 'neutron-router-id',
'mac': '00:00:00:02:04:06',
'name': 'lrp-gw-port-id',
'networks': ['192.168.1.1/24'],
'may_exist': True,
'external_ids': {ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
'gateway_chassis': ['hv1']}
self.fake_floating_ip_attrs = {'floating_ip_address': '192.168.0.10',
'fixed_ip_address': '10.0.0.10'}
self.fake_floating_ip = fakes.FakeFloatingIp.create_one_fip(
@ -192,6 +197,10 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'networking_ovn.common.ovn_client.'
'OVNClient.update_floatingip_status',
return_value=None)
self.bump_rev_p = self._start_mock(
'networking_ovn.db.revision.bump_revision', return_value=None)
self.del_rev_p = self._start_mock(
'networking_ovn.db.revision.delete_revision', return_value=None)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface(self, func):
@ -205,6 +214,8 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
assert_called_once_with('router-port-id', 'lrp-router-port-id',
is_gw_port=False)
self.bump_rev_p.assert_called_once_with(self.fake_router_port,
ovn_const.TYPE_ROUTER_PORTS)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@ -234,6 +245,7 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
interface_info)
called_args_dict = (
self.l3_inst._ovn.update_lrouter_port.call_args_list[0][1])
self.assertEqual(1, self.l3_inst._ovn.update_lrouter_port.call_count)
self.assertItemsEqual(fake_rtr_intf_networks,
called_args_dict.get('networks', []))
@ -250,8 +262,10 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
self.l3_inst._ovn.delete_lrouter_port.assert_called_once_with(
self.l3_inst._ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
self.del_rev_p.assert_called_once_with('router-port-id',
ovn_const.TYPE_ROUTER_PORTS)
def test_remove_router_interface_update_lrouter_port(self):
router_id = 'router-id'
@ -262,7 +276,8 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst._ovn.update_lrouter_port.assert_called_once_with(
if_exists=False, name='lrp-router-port-id',
ipv6_ra_configs={},
networks=['10.0.0.100/24'])
networks=['10.0.0.100/24'],
external_ids={ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'})
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router')
@ -357,18 +372,16 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'neutron-router-id',
ip_prefix='1.1.1.0/24', nexthop='10.0.0.2')
@mock.patch('networking_ovn.db.revision.bump_revision')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
@mock.patch('networking_ovn.common.ovn_client.OVNClient.'
'_get_v4_network_of_all_router_ports')
def test_create_router_with_ext_gw(self, get_rps, get_subnet, get_port,
mock_bump):
def test_create_router_with_ext_gw(self, get_rps, get_subnet, get_port):
self.l3_inst._ovn.is_col_present.return_value = True
router = {'router': {'name': 'router'}}
get_subnet.return_value = self.fake_ext_subnet
get_port.return_value = self.fake_ext_gw_port
get_rps.return_value = []
get_rps.return_value = self.fake_ext_subnet['cidr']
self.l3_inst.create_router(self.context, router)
@ -390,8 +403,15 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
assert_called_once_with('gw-port-id', 'lrp-gw-port-id',
is_gw_port=True)
self.l3_inst._ovn.add_static_route.assert_has_calls(expected_calls)
mock_bump.assert_called_once_with(self.fake_router_with_ext_gw,
ovn_const.TYPE_ROUTERS)
bump_rev_calls = [mock.call(self.fake_ext_gw_port,
ovn_const.TYPE_ROUTER_PORTS),
mock.call(self.fake_router_with_ext_gw,
ovn_const.TYPE_ROUTERS),
]
self.assertEqual(len(bump_rev_calls), self.bump_rev_p.call_count)
self.bump_rev_p.assert_has_calls(bump_rev_calls, any_order=False)
@mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
@ -431,6 +451,9 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'neutron-router-id', logical_ip='10.0.0.0/24',
external_ip='192.168.1.1', type='snat')
self.bump_rev_p.assert_called_with(self.fake_router_port,
ovn_const.TYPE_ROUTER_PORTS)
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
@mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
@ -469,12 +492,15 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
self.l3_inst._ovn.delete_lrouter_port.assert_called_once_with(
self.l3_inst._ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id', logical_ip='10.0.0.0/24',
external_ip='192.168.1.1', type='snat')
self.del_rev_p.assert_called_with('router-port-id',
ovn_const.TYPE_ROUTER_PORTS)
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
@ -506,6 +532,8 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id', type='snat',
logical_ip='10.0.0.0/24', external_ip='192.168.1.1')
self.bump_rev_p.assert_called_with(self.fake_ext_gw_port,
ovn_const.TYPE_ROUTER_PORTS)
@mock.patch.object(utils, 'get_lrouter_ext_gw_static_route')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@ -559,6 +587,11 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
'neutron-router-id', type='snat', logical_ip='10.0.0.0/24',
external_ip='192.168.1.1')
self.bump_rev_p.assert_called_with(self.fake_ext_gw_port,
ovn_const.TYPE_ROUTER_PORTS)
self.del_rev_p.assert_called_once_with('old-gw-port-id',
ovn_const.TYPE_ROUTER_PORTS)
@mock.patch.object(utils, 'get_lrouter_ext_gw_static_route')
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
@mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
@ -618,7 +651,7 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self.l3_inst.update_router(self.context, 'router-id', router)
self.l3_inst._ovn.delete_lrouter_port.assert_not_called()
self.l3_inst._ovn.lrp_del.assert_not_called()
self.l3_inst._ovn.delete_static_route.assert_not_called()
self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_not_called()
self.l3_inst._ovn.add_lrouter_port.assert_not_called()

3
networking_ovn/tests/unit/ml2/test_mech_driver.py

@ -158,7 +158,8 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
ovn_acl_up.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY,
'sg_id', rule, is_add_acl=False)
mock_delrev.assert_called_once_with(rule['id'])
mock_delrev.assert_called_once_with(
rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
def test_add_acls_no_sec_group(self):
acls = ovn_acl.add_acls(self.mech_driver._plugin,

Loading…
Cancel
Save