Merge "ovn: Remove is_port_groups_supported() code"

This commit is contained in:
Zuul 2020-06-10 17:09:23 +00:00 committed by Gerrit Code Review
commit b69e8892d9
19 changed files with 255 additions and 1775 deletions

View File

@ -218,10 +218,23 @@ for the compute node.
Security Groups/Rules
---------------------
Each security group will map to 2 Address_Sets in the OVN NB and SB
tables, one for ipv4 and another for ipv6, which will be used to hold ip
addresses for the ports that belong to the security group, so that rules
with remote_group_id can be efficiently applied.
When a Neutron Security Group is created, the equivalent Port Group in OVN
(pg-<security_group_id> is created). This Port Group references Neutron SG id
in its external_ids column.
When a Neutron Port is created, the equivalent Logical Port in OVN is added to
those Port Groups associated to the Neutron Security Groups this port belongs
to.
When a Neutron Port is deleted, the associated Logical Port in OVN is deleted.
Since the schema includes a weak reference to the port, when the LSP gets
deleted, it is automatically deleted from any Port Group entry where it was
previously present.
Every time a security group rule is created, instead of figuring out the ports
affected by its SG and inserting an ACL row which will be referenced by
different Logical Switches, we just reference it from the associated
Port Group.
.. todo: add block with openstack security group rule example
@ -229,36 +242,52 @@ OVN operations
~~~~~~~~~~~~~~
#. Creating a security group will cause the OVN mechanism driver to create
2 new entries in the Address Set table of the northbound DB:
a port group in the Port_Group table of the northbound DB:
.. code-block:: console
_uuid : 9a9d01bd-4afc-4d12-853a-cd21b547911d
addresses : []
external_ids : {"neutron:security_group_name"=default}
name : "as_ip4_90a78a43_b549_4bee_8822_21fcccab58dc"
_uuid : e96c5994-695d-4b9c-a17b-c7375ad281e2
acls : [33c3c2d0-bc7b-421b-ace9-10884851521a, c22170ec-da5d-4a59-b118-f7f0e370ebc4]
external_ids : {"neutron:security_group_id"="ccbeffee-7b98-4b6f-adf7-d42027ca6447"}
name : pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447
ports : []
_uuid : 27a91327-636e-4125-99f0-6f2937a3b6d8
addresses : []
external_ids : {"neutron:security_group_name"=default}
name : "as_ip6_90a78a43_b549_4bee_8822_21fcccab58dc"
In the above entries, the address set name include the protocol (IPv4
or IPv6, written as ip4 or ip6) and the UUID of the Openstack security
group, dashes translated to underscores.
#. In turn, these new entries will be translated by the OVN northd daemon
into entries in the southbound DB:
And it also creates the default ACLs for egress traffic in the ACL table of
the northbound DB:
.. code-block:: console
_uuid : 886d7b3a-e460-470f-8af2-7c7d88ce45d2
addresses : []
name : "as_ip4_90a78a43_b549_4bee_8822_21fcccab58dc"
_uuid : 33c3c2d0-bc7b-421b-ace9-10884851521a
action : allow-related
direction : from-lport
external_ids : {"neutron:security_group_rule_id"="655b0d7e-144e-4bd8-9243-10a261b91041"}
log : false
match : "inport == @pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447 && ip4"
meter : []
name : []
priority : 1002
severity : []
_uuid : 355ddcba-941d-4f1c-b823-dc811cec59ca
addresses : []
name : "as_ip6_90a78a43_b549_4bee_8822_21fcccab58dc"
_uuid : c22170ec-da5d-4a59-b118-f7f0e370ebc4
action : allow-related
direction : from-lport
external_ids : {"neutron:security_group_rule_id"="a303a34f-5f19-494f-a9e2-e23f246bfcad"}
log : false
match : "inport == @pg_ccbeffee_7b98_4b6f_adf7_d42027ca6447 && ip6"
meter : []
name : []
priority : 1002
severity : []
Ports with no security groups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a port doesn't belong to any Security Group and port security is enabled,
we, by default, drop all the traffic to/from that port. In order to implement
this through Port Groups, we'll create a special Port Group with a fixed name
(``neutron_pg_drop``) which holds the ACLs to drop all the traffic.
This PG is created automatically once before neutron-server forks into workers.
Networks
--------

View File

