Skip to content

Commit bced485

Browse files
committed
Add port ranges on floating ip portforwardings cli
This patch is one 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 Change-Id: If9679c87fd8b770fcd960048e091ee8d59205285 Implements: blueprint floatingips-portforwarding-ranges Related-Bug: #1885921
1 parent 8f07476 commit bced485

File tree

4 files changed

+318
-57
lines changed

4 files changed

+318
-57
lines changed

openstackclient/network/v2/floating_ip_port_forwarding.py

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,61 @@
2525
LOG = logging.getLogger(__name__)
2626

2727

28+
def validate_ports_diff(ports):
29+
if len(ports) == 0:
30+
return 0
31+
32+
ports_diff = ports[-1] - ports[0]
33+
if ports_diff < 0:
34+
msg = _("The last number in port range must be"
35+
" greater or equal to the first")
36+
raise exceptions.CommandError(msg)
37+
return ports_diff
38+
39+
40+
def validate_ports_match(internal_ports, external_ports):
41+
internal_ports_diff = validate_ports_diff(internal_ports)
42+
external_ports_diff = validate_ports_diff(external_ports)
43+
44+
if internal_ports_diff != 0 and internal_ports_diff != external_ports_diff:
45+
msg = _("The relation between internal and external ports does not "
46+
"match the pattern 1:N and N:N")
47+
raise exceptions.CommandError(msg)
48+
49+
50+
def validate_and_assign_port_ranges(parsed_args, attrs):
51+
internal_port_range = parsed_args.internal_protocol_port
52+
external_port_range = parsed_args.external_protocol_port
53+
external_ports = internal_ports = []
54+
if external_port_range:
55+
external_ports = list(map(int, str(external_port_range).split(':')))
56+
if internal_port_range:
57+
internal_ports = list(map(int, str(internal_port_range).split(':')))
58+
59+
validate_ports_match(internal_ports, external_ports)
60+
61+
for port in external_ports + internal_ports:
62+
validate_port(port)
63+
64+
if internal_port_range:
65+
if ':' in internal_port_range:
66+
attrs['internal_port_range'] = internal_port_range
67+
else:
68+
attrs['internal_port'] = int(internal_port_range)
69+
70+
if external_port_range:
71+
if ':' in external_port_range:
72+
attrs['external_port_range'] = external_port_range
73+
else:
74+
attrs['external_port'] = int(external_port_range)
75+
76+
77+
def validate_port(port):
78+
if port <= 0 or port > 65535:
79+
msg = _("The port number range is <1-65535>")
80+
raise exceptions.CommandError(msg)
81+
82+
2883
def _get_columns(item):
2984
column_map = {}
3085
hidden_columns = ['location']
@@ -58,7 +113,6 @@ def get_parser(self, prog_name):
58113
)
59114
parser.add_argument(
60115
'--internal-protocol-port',
61-
type=int,
62116
metavar='<port-number>',
63117
required=True,
64118
help=_("The protocol port number "
@@ -67,7 +121,6 @@ def get_parser(self, prog_name):
67121
)
68122
parser.add_argument(
69123
'--external-protocol-port',
70-
type=int,
71124
metavar='<port-number>',
72125
required=True,
73126
help=_("The protocol port number of "
@@ -92,6 +145,7 @@ def get_parser(self, prog_name):
92145
help=_("Floating IP that the port forwarding belongs to "
93146
"(IP address or ID)")
94147
)
148+
95149
return parser
96150

97151
def take_action(self, parsed_args):
@@ -102,19 +156,7 @@ def take_action(self, parsed_args):
102156
ignore_missing=False,
103157
)
104158

105-
if parsed_args.internal_protocol_port is not None:
106-
if (parsed_args.internal_protocol_port <= 0 or
107-
parsed_args.internal_protocol_port > 65535):
108-
msg = _("The port number range is <1-65535>")
109-
raise exceptions.CommandError(msg)
110-
attrs['internal_port'] = parsed_args.internal_protocol_port
111-
112-
if parsed_args.external_protocol_port is not None:
113-
if (parsed_args.external_protocol_port <= 0 or
114-
parsed_args.external_protocol_port > 65535):
115-
msg = _("The port number range is <1-65535>")
116-
raise exceptions.CommandError(msg)
117-
attrs['external_port'] = parsed_args.external_protocol_port
159+
validate_and_assign_port_ranges(parsed_args, attrs)
118160

119161
if parsed_args.port:
120162
port = client.find_port(parsed_args.port,
@@ -226,7 +268,9 @@ def take_action(self, parsed_args):
226268
'internal_port_id',
227269
'internal_ip_address',
228270
'internal_port',
271+
'internal_port_range',
229272
'external_port',
273+
'external_port_range',
230274
'protocol',
231275
'description',
232276
)
@@ -235,7 +279,9 @@ def take_action(self, parsed_args):
235279
'Internal Port ID',
236280
'Internal IP Address',
237281
'Internal Port',
282+
'Internal Port Range',
238283
'External Port',
284+
'External Port Range',
239285
'Protocol',
240286
'Description',
241287
)
@@ -246,8 +292,13 @@ def take_action(self, parsed_args):
246292
port = client.find_port(parsed_args.port,
247293
ignore_missing=False)
248294
query['internal_port_id'] = port.id
249-
if parsed_args.external_protocol_port is not None:
250-
query['external_port'] = parsed_args.external_protocol_port
295+
external_port = parsed_args.external_protocol_port
296+
if external_port:
297+
if ':' in external_port:
298+
query['external_port_range'] = external_port
299+
else:
300+
query['external_port'] = int(
301+
parsed_args.external_protocol_port)
251302
if parsed_args.protocol is not None:
252303
query['protocol'] = parsed_args.protocol
253304

