Skip to content

Commit 06f5b07

Browse files
committed
Make the "port forwarding create" command idempotent
The `port forwarding create` command would exit with an error if you attempted to create a forwarding configuration that already exists. This made it difficult to identify actual failures. This commit updates the `port forwarding create` command so that it is not an error if the requested forwarding already exists. The old behavior was: $ openstack esi port forwarding create 192.168.11.31 128.31.20.118 -p 8888 +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | ID | Internal Port | External Port | Protocol | Internal IP | External IP | +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | 43c96be3-861d-4818-a32a-079a93b6f9c4 | 8888 | 8888 | tcp | 192.168.11.31 | 128.31.20.118 | +--------------------------------------+---------------+---------------+----------+---------------+---------------+ $ openstack esi port forwarding create esi-MOC-R4PAC24U35-S1B-provisioning 128.31.20.118 -p 8888 -p 8889 BadRequestException: 400: Client Error for url: https://esi.massopen.cloud:13696/v2.0/floatingips/be4b2718-ec5f-4722-80b5-485c421e4a72/port_forwardings, Bad port_forwarding request: A duplicate port forwarding entry with same attributes already exists, conflicting values are {'floatingip_id': 'be4b2718-ec5f-4722-80b5-485c421e4a72', 'external_port': 8888, 'protocol': 'tcp'}. The new behavior looks like this: $ openstack esi port forwarding create 192.168.11.31 128.31.20.118 -p 8888 +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | ID | Internal Port | External Port | Protocol | Internal IP | External IP | +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | 43c96be3-861d-4818-a32a-079a93b6f9c4 | 8888 | 8888 | tcp | 192.168.11.31 | 128.31.20.118 | +--------------------------------------+---------------+---------------+----------+---------------+---------------+ $ openstack esi port forwarding create esi-MOC-R4PAC24U35-S1B-provisioning 128.31.20.118 -p 8888 -p 8889 +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | ID | Internal Port | External Port | Protocol | Internal IP | External IP | +--------------------------------------+---------------+---------------+----------+---------------+---------------+ | exists | 8888 | 8888 | tcp | 192.168.11.31 | 128.31.20.118 | | d7d56201-232a-41fe-8ae6-1f6fb88a37d3 | 8889 | 8889 | tcp | 192.168.11.31 | 128.31.20.118 | +--------------------------------------+---------------+---------------+----------+---------------+---------------+
1 parent 775476e commit 06f5b07

File tree

1 file changed

+41
-15
lines changed

1 file changed

+41
-15
lines changed

esiclient/v1/port_forwarding.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# pyright: basic
2+
13
# Licensed under the Apache License, Version 2.0 (the "License"); you may
24
# not use this file except in compliance with the License. You may obtain
35
# a copy of the License at
@@ -34,6 +36,15 @@ class Protocol(str, Enum):
3436
UDP = "udp"
3537

3638

39+
@dataclass
40+
class FwdSpec:
41+
id: str
42+
internal_port: str
43+
external_port: str
44+
protocol: str
45+
internal_ip_address: str
46+
47+
3748
@dataclass
3849
class PortSpec:
3950
"""Represent a port forwarding from an external port to an internal port"""
@@ -73,7 +84,6 @@ def from_spec(cls, spec: str):
7384

7485

7586
def PortSpecArg(v):
76-
"""this is a test"""
7787
try:
7888
return PortSpec.from_spec(v)
7989
except ValueError as err:
@@ -233,6 +243,18 @@ def find_or_create_port(
233243
)
234244

235245

246+
def port_forwarding_exists(fip, internal_ip_address, port):
247+
for check in fip.port_forwardings:
248+
fwd = FwdSpec(id="exists", **check)
249+
if (
250+
port.int_port == fwd.internal_port
251+
and port.ext_port == fwd.external_port
252+
and internal_ip_address == fwd.internal_ip_address
253+
and port.protocol == fwd.protocol
254+
):
255+
return fwd
256+
257+
236258
def format_forwards(func):
237259
"""A decorator that transforms a list of (floating_ip, port_forwarding) tuples
238260
into a list suitable for a cliff command.Lister"""
@@ -329,21 +351,25 @@ def take_action(self, parsed_args: argparse.Namespace):
329351
# rather than the port.
330352
internal_ip_address = internal_port.fixed_ips[0]["ip_address"]
331353

354+
# [{'external_port': 8888, 'protocol': 'tcp', 'internal_ip_address': '192.168.11.31', 'internal_port': 8888}]
332355
for port in parsed_args.port:
333-
fwd = self.app.client_manager.sdk_connection.network.create_floating_ip_port_forwarding(
334-
fip,
335-
internal_ip_address=internal_ip_address,
336-
internal_port=port.int_port,
337-
internal_port_id=internal_port.id,
338-
external_port=port.ext_port,
339-
protocol=port.protocol,
340-
**(
341-
{"description": parsed_args.description}
342-
if parsed_args.description
343-
else {}
344-
),
345-
)
346-
forwards.append((fip, fwd))
356+
if fwd := port_forwarding_exists(fip, internal_ip_address, port):
357+
forwards.append((fip, fwd))
358+
else:
359+
fwd = self.app.client_manager.sdk_connection.network.create_floating_ip_port_forwarding(
360+
fip,
361+
internal_ip_address=internal_ip_address,
362+
internal_port=port.int_port,
363+
internal_port_id=internal_port.id,
364+
external_port=port.ext_port,
365+
protocol=port.protocol,
366+
**(
367+
{"description": parsed_args.description}
368+
if parsed_args.description
369+
else {}
370+
),
371+
)
372+
forwards.append((fip, fwd))
347373

348374
return forwards
349375

0 commit comments

Comments
 (0)