@ -1,186 +0,0 @@
.. _acl_optimizations:
========================================
ACL Handling optimizations in ovn driver
========================================
This document presents the current problem with ACLs and the design changes
proposed to core OVN as well as the necessary modifications to be made to
ovn driver to improve their usage.
Problem description
===================
There is basically two problems being addressed in this spec:
1. While in Neutron, a ``Security Group Rule`` is tied to a
``Security Group``, in OVN ``ACLs`` are created per port. Therefore,
we'll typically have *many* more ACLs than Security Group Rules, resulting
in a performance hit as the number of ports grows.
2. An ACL in OVN is applied to a ``Logical Switch``. As a result,
``ovn driver`` has to figure out which Logical Switches to apply the
generated ACLs per each Security Rule.
Let's highlight both problems with an example:
- Neutron Networks: NA, NB, NC
- Neutron Security Group: SG1
- Number of Neutron Security Group Rules in SG1: 10
- Neutron Ports in NA: 100
- Neutron Ports in NB: 100
- Neutron Ports in NC: 100
- All ports belong to SG1
When we implement the above scenario in OVN, this is what we'll get:
- OVN Logical Switches: NA, NB, NC
- Number of ACL rows in Northbound DB ACL table: 3000 (10 rules * 100 ports *
3 networks)
- Number of elements in acl column on each Logical_Switch row: 1000 (10 rules
* 100 ports).
And this is how, for example, the ACL match fields for the default Neutron
Security Group would look like::
outport == <port1_uuid> && ip4 && ip4.src == $as_ip4_<sg1_uuid>
outport == <port2_uuid> && ip4 && ip4.src == $as_ip4_<sg1_uuid>
outport == <port3_uuid> && ip4 && ip4.src == $as_ip4_<sg1_uuid>
...
outport == <port300_uuid> && ip4 && ip4.src == $as_ip4_<sg1_uuid>
As you can see, all of them look the same except for the outport field which
is clearly redundant and makes the NB database grow a lot at scale.
Also, ``ovn driver`` had to figure out for each rule in SG1 which Logical
Switches it had to apply the ACLs on (NA, NB and NC). This can be really costly
when the number of networks and port grows.
Proposed optimization
=====================
In the OpenStack context, we'll be facing this scenario most of the time
where the majority of the ACLs will look the same except for the
outport/inport fields in the match column. It would make sense to be able to
substitute all those ACLs by a single one which references all the ports
affected by that SG rule::
outport == @port_group1 && ip4 && ip4.src == $port_group1_ip4
Implementation Details
======================
Core OVN
--------
There's a series of patches in Core OVN that will enable us to achieve this
optimization:
https://github.com/openvswitch/ovs/commit/3d2848bafa93a2b483a4504c5de801454671dccf
https://github.com/openvswitch/ovs/commit/1beb60afd25a64f1779903b22b37ed3d9956d47c
https://github.com/openvswitch/ovs/commit/689829d53612a573f810271a01561f7b0948c8c8
In summary, these patches are:
- Adding a new entity called Port_Group which will hold a list of weak
references to the Logical Switch ports that belong to it.
- Automatically creating/updating two Address Sets (_ip4 and _ip6) in
Southbound database every time a new port is added to the group.
- Support adding a list of ACLs to a Port Group. As the SG rules may
span across different Logical Switches, we used to insert the ACLs in
all the Logical Switches where we have ports in within a SG. Figuring this
out is expensive and this new feature is a huge gain in terms of
performance when creating/deleting ports.
ovn driver
----------
In the OpenStack integration driver, the following changes are required to
accomplish this optimization:
- When a Neutron Security Group is created, create the equivalent Port Group
in OVN (pg-<security_group_id>), instead of creating a pair of Adress Sets
for IPv4 and IPv6. This Port Group will reference Neutron SG id in its
``external_ids`` column.
- When a Neutron Port is created, the equivalent Logical Port in OVN will be
added to those Port Groups associated to the Neutron Security Groups this
port belongs to.
- When a Neutron Port is deleted, we'll delete the associated Logical Port in
OVN. Since the schema includes a weak reference to the port, when the LSP
gets deleted, it will also be automatically deleted from any Port Group
entry where it was previously present.
- Instead of handling SG rules per port, we now need to handle them per SG
referencing the associated Port Group in the outport/inport fields. This
will be the biggest gain in terms of processing since we don't need to
iterate through all the ports anymore. For example:
.. code-block:: python
-def acl_direction(r, port):
+def acl_direction(r):
if r['direction'] == 'ingress':
portdir = 'outport'
else:
portdir = 'inport'
- return '%s == "%s"' % (portdir, port['id'])
+ return '%s == "@%s"' % (portdir, utils.ovn_name(r['security_group_id'])
- Every time a SG rule is created, instead of figuring out the ports affected
by its SG and inserting an ACL row which will be referrenced by different
Logical Switches, we will just reference it from the associated Port Group.
- For Neutron remote security groups, we just need to reference the
automatically created Address_Set for that Port Group.
As a bonus, we are tackling the race conditions that could happen in
Address_Sets right now when we're deleting and creating a port at the same
time. This is thanks to the fact that the Address_Sets in the SB table are
generated automatically by ovn-northd from the Port_Group contents and
Port Group is referencing actual Logical Switch Ports. More info at:
https://bugs.launchpad.net/networking-ovn/+bug/1611852
Backwards compatibility considerations
--------------------------------------
- If the schema doesn't include the ``Port_Group`` table, keep the old
behavior(Address Sets) for backwards compatibility.
- If the schema supports Port Groups, then a migration task will be performed
from an OvnWorker. This way we'll ensure that it'll happen only once across
the cloud thanks the OVSDB lock. This will be done right at the beginning of
the ovn_db_sync process to make sure that when neutron-server starts,
everything is in place to work with Port Groups. This migration process will
perform the following steps:
* Create the default drop Port Group and add all ports with port
security enabled to it.
* Create a Port Group for every existing Neutron Security Group and
add all its Security Group Rules as ACLs to that Port Group.
* Delete all existing Address Sets in NorthBound database which correspond to
a Neutron Security Group.
* Delete all the ACLs in every Logical Switch (Neutron network).
We should eventually remove the backwards compatibility and migration path. At
that point we should require OVS >= 2.10 from neutron ovn driver.
Special cases
-------------
Ports with no security groups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a port doesn't belong to any Security Group and port security is enabled,
we, by default, drop all the traffic to/from that port. In order to implement
this through Port Groups, we'll create a special Port Group with a fixed name
(``neutron_pg_drop``) which holds the ACLs to drop all the traffic.
This PG will be created automatically when we first need it, avoiding the need
to create it beforehand or during deployment.

View File

@ -12,7 +12,6 @@ OVN Design Notes
ovn_worker
metadata_api
database_consistency
acl_optimizations
loadbalancer
distributed_ovsdb_events
l3_ha_rescheduling

View File

@ -12,8 +12,6 @@
# under the License.
#
import netaddr
from neutron_lib import constants as const
from neutron_lib import exceptions as n_exceptions
from oslo_config import cfg
@ -274,44 +272,17 @@ def _get_sg_from_cache(plugin, admin_context, sg_cache, sg_id):
return sg
def acl_remote_group_id(r, ip_version, ovn=None):
def acl_remote_group_id(r, ip_version):
if not r['remote_group_id']:
return ''
src_or_dst = 'src' if r['direction'] == const.INGRESS_DIRECTION else 'dst'
if (ovn and ovn.is_port_groups_supported()):
addrset_name = utils.ovn_pg_addrset_name(r['remote_group_id'],
ip_version)
else:
addrset_name = utils.ovn_addrset_name(r['remote_group_id'],
ip_version)
return ' && %s.%s == $%s' % (ip_version, src_or_dst, addrset_name)
def _add_sg_rule_acl_for_port(port, r):
# Update the match based on which direction this rule is for (ingress
# or egress).
match = acl_direction(r, port)
# Update the match for IPv4 vs IPv6.
ip_match, ip_version, icmp = acl_ethertype(r)
match += ip_match
# Update the match if an IPv4 or IPv6 prefix was specified.
match += acl_remote_ip_prefix(r, ip_version)
# Update the match if remote group id was specified.
match += acl_remote_group_id(r, ip_version)
# Update the match for the protocol (tcp, udp, icmp) and port/type
# range if specified.
match += acl_protocol_and_ports(r, icmp)
# Finally, create the ACL entry for the direction specified.
return add_sg_rule_acl_for_port(port, r, match)
def _add_sg_rule_acl_for_port_group(port_group, r, ovn):
def _add_sg_rule_acl_for_port_group(port_group, r):
# Update the match based on which direction this rule is for (ingress
# or egress).
match = acl_direction(r, port_group=port_group)
@ -324,7 +295,7 @@ def _add_sg_rule_acl_for_port_group(port_group, r, ovn):
match += acl_remote_ip_prefix(r, ip_version)
# Update the match if remote group id was specified.
match += acl_remote_group_id(r, ip_version, ovn)
match += acl_remote_group_id(r, ip_version)
# Update the match for the protocol (tcp, udp, icmp) and port/type
# range if specified.
@ -342,7 +313,7 @@ def _acl_columns_name_severity_supported(nb_idl):
def add_acls_for_sg_port_group(ovn, security_group, txn):
for r in security_group['security_group_rules']:
acl = _add_sg_rule_acl_for_port_group(
utils.ovn_port_group_name(security_group['id']), r, ovn)
utils.ovn_port_group_name(security_group['id']), r)
txn.add(ovn.pg_acl_add(**acl))
@ -351,7 +322,6 @@ def update_acls_for_security_group(plugin,
ovn,
security_group_id,
security_group_rule,
sg_ports_cache=None,
is_add_acl=True):
# Skip ACLs if security groups aren't enabled
@ -361,13 +331,9 @@ def update_acls_for_security_group(plugin,
# Check if ACL log name and severity supported or not
keep_name_severity = _acl_columns_name_severity_supported(ovn)
# If we're using a Port Group for this SG, just update it.
# Otherwise, keep the old behavior.
if (ovn.is_port_groups_supported() and
not ovn.get_address_set(security_group_id)):
acl = _add_sg_rule_acl_for_port_group(
utils.ovn_port_group_name(security_group_id),
security_group_rule, ovn)
security_group_rule)
# Remove ACL log name and severity if not supported
if is_add_acl:
if not keep_name_severity:
@ -378,118 +344,6 @@ def update_acls_for_security_group(plugin,
ovn.pg_acl_del(acl['port_group'], acl['direction'],
acl['priority'], acl['match']).execute(
check_error=True)
return
# Get the security group ports.
sg_ports_cache = sg_ports_cache or {}
sg_ports = _get_sg_ports_from_cache(plugin,
admin_context,
sg_ports_cache,
security_group_id)
# ACLs associated with a security group may span logical switches
sg_port_ids = [binding['port_id'] for binding in sg_ports]
sg_port_ids = list(set(sg_port_ids))
port_list = plugin.get_ports(admin_context,
filters={'id': sg_port_ids})
if not port_list:
return
acl_new_values_dict = {}
update_port_list = []
# NOTE(lizk): We can directly locate the affected acl records,
# so no need to compare new acl values with existing acl objects.
for port in port_list:
# Skip trusted port
if utils.is_lsp_trusted(port):
continue
update_port_list.append(port)
acl = _add_sg_rule_acl_for_port(port, security_group_rule)
# Remove lport and lswitch since we don't need them
acl.pop('lport')
acl.pop('lswitch')
# Remove ACL log name and severity if not supported,
if not keep_name_severity:
acl.pop('name')
acl.pop('severity')
acl_new_values_dict[port['id']] = acl
if not update_port_list:
return
lswitch_names = {p['network_id'] for p in update_port_list}
ovn.update_acls(list(lswitch_names),
iter(update_port_list),
acl_new_values_dict,
need_compare=False,
is_add_acl=is_add_acl).execute(check_error=True)
def add_acls(plugin, admin_context, port, sg_cache, subnet_cache, ovn):
acl_list = []
# Skip ACLs if security groups aren't enabled
if not is_sg_enabled():
return acl_list
sec_groups = utils.get_lsp_security_groups(port)
if not sec_groups:
# If it is a trusted port or port security is disabled, allow all
# traffic. So don't add any ACLs.
if utils.is_lsp_trusted(port) or not utils.is_port_security_enabled(
port):
return acl_list
# if port security is enabled, drop all traffic.
return drop_all_ip_traffic_for_port(port)
# Drop all IP traffic to and from the logical port by default.
acl_list += drop_all_ip_traffic_for_port(port)
# Add DHCP ACLs.
port_subnet_ids = set()
for ip in port['fixed_ips']:
if netaddr.IPNetwork(ip['ip_address']).version != 4:
continue
subnet = _get_subnet_from_cache(plugin,
admin_context,
subnet_cache,
ip['subnet_id'])
# Ignore duplicate DHCP ACLs for the subnet.
if subnet['id'] not in port_subnet_ids:
acl_list += add_acl_dhcp(port, subnet, True)
port_subnet_ids.add(subnet['id'])
# We create an ACL entry for each rule on each security group applied
# to this port.
for sg_id in sec_groups:
sg = _get_sg_from_cache(plugin,
admin_context,
sg_cache,
sg_id)
for r in sg['security_group_rules']:
acl = _add_sg_rule_acl_for_port(port, r)
acl_list.append(acl)
# Remove ACL log name and severity if not supported,
if not _acl_columns_name_severity_supported(ovn):
for acl in acl_list:
acl.pop('name')
acl.pop('severity')
return acl_list
def acl_port_ips(port):
# Skip ACLs if security groups aren't enabled
if not is_sg_enabled():
return {'ip4': [], 'ip6': []}
ip_list = [x['ip_address'] for x in port.get('fixed_ips', [])]
ip_list.extend(utils.get_allowed_address_pairs_ip_addresses(port))
return utils.sort_ips_by_version(ip_list)
def filter_acl_dict(acl, extra_fields=None):

View File

@ -255,20 +255,6 @@ class API(api.API, metaclass=abc.ABCMeta):
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def create_address_set(self, name, may_exist=True, **columns):
"""Create an address set
:param name: The name of the address set
:type name: string
:param may_exist: Do not fail if address set already exists
:type may_exist: bool
:param columns: Dictionary of address set columns
Supported columns: external_ids, addresses
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_address_set(self, name, if_exists=True):
"""Delete an address set
@ -280,35 +266,6 @@ class API(api.API, metaclass=abc.ABCMeta):
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_address_set(self, name, addrs_add, addrs_remove,
if_exists=True):
"""Updates addresses in an address set
:param name: The name of the address set
:type name: string
:param addrs_add: The addresses to be added
:type addrs_add: []
:param addrs_remove: The addresses to be removed
:type addrs_remove: []
:param if_exists: Do not fail if the address set does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_address_set_ext_ids(self, name, external_ids, if_exists=True):
"""Update external IDs for an address set
:param name: The name of the address set
:type name: string
:param external_ids: The external IDs for the address set
:type external_ids: dict
:param if_exists: Do not fail if the address set does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_all_chassis_gateway_bindings(self,
chassis_candidate_list=None):
@ -577,17 +534,6 @@ class API(api.API, metaclass=abc.ABCMeta):
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_address_set(self, addrset_id, ip_version='ip4'):
"""Get a Address Set by its ID.
:param addrset_id: The Address Set ID
:type addrset_id: string
:param ip_version: Either "ip4" or "ip6". Defaults to "ip4"
:type addr_name: string
:returns: The Address Set row or None
"""
@abc.abstractmethod
def set_lswitch_port_to_virtual_type(self, lport_name, vip,
virtual_parent, if_exists=True):

View File

@ -675,25 +675,6 @@ class DelStaticRouteCommand(command.BaseCommand):
break
class AddAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, may_exist, **columns):
super(AddAddrSetCommand, self).__init__(api)
self.name = name
self.columns = columns
self.may_exist = may_exist
def run_idl(self, txn):
if self.may_exist:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name, None)
if addrset:
return
row = txn.insert(self.api._tables['Address_Set'])
row.name = self.name
for col, val in self.columns.items():
setattr(row, col, val)
class DelAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, if_exists):
super(DelAddrSetCommand, self).__init__(api)
@ -714,56 +695,6 @@ class DelAddrSetCommand(command.BaseCommand):
self.api._tables['Address_Set'].rows[addrset.uuid].delete()
class UpdateAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, addrs_add, addrs_remove, if_exists):
super(UpdateAddrSetCommand, self).__init__(api)
self.name = name
self.addrs_add = addrs_add
self.addrs_remove = addrs_remove
self.if_exists = if_exists
def run_idl(self, txn):
try:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Address set %s does not exist. "
"Can't update addresses") % self.name
raise RuntimeError(msg)
_updatevalues_in_list(
addrset, 'addresses',
new_values=self.addrs_add,
old_values=self.addrs_remove)
class UpdateAddrSetExtIdsCommand(command.BaseCommand):
def __init__(self, api, name, external_ids, if_exists):
super(UpdateAddrSetExtIdsCommand, self).__init__(api)
self.name = name
self.external_ids = external_ids
self.if_exists = if_exists
def run_idl(self, txn):
try:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Address set %s does not exist. "
"Can't update external IDs") % self.name
raise RuntimeError(msg)
addrset.verify('external_ids')
addrset_external_ids = getattr(addrset, 'external_ids', {})
for ext_id_key, ext_id_value in self.external_ids.items():
addrset_external_ids[ext_id_key] = ext_id_value
addrset.external_ids = addrset_external_ids
class UpdateChassisExtIdsCommand(command.BaseCommand):
def __init__(self, api, name, external_ids, if_exists):
super(UpdateChassisExtIdsCommand, self).__init__(api)

View File

@ -373,21 +373,9 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
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.
@ -657,14 +645,6 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
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(
@ -691,9 +671,6 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
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)

View File

@ -168,7 +168,7 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
},
ovn_const.TYPE_SECURITY_GROUPS: {
'neutron_get': self._ovn_client._plugin.get_security_group,
'ovn_get': self._get_security_group,
'ovn_get': self._nb_idl.get_port_group,
'ovn_create': self._ovn_client.create_security_group,
'ovn_delete': self._ovn_client.delete_security_group,
},
@ -189,10 +189,6 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
},
}
def _get_security_group(self, uuid):
return (self._nb_idl.get_address_set(uuid) or
self._nb_idl.get_port_group(uuid))
@property
def has_lock(self):
return not self._idl.is_lock_contended
@ -287,8 +283,7 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
# If Port Groups are not supported or we've already migrated, we don't
# need to attempt to migrate again.
if (not self._nb_idl.is_port_groups_supported() or
not self._nb_idl.get_address_sets()):
if not self._nb_idl.get_address_sets():
raise periodics.NeverAgain()
# Only the worker holding a valid lock within OVSDB will perform the

View File