@@ -297,14 +348,12 @@ def get_parser(self, prog_name):
297348
parser.add_argument(
298349
'--internal-protocol-port',
299350
metavar='<port-number>',
300-
type=int,
301351
help=_("The TCP/UDP/other protocol port number of the "
302352
"network port fixed IPv4 address associated to "
303353
"the floating IP port forwarding")
304354
)
305355
parser.add_argument(
306356
'--external-protocol-port',
307-
type=int,
308357
metavar='<port-number>',
309358
help=_("The TCP/UDP/other protocol port number of the "
310359
"port forwarding's floating IP address")
@@ -339,19 +388,8 @@ def take_action(self, parsed_args):
339388

340389
if parsed_args.internal_ip_address:
341390
attrs['internal_ip_address'] = parsed_args.internal_ip_address
342-
if parsed_args.internal_protocol_port is not None:
343-
if (parsed_args.internal_protocol_port <= 0 or
344-
parsed_args.internal_protocol_port > 65535):
345-
msg = _("The port number range is <1-65535>")
346-
raise exceptions.CommandError(msg)
347-
attrs['internal_port'] = parsed_args.internal_protocol_port
348-
349-
if parsed_args.external_protocol_port is not None:
350-
if (parsed_args.external_protocol_port <= 0 or
351-
parsed_args.external_protocol_port > 65535):
352-
msg = _("The port number range is <1-65535>")
353-
raise exceptions.CommandError(msg)
354-
attrs['external_port'] = parsed_args.external_protocol_port
391+
392+
validate_and_assign_port_ranges(parsed_args, attrs)
355393

356394
if parsed_args.protocol:
357395
attrs['protocol'] = parsed_args.protocol

openstackclient/tests/unit/network/v2/fakes.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,11 +1346,13 @@ class FakeFloatingIPPortForwarding(object):
13461346
""""Fake one or more Port forwarding"""
13471347

13481348
@staticmethod
1349-
def create_one_port_forwarding(attrs=None):
1349+
def create_one_port_forwarding(attrs=None, use_range=False):
13501350
"""Create a fake Port Forwarding.
13511351
13521352
:param Dictionary attrs:
13531353
A dictionary with all attributes
1354+
:param Boolean use_range:
1355+
A boolean which defines if we will use ranges or not
13541356
:return:
13551357
A FakeResource object with name, id, etc.
13561358
"""
@@ -1364,13 +1366,29 @@ def create_one_port_forwarding(attrs=None):
13641366
'floatingip_id': floatingip_id,
13651367
'internal_port_id': 'internal-port-id-' + uuid.uuid4().hex,
13661368
'internal_ip_address': '192.168.1.2',
1367-
'internal_port': randint(1, 65535),
1368-
'external_port': randint(1, 65535),
13691369
'protocol': 'tcp',
13701370
'description': 'some description',
13711371
'location': 'MUNCHMUNCHMUNCH',
13721372
}
13731373

