Extend database to support portforwardings with port range
This patch is the second of a series of patches to implement floating ip port forwarding with port ranges. The specification is defined in: https://github.com/openstack/neutron-specs/blob/master/specs/wallaby/port-forwarding-port-ranges.rst Implements: blueprint floatingips-portforwarding-ranges Related-Bug: #1885921 Change-Id: I43e0b669096df865f37c74ddbd050b3b177fd5e5
This commit is contained in:
parent
7f9c8e244a
commit
b271c82d10
@ -154,8 +154,8 @@ class PortForwardingAgentExtension(l3_extension.L3AgentExtension):
|
||||
floating_ip_address = str(port_forward.floating_ip_address)
|
||||
protocol = port_forward.protocol
|
||||
internal_ip_address = str(port_forward.internal_ip_address)
|
||||
internal_port = port_forward.internal_port
|
||||
external_port = port_forward.external_port
|
||||
external_port, internal_port = self.extract_ports(port_forward)
|
||||
|
||||
chain_rule = (pf_chain_name,
|
||||
'-d %s/32 -p %s -m %s --dport %s '
|
||||
'-j DNAT --to-destination %s:%s' % (
|
||||
@ -165,6 +165,41 @@ class PortForwardingAgentExtension(l3_extension.L3AgentExtension):
|
||||
chain_rule_list.append(chain_rule)
|
||||
return chain_rule_list
|
||||
|
||||
@staticmethod
|
||||
def extract_ports(port_forward):
|
||||
# The IP table rules handles internal port ranges with "-"
|
||||
# while the external ports ranges it handles using ":"
|
||||
internal_port = port_forward.internal_port_range.replace(':', '-')
|
||||
external_port = port_forward.external_port_range
|
||||
internal_start, internal_end = internal_port.split('-')
|
||||
external_start, external_end = external_port.split(':')
|
||||
internal_port_one_to_one_mask = ''
|
||||
is_single_external_port = external_start == external_end
|
||||
is_single_internal_port = internal_start == internal_end
|
||||
if is_single_external_port:
|
||||
external_port = external_start
|
||||
else:
|
||||
# This mask will ensure that the rules will be applied in N-N
|
||||
# like 40:50 -> 60:70, the port 40 will be mapped to 60,
|
||||
# the 41 to 61, 42 to 62...50 to 70.
|
||||
# In the case of 40:60 -> 70:80, the ports will be rounded
|
||||
# so the port 41 and 51, will be mapped to 71.
|
||||
internal_port_one_to_one_mask = '/' + external_start
|
||||
if is_single_internal_port:
|
||||
internal_port = internal_start
|
||||
else:
|
||||
internal_port = internal_port + internal_port_one_to_one_mask
|
||||
|
||||
are_ranges_different = (int(internal_end) - int(internal_start)) - (
|
||||
int(external_end) - int(external_start))
|
||||
if are_ranges_different and not (
|
||||
is_single_internal_port or is_single_external_port):
|
||||
LOG.warning("Port forwarding rule with different internal "
|
||||
"and external port ranges applied. Internal "
|
||||
"port range: [%s], external port range: [%s].",
|
||||
internal_port, external_port)
|
||||
return external_port, internal_port
|
||||
|
||||
def _rule_apply(self, iptables_manager, port_forwarding, rule_tag):
|
||||
iptables_manager.ipv4['nat'].clear_rules_by_tag(rule_tag)
|
||||
if DEFAULT_PORT_FORWARDING_CHAIN not in iptables_manager.ipv4[
|
||||
|
@ -27,6 +27,7 @@ from neutron_lib.api.definitions import extra_dhcp_opt
|
||||
from neutron_lib.api.definitions import extraroute
|
||||
from neutron_lib.api.definitions import filter_validation
|
||||
from neutron_lib.api.definitions import fip_pf_description
|
||||
from neutron_lib.api.definitions import fip_pf_port_range
|
||||
from neutron_lib.api.definitions import fip_port_details
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding
|
||||
from neutron_lib.api.definitions import floatingip_pools
|
||||
@ -146,6 +147,7 @@ ML2_SUPPORTED_API_EXTENSIONS = [
|
||||
seg_def.ALIAS,
|
||||
expose_port_forwarding_in_fip.ALIAS,
|
||||
fip_pf_description.ALIAS,
|
||||
fip_pf_port_range.ALIAS,
|
||||
floating_ip_port_forwarding.ALIAS,
|
||||
vlantransparent.ALIAS,
|
||||
logging.ALIAS,
|
||||
|
@ -1 +1 @@
|
||||
34cf8b009713
|
||||
I43e0b669096
|
||||
|
@ -0,0 +1,154 @@
|
||||
# Copyright 2021 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from alembic import op
|
||||
from neutron.db import migration
|
||||
from neutron_lib.db import constants
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
"""port forwarding rule description
|
||||
Revision ID: I43e0b669096
|
||||
Revises: 34cf8b009713
|
||||
Create Date: 2021-12-02 10:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'I43e0b669096'
|
||||
down_revision = '34cf8b009713'
|
||||
|
||||
PF_TABLE_NAME = 'portforwardings'
|
||||
pf_table = sa.Table(
|
||||
PF_TABLE_NAME, sa.MetaData(),
|
||||
sa.Column('id', sa.String(length=constants.UUID_FIELD_SIZE),
|
||||
nullable=False),
|
||||
sa.Column('socket', sa.String(length=36), nullable=False),
|
||||
sa.Column('external_port', sa.Integer(), nullable=False),
|
||||
sa.Column('internal_ip_address', sa.String(length=64), nullable=False),
|
||||
sa.Column('internal_port_start', sa.Integer(), nullable=False),
|
||||
sa.Column('external_port_start', sa.Integer(), nullable=False),
|
||||
sa.Column('internal_port_end', sa.Integer(), nullable=False),
|
||||
sa.Column('external_port_end', sa.Integer(), nullable=False),
|
||||
sa.Column('internal_neutron_port_id', sa.String(constants.UUID_FIELD_SIZE),
|
||||
nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(PF_TABLE_NAME,
|
||||
sa.Column('internal_ip_address', sa.String(length=64),
|
||||
nullable=False))
|
||||
op.add_column(PF_TABLE_NAME, sa.Column('internal_port_start', sa.Integer(),
|
||||
nullable=False))
|
||||
op.add_column(PF_TABLE_NAME, sa.Column('internal_port_end', sa.Integer(),
|
||||
nullable=False))
|
||||
op.add_column(PF_TABLE_NAME, sa.Column('external_port_start', sa.Integer(),
|
||||
nullable=False))
|
||||
op.add_column(PF_TABLE_NAME, sa.Column('external_port_end', sa.Integer(),
|
||||
nullable=False))
|
||||
|
||||
foreign_keys = clear_constraints_and_foreign()
|
||||
migrate_values()
|
||||
op.create_unique_constraint(
|
||||
columns=['floatingip_id', 'protocol',
|
||||
'external_port_start', 'external_port_end'],
|
||||
constraint_name='uniq_port_forwardings0floatingip_id0protocol0'
|
||||
'external_ports',
|
||||
table_name=PF_TABLE_NAME)
|
||||
|
||||
op.create_unique_constraint(
|
||||
columns=['protocol', 'internal_neutron_port_id', 'internal_ip_address',
|
||||
'internal_port_start', 'internal_port_end'],
|
||||
constraint_name='uniq_port_forwardings0ptcl0in_prt_id0in_ip_addr0'
|
||||
'in_prts',
|
||||
table_name=PF_TABLE_NAME)
|
||||
|
||||
op.drop_column(PF_TABLE_NAME, 'socket')
|
||||
|
||||
op.drop_column(PF_TABLE_NAME, 'external_port')
|
||||
|
||||
migration.create_foreign_keys(PF_TABLE_NAME, foreign_keys)
|
||||
|
||||
|
||||
def clear_constraints_and_foreign():
|
||||
inspect = sa.engine.reflection.Inspector.from_engine(op.get_bind())
|
||||
foreign_keys = inspect.get_foreign_keys(PF_TABLE_NAME)
|
||||
migration.remove_foreign_keys(PF_TABLE_NAME,
|
||||
foreign_keys)
|
||||
constraints_name = [
|
||||
'uniq_port_forwardings0internal_neutron_port_id0socket0protocol',
|
||||
'uniq_port_forwardings0floatingip_id0external_port0protocol']
|
||||
for constraint_name in constraints_name:
|
||||
op.drop_constraint(
|
||||
constraint_name=constraint_name,
|
||||
table_name=PF_TABLE_NAME,
|
||||
type_='unique'
|
||||
)
|
||||
|
||||
return foreign_keys
|
||||
|
||||
|
||||
def migrate_values():
|
||||
session = sa.orm.Session(bind=op.get_bind())
|
||||
values = []
|
||||
for row in session.query(pf_table):
|
||||
values.append({'id': row[0],
|
||||
'socket': row[1],
|
||||
'external_port': row[2]})
|
||||
|
||||
with session.begin(subtransactions=True):
|
||||
for value in values:
|
||||
internal_ip_address, internal_port = str(
|
||||
value['socket']).split(':')
|
||||
external_port = value['external_port']
|
||||
internal_port = int(internal_port)
|
||||
session.execute(
|
||||
pf_table.update().values(
|
||||
internal_port_start=internal_port,
|
||||
internal_port_end=internal_port,
|
||||
external_port_start=external_port,
|
||||
external_port_end=external_port,
|
||||
internal_ip_address=internal_ip_address).where(
|
||||
pf_table.c.id == value['id']))
|
||||
session.commit()
|
||||
|
||||
|
||||
def expand_drop_exceptions():
|
||||
"""Drop and replace the unique constraints for table portforwardings
|
||||
|
||||
Drop the existing portforwardings foreign key uniq constraints and then
|
||||
replace them with new unique constraints with column ``protocol``.
|
||||
This is needed to use drop in expand migration to pass test_branches.
|
||||
"""
|
||||
|
||||
return {
|
||||
sa.Column: [
|
||||
'%s.socket' % PF_TABLE_NAME,
|
||||
'%s.external_port' % PF_TABLE_NAME
|
||||
],
|
||||
sa.Constraint: [
|
||||
"portforwardings_ibfk_1",
|
||||
"portforwardings_ibfk_2",
|
||||
"portforwardings_ibfk_3",
|
||||
"portforwardings_ibfk_4",
|
||||
"uniq_port_forwardings0floatingip_id0external_port0protocol",
|
||||
"uniq_port_forwardings0internal_neutron_port_id0socket0protocol",
|
||||
"portforwardings_floatingip_id_fkey",
|
||||
"portforwardings_internal_neutron_port_id_fkey",
|
||||
"portforwardings_standard_attr_id_fkey"
|
||||
]
|
||||
}
|
@ -21,6 +21,7 @@ from sqlalchemy import orm
|
||||
from neutron.db.models import l3
|
||||
from neutron.db import models_v2
|
||||
from neutron_lib.api.definitions import fip_pf_description as apidef
|
||||
from neutron_lib.api.definitions import fip_pf_port_range as range_apidef
|
||||
from neutron_lib.db import constants as db_const
|
||||
|
||||
|
||||
@ -28,28 +29,34 @@ class PortForwarding(standard_attr.HasStandardAttributes,
|
||||
model_base.BASEV2, model_base.HasId):
|
||||
|
||||
__table_args__ = (
|
||||
sa.UniqueConstraint('floatingip_id', 'external_port', 'protocol',
|
||||
sa.UniqueConstraint('floatingip_id', 'protocol',
|
||||
'external_port_start', 'external_port_end',
|
||||
name='uniq_port_forwardings0floatingip_id0'
|
||||
'external_port0protocol'),
|
||||
sa.UniqueConstraint('internal_neutron_port_id', 'socket', 'protocol',
|
||||
name='uniq_port_forwardings0'
|
||||
'internal_neutron_port_id0socket0'
|
||||
'protocol')
|
||||
'protocol0external_ports'),
|
||||
sa.UniqueConstraint('protocol', 'internal_neutron_port_id',
|
||||
'internal_ip_address', 'internal_port_start',
|
||||
'internal_port_end',
|
||||
name='uniq_port_forwardings0ptcl0in_prt_id0'
|
||||
'in_ip_addr0in_prts')
|
||||
)
|
||||
|
||||
floatingip_id = sa.Column(sa.String(db_const.UUID_FIELD_SIZE),
|
||||
sa.ForeignKey('floatingips.id',
|
||||
ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
external_port = sa.Column(sa.Integer, nullable=False)
|
||||
internal_neutron_port_id = sa.Column(
|
||||
sa.String(db_const.UUID_FIELD_SIZE),
|
||||
sa.ForeignKey('ports.id', ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
protocol = sa.Column(sa.String(40), nullable=False)
|
||||
socket = sa.Column(sa.String(36), nullable=False)
|
||||
internal_ip_address = sa.Column(sa.String(64), nullable=False)
|
||||
internal_port_start = sa.Column(sa.Integer, nullable=False)
|
||||
external_port_start = sa.Column(sa.Integer, nullable=False)
|
||||
internal_port_end = sa.Column(sa.Integer, nullable=False)
|
||||
external_port_end = sa.Column(sa.Integer, nullable=False)
|
||||
port = orm.relationship(
|
||||
models_v2.Port, load_on_pending=True,
|
||||
foreign_keys=internal_neutron_port_id,
|
||||
backref=orm.backref("port_forwardings",
|
||||
lazy='subquery', uselist=True,
|
||||
cascade='delete')
|
||||
@ -61,4 +68,4 @@ class PortForwarding(standard_attr.HasStandardAttributes,
|
||||
cascade='delete')
|
||||
)
|
||||
revises_on_change = ('floating_ip', 'port',)
|
||||
api_collections = [apidef.ALIAS]
|
||||
api_collections = [apidef.ALIAS, range_apidef.ALIAS]
|
||||
|
20
neutron/extensions/fip_pf_port_range.py
Normal file
20
neutron/extensions/fip_pf_port_range.py
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron_lib.api.definitions import fip_pf_port_range as apidef
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
|
||||
class Fip_pf_port_range(extensions.APIExtensionDescriptor):
|
||||
|
||||
api_definition = apidef
|
@ -32,7 +32,8 @@ class PortForwarding(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Change unique constraint
|
||||
# Version 1.2: Add "description" field
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Add "external_port_range" and "internal_port_range" fields
|
||||
VERSION = '1.3'
|
||||
|
||||
db_model = models.PortForwarding
|
||||
|
||||
@ -40,27 +41,20 @@ class PortForwarding(base.NeutronDbObject):
|
||||
foreign_keys = {'FloatingIP': {'floatingip_id': 'id'},
|
||||
'Port': {'internal_port_id': 'id'}}
|
||||
|
||||
# Notes: 'socket': 'socket' maybe odd here, but for current OVO and the
|
||||
# definition of PortForwarding obj, this obj doesn't define a field named
|
||||
# "socket", but the db model does, it will get the value to store into db.
|
||||
# And this obj defines some fields like "internal_ip_address" and
|
||||
# "internal_port" which will construct "socket" field. Also there is
|
||||
# a reason why it like this. Please see neutron/objects/base.py#n468
|
||||
# So if we don't set it into fields_need_translation, the OVO base will
|
||||
# default skip the field from db.
|
||||
fields_need_translation = {
|
||||
'socket': 'socket',
|
||||
'internal_port_id': 'internal_neutron_port_id'
|
||||
}
|
||||
|
||||
fields = {
|
||||
'id': common_types.UUIDField(),
|
||||
'floatingip_id': common_types.UUIDField(nullable=False),
|
||||
'external_port': common_types.PortRangeField(nullable=False),
|
||||
'external_port': common_types.PortRangeField(nullable=True),
|
||||
'external_port_range': common_types.PortRangesField(nullable=True),
|
||||
'protocol': common_types.IpProtocolEnumField(nullable=False),
|
||||
'internal_port_id': common_types.UUIDField(nullable=False),
|
||||
'internal_ip_address': obj_fields.IPV4AddressField(),
|
||||
'internal_port': common_types.PortRangeField(nullable=False),
|
||||
'internal_port': common_types.PortRangeField(nullable=True),
|
||||
'internal_port_range': common_types.PortRangesField(nullable=True),
|
||||
'floating_ip_address': obj_fields.IPV4AddressField(),
|
||||
'router_id': common_types.UUIDField(),
|
||||
'description': obj_fields.StringField()
|
||||
@ -85,6 +79,49 @@ class PortForwarding(base.NeutronDbObject):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _new_instance(self, **kwargs):
|
||||
fields_parameters = {f: getattr(self, f)
|
||||
for f in self.fields if hasattr(self, f)}
|
||||
sanitized_kwargs = {k: kwargs[k]
|
||||
for k in kwargs if k in self.fields}
|
||||
fields_parameters.update(sanitized_kwargs)
|
||||
return PortForwarding(**fields_parameters)
|
||||
|
||||
def unroll_port_ranges(self):
|
||||
extrn_port_range = self.external_port_range
|
||||
intrn_port_range = self.internal_port_range
|
||||
if not extrn_port_range:
|
||||
return [self]
|
||||
|
||||
if ':' not in extrn_port_range:
|
||||
return [self._new_instance(
|
||||
external_port=int(extrn_port_range),
|
||||
internal_port=self.internal_port or int(intrn_port_range),
|
||||
external_port_range=None,
|
||||
internal_port_range=None
|
||||
)]
|
||||
|
||||
if ":" not in intrn_port_range:
|
||||
intrn_port_range = "%s:%s" % (intrn_port_range, intrn_port_range)
|
||||
|
||||
extrn_min, extrn_max = map(int, extrn_port_range.split(':'))
|
||||
intrn_min, intrn_max = map(int, intrn_port_range.split(':'))
|
||||
external_ports = list(range(extrn_min, extrn_max + 1))
|
||||
internal_ports = list(range(intrn_min, intrn_max + 1))
|
||||
intrn_multiplier = 1 if intrn_min != intrn_max else 0
|
||||
portforwardings = []
|
||||
for i, external_port in enumerate(external_ports):
|
||||
internal_port = internal_ports[i * intrn_multiplier]
|
||||
portforwardings.append(
|
||||
self._new_instance(
|
||||
external_port=external_port,
|
||||
internal_port=internal_port,
|
||||
external_port_range=None,
|
||||
internal_port_range=None
|
||||
),
|
||||
)
|
||||
return portforwardings
|
||||
|
||||
def obj_load_attr(self, attrname):
|
||||
if attrname in ['floating_ip_address', 'router_id']:
|
||||
return self._load_attr_from_fip(attrname)
|
||||
@ -104,27 +141,127 @@ class PortForwarding(base.NeutronDbObject):
|
||||
_target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if _target_version < (1, 2):
|
||||
primitive.pop('description', None)
|
||||
if _target_version < (1, 3):
|
||||
primitive['internal_port'] = int(
|
||||
str(primitive.pop(
|
||||
'internal_port_range',
|
||||
str(primitive.get('internal_port', '')))).split(':')[0])
|
||||
primitive['external_port'] = int(
|
||||
str(primitive.pop(
|
||||
'external_port_range',
|
||||
str(primitive.get('external_port', '')))).split(':')[0])
|
||||
|
||||
@staticmethod
|
||||
def _modify_single_ports_to_db(result):
|
||||
internal_port = result.pop('internal_port', None)
|
||||
external_port = result.pop('external_port', None)
|
||||
if internal_port:
|
||||
result['internal_port_start'] = internal_port
|
||||
result['internal_port_end'] = internal_port
|
||||
|
||||
if external_port:
|
||||
result['external_port_start'] = external_port
|
||||
result['external_port_end'] = external_port
|
||||
|
||||
@staticmethod
|
||||
def _modify_ports_range_to_db(result):
|
||||
internal_port_range = result.pop('internal_port_range', None)
|
||||
external_port_range = result.pop('external_port_range', None)
|
||||
if internal_port_range:
|
||||
if isinstance(internal_port_range, list):
|
||||
internal_port_range = internal_port_range[0]
|
||||
if isinstance(internal_port_range,
|
||||
int) or internal_port_range.isnumeric():
|
||||
start = end = str(internal_port_range)
|
||||
|
||||
else:
|
||||
start, end = internal_port_range.split(':')
|
||||
|
||||
result['internal_port_start'] = start
|
||||
result['internal_port_end'] = end
|
||||
|
||||
if external_port_range:
|
||||
if isinstance(external_port_range, list):
|
||||
external_port_range = external_port_range[0]
|
||||
if isinstance(external_port_range,
|
||||
int) or external_port_range.isnumeric():
|
||||
start = end = str(external_port_range)
|
||||
|
||||
else:
|
||||
start, end = external_port_range.split(':')
|
||||
|
||||
result['external_port_start'] = start
|
||||
result['external_port_end'] = end
|
||||
|
||||
@staticmethod
|
||||
def _modify_ports_range_from_db(result,
|
||||
internal_port_start=None,
|
||||
internal_port_end=None,
|
||||
external_port_start=None,
|
||||
external_port_end=None):
|
||||
|
||||
if not internal_port_start or not external_port_start:
|
||||
return
|
||||
|
||||
result['external_port_range'] = '%s:%s' % (external_port_start,
|
||||
external_port_end)
|
||||
result['internal_port_range'] = '%s:%s' % (internal_port_start,
|
||||
internal_port_end)
|
||||
|
||||
@staticmethod
|
||||
def _modify_single_ports_from_db(result,
|
||||
internal_port_start=None,
|
||||
internal_port_end=None,
|
||||
external_port_start=None,
|
||||
external_port_end=None):
|
||||
|
||||
if not internal_port_start or not external_port_start:
|
||||
return
|
||||
if internal_port_start == internal_port_end:
|
||||
result['internal_port'] = int(internal_port_start)
|
||||
|
||||
if external_port_start == external_port_end:
|
||||
result['external_port'] = int(external_port_start)
|
||||
|
||||
@classmethod
|
||||
def modify_fields_from_db(cls, db_obj):
|
||||
result = super(PortForwarding, cls).modify_fields_from_db(db_obj)
|
||||
if 'socket' in result:
|
||||
groups = result['socket'].split(":")
|
||||
if 'internal_ip_address' in result:
|
||||
result['internal_ip_address'] = netaddr.IPAddress(
|
||||
groups[0], version=lib_const.IP_VERSION_4)
|
||||
result['internal_port'] = int(groups[1])
|
||||
del result['socket']
|
||||
result['internal_ip_address'], version=lib_const.IP_VERSION_4)
|
||||
|
||||
external_port_start = db_obj.get('external_port_start')
|
||||
external_port_end = db_obj.get('external_port_end')
|
||||
internal_port_start = db_obj.get('internal_port_start')
|
||||
internal_port_end = db_obj.get('internal_port_end')
|
||||
|
||||
cls._modify_single_ports_from_db(
|
||||
result,
|
||||
internal_port_start=internal_port_start,
|
||||
external_port_start=external_port_start,
|
||||
internal_port_end=internal_port_end,
|
||||
external_port_end=external_port_end)
|
||||
cls._modify_ports_range_from_db(
|
||||
result,
|
||||
internal_port_start=internal_port_start,
|
||||
external_port_start=external_port_start,
|
||||
internal_port_end=internal_port_end,
|
||||
external_port_end=external_port_end)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def modify_fields_to_db(cls, fields):
|
||||
result = super(PortForwarding, cls).modify_fields_to_db(fields)
|
||||
if 'internal_ip_address' in result and 'internal_port' in result:
|
||||
result['socket'] = str(
|
||||
result['internal_ip_address']) + ":" + str(
|
||||
result['internal_port'])
|
||||
del result['internal_ip_address']
|
||||
del result['internal_port']
|
||||
cls._modify_ports_range_to_db(result)
|
||||
cls._modify_single_ports_to_db(result)
|
||||
if 'internal_ip_address' in result:
|
||||
if isinstance(result['internal_ip_address'], list):
|
||||
result['internal_ip_address'] = list(
|
||||
map(str, result['internal_ip_address']))
|
||||
else:
|
||||
result['internal_ip_address'] = str(
|
||||
result['internal_ip_address'])
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
|
@ -31,6 +31,7 @@ from neutron.common.ovn import constants as ovn_const
|
||||
from neutron.common.ovn import utils
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron import manager
|
||||
from neutron.objects.port_forwarding import PortForwarding
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions import qos \
|
||||
as ovn_qos
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
|
||||
@ -369,6 +370,11 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
|
||||
|
||||
return to_add, to_remove
|
||||
|
||||
def _unroll_port_forwarding(self, db_pf):
|
||||
pf = PortForwarding(**db_pf)
|
||||
pfs = pf.unroll_port_ranges()
|
||||
return [p.to_dict() for p in pfs]
|
||||
|
||||
def _calculate_fip_pfs_differences(self, ovn_rtr_lb_pfs, db_pfs):
|
||||
to_add_or_update = set()
|
||||
to_remove = []
|
||||
@ -378,20 +384,21 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
|
||||
# a set for each protocol and then comparing it with ovn_pfs
|
||||
db_mapped_pfs = {}
|
||||
for db_pf in db_pfs:
|
||||
fip_id = db_pf.get('floatingip_id')
|
||||
protocol = self.l3_plugin.port_forwarding.ovn_lb_protocol(
|
||||
db_pf.get('protocol'))
|
||||
db_vip = "{}:{} {}:{}".format(
|
||||
db_pf.get('floating_ip_address'), db_pf.get('external_port'),
|
||||
db_pf.get('internal_ip_address'), db_pf.get('internal_port'))
|
||||
for pf in self._unroll_port_forwarding(db_pf):
|
||||
fip_id = pf.get('floatingip_id')
|
||||
protocol = self.l3_plugin.port_forwarding.ovn_lb_protocol(
|
||||
pf.get('protocol'))
|
||||
db_vip = "{}:{} {}:{}".format(
|
||||
pf.get('floating_ip_address'), pf.get('external_port'),
|
||||
pf.get('internal_ip_address'), pf.get('internal_port'))
|
||||
|
||||
fip_dict = db_mapped_pfs.get(fip_id, {})
|
||||
fip_dict_proto = fip_dict.get(protocol, set())
|
||||
fip_dict_proto.add(db_vip)
|
||||
if protocol not in fip_dict:
|
||||
fip_dict[protocol] = fip_dict_proto
|
||||
if fip_id not in db_mapped_pfs:
|
||||
db_mapped_pfs[fip_id] = fip_dict
|
||||
fip_dict = db_mapped_pfs.get(fip_id, {})
|
||||
fip_dict_proto = fip_dict.get(protocol, set())
|
||||
fip_dict_proto.add(db_vip)
|
||||
if protocol not in fip_dict:
|
||||
fip_dict[protocol] = fip_dict_proto
|
||||
if fip_id not in db_mapped_pfs:
|
||||
db_mapped_pfs[fip_id] = fip_dict
|
||||
for fip_id in db_mapped_pfs:
|
||||
ovn_pfs_fip_id = ovn_pfs.get(fip_id, {})
|
||||
# check for cases when ovn has lbs for protocols that are not in
|
||||
|
@ -35,9 +35,11 @@ class OVNPortForwardingHandler(object):
|
||||
return pf_const.LB_PROTOCOL_MAP[pf_obj.protocol]
|
||||
|
||||
@staticmethod
|
||||
def lb_name(fip_id, proto):
|
||||
return "{}-{}-{}".format(
|
||||
pf_const.PORT_FORWARDING_PREFIX, fip_id, proto)
|
||||
def lb_name(fip_id, proto, external_port=''):
|
||||
if external_port:
|
||||
external_port = '-%s' % external_port
|
||||
return "{}-{}-{}{}".format(
|
||||
pf_const.PORT_FORWARDING_PREFIX, fip_id, proto, external_port)
|
||||
|
||||
@classmethod
|
||||
def lb_names(cls, fip_id):
|
||||
@ -45,19 +47,23 @@ class OVNPortForwardingHandler(object):
|
||||
for proto in pf_const.LB_PROTOCOL_MAP.values()]
|
||||
|
||||
@classmethod
|
||||
def _get_lb_attributes(cls, pf_obj):
|
||||
def _get_lb_attributes(cls, pf_obj, is_range=False):
|
||||
external_port = pf_obj.external_port if is_range else ''
|
||||
lb_name = cls.lb_name(pf_obj.floatingip_id,
|
||||
cls._get_lb_protocol(pf_obj))
|
||||
cls._get_lb_protocol(pf_obj),
|
||||
external_port)
|
||||
vip = "{}:{}".format(pf_obj.floating_ip_address, pf_obj.external_port)
|
||||
internal_ip = "{}:{}".format(pf_obj.internal_ip_address,
|
||||
pf_obj.internal_port)
|
||||
rtr_name = 'neutron-{}'.format(pf_obj.router_id)
|
||||
return lb_name, vip, [internal_ip], rtr_name
|
||||
|
||||
def _port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj):
|
||||
def _port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj,
|
||||
is_range=False):
|
||||
# Add vip to its corresponding load balancer. There can be multiple
|
||||
# vips, so load balancer may already be present.
|
||||
lb_name, vip, internal_ips, rtr_name = self._get_lb_attributes(pf_obj)
|
||||
lb_name, vip, internal_ips, rtr_name = self._get_lb_attributes(
|
||||
pf_obj, is_range=is_range)
|
||||
external_ids = {
|
||||
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
|
||||
pf_const.PORT_FORWARDING_PLUGIN,
|
||||
@ -72,21 +78,34 @@ class OVNPortForwardingHandler(object):
|
||||
ovn_txn.add(nb_ovn.lr_lb_add(rtr_name, lb_name, may_exist=True))
|
||||
|
||||
def port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj):
|
||||
LOG.info("CREATE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_created(ovn_txn, nb_ovn, pf_obj)
|
||||
pf_objs = pf_obj.unroll_port_ranges()
|
||||
is_range = len(pf_objs) > 1
|
||||
for pf_obj in pf_objs:
|
||||
LOG.info("CREATE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_created(ovn_txn, nb_ovn, pf_obj,
|
||||
is_range=is_range)
|
||||
|
||||
def port_forwarding_updated(self, ovn_txn, nb_ovn, pf_obj, orig_pf_obj):
|
||||
LOG.info("UPDATE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_deleted(ovn_txn, nb_ovn, orig_pf_obj)
|
||||
self._port_forwarding_created(ovn_txn, nb_ovn, pf_obj)
|
||||
orig_pf_objs = orig_pf_obj.unroll_port_ranges()
|
||||
is_range = len(orig_pf_objs) > 1
|
||||
for orig_pf_obj in orig_pf_objs:
|
||||
self._port_forwarding_deleted(ovn_txn, nb_ovn, orig_pf_obj,
|
||||
is_range=is_range)
|
||||
pf_objs = pf_obj.unroll_port_ranges()
|
||||
is_range = len(pf_objs) > 1
|
||||
for pf_obj in pf_objs:
|
||||
LOG.info("UPDATE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_created(ovn_txn, nb_ovn, pf_obj,
|
||||
is_range=is_range)
|
||||
|
||||
def _port_forwarding_deleted(self, ovn_txn, nb_ovn, pf_obj):
|
||||
def _port_forwarding_deleted(self, ovn_txn, nb_ovn, pf_obj,
|
||||
is_range=False):
|
||||
# NOTE: load balancer instance is expected to be removed by api once
|
||||
# last vip is removed.
|
||||
# Since router has weak ref to the lb, that gets taken care
|
||||
@ -97,15 +116,23 @@ class OVNPortForwardingHandler(object):
|
||||
# TODO(flaviof): see about enhancing lb_del so that removal of lb
|
||||
# can optionally take a logical router, which explicitly dissociates
|
||||
# router from removed lb.
|
||||
lb_name, vip, _internal_ips, _rtr = self._get_lb_attributes(pf_obj)
|
||||
ovn_txn.add(nb_ovn.lb_del(lb_name, vip, if_exists=True))
|
||||
pf_objs = pf_obj.unroll_port_ranges()
|
||||
is_range = is_range or len(pf_objs) > 1
|
||||
for pf_obj in pf_objs:
|
||||
lb_name, vip, _internal_ips, _rtr = self._get_lb_attributes(
|
||||
pf_obj, is_range=is_range)
|
||||
ovn_txn.add(nb_ovn.lb_del(lb_name, vip, if_exists=True))
|
||||
|
||||
def port_forwarding_deleted(self, ovn_txn, nb_ovn, pf_obj):
|
||||
LOG.info("DELETE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_deleted(ovn_txn, nb_ovn, pf_obj)
|
||||
pf_objs = pf_obj.unroll_port_ranges()
|
||||
is_range = len(pf_objs) > 1
|
||||
for pf_obj in pf_objs:
|
||||
LOG.info("DELETE for port-forwarding %s vip %s:%s to %s:%s",
|
||||
pf_obj.protocol,
|
||||
pf_obj.floating_ip_address, pf_obj.external_port,
|
||||
pf_obj.internal_ip_address, pf_obj.internal_port)
|
||||
self._port_forwarding_deleted(ovn_txn, nb_ovn, pf_obj,
|
||||
is_range=is_range)
|
||||
|
||||
|
||||
@registry.has_registry_receivers
|
||||
|
@ -19,6 +19,7 @@ import copy
|
||||
import netaddr
|
||||
from neutron_lib.api.definitions import expose_port_forwarding_in_fip
|
||||
from neutron_lib.api.definitions import fip_pf_description
|
||||
from neutron_lib.api.definitions import fip_pf_port_range
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding as apidef
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.callbacks import events
|
||||
@ -90,7 +91,8 @@ class PortForwardingPlugin(fip_pf.PortForwardingPluginBase):
|
||||
|
||||
supported_extension_aliases = [apidef.ALIAS,
|
||||
expose_port_forwarding_in_fip.ALIAS,
|
||||
fip_pf_description.ALIAS]
|
||||
fip_pf_description.ALIAS,
|
||||
fip_pf_port_range.ALIAS]
|
||||
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
@ -352,6 +354,7 @@ class PortForwardingPlugin(fip_pf.PortForwardingPluginBase):
|
||||
port_forwarding = port_forwarding.get(apidef.RESOURCE_NAME)
|
||||
port_forwarding['floatingip_id'] = floatingip_id
|
||||
|
||||
self._check_port_collisions(context, floatingip_id, port_forwarding)
|
||||
self._check_port_has_binding_floating_ip(context, port_forwarding)
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
fip_obj = self._get_fip_obj(context, floatingip_id)
|
||||
@ -438,6 +441,17 @@ class PortForwardingPlugin(fip_pf.PortForwardingPluginBase):
|
||||
})
|
||||
pf_obj.update_fields(port_forwarding, reset_changes=True)
|
||||
self._check_port_forwarding_update(context, pf_obj)
|
||||
|
||||
port_changed_keys = ['internal_port', 'internal_port_range',
|
||||
'external_port', 'external_port_range']
|
||||
|
||||
if [k for k in port_changed_keys if k in port_forwarding]:
|
||||
self._check_port_collisions(
|
||||
context, floatingip_id, port_forwarding,
|
||||
id, pf_obj.get('internal_port_id'),
|
||||
pf_obj.get('protocol'),
|
||||
pf_obj.get('internal_ip_address'))
|
||||
|
||||
pf_obj.update()
|
||||
except oslo_db_exc.DBDuplicateEntry:
|
||||
(__, conflict_params) = self._find_existing_port_forwarding(
|
||||
@ -456,6 +470,103 @@ class PortForwardingPlugin(fip_pf.PortForwardingPluginBase):
|
||||
states=(original_pf_obj, pf_obj)))
|
||||
return pf_obj
|
||||
|
||||
def _check_collision(self, pf_objs, port, port_key, id):
|
||||
for port_forwarding_registry in pf_objs:
|
||||
if id == port_forwarding_registry['id']:
|
||||
continue
|
||||
|
||||
existing_port = port_forwarding_registry.get(port_key)
|
||||
err_msg = _("There is a port collision with the %s. The "
|
||||
"following ranges collides: %s and %s")
|
||||
|
||||
if self._range_collides(existing_port, port):
|
||||
raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME,
|
||||
msg=err_msg % (
|
||||
port_key,
|
||||
existing_port,
|
||||
port))
|
||||
|
||||
def _check_port_collisions(self, context, floatingip_id, pf_dict,
|
||||
id=None, internal_port_id=None,
|
||||
protocol=None, internal_ip_address=None):
|
||||
external_range_pf_dict = pf_dict.get('external_port_range')
|
||||
if not external_range_pf_dict and 'external_port' in pf_dict:
|
||||
external_range_pf_dict = '%(port)s:%(port)s' % {
|
||||
'port': pf_dict.get('external_port')}
|
||||
|
||||
internal_range_pf_dict = pf_dict.get('internal_port_range')
|
||||
if not internal_range_pf_dict and 'internal_port' in pf_dict:
|
||||
internal_range_pf_dict = '%(port)s:%(port)s' % {
|
||||
'port': pf_dict.get('internal_port')}
|
||||
|
||||
internal_port_id = pf_dict.get('internal_port_id') or internal_port_id
|
||||
protocol = pf_dict.get('protocol') or protocol
|
||||
internal_ip_address = pf_dict.get(
|
||||
'internal_ip_address') or internal_ip_address
|
||||
self._validate_ranges(
|
||||
internal_port_range=internal_range_pf_dict,
|
||||
external_port_range=external_range_pf_dict)
|
||||
|
||||
if internal_range_pf_dict:
|
||||
pf_same_internal_port = pf.PortForwarding.get_objects(
|
||||
context, internal_port_id=internal_port_id,
|
||||
protocol=protocol, internal_ip_address=internal_ip_address)
|
||||
self._check_collision(pf_same_internal_port,
|
||||
internal_range_pf_dict,
|
||||
'internal_port_range',
|
||||
id)
|
||||
|
||||
if external_range_pf_dict:
|
||||
pf_same_fips = pf.PortForwarding.get_objects(
|
||||
context, floatingip_id=floatingip_id,
|
||||
protocol=protocol)
|
||||
self._check_collision(pf_same_fips,
|
||||
external_range_pf_dict,
|
||||
'external_port_range',
|
||||
id)
|
||||
|
||||
def _validate_ranges(self, internal_port_range=None,
|
||||
external_port_range=None):
|
||||
err_invalid_relation = _(
|
||||
"Invalid ranges of internal and/or external ports. "
|
||||
"The relation between internal and external ports "
|
||||
"must be N-N or 1-N")
|
||||
|
||||
internal_dif = self._get_port_range(internal_port_range)
|
||||
external_dif = self._get_port_range(external_port_range)
|
||||
|
||||
if internal_dif > 0 and internal_dif != external_dif:
|
||||
raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME,
|
||||
msg=err_invalid_relation)
|
||||
|
||||
def _get_port_range(self, port_range=None):
|
||||
if not port_range:
|
||||
return 0
|
||||
|
||||
if ':' in port_range:
|
||||
initial_port, final_port = list(map(int,
|
||||
str(port_range).split(':')))
|
||||
return final_port - initial_port
|
||||
|
||||
return 0
|
||||
|
||||
def _range_collides(self, range_a, range_b):
|
||||
range_a = list(map(int, str(range_a).split(':')))
|
||||
range_b = list(map(int, str(range_b).split(':')))
|
||||
|
||||
invalid_port = next((port for port in (range_a + range_b)
|
||||
if 65535 < port or port < 1), None)
|
||||
|
||||
if invalid_port:
|
||||
raise lib_exc.BadRequest(resource=apidef.RESOURCE_NAME,
|
||||
msg="Invalid port value, the "
|
||||
"port value must be "
|
||||
"a value between 1 and 65535.")
|
||||
|
||||
initial_intersection = max(range_a[0], range_b[0])
|
||||
final_intersection = min(range_a[-1], range_b[-1])
|
||||
return initial_intersection <= final_intersection
|
||||
|
||||
def _check_router_match(self, context, fip_obj, router_id, pf_dict):
|
||||
internal_port_id = pf_dict['internal_port_id']
|
||||
if fip_obj.router_id and fip_obj.router_id != router_id:
|
||||
|
@ -48,21 +48,29 @@ class L3AgentFipPortForwardingExtensionTestFramework(
|
||||
self.portforwarding1 = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.fip_id1,
|
||||
external_port=1111, protocol='tcp', internal_port_id=_uuid(),
|
||||
external_port_range='1111:1111',
|
||||
internal_port_range='11111:11111',
|
||||
internal_ip_address='1.1.1.1', internal_port=11111,
|
||||
floating_ip_address='111.111.111.111', router_id=_uuid())
|
||||
self.portforwarding2 = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.fip_id1,
|
||||
external_port=1112, protocol='tcp', internal_port_id=_uuid(),
|
||||
external_port_range='1112:1112',
|
||||
internal_port_range='11112:11112',
|
||||
internal_ip_address='1.1.1.2', internal_port=11112,
|
||||
floating_ip_address='111.111.111.111', router_id=_uuid())
|
||||
self.portforwarding3 = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.fip_id2,
|
||||
external_port=1113, protocol='tcp', internal_port_id=_uuid(),
|
||||
internal_ip_address='1.1.1.3', internal_port=11113,
|
||||
external_port_range='1113:1113',
|
||||
internal_port_range='11113:11113',
|
||||
floating_ip_address='111.222.111.222', router_id=_uuid())
|
||||
self.portforwarding4 = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.fip_id3,
|
||||
external_port=2222, protocol='tcp', internal_port_id=_uuid(),
|
||||
external_port_range='2222:2222',
|
||||
internal_port_range='22222:22222',
|
||||
internal_ip_address='2.2.2.2', internal_port=22222,
|
||||
floating_ip_address='222.222.222.222', router_id=_uuid())
|
||||
self.port_forwardings = [self.portforwarding1, self.portforwarding2,
|
||||
|
@ -17,6 +17,7 @@ from collections import namedtuple
|
||||
import netaddr
|
||||
from neutron_lib.api.definitions import dns as dns_apidef
|
||||
from neutron_lib.api.definitions import fip_pf_description as ext_pf_def
|
||||
from neutron_lib.api.definitions import fip_pf_port_range as ranges_pf_def
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import port_security as ps
|
||||
@ -465,8 +466,8 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
|
||||
|
||||
p5_ip = n1_port_details_dict['p5']['fixed_ips'][0]['ip_address']
|
||||
fip_pf_args = {
|
||||
pf_def.EXTERNAL_PORT: 2222,
|
||||
pf_def.INTERNAL_PORT: 22,
|
||||
ranges_pf_def.EXTERNAL_PORT_RANGE: '2222:2223',
|
||||
ranges_pf_def.INTERNAL_PORT_RANGE: '22:23',
|
||||
pf_def.INTERNAL_PORT_ID: n1_port_dict['p5'],
|
||||
pf_def.PROTOCOL: "tcp",
|
||||
ext_pf_def.DESCRIPTION_FIELD: 'PortFwd r1_f3_p5:22 tcp',
|
||||
@ -476,8 +477,8 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
|
||||
self.context, r1_f3['id'], **fip_args)
|
||||
|
||||
# Add port forwarding with same external and internal value
|
||||
fip_pf_args[pf_def.EXTERNAL_PORT] = 80
|
||||
fip_pf_args[pf_def.INTERNAL_PORT] = 80
|
||||
fip_pf_args[ranges_pf_def.EXTERNAL_PORT_RANGE] = '80:81'
|
||||
fip_pf_args[ranges_pf_def.INTERNAL_PORT_RANGE] = '80:81'
|
||||
fip_pf_args[ext_pf_def.DESCRIPTION_FIELD] = 'PortFwd r1_f3_p5:80 tcp'
|
||||
self.pf_plugin.create_floatingip_port_forwarding(
|
||||
self.context, r1_f3['id'], **fip_args)
|
||||
@ -1420,14 +1421,37 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
|
||||
for fip in fips['floatingips']:
|
||||
for pf in self.pf_plugin.get_floatingip_port_forwardings(
|
||||
self.ctx, floatingip_id=fip['id']):
|
||||
if pf.get('external_port'):
|
||||
db_pfs.append(fip_pf_cmp(
|
||||
fip['id'],
|
||||
ovn_pf.ovn_lb_protocol(pf['protocol']),
|
||||
utils.ovn_name(pf['router_id']),
|
||||
pf['floating_ip_address'],
|
||||
pf['external_port'],
|
||||
pf['internal_ip_address'],
|
||||
pf['internal_port'],
|
||||
))
|
||||
continue
|
||||
|
||||
extrn_1, extrn_2 = pf['external_port_range'].split(':')
|
||||
intrn_1, intrn_2 = pf['internal_port_range'].split(':')
|
||||
db_pfs.append(fip_pf_cmp(
|
||||
fip['id'],
|
||||
ovn_pf.ovn_lb_protocol(pf['protocol']),
|
||||
utils.ovn_name(pf['router_id']),
|
||||
pf['floating_ip_address'],
|
||||
pf['external_port'],
|
||||
int(extrn_1),
|
||||
pf['internal_ip_address'],
|
||||
pf['internal_port'],
|
||||
int(intrn_1),
|
||||
))
|
||||
db_pfs.append(fip_pf_cmp(
|
||||
fip['id'],
|
||||
ovn_pf.ovn_lb_protocol(pf['protocol']),
|
||||
utils.ovn_name(pf['router_id']),
|
||||
pf['floating_ip_address'],
|
||||
int(extrn_2),
|
||||
pf['internal_ip_address'],
|
||||
int(intrn_2)
|
||||
))
|
||||
|
||||
nb_pfs = []
|
||||
|
@ -13,6 +13,7 @@
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib.api.definitions import fip_pf_description as ext_apidef
|
||||
from neutron_lib.api.definitions import fip_pf_port_range as ext_range_apidef
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding as apidef
|
||||
from neutron_lib import exceptions as lib_exc
|
||||
from neutron_lib.exceptions import l3 as lib_l3_exc
|
||||
@ -120,6 +121,8 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
apidef.RESOURCE_NAME:
|
||||
{apidef.EXTERNAL_PORT: 2225,
|
||||
apidef.INTERNAL_PORT: 25,
|
||||
ext_range_apidef.EXTERNAL_PORT_RANGE: '2225:2225',
|
||||
ext_range_apidef.INTERNAL_PORT_RANGE: '25:25',
|
||||
apidef.INTERNAL_PORT_ID: self.port['id'],
|
||||
apidef.PROTOCOL: "tcp",
|
||||
ext_apidef.DESCRIPTION_FIELD: 'Some description',
|
||||
@ -139,6 +142,8 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
expect = {
|
||||
"external_port": 2225,
|
||||
"internal_port": 25,
|
||||
"internal_port_range": '25:25',
|
||||
"external_port_range": '2225:2225',
|
||||
"internal_port_id": self.port['id'],
|
||||
"protocol": "tcp",
|
||||
"internal_ip_address": self.port['fixed_ips'][0]['ip_address'],
|
||||
@ -201,6 +206,8 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
expect = {
|
||||
"external_port": 2225,
|
||||
"internal_port": 25,
|
||||
"external_port_range": '2225:2225',
|
||||
"internal_port_range": '25:25',
|
||||
"internal_port_id": self.port['id'],
|
||||
"protocol": "tcp",
|
||||
"internal_ip_address": self.port['fixed_ips'][0]['ip_address'],
|
||||
@ -242,6 +249,8 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
expect = {
|
||||
"external_port": 2226,
|
||||
"internal_port": 26,
|
||||
"external_port_range": '2226:2226',
|
||||
"internal_port_range": '26:26',
|
||||
"internal_port_id": self.port['id'],
|
||||
"protocol": "udp",
|
||||
"internal_ip_address": self.port['fixed_ips'][0]['ip_address'],
|
||||
@ -269,7 +278,9 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
self.context, res['id'], self.fip['id'], update_body)
|
||||
expect = {
|
||||
"external_port": 2227,
|
||||
"external_port_range": '2227:2227',
|
||||
"internal_port": 27,
|
||||
"internal_port_range": '27:27',
|
||||
"internal_port_id": new_port['id'],
|
||||
"protocol": "tcp",
|
||||
"internal_ip_address": new_port['fixed_ips'][0]['ip_address'],
|
||||
@ -321,7 +332,10 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
'internal_port_id': new_port['id'],
|
||||
'internal_ip_address': new_port['fixed_ips'][0]['ip_address'],
|
||||
'external_port': self.port_forwarding[
|
||||
apidef.RESOURCE_NAME]['external_port'] + 1
|
||||
apidef.RESOURCE_NAME]['external_port'] + 1,
|
||||
'external_port_range': '%(port)s:%(port)s' % {
|
||||
'port': self.port_forwarding[
|
||||
apidef.RESOURCE_NAME]['external_port'] + 1}
|
||||
})
|
||||
new_res = self.pf_plugin.create_floatingip_port_forwarding(
|
||||
self.context, self.fip['id'], self.port_forwarding)
|
||||
@ -345,6 +359,7 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
new_port = self._create_port(self.fmt, self.net['id']).json['port']
|
||||
self.port_forwarding[apidef.RESOURCE_NAME].update({
|
||||
'external_port': 2226,
|
||||
'external_port_range': '2226:2226',
|
||||
'internal_port_id': new_port['id'],
|
||||
'internal_ip_address': new_port['fixed_ips'][0]['ip_address']
|
||||
})
|
||||
@ -392,6 +407,8 @@ class PortForwardingTestCase(PortForwardingTestCaseBase):
|
||||
expected = {
|
||||
"external_port": 2225,
|
||||
"internal_port": 25,
|
||||
"external_port_range": '2225:2225',
|
||||
"internal_port_range": '25:25',
|
||||
"internal_port_id": self.port['id'],
|
||||
"protocol": "tcp",
|
||||
"internal_ip_address": self.port['fixed_ips'][0]['ip_address'],
|
||||
|
@ -37,6 +37,8 @@ try:
|
||||
except ImportError:
|
||||
_CALLBACK_PRIORITY_SUPPORTED = False
|
||||
|
||||
LAST_RANDOM_PORT_RANGE_GENERATED = 1
|
||||
|
||||
|
||||
class SafeCleanupFixture(fixtures.Fixture):
|
||||
"""Catch errors in daughter fixture cleanup."""
|
||||
@ -196,7 +198,10 @@ def get_random_prefixlen(version=4):
|
||||
|
||||
|
||||
def get_random_port(start=constants.PORT_RANGE_MIN):
|
||||
return random.randint(start, constants.PORT_RANGE_MAX)
|
||||
global LAST_RANDOM_PORT_RANGE_GENERATED
|
||||
LAST_RANDOM_PORT_RANGE_GENERATED = random.randint(
|
||||
start, constants.PORT_RANGE_MAX)
|
||||
return LAST_RANDOM_PORT_RANGE_GENERATED
|
||||
|
||||
|
||||
def get_random_vlan():
|
||||
|
@ -60,6 +60,8 @@ class PortForwardingExtensionBaseTestCase(
|
||||
self.portforwarding1 = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.floatingip2.id,
|
||||
external_port=1111, protocol='tcp', internal_port_id=_uuid(),
|
||||
external_port_range='1111:1111',
|
||||
internal_port_range='11111:11111',
|
||||
internal_ip_address='1.1.1.1', internal_port=11111,
|
||||
floating_ip_address=self.floatingip2.floating_ip_address,
|
||||
router_id=self.floatingip2.router_id)
|
||||
@ -148,15 +150,16 @@ class FipPortForwardingExtensionTestCase(PortForwardingExtensionBaseTestCase):
|
||||
rule_tag = 'fip_portforwarding-' + target_obj.id
|
||||
chain_name = (
|
||||
'pf-' + target_obj.id)[:lib_const.MAX_IPTABLES_CHAIN_LEN_WRAP]
|
||||
ports = pf.PortForwardingAgentExtension.extract_ports(target_obj)
|
||||
chain_rule = (chain_name,
|
||||
'-d %s/32 -p %s -m %s --dport %s '
|
||||
'-j DNAT --to-destination %s:%s' % (
|
||||
target_obj.floating_ip_address,
|
||||
target_obj.protocol,
|
||||
target_obj.protocol,
|
||||
target_obj.external_port,
|
||||
ports[0],
|
||||
target_obj.internal_ip_address,
|
||||
target_obj.internal_port))
|
||||
ports[1]))
|
||||
return chain_name, chain_rule, rule_tag
|
||||
|
||||
def _assert_called_iptables_process(self, mock_add_chain,
|
||||
@ -183,6 +186,69 @@ class FipPortForwardingExtensionTestCase(PortForwardingExtensionBaseTestCase):
|
||||
lib_const.FLOATINGIP_STATUS_ACTIVE}
|
||||
mock_send_fip_status.assert_called_once_with(mock.ANY, fip_status)
|
||||
|
||||
def _test_extract_ports(self, internal_port_range, external_port_range,
|
||||
expected_internal_port, expected_external_port,
|
||||
log_called=False):
|
||||
mock_pf = mock.Mock()
|
||||
mock_log = mock.patch.object(pf.LOG, 'warning').start()
|
||||
mock_pf.internal_port_range = internal_port_range
|
||||
mock_pf.external_port_range = external_port_range
|
||||
ports = pf.PortForwardingAgentExtension.extract_ports(mock_pf)
|
||||
self.assertEqual(expected_internal_port, ports[1])
|
||||
self.assertEqual(expected_external_port, ports[0])
|
||||
if log_called:
|
||||
mock_log.assert_called_with(
|
||||
"Port forwarding rule with different internal "
|
||||
"and external port ranges applied. Internal "
|
||||
"port range: [%s], external port range: [%s].",
|
||||
ports[1], ports[0])
|
||||
else:
|
||||
self.assertFalse(mock_log.called)
|
||||
|
||||
def test_extract_ports(self):
|
||||
test_cases = {
|
||||
'internal port range = external port range': {
|
||||
'internal': '10:12',
|
||||
'external': '13:15',
|
||||
'expected_internal': '10-12/13',
|
||||
'expected_external': '13:15',
|
||||
},
|
||||
'internal port range = external port range = 1': {
|
||||
'internal': '10:10',
|
||||
'external': '13:13',
|
||||
'expected_internal': '10',
|
||||
'expected_external': '13',
|
||||
},
|
||||
'internal port range < external port range': {
|
||||
'internal': '10:12',
|
||||
'external': '13:16',
|
||||
'expected_internal': '10-12/13',
|
||||
'expected_external': '13:16',
|
||||
'log_called': True
|
||||
},
|
||||
'internal port range > external port range': {
|
||||
'internal': '9:12',
|
||||
'external': '13:15',
|
||||
'expected_internal': '9-12/13',
|
||||
'expected_external': '13:15',
|
||||
'log_called': True
|
||||
},
|
||||
'internal port range = 1': {
|
||||
'internal': '10:10',
|
||||
'external': '13:15',
|
||||
'expected_internal': '10',
|
||||
'expected_external': '13:15',
|
||||
},
|
||||
'external port range = 1': {
|
||||
'internal': '10:12',
|
||||
'external': '13:13',
|
||||
'expected_internal': '10-12',
|
||||
'expected_external': '13',
|
||||
}
|
||||
}
|
||||
for case in test_cases.values():
|
||||
self._test_extract_ports(*case.values())
|
||||
|
||||
@mock.patch.object(pf.PortForwardingAgentExtension,
|
||||
'_sending_port_forwarding_fip_status')
|
||||
@mock.patch.object(iptables_manager.IptablesTable, 'add_rule')
|
||||
@ -208,6 +274,8 @@ class FipPortForwardingExtensionTestCase(PortForwardingExtensionBaseTestCase):
|
||||
test_portforwarding = pf_obj.PortForwarding(
|
||||
context=None, id=_uuid(), floatingip_id=self.floatingip2.id,
|
||||
external_port=2222, protocol='tcp', internal_port_id=_uuid(),
|
||||
external_port_range='2222:2222',
|
||||
internal_port_range='22222:22222',
|
||||
internal_ip_address='2.2.2.2', internal_port=22222,
|
||||
floating_ip_address=self.floatingip2.floating_ip_address,
|
||||
router_id=self.floatingip2.router_id)
|
||||
@ -229,8 +297,9 @@ class FipPortForwardingExtensionTestCase(PortForwardingExtensionBaseTestCase):
|
||||
update_portforwarding = pf_obj.PortForwarding(
|
||||
context=None, id=self.portforwarding1.id,
|
||||
floatingip_id=self.portforwarding1.floatingip_id,
|
||||
external_port=2222, protocol='tcp', internal_port_id=_uuid(),
|
||||
internal_ip_address='2.2.2.2', internal_port=22222,
|
||||
external_port_range='2222:2223', protocol='tcp',
|
||||
internal_port_id=_uuid(), internal_ip_address='2.2.2.2',
|
||||
internal_port_range='22222:22223',
|
||||
floating_ip_address=self.portforwarding1.floating_ip_address,
|
||||
router_id=self.portforwarding1.router_id)
|
||||
self.port_forwardings = [update_portforwarding]
|
||||
|
@ -48,15 +48,23 @@ class FloatingIPPorForwardingTestCase(test_l3.L3BaseForIntTests,
|
||||
internal_ip_address,
|
||||
internal_port_id,
|
||||
tenant_id=None,
|
||||
description=None):
|
||||
description=None,
|
||||
external_port_range=None,
|
||||
internal_port_range=None):
|
||||
tenant_id = tenant_id or _uuid()
|
||||
data = {'port_forwarding': {
|
||||
"external_port": external_port,
|
||||
"internal_port": internal_port,
|
||||
"protocol": protocol,
|
||||
"internal_ip_address": internal_ip_address,
|
||||
"internal_port_id": internal_port_id}
|
||||
}
|
||||
if external_port_range and internal_port_range:
|
||||
data['port_forwarding'][
|
||||
'internal_port_range'] = internal_port_range
|
||||
data['port_forwarding'][
|
||||
'external_port_range'] = external_port_range
|
||||
else:
|
||||
data['port_forwarding']['internal_port'] = internal_port
|
||||
data['port_forwarding']['external_port'] = external_port
|
||||
|
||||
if description:
|
||||
data['port_forwarding']['description'] = description
|
||||
@ -152,6 +160,87 @@ class FloatingIPPorForwardingTestCase(test_l3.L3BaseForIntTests,
|
||||
self.assertEqual(
|
||||
"blablablabla", pf_body['port_forwarding']['description'])
|
||||
|
||||
def test_create_floatingip_port_forwarding_with_ranges(self):
|
||||
internal_port_range = '22:24'
|
||||
external_port_range = '2222:2224'
|
||||
with self.network() as ext_net:
|
||||
network_id = ext_net['network']['id']
|
||||
self._set_net_external(network_id)
|
||||
with self.subnet(ext_net, cidr='10.10.10.0/24'), \
|
||||
self.router() as router, \
|
||||
self.subnet(cidr='11.0.0.0/24') as private_subnet, \
|
||||
self.port(private_subnet) as port:
|
||||
self._add_external_gateway_to_router(
|
||||
router['router']['id'],
|
||||
network_id)
|
||||
self._router_interface_action(
|
||||
'add', router['router']['id'],
|
||||
private_subnet['subnet']['id'],
|
||||
None)
|
||||
fip = self._make_floatingip(
|
||||
self.fmt,
|
||||
network_id)
|
||||
self.assertIsNone(fip['floatingip'].get('port_id'))
|
||||
res = self._create_fip_port_forwarding(
|
||||
self.fmt, fip['floatingip']['id'],
|
||||
None, None,
|
||||
'tcp',
|
||||
port['port']['fixed_ips'][0]['ip_address'],
|
||||
port['port']['id'],
|
||||
internal_port_range=internal_port_range,
|
||||
external_port_range=external_port_range)
|
||||
self.assertEqual(exc.HTTPCreated.code, res.status_int)
|
||||
pf_body = self.deserialize(self.fmt, res)
|
||||
self.assertEqual(
|
||||
internal_port_range,
|
||||
pf_body['port_forwarding']['internal_port_range'])
|
||||
self.assertEqual(
|
||||
external_port_range,
|
||||
pf_body['port_forwarding']['external_port_range'])
|
||||
|
||||
def test_create_floatingip_port_forwarding_with_ranges_port_collisions(
|
||||
self):
|
||||
internal_port_range1 = '22:24'
|
||||
internal_port_range2 = '23:25'
|
||||
external_port_range1 = '2222:2224'
|
||||
external_port_range2 = '2223:2225'
|
||||
with self.network() as ext_net:
|
||||
network_id = ext_net['network']['id']
|
||||
self._set_net_external(network_id)
|
||||
with self.subnet(ext_net, cidr='10.10.10.0/24'), \
|
||||
self.router() as router, \
|
||||
self.subnet(cidr='11.0.0.0/24') as private_subnet, \
|
||||
self.port(private_subnet) as port:
|
||||
self._add_external_gateway_to_router(
|
||||
router['router']['id'],
|
||||
network_id)
|
||||
self._router_interface_action(
|
||||
'add', router['router']['id'],
|
||||
private_subnet['subnet']['id'],
|
||||
None)
|
||||
fip = self._make_floatingip(
|
||||
self.fmt,
|
||||
network_id)
|
||||
self.assertIsNone(fip['floatingip'].get('port_id'))
|
||||
self._create_fip_port_forwarding(
|
||||
self.fmt, fip['floatingip']['id'],
|
||||
None, None,
|
||||
'tcp',
|
||||
port['port']['fixed_ips'][0]['ip_address'],
|
||||
port['port']['id'],
|
||||
internal_port_range=internal_port_range1,
|
||||
external_port_range=external_port_range1)
|
||||
response = self._create_fip_port_forwarding(
|
||||
self.fmt, fip['floatingip']['id'],
|
||||
None, None,
|
||||
'tcp',
|
||||
port['port']['fixed_ips'][0]['ip_address'],
|
||||
port['port']['id'],
|
||||
internal_port_range=internal_port_range2,
|
||||
external_port_range=external_port_range2)
|
||||
self.assertEqual(exc.HTTPBadRequest.code,
|
||||
response.status_int)
|
||||
|
||||
def test_update_floatingip_port_forwarding_with_dup_internal_port(self):
|
||||
with self.network() as ext_net:
|
||||
network_id = ext_net['network']['id']
|
||||
|
@ -505,6 +505,11 @@ def get_set_of_random_uuids():
|
||||
}
|
||||
|
||||
|
||||
def get_random_port_ranges():
|
||||
port = tools.LAST_RANDOM_PORT_RANGE_GENERATED
|
||||
return "%d:%d" % (port, port)
|
||||
|
||||
|
||||
# NOTE: The keys in this dictionary have alphabetic order.
|
||||
FIELD_TYPE_VALUE_GENERATOR_MAP = {
|
||||
common_types.DictOfMiscValuesField: get_random_dict,
|
||||
@ -552,7 +557,8 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = {
|
||||
port_numa_affinity_policy.NumaAffinityPoliciesEnumField:
|
||||
tools.get_random_port_numa_affinity_policy,
|
||||
port_device_profile.PortDeviceProfile:
|
||||
lambda: helpers.get_random_string(255)
|
||||
lambda: helpers.get_random_string(255),
|
||||
common_types.PortRangesField: get_random_port_ranges
|
||||
}
|
||||
|
||||
|
||||
|
@ -80,7 +80,7 @@ object_data = {
|
||||
'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5',
|
||||
'PortDataPlaneStatus': '1.0-25be74bda46c749653a10357676c0ab2',
|
||||
'PortDNS': '1.1-c5ca2dc172bdd5fafee3fc986d1d7023',
|
||||
'PortForwarding': '1.2-f772f03b82a616603c7f3d4497bf577f',
|
||||
'PortForwarding': '1.3-402b1fb5a754808b82a966c95f468113',
|
||||
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
|
||||
'PortUplinkStatusPropagation': '1.1-f0a4ca451a941910376c33616dea5de2',
|
||||
'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34',
|
||||
|
@ -60,10 +60,12 @@ class PortForwardingObjectTestCase(obj_test_base.BaseObjectIfaceTestCase):
|
||||
if not fp_obj.db_obj:
|
||||
fp_db_attrs = {
|
||||
'floatingip_id': fp_obj.floatingip_id,
|
||||
'external_port': fp_obj.external_port,
|
||||
'external_port_start': fp_obj.external_port,
|
||||
'external_port_end': fp_obj.external_port,
|
||||
'internal_neutron_port_id': fp_obj.internal_port_id,
|
||||
'protocol': fp_obj.protocol,
|
||||
'socket': fp_obj.internal_port,
|
||||
'internal_port_start': fp_obj.internal_port,
|
||||
'internal_port_end': fp_obj.internal_port,
|
||||
'floating_ip': random_generate_fip_db(fp_obj.floatingip_id)
|
||||
}
|
||||
fp_obj._captured_db_model = (
|
||||
@ -134,10 +136,6 @@ class PortForwardingDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
|
||||
obj.create()
|
||||
self.assertIsNotNone(obj.db_obj)
|
||||
# Make sure the created obj socket field is correct.
|
||||
created_socket = obj.db_obj.socket.split(":")
|
||||
self.assertEqual(created_socket[0], str(obj.internal_ip_address))
|
||||
self.assertEqual(created_socket[1], str(obj.internal_port))
|
||||
|
||||
fields_to_update = self.get_updatable_fields(self.obj_fields[1])
|
||||
if fields_to_update:
|
||||
@ -153,12 +151,6 @@ class PortForwardingDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
setattr(obj, key, val)
|
||||
obj.update()
|
||||
self.assertIsNotNone(obj.db_obj)
|
||||
# Make sure the updated obj socket field is correct.
|
||||
updated_socket = obj.db_obj.socket.split(":")
|
||||
self.assertEqual(updated_socket[0],
|
||||
str(self.obj_fields[1]['internal_ip_address']))
|
||||
self.assertEqual(updated_socket[1],
|
||||
str(self.obj_fields[1]['internal_port']))
|
||||
# Then check all update fields had been updated.
|
||||
for k, v in obj.modify_fields_to_db(fields_to_update).items():
|
||||
self.assertEqual(v, obj.db_obj[k], '%s attribute differs' % k)
|
||||
|
@ -15,6 +15,7 @@
|
||||
from unittest import mock
|
||||
|
||||
from neutron.common.ovn import constants as ovn_const
|
||||
from neutron.objects import port_forwarding as port_forwarding_obj
|
||||
from neutron.services.portforwarding.constants import PORT_FORWARDING
|
||||
from neutron.services.portforwarding.constants import PORT_FORWARDING_PLUGIN
|
||||
from neutron.services.portforwarding.drivers.ovn import driver \
|
||||
@ -37,8 +38,9 @@ class TestOVNPortForwardingBase(base.BaseTestCase):
|
||||
self.l3_plugin._nb_ovn = fake_resources.FakeOvsdbNbOvnIdl()
|
||||
self.txn = self.l3_plugin._nb_ovn.transaction
|
||||
|
||||
def _fake_pf_obj(self, **kwargs):
|
||||
def _fake_pf_obj(self, is_object=False, **kwargs):
|
||||
pf_obj_defaults_dict = {
|
||||
'id': 'id',
|
||||
'floatingip_id': 'fip_id',
|
||||
'protocol': 'udp',
|
||||
'floating_ip_address': 'fip_addr',
|
||||
@ -48,6 +50,8 @@ class TestOVNPortForwardingBase(base.BaseTestCase):
|
||||
'router_id': 'rtr_id'
|
||||
}
|
||||
pf_obj_dict = {**pf_obj_defaults_dict, **kwargs}
|
||||
if is_object:
|
||||
return port_forwarding_obj.PortForwarding(**pf_obj_dict)
|
||||
return mock.Mock(**pf_obj_dict)
|
||||
|
||||
def _fake_pf_payload_entry(self, curr_pf_id, orig_pf_id=None, **kwargs):
|
||||
@ -103,9 +107,19 @@ class TestOVNPortForwardingHandler(TestOVNPortForwardingBase):
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
def test_port_forwarding_created(self, m_info):
|
||||
fake_pf_obj = self._fake_pf_obj()
|
||||
exp_lb_name, exp_vip, exp_internal_ips, exp_rtr_name = (self.handler.
|
||||
_get_lb_attributes(fake_pf_obj))
|
||||
fake_pf_obj = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=uuidutils.generate_uuid(),
|
||||
floatingip_id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
)
|
||||
exp_lb_name, exp_vip, exp_internal_ips, exp_rtr_name = (
|
||||
self.handler._get_lb_attributes(fake_pf_obj))
|
||||
exp_protocol = self.handler._get_lb_protocol(fake_pf_obj)
|
||||
self.handler.port_forwarding_created(
|
||||
self.txn, self.l3_plugin._nb_ovn, fake_pf_obj)
|
||||
@ -123,26 +137,304 @@ class TestOVNPortForwardingHandler(TestOVNPortForwardingBase):
|
||||
self.l3_plugin._nb_ovn.lr_lb_add.assert_called_once_with(
|
||||
exp_rtr_name, exp_lb_name, may_exist=True)
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
def test_port_forwarding_created_with_ranges(self, m_info):
|
||||
id = uuidutils.generate_uuid()
|
||||
floatingip_id = uuidutils.generate_uuid()
|
||||
router_id = uuidutils.generate_uuid()
|
||||
fake_pf_obj_ranges = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=None,
|
||||
external_port_range='2222:2223',
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=None,
|
||||
internal_port_range='22:23'
|
||||
)
|
||||
fake_pf_obj_1 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
internal_port_range=None
|
||||
)
|
||||
fake_pf_obj_2 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2223,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=23,
|
||||
internal_port_range=None
|
||||
)
|
||||
exp_lb_name_1, exp_vip_1, exp_internal_ips_1, exp_rtr_name_1 = (
|
||||
self.handler._get_lb_attributes(fake_pf_obj_1, True))
|
||||
exp_lb_name_2, exp_vip_2, exp_internal_ips_2, exp_rtr_name_2 = (
|
||||
self.handler._get_lb_attributes(fake_pf_obj_2, True))
|
||||
exp_protocol_1 = self.handler._get_lb_protocol(fake_pf_obj_1)
|
||||
exp_protocol_2 = self.handler._get_lb_protocol(fake_pf_obj_2)
|
||||
self.handler.port_forwarding_created(
|
||||
self.txn, self.l3_plugin._nb_ovn, fake_pf_obj_ranges)
|
||||
info_args, _info_kwargs = m_info.call_args_list[0]
|
||||
self.assertIn('CREATE for port-forwarding', info_args[0])
|
||||
self.assertEqual(4, len(self.txn.add.call_args_list))
|
||||
info_args, _info_kwargs = m_info.call_args_list[1]
|
||||
self.assertIn('CREATE for port-forwarding', info_args[0])
|
||||
self.assertEqual(4, len(self.txn.add.call_args_list))
|
||||
exp_external_ids_1 = {
|
||||
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: PORT_FORWARDING_PLUGIN,
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: fake_pf_obj_1.floatingip_id,
|
||||
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: exp_rtr_name_1,
|
||||
}
|
||||
exp_external_ids_2 = {
|
||||
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: PORT_FORWARDING_PLUGIN,
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: fake_pf_obj_1.floatingip_id,
|
||||
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: exp_rtr_name_2,
|
||||
}
|
||||
self.l3_plugin._nb_ovn.lb_add.assert_any_call(
|
||||
exp_lb_name_1, exp_vip_1, exp_internal_ips_1, exp_protocol_1,
|
||||
may_exist=True, external_ids=exp_external_ids_1)
|
||||
self.l3_plugin._nb_ovn.lr_lb_add.assert_any_call(
|
||||
exp_rtr_name_1, exp_lb_name_1, may_exist=True)
|
||||
self.l3_plugin._nb_ovn.lb_add.assert_any_call(
|
||||
exp_lb_name_2, exp_vip_2, exp_internal_ips_2, exp_protocol_2,
|
||||
may_exist=True, external_ids=exp_external_ids_2)
|
||||
self.l3_plugin._nb_ovn.lr_lb_add.assert_any_call(
|
||||
exp_rtr_name_2, exp_lb_name_2, may_exist=True)
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
@mock.patch.object(
|
||||
port_forwarding.OVNPortForwardingHandler, '_port_forwarding_deleted')
|
||||
@mock.patch.object(
|
||||
port_forwarding.OVNPortForwardingHandler, '_port_forwarding_created')
|
||||
def test_port_forwarding_updated_with_ranges(self, m_created, m_deleted,
|
||||
m_info):
|
||||
id = uuidutils.generate_uuid()
|
||||
floatingip_id = uuidutils.generate_uuid()
|
||||
router_id = uuidutils.generate_uuid()
|
||||
fake_pf_obj_ranges_old = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=None,
|
||||
external_port_range='2222:2223',
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=None,
|
||||
internal_port_range='22:23'
|
||||
)
|
||||
fake_pf_obj_1_old = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
internal_port_range=None
|
||||
)
|
||||
fake_pf_obj_2_old = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2223,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=23,
|
||||
internal_port_range=None
|
||||
)
|
||||
fake_pf_obj_ranges = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='tcp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=None,
|
||||
external_port_range='2222:2223',
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=None,
|
||||
internal_port_range='22:23'
|
||||
)
|
||||
fake_pf_obj_1 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='tcp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
internal_port_range=None
|
||||
)
|
||||
fake_pf_obj_2 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='tcp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2223,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=23,
|
||||
internal_port_range=None
|
||||
)
|
||||
|
||||
def do_simple_comparation(pf_1, pf_2):
|
||||
pf_1_dict = {f: getattr(pf_1, f)
|
||||
for f in pf_1.fields if hasattr(pf_1, f)}
|
||||
pf_2_dict = {f: getattr(pf_2, f)
|
||||
for f in pf_2.fields if hasattr(pf_2, f)}
|
||||
self.assertEqual(pf_1_dict, pf_2_dict)
|
||||
|
||||
self.handler.port_forwarding_updated(
|
||||
self.txn, self.l3_plugin._nb_ovn, fake_pf_obj_ranges,
|
||||
fake_pf_obj_ranges_old)
|
||||
info_args, _info_kwargs = m_info.call_args_list[0]
|
||||
self.assertIn('UPDATE for port-forwarding', info_args[0])
|
||||
deleted_pf_1 = m_deleted.call_args_list[0][0][2]
|
||||
deleted_pf_2 = m_deleted.call_args_list[1][0][2]
|
||||
do_simple_comparation(deleted_pf_1, fake_pf_obj_1_old)
|
||||
do_simple_comparation(deleted_pf_2, fake_pf_obj_2_old)
|
||||
created_pf_1 = m_created.call_args_list[0][0][2]
|
||||
created_pf_2 = m_created.call_args_list[1][0][2]
|
||||
do_simple_comparation(created_pf_1, fake_pf_obj_1)
|
||||
do_simple_comparation(created_pf_2, fake_pf_obj_2)
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
@mock.patch.object(
|
||||
port_forwarding.OVNPortForwardingHandler, '_port_forwarding_deleted')
|
||||
@mock.patch.object(
|
||||
port_forwarding.OVNPortForwardingHandler, '_port_forwarding_created')
|
||||
def test_port_forwarding_updated(self, m_created, m_deleted, m_info):
|
||||
fake_pf_obj = self._fake_pf_obj(protocol='udp')
|
||||
fake_orig_pf_obj = self._fake_pf_obj(protocol='tcp')
|
||||
fake_pf_obj = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=uuidutils.generate_uuid(),
|
||||
floatingip_id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
)
|
||||
fake_orig_pf_obj = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=uuidutils.generate_uuid(),
|
||||
floatingip_id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
protocol='tcp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
)
|
||||
self.handler.port_forwarding_updated(
|
||||
self.txn, self.l3_plugin._nb_ovn, fake_pf_obj, fake_orig_pf_obj)
|
||||
info_args, _info_kwargs = m_info.call_args_list[0]
|
||||
self.assertIn('UPDATE for port-forwarding', info_args[0])
|
||||
m_deleted.assert_called_once_with(self.txn, self.l3_plugin._nb_ovn,
|
||||
fake_orig_pf_obj)
|
||||
fake_orig_pf_obj, is_range=False)
|
||||
m_created.assert_called_once_with(self.txn, self.l3_plugin._nb_ovn,
|
||||
fake_pf_obj)
|
||||
fake_pf_obj, is_range=False)
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
def test_port_forwarding_deleted_with_ranges(self, m_info):
|
||||
id = uuidutils.generate_uuid()
|
||||
floatingip_id = uuidutils.generate_uuid()
|
||||
router_id = uuidutils.generate_uuid()
|
||||
fake_pf_obj_ranges = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=None,
|
||||
external_port_range='2222:2223',
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=None,
|
||||
internal_port_range='22:23'
|
||||
)
|
||||
fake_pf_obj_1 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
internal_port_range=None
|
||||
)
|
||||
fake_pf_obj_2 = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=id,
|
||||
floatingip_id=floatingip_id,
|
||||
router_id=router_id,
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2223,
|
||||
external_port_range=None,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=23,
|
||||
internal_port_range=None
|
||||
)
|
||||
|
||||
exp_lb_name_1, exp_vip_1, _, _ = self.handler._get_lb_attributes(
|
||||
fake_pf_obj_1, True)
|
||||
exp_lb_name_2, exp_vip_2, _, _ = self.handler._get_lb_attributes(
|
||||
fake_pf_obj_2, True)
|
||||
self.handler.port_forwarding_deleted(
|
||||
self.txn, self.l3_plugin._nb_ovn, fake_pf_obj_ranges)
|
||||
info_args, _info_kwargs = m_info.call_args_list[0]
|
||||
self.assertIn('DELETE for port-forwarding', info_args[0])
|
||||
self.assertEqual(2, len(self.txn.add.call_args_list))
|
||||
self.l3_plugin._nb_ovn.lb_del.assert_any_call(
|
||||
exp_lb_name_1, exp_vip_1, if_exists=mock.ANY)
|
||||
self.l3_plugin._nb_ovn.lb_del.assert_any_call(
|
||||
exp_lb_name_2, exp_vip_2, if_exists=mock.ANY)
|
||||
|
||||
@mock.patch.object(port_forwarding.LOG, 'info')
|
||||
def test_port_forwarding_deleted(self, m_info):
|
||||
fake_pf_obj = self._fake_pf_obj()
|
||||
fake_pf_obj = self._fake_pf_obj(
|
||||
is_object=True,
|
||||
id=uuidutils.generate_uuid(),
|
||||
floatingip_id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
protocol='udp',
|
||||
floating_ip_address='10.0.0.1',
|
||||
external_port=2222,
|
||||
internal_ip_address='192.168.0.1',
|
||||
internal_port=22,
|
||||
)
|
||||
exp_lb_name, exp_vip, _, _ = self.handler._get_lb_attributes(
|
||||
fake_pf_obj)
|
||||
self.handler.port_forwarding_deleted(
|
||||
|
@ -184,6 +184,8 @@ class TestPortForwardingPlugin(testlib_api.SqlTestCase):
|
||||
'floatingip_id': 'fip_id'}
|
||||
pf_obj = mock.Mock()
|
||||
pf_obj.internal_ip_address = "10.0.0.1"
|
||||
pf_obj.internal_port = 22
|
||||
pf_obj.external_port = 222
|
||||
mock_pf_get_object.return_value = pf_obj
|
||||
port_dict = {'id': 'ID', 'fixed_ips': [{"subnet_id": "test-subnet-id",
|
||||
"ip_address": "10.0.0.1"}]}
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
|
||||
features:
|
||||
- |
|
||||
Add support for port ranges in the port forwarding rules.
|
||||
The supported ranges are N:M with N <= M. Also, the ranges
|
||||
of internal and external ports relation must be:
|
||||
internal range = external range or internal range = 1.
|
Loading…
Reference in New Issue
Block a user