@ -322,8 +322,6 @@ class OVNClient(object):
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
lswitch_name = utils.ovn_name(port['network_id'])
sg_cache = {}
subnet_cache = {}
# It's possible to have a network created on one controller and then a
# port created on a different controller quickly enough that the second
@ -392,45 +390,16 @@ class OVNClient(object):
port_cmd = txn.add(self._nb_idl.create_lswitch_port(
**kwargs))
# Handle ACL's for this port. If we're not using Port Groups
# because either the schema doesn't support it or we didn't
# migrate old SGs from Address Sets to Port Groups, then we
# keep the old behavior. For those SGs this port belongs to
# that are modelled as a Port Group, we'll use it.
sg_ids = utils.get_lsp_security_groups(port)
if self._nb_idl.is_port_groups_supported():
# If this is not a trusted port or port security is enabled,
# add it to the default drop Port Group so that all traffic
# is dropped by default.
if not utils.is_lsp_trusted(port) or port_info.port_security:
self._add_port_to_drop_port_group(port_cmd, txn)
# For SGs modelled as OVN Port Groups, just add the port to
# its Port Group.
# Just add the port to its Port Group.
for sg in sg_ids:
txn.add(self._nb_idl.pg_add_ports(
utils.ovn_port_group_name(sg), port_cmd))
else:
# SGs modelled as Address Sets:
acls_new = ovn_acl.add_acls(self._plugin, context,
port, sg_cache, subnet_cache,
self._nb_idl)
for acl in acls_new:
txn.add(self._nb_idl.add_acl(**acl))
if port.get('fixed_ips') and sg_ids:
addresses = ovn_acl.acl_port_ips(port)
# NOTE(rtheis): Fail port creation if the address set
# doesn't exist. This prevents ports from being created on
# any security groups out-of-sync between neutron and OVN.
for sg_id in sg_ids:
for ip_version in addresses:
if addresses[ip_version]:
txn.add(self._nb_idl.update_address_set(
name=utils.ovn_addrset_name(sg_id,
ip_version),
addrs_add=addresses[ip_version],
addrs_remove=None,
if_exists=False))
if self.is_dns_required_for_port(port):
self.add_txns_to_sync_port_dns_records(txn, port)
@ -481,8 +450,6 @@ class OVNClient(object):
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
sg_cache = {}
subnet_cache = {}
check_rev_cmd = self._nb_idl.check_revision_number(
port['id'], port, ovn_const.TYPE_PORTS)
@ -560,7 +527,6 @@ class OVNClient(object):
detached_sg_ids = old_sg_ids - new_sg_ids
attached_sg_ids = new_sg_ids - old_sg_ids
if self._nb_idl.is_port_groups_supported():
for sg in detached_sg_ids:
txn.add(self._nb_idl.pg_del_ports(
utils.ovn_port_group_name(sg), port['id']))
@ -577,79 +543,6 @@ class OVNClient(object):
not utils.is_port_security_enabled(port)) or
utils.is_lsp_trusted(port)):
self._del_port_from_drop_port_group(port['id'], txn)
else:
old_fixed_ips = utils.remove_macs_from_lsp_addresses(
ovn_port.addresses)
new_fixed_ips = [x['ip_address'] for x in
port.get('fixed_ips', [])]
is_fixed_ips_updated = (
sorted(old_fixed_ips) != sorted(new_fixed_ips))
port_security_changed = (
utils.is_port_security_enabled(port) !=
bool(ovn_port.port_security))
# Refresh ACLs for changed security groups or fixed IPs.
if (detached_sg_ids or attached_sg_ids or
is_fixed_ips_updated or port_security_changed):
# Note that update_acls will compare the port's ACLs to
# ensure only the necessary ACLs are added and deleted
# on the transaction.
acls_new = ovn_acl.add_acls(self._plugin,
context,
port,
sg_cache,
subnet_cache,
self._nb_idl)
txn.add(self._nb_idl.update_acls([port['network_id']],
[port],
{port['id']: acls_new},
need_compare=True))
# Refresh address sets for changed security groups or fixed
# IPs.
if len(old_fixed_ips) != 0 or len(new_fixed_ips) != 0:
addresses = ovn_acl.acl_port_ips(port)
addresses_old = utils.sort_ips_by_version(
utils.get_ovn_port_addresses(ovn_port))
# Add current addresses to attached security groups.
for sg_id in attached_sg_ids:
for ip_version in addresses:
if addresses[ip_version]:
txn.add(self._nb_idl.update_address_set(
name=utils.ovn_addrset_name(sg_id,
ip_version),
addrs_add=addresses[ip_version],
addrs_remove=None))
# Remove old addresses from detached security groups.
for sg_id in detached_sg_ids:
for ip_version in addresses_old:
if addresses_old[ip_version]:
txn.add(self._nb_idl.update_address_set(
name=utils.ovn_addrset_name(sg_id,
ip_version),
addrs_add=None,
addrs_remove=addresses_old[ip_version]))
if is_fixed_ips_updated or addr_pairs_diff.changed:
# We have refreshed address sets for attached and
# detached security groups, so now we only need to take
# care of unchanged security groups.
unchanged_sg_ids = new_sg_ids & old_sg_ids
for sg_id in unchanged_sg_ids:
for ip_version in addresses:
addr_add = ((set(addresses[ip_version]) -
set(addresses_old[ip_version])) or
None)
addr_remove = (
(set(addresses_old[ip_version]) -
set(addresses[ip_version])) or None)
if addr_add or addr_remove:
txn.add(self._nb_idl.update_address_set(
name=utils.ovn_addrset_name(
sg_id, ip_version),
addrs_add=addr_add,
addrs_remove=addr_remove))
self._qos_driver.update_port(txn, port, port_object)
@ -681,24 +574,6 @@ class OVNClient(object):
txn.add(self._nb_idl.delete_lswitch_port(
port_id, network_id))
if not self._nb_idl.is_port_groups_supported():
txn.add(self._nb_idl.delete_acl(
network_id, port_id, if_exists=True))
addresses = utils.sort_ips_by_version(
utils.get_ovn_port_addresses(ovn_port))
sec_groups = utils.get_ovn_port_security_groups(
ovn_port, skip_trusted_port=False)
for sg_id in sec_groups:
for ip_version, addr_list in addresses.items():
if not addr_list:
continue
txn.add(self._nb_idl.update_address_set(
name=utils.ovn_addrset_name(sg_id, ip_version),
addrs_add=None,
addrs_remove=addr_list,
if_exists=True))
p_object = ({'id': port_id, 'network_id': network_id}
if not port_object else port_object)
self._qos_driver.delete_port(txn, p_object)
@ -2061,13 +1936,8 @@ class OVNClient(object):
context, subnet_id, ovn_const.TYPE_SUBNETS)
def create_security_group(self, context, security_group):
# If the OVN schema supports Port Groups, we'll model security groups
# as such. Otherwise, for backwards compatibility, we'll keep creating
# two Address Sets for each Neutron SG (one for IPv4 and one for
# IPv6).
with self._nb_idl.transaction(check_error=True) as txn:
ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: security_group['id']}
if self._nb_idl.is_port_groups_supported():
name = utils.ovn_port_group_name(security_group['id'])
txn.add(self._nb_idl.pg_add(
name=name, acls=[], external_ids=ext_ids))
@ -2075,12 +1945,6 @@ class OVNClient(object):
# so we'll apply them to the Port Group.
ovn_acl.add_acls_for_sg_port_group(self._nb_idl,
security_group, txn)
else:
for ip_version in ('ip4', 'ip6'):
name = utils.ovn_addrset_name(security_group['id'],
ip_version)
txn.add(self._nb_idl.create_address_set(
name=name, external_ids=ext_ids))
db_rev.bump_revision(
context, security_group, ovn_const.TYPE_SECURITY_GROUPS)
@ -2095,14 +1959,8 @@ class OVNClient(object):
def delete_security_group(self, context, security_group_id):
with self._nb_idl.transaction(check_error=True) as txn:
if self._nb_idl.is_port_groups_supported():
name = utils.ovn_port_group_name(security_group_id)
txn.add(self._nb_idl.pg_del(name=name))
else:
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(context, security_group_id,
ovn_const.TYPE_SECURITY_GROUPS)

View File