1374+
if use_range:
1375+
port_range = randint(0, 100)
1376+
internal_start = randint(1, 65535 - port_range)
1377+
internal_end = internal_start + port_range
1378+
internal_range = ':'.join(map(str, [internal_start, internal_end]))
1379+
external_start = randint(1, 65535 - port_range)
1380+
external_end = external_start + port_range
1381+
external_range = ':'.join(map(str, [external_start, external_end]))
1382+
port_forwarding_attrs['internal_port_range'] = internal_range
1383+
port_forwarding_attrs['external_port_range'] = external_range
1384+
port_forwarding_attrs['internal_port'] = None
1385+
port_forwarding_attrs['external_port'] = None
1386+
else:
1387+
port_forwarding_attrs['internal_port'] = randint(1, 65535)
1388+
port_forwarding_attrs['external_port'] = randint(1, 65535)
1389+
port_forwarding_attrs['internal_port_range'] = ''
1390+
port_forwarding_attrs['external_port_range'] = ''
1391+
13741392
# Overwrite default attributes.
13751393
port_forwarding_attrs.update(attrs)
13761394

@@ -1381,25 +1399,28 @@ def create_one_port_forwarding(attrs=None):
13811399
return port_forwarding
13821400

13831401
@staticmethod
1384-
def create_port_forwardings(attrs=None, count=2):
1402+
def create_port_forwardings(attrs=None, count=2, use_range=False):
13851403
"""Create multiple fake Port Forwarding.
13861404
13871405
:param Dictionary attrs:
13881406
A dictionary with all attributes
13891407
:param int count:
13901408
The number of Port Forwarding rule to fake
1409+
:param Boolean use_range:
1410+
A boolean which defines if we will use ranges or not
13911411
:return:
13921412
A list of FakeResource objects faking the Port Forwardings
13931413
"""
13941414
port_forwardings = []
13951415
for i in range(0, count):
13961416
port_forwardings.append(
1397-
FakeFloatingIPPortForwarding.create_one_port_forwarding(attrs)
1417+
FakeFloatingIPPortForwarding.create_one_port_forwarding(
1418+
attrs, use_range=use_range)
13981419
)
13991420
return port_forwardings
14001421

14011422
@staticmethod
1402-
def get_port_forwardings(port_forwardings=None, count=2):
1423+
def get_port_forwardings(port_forwardings=None, count=2, use_range=False):
14031424
"""Get a list of faked Port Forwardings.
14041425
14051426
If port forwardings list is provided, then initialize the Mock object
@@ -1409,13 +1430,16 @@ def get_port_forwardings(port_forwardings=None, count=2):
14091430
A list of FakeResource objects faking port forwardings
14101431
:param int count:
14111432
The number of Port Forwardings to fake
1433+
:param Boolean use_range:
1434+
A boolean which defines if we will use ranges or not
14121435
:return:
14131436
An iterable Mock object with side_effect set to a list of faked
14141437
Port Forwardings
14151438
"""
14161439
if port_forwardings is None:
14171440
port_forwardings = (
1418-
FakeFloatingIPPortForwarding.create_port_forwardings(count)
1441+
FakeFloatingIPPortForwarding.create_port_forwardings(
1442+
count, use_range=use_range)
14191443
)
14201444

14211445
return mock.Mock(side_effect=port_forwardings)

0 commit comments

Comments
 (0)