@ -86,7 +86,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
ctx = context.get_admin_context()
self.sync_address_sets(ctx)
self.sync_port_groups(ctx)
self.sync_networks_ports_and_dhcp_opts(ctx)
self.sync_port_dns_records(ctx)
@ -118,24 +117,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
neutron_acls[port].remove(acl)
nb_acls[port].remove(acl)
def compute_address_set_difference(self, neutron_sgs, nb_sgs):
neutron_sgs_name_set = set(neutron_sgs.keys())
nb_sgs_name_set = set(nb_sgs.keys())
sgnames_to_add = list(neutron_sgs_name_set - nb_sgs_name_set)
sgnames_to_delete = list(nb_sgs_name_set - neutron_sgs_name_set)
sgs_common = list(neutron_sgs_name_set & nb_sgs_name_set)
sgs_to_update = {}
for sg_name in sgs_common:
neutron_addr_set = set(neutron_sgs[sg_name]['addresses'])
nb_addr_set = set(nb_sgs[sg_name]['addresses'])
addrs_to_add = list(neutron_addr_set - nb_addr_set)
addrs_to_delete = list(nb_addr_set - neutron_addr_set)
if addrs_to_add or addrs_to_delete:
sgs_to_update[sg_name] = {'name': sg_name,
'addrs_add': addrs_to_add,
'addrs_remove': addrs_to_delete}
return sgnames_to_add, sgnames_to_delete, sgs_to_update
def get_acls(self, context):
"""create the list of ACLS in OVN.
@ -163,17 +144,12 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
acl_list_dict[key] = list([acl])
return acl_list_dict
def get_address_sets(self):
return self.ovn_api.get_address_sets()
def sync_port_groups(self, ctx):
"""Sync Port Groups between neutron and NB.
@param ctx: neutron_lib.context
@type ctx: object of type neutron_lib.context.Context
"""
if not self.ovn_api.is_port_groups_supported():
return
neutron_sgs = {}
neutron_pgs = set()
@ -234,68 +210,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
LOG.debug('Port-Group-SYNC: transaction finished @ %s',
str(datetime.now()))
def sync_address_sets(self, ctx):
"""Sync Address Sets between neutron and NB.
@param ctx: neutron_lib.context
@type ctx: object of type neutron_lib.context.Context
@var db_ports: List of ports from neutron DB
"""
LOG.debug('Address-Set-SYNC: started @ %s', str(datetime.now()))
sgnames_to_add = sgnames_to_delete = []
sgs_to_update = {}
nb_sgs = self.get_address_sets()
if self.ovn_api.is_port_groups_supported():
# If Port Groups are supported, we just need to delete all Address
# Sets from NB database.
sgnames_to_delete = nb_sgs.keys()
else:
neutron_sgs = {}
with ctx.session.begin(subtransactions=True):
db_sgs = self.core_plugin.get_security_groups(ctx)
db_ports = self.core_plugin.get_ports(ctx)
for sg in db_sgs:
for ip_version in ['ip4', 'ip6']:
name = utils.ovn_addrset_name(sg['id'], ip_version)
neutron_sgs[name] = {
'name': name, 'addresses': [],
'external_ids': {
ovn_const.OVN_SG_EXT_ID_KEY: sg['id']}}
for port in db_ports:
sg_ids = utils.get_lsp_security_groups(port)
if port.get('fixed_ips') and sg_ids:
addresses = acl_utils.acl_port_ips(port)
for sg_id in sg_ids:
for ip_version in addresses:
name = utils.ovn_addrset_name(sg_id, ip_version)
neutron_sgs[name]['addresses'].extend(
addresses[ip_version])
sgnames_to_add, sgnames_to_delete, sgs_to_update = (
self.compute_address_set_difference(neutron_sgs, nb_sgs))
LOG.debug('Address_Sets added %d, removed %d, updated %d',
len(sgnames_to_add), len(sgnames_to_delete),
len(sgs_to_update))
if self.mode == SYNC_MODE_REPAIR:
LOG.debug('Address-Set-SYNC: transaction started @ %s',
str(datetime.now()))
with self.ovn_api.transaction(check_error=True) as txn:
for sgname in sgnames_to_add:
sg = neutron_sgs[sgname]
txn.add(self.ovn_api.create_address_set(**sg))
for sgname, sg in sgs_to_update.items():
txn.add(self.ovn_api.update_address_set(**sg))
for sgname in sgnames_to_delete:
txn.add(self.ovn_api.delete_address_set(name=sgname))
LOG.debug('Address-Set-SYNC: transaction finished @ %s',
str(datetime.now()))
def _get_acls_from_port_groups(self):
ovn_acls = []
port_groups = self.ovn_api.db_list_rows('Port_Group').execute()
@ -311,15 +225,20 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
ovn_acls.append(acl_string)
return ovn_acls
def _sync_acls_port_groups(self, ctx):
# If Port Groups are supported, the ACLs in the system will equal
# the number of SG rules plus the default drop rules as OVN would
# allow all traffic by default if those are not added.
def sync_acls(self, ctx):
"""Sync ACLs between neutron and NB.
@param ctx: neutron_lib.context
@type ctx: object of type neutron_lib.context.Context
@return: Nothing
"""
LOG.debug('ACL-SYNC: started @ %s', str(datetime.now()))
neutron_acls = []
for sgr in self.core_plugin.get_security_group_rules(ctx):
pg_name = utils.ovn_port_group_name(sgr['security_group_id'])
neutron_acls.append(acl_utils._add_sg_rule_acl_for_port_group(
pg_name, sgr, self.ovn_api))
pg_name, sgr))
neutron_acls += acl_utils.add_acls_for_drop_port_group(
ovn_const.OVN_DROP_PORT_GROUP_NAME)
@ -373,88 +292,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
'Switch %s', aclr[0])
txn.add(self.ovn_api.acl_del(aclr[0]))
def _sync_acls(self, ctx):
"""Sync ACLs between neutron and NB when not using Port Groups.
@param ctx: neutron_lib.context
@type ctx: object of type neutron_lib.context.Context
@var db_ports: List of ports from neutron DB
@var neutron_acls: neutron dictionary of port
vs list-of-acls
@var nb_acls: NB dictionary of port
vs list-of-acls
@var subnet_cache: cache for subnets
@return: Nothing
"""
db_ports = {}
for port in self.core_plugin.get_ports(ctx):
db_ports[port['id']] = port
sg_cache = {}
subnet_cache = {}
neutron_acls = {}
for port_id, port in db_ports.items():
if utils.get_lsp_security_groups(port):
acl_list = acl_utils.add_acls(self.core_plugin,
ctx,
port,
sg_cache,
subnet_cache,
self.ovn_api)
if port_id in neutron_acls:
neutron_acls[port_id].extend(acl_list)
else:
neutron_acls[port_id] = acl_list
nb_acls = self.get_acls(ctx)
self.remove_common_acls(neutron_acls, nb_acls)
num_acls_to_add = len(list(itertools.chain(*neutron_acls.values())))
num_acls_to_remove = len(list(itertools.chain(*nb_acls.values())))
if 0 != num_acls_to_add or 0 != num_acls_to_remove:
LOG.warning('ACLs-to-be-added %(add)d '
'ACLs-to-be-removed %(remove)d',
{'add': num_acls_to_add,
'remove': num_acls_to_remove})
if self.mode == SYNC_MODE_REPAIR:
with self.ovn_api.transaction(check_error=True) as txn:
for acla in list(itertools.chain(*neutron_acls.values())):
LOG.warning('ACL found in Neutron but not in '
'OVN DB for port %s', acla['lport'])
txn.add(self.ovn_api.add_acl(**acla))
with self.ovn_api.transaction(check_error=True) as txn:
for aclr in list(itertools.chain(*nb_acls.values())):
# Both lswitch and lport aren't needed within the ACL.
lswitchr = aclr.pop('lswitch').replace('neutron-', '')
lportr = aclr.pop('lport')
aclr_dict = {lportr: aclr}
LOG.warning('ACLs found in OVN DB but not in '
'Neutron for port %s', lportr)
txn.add(self.ovn_api.update_acls(
[lswitchr],
[lportr],
aclr_dict,
need_compare=False,
is_add_acl=False
))
def sync_acls(self, ctx):
"""Sync ACLs between neutron and NB.
@param ctx: neutron_lib.context
@type ctx: object of type neutron_lib.context.Context
@return: Nothing
"""
LOG.debug('ACL-SYNC: started @ %s', str(datetime.now()))
if self.ovn_api.is_port_groups_supported():
self._sync_acls_port_groups(ctx)
else:
self._sync_acls(ctx)
LOG.debug('ACL-SYNC: finished @ %s', str(datetime.now()))
def _calculate_fips_differences(self, ovn_fips, db_fips):
@ -1126,20 +963,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
# all the ACLs belonging to that Logical Switch.
txn.add(self.ovn_api.acl_del(utils.ovn_name(net['id'])))
def _create_default_drop_port_group(self, db_ports):
with self.ovn_api.transaction(check_error=True) as txn:
pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME
if not self.ovn_api.get_port_group(pg_name):
# If drop Port Group doesn't exist yet, create it.
txn.add(self.ovn_api.pg_add(pg_name, acls=[]))
# Add ACLs to this Port Group so that all traffic is dropped.
acls = acl_utils.add_acls_for_drop_port_group(pg_name)
for acl in acls:
txn.add(self.ovn_api.pg_acl_add(**acl))
ports_ids = [port['id'] for port in db_ports]
# Add the ports to the default Port Group
txn.add(self.ovn_api.pg_add_ports(pg_name, ports_ids))
def _create_sg_port_groups_and_acls(self, ctx, db_ports):
# Create a Port Group per Neutron Security Group
with self.ovn_api.transaction(check_error=True) as txn:
@ -1159,17 +982,14 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
def migrate_to_port_groups(self, ctx):
# This routine is responsible for migrating the current Security
# Groups and SG Rules to the new Port Groups implementation.
# 1. Create the default drop Port Group and add all ports with port
# security enabled to it.
# 2. Create a Port Group for every existing Neutron Security Group and
# 1. Create a Port Group for every existing Neutron Security Group and
# add all its Security Group Rules as ACLs to that Port Group.
# 3. Delete all existing Address Sets in NorthBound database which
# 2. Delete all existing Address Sets in NorthBound database which
# correspond to a Neutron Security Group.
# 4. Delete all the ACLs in every Logical Switch (Neutron network).
# 3. Delete all the ACLs in every Logical Switch (Neutron network).
# If Port Groups are not supported or we've already migrated, return
if (not self.ovn_api.is_port_groups_supported() or
not self.ovn_api.get_address_sets()):
# If we've already migrated, return
if not self.ovn_api.get_address_sets():
return
LOG.debug('Port Groups Migration task started')
@ -1182,7 +1002,6 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
utils.is_lsp_trusted(port) and
utils.is_port_security_enabled(port)]
self._create_default_drop_port_group(db_ports)
self._create_sg_port_groups_and_acls(ctx, db_ports)
self._delete_address_sets(ctx)
self._delete_acls_from_lswitches(ctx)

View File

@ -170,15 +170,9 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase):
return self.deserialize(self.fmt, res)['security_group']
def _find_security_group_row_by_id(self, sg_id):
if self.nb_api.is_port_groups_supported():
for row in self.nb_api._tables['Port_Group'].rows.values():
if row.name == utils.ovn_port_group_name(sg_id):
return row
else:
for row in self.nb_api._tables['Address_Set'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_SG_EXT_ID_KEY) == sg_id):
return row
def _create_security_group_rule(self, sg_id):
data = {'security_group_rule': {'security_group_id': sg_id,

View File

@ -821,104 +821,10 @@ class TestPortSecurity(base.TestOVNFunctionalBase):
return port_acls
def _verify_port_acls(self, port_id, expected_acls):
if self.nb_api.is_port_groups_supported():
port_acls = self._get_port_related_acls(port_id)
else:
port_acls = self._get_port_related_acls_port_group_not_supported(
port_id)
self.assertItemsEqual(expected_acls, port_acls)
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'impl_idl_ovn.OvsdbNbOvnIdl.is_port_groups_supported',
lambda *args: False)
def test_port_security_port_group_not_supported(self):
n1 = self._make_network(self.fmt, 'n1', True)
res = self._create_subnet(self.fmt, n1['network']['id'], '10.0.0.0/24')
subnet = self.deserialize(self.fmt, res)['subnet']
p = self._make_port(self.fmt, n1['network']['id'],
fixed_ips=[{'subnet_id': subnet['id']}])
port_id = p['port']['id']
sg_id = p['port']['security_groups'][0].replace('-', '_')
expected_acls_with_sg_ps_enabled = [
{'match': 'inport == "' + str(port_id) + '" && ip',
'action': 'drop',
'priority': 1001,
'direction': 'from-lport'},
{'match': 'outport == "' + str(port_id) + '" && ip',
'action': 'drop',
'priority': 1001,
'direction': 'to-lport'},
{'match': 'inport == "' + str(port_id) + '" && ip4 && ip4.dst == '
'{255.255.255.255, 10.0.0.0/24} && udp && udp.src == 68 '
'&& udp.dst == 67',
'action': 'allow',
'priority': 1002,
'direction': 'from-lport'},
{'match': 'inport == "' + str(port_id) + '" && ip6',
'action': 'allow-related',
'priority': 1002,
'direction': 'from-lport'},
{'match': 'inport == "' + str(port_id) + '" && ip4',
'action': 'allow-related',
'priority': 1002,
'direction': 'from-lport'},
{'match': 'outport == "' + str(port_id) + '" && ip4 && '
'ip4.src == $as_ip4_' + str(sg_id),
'action': 'allow-related',
'priority': 1002,
'direction': 'to-lport'},
{'match': 'outport == "' + str(port_id) + '" && ip6 && '
'ip6.src == $as_ip6_' + str(sg_id),
'action': 'allow-related',
'priority': 1002,
'direction': 'to-lport'},
]
self._verify_port_acls(port_id, expected_acls_with_sg_ps_enabled)
# clear the security groups.
data = {'port': {'security_groups': []}}
port_req = self.new_update_request('ports', data, p['port']['id'])
port_req.get_response(self.api)
# No security groups and port security enabled - > ACLs should be
# added to drop the packets.
expected_acls_with_no_sg_ps_enabled = [
{'match': 'inport == "' + str(port_id) + '" && ip',
'action': 'drop',
'priority': 1001,
'direction': 'from-lport'},
{'match': 'outport == "' + str(port_id) + '" && ip',
'action': 'drop',
'priority': 1001,
'direction': 'to-lport'},
]
self._verify_port_acls(port_id, expected_acls_with_no_sg_ps_enabled)
# Disable port security
data = {'port': {'port_security_enabled': False}}
port_req = self.new_update_request('ports', data, p['port']['id'])
port_req.get_response(self.api)
# No security groups and port security disabled - > No ACLs should be
# added (allowing all the traffic).
self._verify_port_acls(port_id, [])
# Enable port security again with no security groups - > ACLs should
# be added back to drop the packets.
data = {'port': {'port_security_enabled': True}}
port_req = self.new_update_request('ports', data, p['port']['id'])
port_req.get_response(self.api)
self._verify_port_acls(port_id, expected_acls_with_no_sg_ps_enabled)
# Set security groups back
data = {'port': {'security_groups': p['port']['security_groups']}}
port_req = self.new_update_request('ports', data, p['port']['id'])
port_req.get_response(self.api)
self._verify_port_acls(port_id, expected_acls_with_sg_ps_enabled)
def test_port_security_port_group(self):
if not self.nb_api.is_port_groups_supported():
self.skipTest('Port groups is not supported')
n1 = self._make_network(self.fmt, 'n1', True)
res = self._create_subnet(self.fmt, n1['network']['id'], '10.0.0.0/24')
subnet = self.deserialize(self.fmt, res)['subnet']

View File

@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from neutron.common.ovn import acl as acl_utils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
@ -60,9 +58,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
self.delete_lrouter_routes = []
self.delete_lrouter_nats = []
self.delete_acls = []
self.create_address_sets = []
self.delete_address_sets = []
self.update_address_sets = []
self.create_port_groups = []
self.delete_port_groups = []
self.expected_dhcp_options_rows = []
@ -579,17 +574,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
'neutron-' + r1['id']))
self.delete_lrouters.append('neutron-' + r2['id'])
address_set_name = n1_prtr['port']['security_groups'][0]
self.create_address_sets.extend([('fake_sg', 'ip4'),
('fake_sg', 'ip6')])
self.delete_address_sets.append((address_set_name, 'ip6'))
address_adds = ['10.0.0.101', '10.0.0.102']
address_dels = []
for address in n1_prtr['port']['fixed_ips']:
address_dels.append(address['ip_address'])
self.update_address_sets.append((address_set_name, 'ip4',
address_adds, address_dels))
self.create_port_groups.extend([{'name': 'pg1', 'acls': []},
{'name': 'pg2', 'acls': []}])
self.delete_port_groups.append(
@ -756,22 +740,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
txn.add(self.nb_api.delete_acl(lswitch_name,
lport_name, True))
for name, ip_version in self.create_address_sets:
ovn_name = utils.ovn_addrset_name(name, ip_version)
external_ids = {ovn_const.OVN_SG_EXT_ID_KEY: name}
txn.add(self.nb_api.create_address_set(
ovn_name, True, external_ids=external_ids))
for name, ip_version in self.delete_address_sets:
ovn_name = utils.ovn_addrset_name(name, ip_version)
txn.add(self.nb_api.delete_address_set(ovn_name, True))
for name, ip_version, ip_adds, ip_dels in self.update_address_sets:
ovn_name = utils.ovn_addrset_name(name, ip_version)
txn.add(self.nb_api.update_address_set(ovn_name,
ip_adds, ip_dels, True))
if self.nb_api.is_port_groups_supported():
for pg in self.create_port_groups:
txn.add(self.nb_api.pg_add(**pg))
for pg in self.delete_port_groups:
@ -1077,27 +1045,14 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
def _validate_acls(self, should_match=True):
# Get the neutron DB ACLs.
db_acls = []
sg_cache = {}
subnet_cache = {}
_plugin_nb_ovn = self.mech_driver._nb_ovn
if not _plugin_nb_ovn.is_port_groups_supported():
for db_port in self._list('ports')['ports']:
acls = acl_utils.add_acls(self.plugin,
context.get_admin_context(),
db_port,
sg_cache,
subnet_cache,
self.mech_driver._nb_ovn)
for acl in acls:
db_acls.append(acl_utils.filter_acl_dict(acl))
else:
# ACLs due to SGs and default drop port group
for sg in self._list('security-groups')['security_groups']:
for sgr in sg['security_group_rules']:
acl = acl_utils._add_sg_rule_acl_for_port_group(
utils.ovn_port_group_name(sg['id']), sgr,
self.mech_driver._nb_ovn)
utils.ovn_port_group_name(sg['id']), sgr)
db_acls.append(TestOvnNbSync._build_acl_for_pgs(**acl))
for acl in acl_utils.add_acls_for_drop_port_group(
@ -1109,7 +1064,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
for row in _plugin_nb_ovn._tables['Logical_Switch'].rows.values():
for acl in getattr(row, 'acls', []):
plugin_acls.append(self._build_acl_to_compare(acl))
if self.nb_api.is_port_groups_supported():
for row in _plugin_nb_ovn._tables['Port_Group'].rows.values():
for acl in getattr(row, 'acls', []):
plugin_acls.append(
@ -1121,7 +1075,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
for row in self.nb_api.tables['Logical_Switch'].rows.values():
for acl in getattr(row, 'acls', []):
monitor_acls.append(self._build_acl_to_compare(acl))
if _plugin_nb_ovn.is_port_groups_supported():
for row in self.nb_api.tables['Port_Group'].rows.values():
for acl in getattr(row, 'acls', []):
monitor_acls.append(self._build_acl_to_compare(acl))
@ -1346,57 +1299,8 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
AssertionError, self.assertItemsEqual, r_nats,
monitor_nats)
def _validate_address_sets(self, should_match=True):
_plugin_nb_ovn = self.mech_driver._nb_ovn
if _plugin_nb_ovn.is_port_groups_supported():
# If Port Groups are supported, no Address Sets are expected.
# This validation is still useful as we expect existing ones to
# be deleted after the sync.
db_sgs = []
else:
db_ports = self._list('ports')['ports']
sgs = self._list('security-groups')['security_groups']
db_sgs = {}
for sg in sgs:
for ip_version in ['ip4', 'ip6']:
name = utils.ovn_addrset_name(sg['id'], ip_version)
db_sgs[name] = []
for port in db_ports:
sg_ids = utils.get_lsp_security_groups(port)
addresses = acl_utils.acl_port_ips(port)
for sg_id in sg_ids:
for ip_version in addresses:
name = utils.ovn_addrset_name(sg_id, ip_version)
db_sgs[name].extend(addresses[ip_version])
nb_address_sets = _plugin_nb_ovn.get_address_sets()
nb_sgs = {}
for nb_sgid, nb_values in nb_address_sets.items():
nb_sgs[nb_sgid] = nb_values['addresses']
mn_sgs = {}
for row in self.nb_api.tables['Address_Set'].rows.values():
mn_sgs[getattr(row, 'name')] = getattr(row, 'addresses')
if should_match:
self.assertItemsEqual(nb_sgs, db_sgs)
self.assertItemsEqual(mn_sgs, db_sgs)
else:
# This condition is to cover the case when we use Port Groups
# and we completely deleted the NB DB. At this point, the expected
# number of Address Sets is 0 and the observed number in NB is
# also 0 so we can't have the asserts below as both will be empty.
if _plugin_nb_ovn.is_port_groups_supported() and nb_sgs:
self.assertRaises(AssertionError, self.assertItemsEqual,
nb_sgs, db_sgs)
self.assertRaises(AssertionError, self.assertItemsEqual,
mn_sgs, db_sgs)
def _validate_port_groups(self, should_match=True):
_plugin_nb_ovn = self.mech_driver._nb_ovn
if not _plugin_nb_ovn.is_port_groups_supported():
return
db_pgs = []
for sg in self._list('security-groups')['security_groups']:
@ -1469,7 +1373,6 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
self._validate_dhcp_opts(should_match=should_match)
self._validate_acls(should_match=should_match)
self._validate_routers_and_router_ports(should_match=should_match)
self._validate_address_sets(should_match=should_match)
self._validate_port_groups(should_match=should_match)
self._validate_dns_records(should_match=should_match)
@ -1624,9 +1527,3 @@ class TestOvnNbSyncOverSsl(TestOvnNbSync):
class TestOvnSbSyncOverSsl(TestOvnSbSync):
def get_ovsdb_server_protocol(self):
return 'ssl'
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.impl_idl_ovn.'
'OvsdbNbOvnIdl.is_port_groups_supported', lambda *args: False)
class TestOvnNbSyncNoPgs(TestOvnNbSync):
pass

View File

@ -15,7 +15,6 @@ import copy
from unittest import mock
from neutron_lib import constants as const
from oslo_config import cfg
from ovsdbapp.backend.ovs_idl import idlutils
from neutron.common.ovn import acl as ovn_acl
@ -548,114 +547,15 @@ class TestACLs(base.BaseTestCase):
ip_version = 'ip4'
sg_id = sg_rule['security_group_id']
addrset_name = ovn_utils.ovn_addrset_name(sg_id, ip_version)
pg_name = ovn_utils.ovn_pg_addrset_name(sg_id, ip_version)
match = ovn_acl.acl_remote_group_id(sg_rule, ip_version)
self.assertEqual('', match)
sg_rule['remote_group_id'] = sg_id
match = ovn_acl.acl_remote_group_id(sg_rule, ip_version)
self.assertEqual(' && ip4.src == $' + addrset_name, match)
self.assertEqual(' && ip4.src == $' + pg_name, match)
sg_rule['direction'] = 'egress'
match = ovn_acl.acl_remote_group_id(sg_rule, ip_version)
self.assertEqual(' && ip4.dst == $' + addrset_name, match)
def _test_update_acls_for_security_group(self, use_cache=True):
sg = fakes.FakeSecurityGroup.create_one_security_group().info()
remote_sg = fakes.FakeSecurityGroup.create_one_security_group().info()
sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({
'security_group_id': sg['id'],
'remote_group_id': remote_sg['id']
}).info()
port = fakes.FakePort.create_one_port({
'security_groups': [sg['id']]
}).info()
self.plugin.get_ports.return_value = [port]
if use_cache:
sg_ports_cache = {sg['id']: [{'port_id': port['id']}],
remote_sg['id']: []}
else:
sg_ports_cache = None
self.plugin._get_port_security_group_bindings.return_value = \
[{'port_id': port['id']}]
# Build ACL for validation.
expected_acl = ovn_acl._add_sg_rule_acl_for_port(port, sg_rule)
expected_acl.pop('lport')
expected_acl.pop('lswitch')
# Validate ACLs when port has security groups.
ovn_acl.update_acls_for_security_group(self.plugin,
self.admin_context,
self.driver._nb_ovn,
sg['id'],
sg_rule,
sg_ports_cache=sg_ports_cache)
self.driver._nb_ovn.update_acls.assert_called_once_with(
[port['network_id']],
mock.ANY,
{port['id']: expected_acl},
need_compare=False,
is_add_acl=True
)
def test_update_acls_for_security_group_cache(self):
self._test_update_acls_for_security_group(use_cache=True)
def test_update_acls_for_security_group_no_cache(self):
self._test_update_acls_for_security_group(use_cache=False)
def test_acl_port_ips(self):
port4 = fakes.FakePort.create_one_port({
'fixed_ips': [{'subnet_id': 'subnet-ipv4',
'ip_address': '10.0.0.1'}],
}).info()
port46 = fakes.FakePort.create_one_port({
'fixed_ips': [{'subnet_id': 'subnet-ipv4',
'ip_address': '10.0.0.2'},
{'subnet_id': 'subnet-ipv6',
'ip_address': 'fde3:d45:df72::1'}],
}).info()
port6 = fakes.FakePort.create_one_port({
'fixed_ips': [{'subnet_id': 'subnet-ipv6',
'ip_address': '2001:db8::8'}],
}).info()
addresses = ovn_acl.acl_port_ips(port4)
self.assertEqual({'ip4': [port4['fixed_ips'][0]['ip_address']],
'ip6': []},
addresses)
addresses = ovn_acl.acl_port_ips(port46)
self.assertEqual({'ip4': [port46['fixed_ips'][0]['ip_address']],
'ip6': [port46['fixed_ips'][1]['ip_address']]},
addresses)
addresses = ovn_acl.acl_port_ips(port6)
self.assertEqual({'ip4': [],
'ip6': [port6['fixed_ips'][0]['ip_address']]},
addresses)
def test_sg_disabled(self):
sg = fakes.FakeSecurityGroup.create_one_security_group().info()
port = fakes.FakePort.create_one_port({
'security_groups': [sg['id']]
}).info()
cfg.CONF.set_override('enable_security_group', 'False',
'SECURITYGROUP')
acl_list = ovn_acl.add_acls(self.plugin,
self.admin_context,
port, {}, {}, self.driver._ovn)
self.assertEqual([], acl_list)
ovn_acl.update_acls_for_security_group(self.plugin,
self.admin_context,
self.driver._ovn,
sg['id'],
None)
self.driver._ovn.update_acls.assert_not_called()
addresses = ovn_acl.acl_port_ips(port)
self.assertEqual({'ip4': [], 'ip6': []}, addresses)
self.assertEqual(' && ip4.dst == $' + pg_name, match)

View File

@ -71,10 +71,6 @@ class FakeOvsdbNbOvnIdl(object):
self.idl = mock.Mock()
self.add_static_route = mock.Mock()
self.delete_static_route = mock.Mock()
self.create_address_set = mock.Mock()
self.update_address_set_ext_ids = mock.Mock()
self.delete_address_set = mock.Mock()
self.update_address_set = mock.Mock()
self.get_all_chassis_gateway_bindings = mock.Mock()
self.get_chassis_gateways = mock.Mock()
self.get_gateway_chassis_binding = mock.Mock()
@ -121,12 +117,6 @@ class FakeOvsdbNbOvnIdl(object):
self.get_lrouter.return_value = None
self.delete_lrouter_ext_gw = mock.Mock()
self.delete_lrouter_ext_gw.return_value = None
self.is_port_groups_supported = mock.Mock()
# TODO(lucasagomes): Flip this return value to True at some point,
# port groups should be the default method used by networking-ovn
self.is_port_groups_supported.return_value = False
self.get_address_set = mock.Mock()
self.get_address_set.return_value = None
self.pg_acl_add = mock.Mock()
self.pg_acl_del = mock.Mock()
self.pg_del = mock.Mock()

View File

@ -936,171 +936,6 @@ class TestDelStaticRouteCommand(TestBaseCommand):
self.assertEqual([mock.ANY], fake_lrouter.static_routes)
class TestAddAddrSetCommand(TestBaseCommand):
def test_addrset_exists(self):
with mock.patch.object(idlutils, 'row_by_value',
return_value=mock.ANY):
cmd = commands.AddAddrSetCommand(
self.ovn_api, 'fake-addrset', may_exist=True)
cmd.run_idl(self.transaction)
self.transaction.insert.assert_not_called()
def test_addrset_add_exists(self):
fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row()
self.ovn_api._tables['Address_Set'].rows[fake_addrset.uuid] = \
fake_addrset
self.transaction.insert.return_value = fake_addrset
cmd = commands.AddAddrSetCommand(
self.ovn_api, fake_addrset.name, may_exist=False)
cmd.run_idl(self.transaction)
# NOTE(rtheis): Mocking the transaction allows this insert
# to succeed when it normally would fail due the duplicate name.
self.transaction.insert.assert_called_once_with(
self.ovn_api._tables['Address_Set'])
def _test_addrset_add(self, may_exist=True):
with mock.patch.object(idlutils, 'row_by_value',
return_value=None):
fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'foo': ''})
self.transaction.insert.return_value = fake_addrset
cmd = commands.AddAddrSetCommand(
self.ovn_api, 'fake-addrset', may_exist=may_exist,
foo='bar')
cmd.run_idl(self.transaction)
self.transaction.insert.assert_called_once_with(
self.ovn_api._tables['Address_Set'])
self.assertEqual('fake-addrset', fake_addrset.name)
self.assertEqual('bar', fake_addrset.foo)
def test_addrset_add_may_exist(self):
self._test_addrset_add(may_exist=True)
def test_addrset_add_ignore_exists(self):
self._test_addrset_add(may_exist=False)
class TestDelAddrSetCommand(TestBaseCommand):
def _test_addrset_del_no_exist(self, if_exists=True):
with mock.patch.object(idlutils, 'row_by_value',
side_effect=idlutils.RowNotFound):
cmd = commands.DelAddrSetCommand(
self.ovn_api, 'fake-addrset', if_exists=if_exists)
if if_exists:
cmd.run_idl(self.transaction)
else:
self.assertRaises(RuntimeError, cmd.run_idl, self.transaction)
def test_addrset_no_exist_ignore(self):
self._test_addrset_del_no_exist(if_exists=True)
def test_addrset_no_exist_fail(self):
self._test_addrset_del_no_exist(if_exists=False)
def test_addrset_del(self):
fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row()
self.ovn_api._tables['Address_Set'].rows[fake_addrset.uuid] = \
fake_addrset
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_addrset):
cmd = commands.DelAddrSetCommand(
self.ovn_api, fake_addrset.name, if_exists=True)
cmd.run_idl(self.transaction)
fake_addrset.delete.assert_called_once_with()
class TestUpdateAddrSetCommand(TestBaseCommand):
def _test_addrset_update_no_exist(self, if_exists=True):
with mock.patch.object(idlutils, 'row_by_value',
side_effect=idlutils.RowNotFound):
cmd = commands.UpdateAddrSetCommand(
self.ovn_api, 'fake-addrset',
addrs_add=[], addrs_remove=[],
if_exists=if_exists)
if if_exists:
cmd.run_idl(self.transaction)
else:
self.assertRaises(RuntimeError, cmd.run_idl, self.transaction)
def test_addrset_no_exist_ignore(self):
self._test_addrset_update_no_exist(if_exists=True)
def test_addrset_no_exist_fail(self):
self._test_addrset_update_no_exist(if_exists=False)
def _test_addrset_update(self, addrs_add=None, addrs_del=None):
save_address = '10.0.0.1'
initial_addresses = [save_address]
final_addresses = [save_address]
expected_addvalue_calls = []
expected_delvalue_calls = []
if addrs_add:
for addr_add in addrs_add:
final_addresses.append(addr_add)
expected_addvalue_calls.append(
mock.call('addresses', addr_add))
if addrs_del:
for addr_del in addrs_del:
initial_addresses.append(addr_del)
expected_delvalue_calls.append(
mock.call('addresses', addr_del))
fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'addresses': initial_addresses})
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_addrset):
cmd = commands.UpdateAddrSetCommand(
self.ovn_api, fake_addrset.name,
addrs_add=addrs_add, addrs_remove=addrs_del,
if_exists=True)
cmd.run_idl(self.transaction)
fake_addrset.addvalue.assert_has_calls(expected_addvalue_calls)
fake_addrset.delvalue.assert_has_calls(expected_delvalue_calls)
def test_addrset_update_add(self):
self._test_addrset_update(addrs_add=['10.0.0.4'])
def test_addrset_update_del(self):
self._test_addrset_update(addrs_del=['10.0.0.2'])
class TestUpdateAddrSetExtIdsCommand(TestBaseCommand):
def setUp(self):
super(TestUpdateAddrSetExtIdsCommand, self).setUp()
self.ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: 'default'}
def _test_addrset_extids_update_no_exist(self, if_exists=True):
with mock.patch.object(idlutils, 'row_by_value',
side_effect=idlutils.RowNotFound):
cmd = commands.UpdateAddrSetExtIdsCommand(
self.ovn_api, 'fake-addrset', self.ext_ids,
if_exists=if_exists)
if if_exists:
cmd.run_idl(self.transaction)
else:
self.assertRaises(RuntimeError, cmd.run_idl, self.transaction)
def test_addrset_no_exist_ignore(self):
self._test_addrset_extids_update_no_exist(if_exists=True)
def test_addrset_no_exist_fail(self):
self._test_addrset_extids_update_no_exist(if_exists=False)
def test_addrset_extids_update(self):
new_ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: 'default-new'}
fake_addrset = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'external_ids': self.ext_ids})
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_addrset):
cmd = commands.UpdateAddrSetExtIdsCommand(
self.ovn_api, fake_addrset.name,
new_ext_ids, if_exists=True)
cmd.run_idl(self.transaction)
self.assertEqual(new_ext_ids, fake_addrset.external_ids)
class TestUpdateChassisExtIdsCommand(TestBaseCommand):
def setUp(self):
super(TestUpdateChassisExtIdsCommand, self).setUp()

View File

@ -100,10 +100,8 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
self.periodic.check_for_inconsistencies()
mock_fix_net.assert_called_once_with(mock.ANY, fake_row)
def _test_migrate_to_port_groups_helper(self, pg_supported, a_sets,
migration_expected, never_again):
self.fake_ovn_client._nb_idl.is_port_groups_supported.return_value = (
pg_supported)
def _test_migrate_to_port_groups_helper(self, a_sets, migration_expected,
never_again):
self.fake_ovn_client._nb_idl.get_address_sets.return_value = a_sets
with mock.patch.object(ovn_db_sync.OvnNbSynchronizer,
'migrate_to_port_groups') as mtpg:
@ -118,24 +116,15 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
else:
mtpg.assert_not_called()
def test_migrate_to_port_groups_port_groups_not_supported(self):
self._test_migrate_to_port_groups_helper(pg_supported=False,
a_sets=None,
migration_expected=False,
never_again=True)
def test_migrate_to_port_groups_not_needed(self):
self._test_migrate_to_port_groups_helper(pg_supported=True,
a_sets=None,
self._test_migrate_to_port_groups_helper(a_sets=None,
migration_expected=False,
never_again=True)
def test_migrate_to_port_groups(self):
# Check normal migration path: if port groups are supported by the
# schema and the migration has to be done, it will take place and
# won't be attempted in the future.
self._test_migrate_to_port_groups_helper(pg_supported=True,
a_sets=['as1', 'as2'],
# Check normal migration path: if the migration has to be done, it will
# take place and won't be attempted in the future.
self._test_migrate_to_port_groups_helper(a_sets=['as1', 'as2'],
migration_expected=True,
never_again=True)
@ -145,8 +134,7 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
return_value=False)):
# Check that if this worker doesn't have the lock, it won't
# perform the migration and it will try again later.
self._test_migrate_to_port_groups_helper(pg_supported=True,
a_sets=['as1', 'as2'],
self._test_migrate_to_port_groups_helper(a_sets=['as1', 'as2'],
migration_expected=False,
never_again=False)

View File

@ -213,25 +213,6 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
'match': 'outport == "p2n2" && ip4 && '
'ip4.src == 10.0.0.0/24 && udp && '
'udp.src == 67 && udp.dst == 68'}]}
self.address_sets_ovn = {
'as_ip4_sg1': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY:
'all-tcp'},
'name': 'as_ip4_sg1',
'addresses': ['10.0.0.4']},
'as_ip4_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY:
'all-tcpe'},
'name': 'as_ip4_sg2',
'addresses': []},
'as_ip6_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY:
'all-tcpe'},
'name': 'as_ip6_sg2',
'addresses': ['fd79:e1c:a55::816:eff:eff:ff2',
'fd79:e1c:a55::816:eff:eff:ff3']},
'as_ip4_del': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY:
'all-delete'},
'name': 'as_ip4_delete',
'addresses': ['10.0.0.4']},
}
self.routers = [{'id': 'r1', 'routes': [{'nexthop': '20.0.0.100',
'destination': '11.0.0.0/24'}, {
@ -392,9 +373,6 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
ovn_nb_synchronizer.get_acls.return_value = self.acls_ovn
core_plugin.get_security_groups = mock.MagicMock(
return_value=self.security_groups)
ovn_nb_synchronizer.get_address_sets = mock.Mock()
ovn_nb_synchronizer.get_address_sets.return_value =\
self.address_sets_ovn
get_port_groups = mock.MagicMock()
get_port_groups.execute.return_value = self.port_groups_ovn
ovn_api.db_list_rows.return_value = get_port_groups
@ -505,9 +483,6 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
'port_id': 'p1n2'},
'uuid': 'UUID6'}}}
ovn_api.create_address_set = mock.Mock()
ovn_api.delete_address_set = mock.Mock()
ovn_api.update_address_set = mock.Mock()
ovn_nb_synchronizer._ovn_client._add_subnet_dhcp_options = mock.Mock()
ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options = mock.Mock()
ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options.side_effect = (
@ -528,54 +503,17 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
add_static_route_list, del_static_route_list,
add_snat_list, del_snat_list,
add_floating_ip_list, del_floating_ip_list,
add_address_set_list, del_address_set_list,
update_address_set_list,
add_subnet_dhcp_options_list,
delete_dhcp_options_list,
add_port_groups_list,
del_port_groups_list,
port_groups_supported=False):
del_port_groups_list):
self._test_mocks_helper(ovn_nb_synchronizer)
core_plugin = ovn_nb_synchronizer.core_plugin
ovn_api = ovn_nb_synchronizer.ovn_api
ovn_api.is_port_groups_supported.return_value = port_groups_supported
mock.patch.object(impl_idl_ovn, 'get_connection').start()
ovn_nb_synchronizer.do_sync()
if not ovn_api.is_port_groups_supported():
get_security_group_calls = [mock.call(mock.ANY, sg['id'])
for sg in self.security_groups]
self.assertEqual(len(self.security_groups),
core_plugin.get_security_group.call_count)
core_plugin.get_security_group.assert_has_calls(
get_security_group_calls, any_order=True)
create_address_set_calls = [mock.call(**a)
for a in add_address_set_list]
self.assertEqual(
len(add_address_set_list),
ovn_api.create_address_set.call_count)
ovn_api.create_address_set.assert_has_calls(
create_address_set_calls, any_order=True)
del_address_set_calls = [mock.call(**d)
for d in del_address_set_list]
self.assertEqual(
len(del_address_set_list),
ovn_api.delete_address_set.call_count)
ovn_api.delete_address_set.assert_has_calls(
del_address_set_calls, any_order=True)
update_address_set_calls = [mock.call(**u)
for u in update_address_set_list]
self.assertEqual(
len(update_address_set_list),
ovn_api.update_address_set.call_count)
ovn_api.update_address_set.assert_has_calls(
update_address_set_calls, any_order=True)
create_port_groups_calls = [mock.call(**a)
for a in add_port_groups_list]
self.assertEqual(
@ -740,7 +678,7 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
ovn_api.delete_dhcp_options.assert_has_calls(
delete_dhcp_options_calls, any_order=True)
def _test_ovn_nb_sync_mode_repair_helper(self, port_groups_supported=True):
def test_ovn_nb_sync_mode_repair(self):
create_network_list = [{'net': {'id': 'n2', 'mtu': 1450},
'ext_ids': {}}]
@ -818,37 +756,11 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
update_router_port_list[0].update(
{'networks': self.lrport_networks})
if not port_groups_supported:
add_address_set_list = [
{'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg1'},
'name': 'as_ip6_sg1',
'addresses': ['fd79:e1c:a55::816:eff:eff:ff2']}]
del_address_set_list = [{'name': 'as_ip4_del'}]
update_address_set_list = [
{'addrs_remove': [],
'addrs_add': ['10.0.0.4'],
'name': 'as_ip4_sg2'},
{'addrs_remove': ['fd79:e1c:a55::816:eff:eff:ff3'],
'addrs_add': [],
'name': 'as_ip6_sg2'}]
# If Port Groups are not supported, we don't expect any of those
# to be created/deleted.
add_port_groups_list = []
del_port_groups_list = []
else:
add_port_groups_list = [
{'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg2'},
'name': 'pg_sg2',
'acls': []}]
del_port_groups_list = ['pg_unknown_del']
# If using Port Groups, no Address Set shall be created/updated
# and all the existing ones have to be removed.
add_address_set_list = []
update_address_set_list = []
del_address_set_list = [{'name': 'as_ip4_sg1'},
{'name': 'as_ip4_sg2'},
{'name': 'as_ip6_sg2'},
{'name': 'as_ip4_del'}]
add_subnet_dhcp_options_list = [(self.subnets[2], self.networks[1]),
(self.subnets[1], self.networks[0])]
@ -875,22 +787,12 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
del_snat_list,
add_floating_ip_list,
del_floating_ip_list,
add_address_set_list,
del_address_set_list,
update_address_set_list,
add_subnet_dhcp_options_list,
delete_dhcp_options_list,
add_port_groups_list,
del_port_groups_list,
port_groups_supported)
del_port_groups_list)
def test_ovn_nb_sync_mode_repair_no_pgs(self):
self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=False)
def test_ovn_nb_sync_mode_repair_pgs(self):
self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=True)
def _test_ovn_nb_sync_mode_log_helper(self, port_groups_supported=True):
def test_ovn_nb_sync_mode_log(self):
create_network_list = []
create_port_list = []
create_provnet_port_list = []
@ -907,9 +809,6 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
del_snat_list = []
add_floating_ip_list = []
del_floating_ip_list = []
add_address_set_list = []
del_address_set_list = []
update_address_set_list = []
add_subnet_dhcp_options_list = []
delete_dhcp_options_list = []
add_port_groups_list = []
@ -936,20 +835,10 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
del_snat_list,
add_floating_ip_list,
del_floating_ip_list,
add_address_set_list,
del_address_set_list,
update_address_set_list,
add_subnet_dhcp_options_list,
delete_dhcp_options_list,
add_port_groups_list,
del_port_groups_list,
port_groups_supported)
def test_ovn_nb_sync_mode_log_pgs(self):
self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=True)
def test_ovn_nb_sync_mode_log_no_pgs(self):
self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=False)
del_port_groups_list)
class TestOvnSbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):

View File

@ -159,28 +159,26 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
resources.SECURITY_GROUP, events.AFTER_CREATE, {},
security_group=self.fake_sg, context=self.context)
external_ids = {ovn_const.OVN_SG_EXT_ID_KEY: self.fake_sg['id']}
ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4')
ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6')
create_address_set_calls = [mock.call(name=name,
external_ids=external_ids)
for name in [ip4_name, ip6_name]]
pg_name = ovn_utils.ovn_port_group_name(self.fake_sg['id'])
self.nb_ovn.pg_add.assert_called_once_with(
name=pg_name, acls=[], external_ids=external_ids)
self.nb_ovn.create_address_set.assert_has_calls(
create_address_set_calls, any_order=True)
mock_bump.assert_called_once_with(
mock.ANY, self.fake_sg, ovn_const.TYPE_SECURITY_GROUPS)
def test__delete_security_group(self):
@mock.patch.object(ovn_revision_numbers_db, 'delete_revision')
def test__delete_security_group(self, mock_del_rev):
self.mech_driver._delete_security_group(
resources.SECURITY_GROUP, events.AFTER_CREATE, {},
security_group_id=self.fake_sg['id'], context=self.context)
ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4')
ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6')
delete_address_set_calls = [mock.call(name=name)
for name in [ip4_name, ip6_name]]
pg_name = ovn_utils.ovn_port_group_name(self.fake_sg['id'])
self.nb_ovn.delete_address_set.assert_has_calls(
delete_address_set_calls, any_order=True)
self.nb_ovn.pg_del.assert_called_once_with(
name=pg_name)
mock_del_rev.assert_called_once_with(
mock.ANY, self.fake_sg['id'], ovn_const.TYPE_SECURITY_GROUPS)
@mock.patch.object(ovn_revision_numbers_db, 'bump_revision')
def test__process_sg_rule_notifications_sgr_create(self, mock_bump):
@ -213,88 +211,6 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
mock_delrev.assert_called_once_with(
mock.ANY, rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
def test_add_acls_no_sec_group(self):
fake_port_no_sg = fakes.FakePort.create_one_port().info()
expected_acls = ovn_acl.drop_all_ip_traffic_for_port(fake_port_no_sg)
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_no_sg,
{}, {}, self.mech_driver._nb_ovn)
self.assertEqual(expected_acls, acls)
def test_add_acls_no_sec_group_no_port_security(self):
fake_port_no_sg_no_ps = fakes.FakePort.create_one_port(
attrs={'port_security_enabled': False}).info()
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_no_sg_no_ps,
{}, {}, self.mech_driver._nb_ovn)
self.assertEqual([], acls)
def _test_add_acls_with_sec_group_helper(self, native_dhcp=True):
fake_port_sg = fakes.FakePort.create_one_port(
attrs={'security_groups': [self.fake_sg['id']],
'fixed_ips': [{'subnet_id': self.fake_subnet['id'],
'ip_address': '10.10.10.20'}]}
).info()
expected_acls = []
expected_acls += ovn_acl.drop_all_ip_traffic_for_port(
fake_port_sg)
expected_acls += ovn_acl.add_acl_dhcp(
fake_port_sg, self.fake_subnet, native_dhcp)
sg_rule_acl = ovn_acl.add_sg_rule_acl_for_port(
fake_port_sg, self.fake_sg_rule,
'outport == "' + fake_port_sg['id'] + '" ' +
'&& ip4 && ip4.src == 0.0.0.0/0 ' +
'&& tcp && tcp.dst == 22')
expected_acls.append(sg_rule_acl)
# Test with caches
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_sg,
self.sg_cache,
self.subnet_cache,
self.mech_driver._nb_ovn)
self.assertEqual(expected_acls, acls)
# Test without caches
with mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2,
'get_subnet', return_value=self.fake_subnet), \
mock.patch.object(securitygroups_db.SecurityGroupDbMixin,
'get_security_group',
return_value=self.fake_sg):
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_sg,
{}, {}, self.mech_driver._nb_ovn)
self.assertEqual(expected_acls, acls)
# Test with security groups disabled
with mock.patch.object(ovn_acl, 'is_sg_enabled', return_value=False):
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_sg,
self.sg_cache,
self.subnet_cache,
self.mech_driver._nb_ovn)
self.assertEqual([], acls)
# Test with multiple fixed IPs on the same subnet.
fake_port_sg['fixed_ips'].append({'subnet_id': self.fake_subnet['id'],
'ip_address': '10.10.10.21'})
acls = ovn_acl.add_acls(self.mech_driver._plugin,
mock.Mock(),
fake_port_sg,
self.sg_cache,
self.subnet_cache,
self.mech_driver._nb_ovn)
self.assertEqual(expected_acls, acls)
def test_add_acls_with_sec_group_native_dhcp_enabled(self):
self._test_add_acls_with_sec_group_helper()
def test_port_invalid_binding_profile(self):
invalid_binding_profiles = [
{'tag': 0,
@ -694,8 +610,7 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
**kwargs):
self.assertEqual(
1, self.nb_ovn.create_lswitch_port.call_count)
self.assertEqual(2, self.nb_ovn.add_acl.call_count)
self.nb_ovn.update_address_set.assert_not_called()
self.assertFalse(self.nb_ovn.add_acl.called)
def test_create_port_without_security_groups_no_ps(self):
kwargs = {'security_groups': [], 'port_security_enabled': False}
@ -709,23 +624,6 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
self.assertEqual(
1, self.nb_ovn.create_lswitch_port.call_count)
self.nb_ovn.add_acl.assert_not_called()
self.nb_ovn.update_address_set.assert_not_called()
def _test_create_port_with_security_groups_helper(self,
add_acl_call_count):
with self.network(set_context=True, tenant_id='test') as net1:
with self.subnet(network=net1) as subnet1:
with self.port(subnet=subnet1,
set_context=True, tenant_id='test'):
self.assertEqual(
1, self.nb_ovn.create_lswitch_port.call_count)
self.assertEqual(
add_acl_call_count, self.nb_ovn.add_acl.call_count)
self.assertEqual(
1, self.nb_ovn.update_address_set.call_count)
def test_create_port_with_security_groups_native_dhcp_enabled(self):
self._test_create_port_with_security_groups_helper(7)
def test_update_port_changed_security_groups(self):
with self.network(set_context=True, tenant_id='test') as net1:
@ -741,29 +639,21 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
# Remove the default security group.
self.nb_ovn.set_lswitch_port.reset_mock()
self.nb_ovn.update_acls.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
data = {'port': {'security_groups': []}}
self._update('ports', port1['port']['id'], data)
self.assertEqual(
1, self.nb_ovn.set_lswitch_port.call_count)
self.assertEqual(
1, self.nb_ovn.update_acls.call_count)
self.assertEqual(
1, self.nb_ovn.update_address_set.call_count)
self.assertFalse(self.nb_ovn.update_acls.called)
self.assertTrue(self.nb_ovn.pg_add_ports.called)
# Add the default security group.
self.nb_ovn.set_lswitch_port.reset_mock()
self.nb_ovn.update_acls.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
fake_lsp.external_ids.pop(ovn_const.OVN_SG_IDS_EXT_ID_KEY)
data = {'port': {'security_groups': [sg_id]}}
self._update('ports', port1['port']['id'], data)
self.assertEqual(
1, self.nb_ovn.set_lswitch_port.call_count)
self.assertEqual(
1, self.nb_ovn.update_acls.call_count)
self.assertEqual(
1, self.nb_ovn.update_address_set.call_count)
self.assertFalse(self.nb_ovn.update_acls.called)
self.assertTrue(self.nb_ovn.pg_add_ports.called)
def test_update_port_unchanged_security_groups(self):
with self.network(set_context=True, tenant_id='test') as net1:
@ -778,26 +668,20 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
# Update the port name.
self.nb_ovn.set_lswitch_port.reset_mock()
self.nb_ovn.update_acls.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
data = {'port': {'name': 'rtheis'}}
self._update('ports', port1['port']['id'], data)
self.assertEqual(
1, self.nb_ovn.set_lswitch_port.call_count)
self.nb_ovn.update_acls.assert_not_called()
self.nb_ovn.update_address_set.assert_not_called()
# Update the port fixed IPs
self.nb_ovn.set_lswitch_port.reset_mock()
self.nb_ovn.update_acls.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
data = {'port': {'fixed_ips': []}}
self._update('ports', port1['port']['id'], data)
self.assertEqual(
1, self.nb_ovn.set_lswitch_port.call_count)
self.assertEqual(
1, self.nb_ovn.update_acls.call_count)
self.assertEqual(
1, self.nb_ovn.update_address_set.call_count)
self.assertFalse(self.nb_ovn.update_acls.called)
def _test_update_port_vip(self, is_vip=True):
kwargs = {}
@ -847,33 +731,9 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
self.nb_ovn.lookup.return_value = fake_lsp
self.nb_ovn.delete_lswitch_port.reset_mock()
self.nb_ovn.delete_acl.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
self._delete('ports', port1['port']['id'])
self.assertEqual(
1, self.nb_ovn.delete_lswitch_port.call_count)
self.assertEqual(
1, self.nb_ovn.delete_acl.call_count)
self.nb_ovn.update_address_set.assert_not_called()
def test_delete_port_with_security_groups(self):
with self.network(set_context=True, tenant_id='test') as net1:
with self.subnet(network=net1) as subnet1:
with self.port(subnet=subnet1,
set_context=True, tenant_id='test') as port1:
fake_lsp = (
fakes.FakeOVNPort.from_neutron_port(
port1['port']))
self.nb_ovn.lookup.return_value = fake_lsp
self.nb_ovn.delete_lswitch_port.reset_mock()
self.nb_ovn.delete_acl.reset_mock()
self.nb_ovn.update_address_set.reset_mock()
self._delete('ports', port1['port']['id'])
self.assertEqual(
1, self.nb_ovn.delete_lswitch_port.call_count)
self.assertEqual(
1, self.nb_ovn.delete_acl.call_count)
self.assertEqual(
1, self.nb_ovn.update_address_set.call_count)
@mock.patch.object(ovn_revision_numbers_db, 'delete_revision')
@mock.patch.object(ovn_client.OVNClient, '_delete_port')
@ -2600,8 +2460,7 @@ class TestOVNMechanismDriverSecurityGroup(
def _delete_sg_rule(self, rule_id):
self._delete('security-group-rules', rule_id)
def test_create_security_group_with_port_group(self):
self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
def test_create_security_group(self):
sg = self._create_sg('sg')
expected_pg_name = ovn_utils.ovn_port_group_name(sg['id'])
@ -2613,8 +2472,7 @@ class TestOVNMechanismDriverSecurityGroup(
self.mech_driver._nb_ovn.pg_add.assert_has_calls(
expected_pg_add_calls)
def test_delete_security_group_with_port_group(self):
self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
def test_delete_security_group(self):
sg = self._create_sg('sg')
self._delete('security-groups', sg['id'])
@ -2625,8 +2483,7 @@ class TestOVNMechanismDriverSecurityGroup(
self.mech_driver._nb_ovn.pg_del.assert_has_calls(
expected_pg_del_calls)
def test_create_port_with_port_group(self):
self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
def test_create_port(self):
with self.network() as n, self.subnet(n):
sg = self._create_empty_sg('sg')
self._make_port(self.fmt, n['network']['id'],
@ -2646,56 +2503,53 @@ class TestOVNMechanismDriverSecurityGroup(
def test_create_port_with_sg_default_rules(self):
with self.network() as n, self.subnet(n):
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
sg = self._create_sg('sg')
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg['id']])
# One DHCP rule, one IPv6 rule, one IPv4 rule and
# two default dropping rules.
# egress traffic for ipv4 and ipv6 is allowed by default
self.assertEqual(
5, self.mech_driver._nb_ovn.add_acl.call_count)
2, self.mech_driver._nb_ovn.pg_acl_add.call_count)
def test_create_port_with_empty_sg(self):
with self.network() as n, self.subnet(n):
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
sg = self._create_empty_sg('sg')
# Implicit egress rules for ipv4 and ipv6
self.assertEqual(2, self.mech_driver._nb_ovn.pg_acl_add.call_count)
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
self.mech_driver._nb_ovn.pg_add.reset_mock()
self.mech_driver._nb_ovn.pg_add_ports.reset_mock()
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg['id']])
# One DHCP rule and two default dropping rules.
self.assertEqual(
3, self.mech_driver._nb_ovn.add_acl.call_count)
def test_create_port_with_multi_sgs(self):
with self.network() as n, self.subnet(n):
sg1 = self._create_empty_sg('sg1')
sg2 = self._create_empty_sg('sg2')
self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
port_range_min=22, port_range_max=23)
self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP,
remote_ip_prefix='0.0.0.0/0')
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg1['id'], sg2['id']])
# One DHCP rule, one TCP rule, one UDP rule and
# two default dropping rules.
self.assertEqual(
5, self.mech_driver._nb_ovn.add_acl.call_count)
self.assertFalse(self.mech_driver._nb_ovn.pg_acl_add.called)
self.assertFalse(self.mech_driver._nb_ovn.pg_add.called)
self.assertEqual(1, self.mech_driver._nb_ovn.pg_add_ports.called)
def test_create_port_with_multi_sgs_duplicate_rules(self):
with self.network() as n, self.subnet(n):
self.mech_driver._nb_ovn.pg_add.reset_mock()
sg1 = self._create_empty_sg('sg1')
sg2 = self._create_empty_sg('sg2')
self.assertEqual(
2, self.mech_driver._nb_ovn.pg_add.call_count)
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
port_range_min=22, port_range_max=23,
remote_ip_prefix='20.0.0.0/24')
self._create_sg_rule(sg2['id'], 'ingress', const.PROTO_NAME_TCP,
port_range_min=22, port_range_max=23,
remote_ip_prefix='20.0.0.0/24')
self.assertEqual(
2, self.mech_driver._nb_ovn.pg_acl_add.call_count)
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg1['id'], sg2['id']])
# One DHCP rule, two TCP rule and two default dropping rules.
# Default drop group, two security groups
self.assertEqual(
5, self.mech_driver._nb_ovn.add_acl.call_count)
3, self.mech_driver._nb_ovn.pg_add_ports.call_count)
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient.is_external_ports_supported',
@ -2739,18 +2593,18 @@ class TestOVNMechanismDriverSecurityGroup(
p = self._make_port(self.fmt, n['network']['id'],
security_groups=[sg1['id']])['port']
# One DHCP rule, one TCP rule and two default dropping rules.
self.assertEqual(
4, self.mech_driver._nb_ovn.add_acl.call_count)
sg2 = self._create_empty_sg('sg2')
self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP,
remote_ip_prefix='30.0.0.0/24')
data = {'port': {'security_groups': [sg1['id'], sg2['id']]}}
self.mech_driver._nb_ovn.pg_add_ports.reset_mock()
req = self.new_update_request('ports', data, p['id'])
req.get_response(self.api)
self.assertEqual(
1, self.mech_driver._nb_ovn.update_acls.call_count)
2, self.mech_driver._nb_ovn.pg_add_ports.call_count)
def test_update_sg_change_rule(self):
with self.network() as n, self.subnet(n):
@ -2758,39 +2612,18 @@ class TestOVNMechanismDriverSecurityGroup(
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg['id']])
# One DHCP rule and two default dropping rules.
self.assertEqual(
3, self.mech_driver._nb_ovn.add_acl.call_count)
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
sg_r = self._create_sg_rule(sg['id'], 'ingress',
const.PROTO_NAME_UDP,
ethertype=const.IPv6)
self.assertEqual(
1, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_add.call_count)
self.mech_driver._nb_ovn.pg_acl_del.reset_mock()
self._delete_sg_rule(sg_r['id'])
self.assertEqual(
2, self.mech_driver._nb_ovn.update_acls.call_count)
def test_update_sg_change_rule_unrelated_port(self):
with self.network() as n, self.subnet(n):
sg1 = self._create_empty_sg('sg1')
sg2 = self._create_empty_sg('sg2')
self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
remote_group_id=sg2['id'])
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg1['id']])
# One DHCP rule, one TCP rule and two default dropping rules.
self.assertEqual(
4, self.mech_driver._nb_ovn.add_acl.call_count)
sg2_r = self._create_sg_rule(sg2['id'], 'egress',
const.PROTO_NAME_UDP)
self.mech_driver._nb_ovn.update_acls.assert_not_called()
self._delete_sg_rule(sg2_r['id'])
self.mech_driver._nb_ovn.update_acls.assert_not_called()
1, self.mech_driver._nb_ovn.pg_acl_del.call_count)
def test_update_sg_duplicate_rule(self):
with self.network() as n, self.subnet(n):
@ -2801,27 +2634,31 @@ class TestOVNMechanismDriverSecurityGroup(
port_range_min=22, port_range_max=23)
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg1['id'], sg2['id']])
# One DHCP rule, one UDP rule and two default dropping rules.
# One default drop rule, two SGs
self.assertEqual(
4, self.mech_driver._nb_ovn.add_acl.call_count)
3, self.mech_driver._nb_ovn.pg_add_ports.call_count)
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
# Add a new duplicate rule to sg2. It's expected to be added.
sg2_r = self._create_sg_rule(sg2['id'], 'ingress',
const.PROTO_NAME_UDP,
port_range_min=22, port_range_max=23)
self.assertEqual(
1, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_add.call_count)
self.mech_driver._nb_ovn.pg_acl_del.reset_mock()
# Delete the duplicate rule. It's expected to be deleted.
self._delete_sg_rule(sg2_r['id'])
self.assertEqual(
2, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_del.call_count)
def test_update_sg_duplicate_rule_multi_ports(self):
with self.network() as n, self.subnet(n):
sg1 = self._create_empty_sg('sg1')
sg2 = self._create_empty_sg('sg2')
sg3 = self._create_empty_sg('sg3')
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
self._create_sg_rule(sg1['id'], 'ingress',
const.PROTO_NAME_UDP,
remote_group_id=sg3['id'])
@ -2834,34 +2671,56 @@ class TestOVNMechanismDriverSecurityGroup(
security_groups=[sg1['id'], sg2['id']])
self._make_port(self.fmt, n['network']['id'],
security_groups=[sg2['id'], sg3['id']])
# Rules include 5 + 5 + 4
# No matter how many ports are there, there are two rules only
self.assertEqual(
14, self.mech_driver._nb_ovn.add_acl.call_count)
2, self.mech_driver._nb_ovn.pg_acl_add.call_count)
# Add a rule to sg1 duplicate with sg2. It's expected to be added.
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
sg1_r = self._create_sg_rule(sg1['id'], 'egress',
const.PROTO_NAME_TCP,
port_range_min=60, port_range_max=70)
self.assertEqual(
1, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_add.call_count)
# Add a rule to sg2 duplicate with sg1 but not duplicate with sg3.
# It's expected to be added as well.
self.mech_driver._nb_ovn.pg_acl_add.reset_mock()
sg2_r = self._create_sg_rule(sg2['id'], 'ingress',
const.PROTO_NAME_UDP,
remote_group_id=sg3['id'])
self.assertEqual(
2, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_add.call_count)
# Delete the duplicate rule in sg1. It's expected to be deleted.
self.mech_driver._nb_ovn.pg_acl_del.reset_mock()
self._delete_sg_rule(sg1_r['id'])
self.assertEqual(
3, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_del.call_count)
# Delete the duplicate rule in sg2. It's expected to be deleted.
self.mech_driver._nb_ovn.pg_acl_del.reset_mock()
self._delete_sg_rule(sg2_r['id'])
self.assertEqual(
4, self.mech_driver._nb_ovn.update_acls.call_count)
1, self.mech_driver._nb_ovn.pg_acl_del.call_count)
def test_delete_port_with_security_groups_port_doesnt_remove_pg(self):
with self.network(set_context=True, tenant_id='test') as net1:
with self.subnet(network=net1):
sg = self._create_sg('sg')
port = self._make_port(
self.fmt, net1['network']['id'],
security_groups=[sg['id']])['port']
fake_lsp = fakes.FakeOVNPort.from_neutron_port(port)
self.mech_driver._nb_ovn.lookup.return_value = fake_lsp
self.mech_driver._nb_ovn.delete_lswitch_port.reset_mock()
self.mech_driver._nb_ovn.delete_acl.reset_mock()
self._delete('ports', port['id'])
self.assertEqual(
1, self.mech_driver._nb_ovn.delete_lswitch_port.call_count)
self.assertFalse(self.mech_driver._nb_ovn.pg_del.called)
self.assertFalse(self.mech_driver._nb_ovn.delete_acl.called)
class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase):