diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index b33633d346..cd71c05a9e 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -25,6 +25,61 @@ from openstackclient.network import common LOG = logging.getLogger(__name__) +def validate_ports_diff(ports): + if len(ports) == 0: + return 0 + + ports_diff = ports[-1] - ports[0] + if ports_diff < 0: + msg = _("The last number in port range must be" + " greater or equal to the first") + raise exceptions.CommandError(msg) + return ports_diff + + +def validate_ports_match(internal_ports, external_ports): + internal_ports_diff = validate_ports_diff(internal_ports) + external_ports_diff = validate_ports_diff(external_ports) + + if internal_ports_diff != 0 and internal_ports_diff != external_ports_diff: + msg = _("The relation between internal and external ports does not " + "match the pattern 1:N and N:N") + raise exceptions.CommandError(msg) + + +def validate_and_assign_port_ranges(parsed_args, attrs): + internal_port_range = parsed_args.internal_protocol_port + external_port_range = parsed_args.external_protocol_port + external_ports = internal_ports = [] + if external_port_range: + external_ports = list(map(int, str(external_port_range).split(':'))) + if internal_port_range: + internal_ports = list(map(int, str(internal_port_range).split(':'))) + + validate_ports_match(internal_ports, external_ports) + + for port in external_ports + internal_ports: + validate_port(port) + + if internal_port_range: + if ':' in internal_port_range: + attrs['internal_port_range'] = internal_port_range + else: + attrs['internal_port'] = int(internal_port_range) + + if external_port_range: + if ':' in external_port_range: + attrs['external_port_range'] = external_port_range + else: + attrs['external_port'] = int(external_port_range) + + +def validate_port(port): + if port <= 0 or port > 65535: + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + + def _get_columns(item): column_map = {} hidden_columns = ['location'] @@ -58,7 +113,6 @@ class CreateFloatingIPPortForwarding(command.ShowOne, ) parser.add_argument( '--internal-protocol-port', - type=int, metavar='', required=True, help=_("The protocol port number " @@ -67,7 +121,6 @@ class CreateFloatingIPPortForwarding(command.ShowOne, ) parser.add_argument( '--external-protocol-port', - type=int, metavar='', required=True, help=_("The protocol port number of " @@ -92,6 +145,7 @@ class CreateFloatingIPPortForwarding(command.ShowOne, help=_("Floating IP that the port forwarding belongs to " "(IP address or ID)") ) + return parser def take_action(self, parsed_args): @@ -102,19 +156,7 @@ class CreateFloatingIPPortForwarding(command.ShowOne, ignore_missing=False, ) - if parsed_args.internal_protocol_port is not None: - if (parsed_args.internal_protocol_port <= 0 or - parsed_args.internal_protocol_port > 65535): - msg = _("The port number range is <1-65535>") - raise exceptions.CommandError(msg) - attrs['internal_port'] = parsed_args.internal_protocol_port - - if parsed_args.external_protocol_port is not None: - if (parsed_args.external_protocol_port <= 0 or - parsed_args.external_protocol_port > 65535): - msg = _("The port number range is <1-65535>") - raise exceptions.CommandError(msg) - attrs['external_port'] = parsed_args.external_protocol_port + validate_and_assign_port_ranges(parsed_args, attrs) if parsed_args.port: port = client.find_port(parsed_args.port, @@ -226,7 +268,9 @@ class ListFloatingIPPortForwarding(command.Lister): 'internal_port_id', 'internal_ip_address', 'internal_port', + 'internal_port_range', 'external_port', + 'external_port_range', 'protocol', 'description', ) @@ -235,7 +279,9 @@ class ListFloatingIPPortForwarding(command.Lister): 'Internal Port ID', 'Internal IP Address', 'Internal Port', + 'Internal Port Range', 'External Port', + 'External Port Range', 'Protocol', 'Description', ) @@ -246,8 +292,13 @@ class ListFloatingIPPortForwarding(command.Lister): port = client.find_port(parsed_args.port, ignore_missing=False) query['internal_port_id'] = port.id - if parsed_args.external_protocol_port is not None: - query['external_port'] = parsed_args.external_protocol_port + external_port = parsed_args.external_protocol_port + if external_port: + if ':' in external_port: + query['external_port_range'] = external_port + else: + query['external_port'] = int( + parsed_args.external_protocol_port) if parsed_args.protocol is not None: query['protocol'] = parsed_args.protocol @@ -297,14 +348,12 @@ class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs): parser.add_argument( '--internal-protocol-port', metavar='', - type=int, help=_("The TCP/UDP/other protocol port number of the " "network port fixed IPv4 address associated to " "the floating IP port forwarding") ) parser.add_argument( '--external-protocol-port', - type=int, metavar='', help=_("The TCP/UDP/other protocol port number of the " "port forwarding's floating IP address") @@ -339,19 +388,8 @@ class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs): if parsed_args.internal_ip_address: attrs['internal_ip_address'] = parsed_args.internal_ip_address - if parsed_args.internal_protocol_port is not None: - if (parsed_args.internal_protocol_port <= 0 or - parsed_args.internal_protocol_port > 65535): - msg = _("The port number range is <1-65535>") - raise exceptions.CommandError(msg) - attrs['internal_port'] = parsed_args.internal_protocol_port - if parsed_args.external_protocol_port is not None: - if (parsed_args.external_protocol_port <= 0 or - parsed_args.external_protocol_port > 65535): - msg = _("The port number range is <1-65535>") - raise exceptions.CommandError(msg) - attrs['external_port'] = parsed_args.external_protocol_port + validate_and_assign_port_ranges(parsed_args, attrs) if parsed_args.protocol: attrs['protocol'] = parsed_args.protocol diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 912f451fea..bb113d3c95 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1346,11 +1346,13 @@ class FakeFloatingIPPortForwarding(object): """"Fake one or more Port forwarding""" @staticmethod - def create_one_port_forwarding(attrs=None): + def create_one_port_forwarding(attrs=None, use_range=False): """Create a fake Port Forwarding. :param Dictionary attrs: A dictionary with all attributes + :param Boolean use_range: + A boolean which defines if we will use ranges or not :return: A FakeResource object with name, id, etc. """ @@ -1364,13 +1366,29 @@ class FakeFloatingIPPortForwarding(object): 'floatingip_id': floatingip_id, 'internal_port_id': 'internal-port-id-' + uuid.uuid4().hex, 'internal_ip_address': '192.168.1.2', - 'internal_port': randint(1, 65535), - 'external_port': randint(1, 65535), 'protocol': 'tcp', 'description': 'some description', 'location': 'MUNCHMUNCHMUNCH', } + if use_range: + port_range = randint(0, 100) + internal_start = randint(1, 65535 - port_range) + internal_end = internal_start + port_range + internal_range = ':'.join(map(str, [internal_start, internal_end])) + external_start = randint(1, 65535 - port_range) + external_end = external_start + port_range + external_range = ':'.join(map(str, [external_start, external_end])) + port_forwarding_attrs['internal_port_range'] = internal_range + port_forwarding_attrs['external_port_range'] = external_range + port_forwarding_attrs['internal_port'] = None + port_forwarding_attrs['external_port'] = None + else: + port_forwarding_attrs['internal_port'] = randint(1, 65535) + port_forwarding_attrs['external_port'] = randint(1, 65535) + port_forwarding_attrs['internal_port_range'] = '' + port_forwarding_attrs['external_port_range'] = '' + # Overwrite default attributes. port_forwarding_attrs.update(attrs) @@ -1381,25 +1399,28 @@ class FakeFloatingIPPortForwarding(object): return port_forwarding @staticmethod - def create_port_forwardings(attrs=None, count=2): + def create_port_forwardings(attrs=None, count=2, use_range=False): """Create multiple fake Port Forwarding. :param Dictionary attrs: A dictionary with all attributes :param int count: The number of Port Forwarding rule to fake + :param Boolean use_range: + A boolean which defines if we will use ranges or not :return: A list of FakeResource objects faking the Port Forwardings """ port_forwardings = [] for i in range(0, count): port_forwardings.append( - FakeFloatingIPPortForwarding.create_one_port_forwarding(attrs) + FakeFloatingIPPortForwarding.create_one_port_forwarding( + attrs, use_range=use_range) ) return port_forwardings @staticmethod - def get_port_forwardings(port_forwardings=None, count=2): + def get_port_forwardings(port_forwardings=None, count=2, use_range=False): """Get a list of faked Port Forwardings. If port forwardings list is provided, then initialize the Mock object @@ -1409,13 +1430,16 @@ class FakeFloatingIPPortForwarding(object): A list of FakeResource objects faking port forwardings :param int count: The number of Port Forwardings to fake + :param Boolean use_range: + A boolean which defines if we will use ranges or not :return: An iterable Mock object with side_effect set to a list of faked Port Forwardings """ if port_forwardings is None: port_forwardings = ( - FakeFloatingIPPortForwarding.create_port_forwardings(count) + FakeFloatingIPPortForwarding.create_port_forwardings( + count, use_range=use_range) ) return mock.Mock(side_effect=port_forwardings) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index 7b9e3aa6e7..4c82fd17b7 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -49,6 +49,18 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): } ) ) + + self.new_port_forwarding_with_ranges = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + use_range=True, + attrs={ + 'internal_port_id': self.port.id, + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.create_floating_ip_port_forwarding = mock.Mock( return_value=self.new_port_forwarding) @@ -63,22 +75,26 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): self.columns = ( 'description', 'external_port', + 'external_port_range', 'floatingip_id', 'id', 'internal_ip_address', 'internal_port', 'internal_port_id', + 'internal_port_range', 'protocol' ) self.data = ( self.new_port_forwarding.description, self.new_port_forwarding.external_port, + self.new_port_forwarding.external_port_range, self.new_port_forwarding.floatingip_id, self.new_port_forwarding.id, self.new_port_forwarding.internal_ip_address, self.new_port_forwarding.internal_port, self.new_port_forwarding.internal_port_id, + self.new_port_forwarding.internal_port_range, self.new_port_forwarding.protocol, ) @@ -90,6 +106,160 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_create_all_options_with_range(self): + arglist = [ + '--port', self.new_port_forwarding_with_ranges.internal_port_id, + '--internal-protocol-port', + self.new_port_forwarding_with_ranges.internal_port_range, + '--external-protocol-port', + self.new_port_forwarding_with_ranges.external_port_range, + '--protocol', self.new_port_forwarding_with_ranges.protocol, + self.new_port_forwarding_with_ranges.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding_with_ranges.internal_ip_address, + '--description', + self.new_port_forwarding_with_ranges.description, + ] + verifylist = [ + ('port', self.new_port_forwarding_with_ranges.internal_port_id), + ('internal_protocol_port', + self.new_port_forwarding_with_ranges.internal_port_range), + ('external_protocol_port', + self.new_port_forwarding_with_ranges.external_port_range), + ('protocol', self.new_port_forwarding_with_ranges.protocol), + ('floating_ip', + self.new_port_forwarding_with_ranges.floatingip_id), + ('internal_ip_address', self.new_port_forwarding_with_ranges. + internal_ip_address), + ('description', self.new_port_forwarding_with_ranges.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_floating_ip_port_forwarding.\ + assert_called_once_with( + self.new_port_forwarding.floatingip_id, + **{ + 'external_port_range': + self.new_port_forwarding_with_ranges. + external_port_range, + 'internal_ip_address': + self.new_port_forwarding_with_ranges. + internal_ip_address, + 'internal_port_range': + self.new_port_forwarding_with_ranges. + internal_port_range, + 'internal_port_id': + self.new_port_forwarding_with_ranges.internal_port_id, + 'protocol': self.new_port_forwarding_with_ranges.protocol, + 'description': + self.new_port_forwarding_with_ranges.description, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options_with_range_invalid_port_exception(self): + invalid_port_range = '999999:999999' + arglist = [ + '--port', self.new_port_forwarding_with_ranges.internal_port_id, + '--internal-protocol-port', invalid_port_range, + '--external-protocol-port', invalid_port_range, + '--protocol', self.new_port_forwarding_with_ranges.protocol, + self.new_port_forwarding_with_ranges.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding_with_ranges.internal_ip_address, + '--description', + self.new_port_forwarding_with_ranges.description, + ] + verifylist = [ + ('port', self.new_port_forwarding_with_ranges.internal_port_id), + ('internal_protocol_port', invalid_port_range), + ('external_protocol_port', invalid_port_range), + ('protocol', self.new_port_forwarding_with_ranges.protocol), + ('floating_ip', + self.new_port_forwarding_with_ranges.floatingip_id), + ('internal_ip_address', self.new_port_forwarding_with_ranges. + internal_ip_address), + ('description', self.new_port_forwarding_with_ranges.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + msg = 'The port number range is <1-65535>' + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual(msg, str(e)) + self.network.create_floating_ip_port_forwarding.assert_not_called() + + def test_create_all_options_with_invalid_range_exception(self): + invalid_port_range = '80:70' + arglist = [ + '--port', self.new_port_forwarding_with_ranges.internal_port_id, + '--internal-protocol-port', invalid_port_range, + '--external-protocol-port', invalid_port_range, + '--protocol', self.new_port_forwarding_with_ranges.protocol, + self.new_port_forwarding_with_ranges.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding_with_ranges.internal_ip_address, + '--description', + self.new_port_forwarding_with_ranges.description, + ] + verifylist = [ + ('port', self.new_port_forwarding_with_ranges.internal_port_id), + ('internal_protocol_port', invalid_port_range), + ('external_protocol_port', invalid_port_range), + ('protocol', self.new_port_forwarding_with_ranges.protocol), + ('floating_ip', + self.new_port_forwarding_with_ranges.floatingip_id), + ('internal_ip_address', self.new_port_forwarding_with_ranges. + internal_ip_address), + ('description', self.new_port_forwarding_with_ranges.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + msg = 'The last number in port range must be greater or equal to ' \ + 'the first' + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual(msg, str(e)) + self.network.create_floating_ip_port_forwarding.assert_not_called() + + def test_create_all_options_with_unmatch_ranges_exception(self): + internal_range = '80:90' + external_range = '8080:8100' + arglist = [ + '--port', self.new_port_forwarding_with_ranges.internal_port_id, + '--internal-protocol-port', internal_range, + '--external-protocol-port', external_range, + '--protocol', self.new_port_forwarding_with_ranges.protocol, + self.new_port_forwarding_with_ranges.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding_with_ranges.internal_ip_address, + '--description', + self.new_port_forwarding_with_ranges.description, + ] + verifylist = [ + ('port', self.new_port_forwarding_with_ranges.internal_port_id), + ('internal_protocol_port', internal_range), + ('external_protocol_port', external_range), + ('protocol', self.new_port_forwarding_with_ranges.protocol), + ('floating_ip', + self.new_port_forwarding_with_ranges.floatingip_id), + ('internal_ip_address', self.new_port_forwarding_with_ranges. + internal_ip_address), + ('description', self.new_port_forwarding_with_ranges.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + msg = "The relation between internal and external ports does not " \ + "match the pattern 1:N and N:N" + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual(msg, str(e)) + self.network.create_floating_ip_port_forwarding.assert_not_called() + def test_create_all_options(self): arglist = [ '--port', self.new_port_forwarding.internal_port_id, @@ -106,8 +276,10 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): ] verifylist = [ ('port', self.new_port_forwarding.internal_port_id), - ('internal_protocol_port', self.new_port_forwarding.internal_port), - ('external_protocol_port', self.new_port_forwarding.external_port), + ('internal_protocol_port', + str(self.new_port_forwarding.internal_port)), + ('external_protocol_port', + str(self.new_port_forwarding.external_port)), ('protocol', self.new_port_forwarding.protocol), ('floating_ip', self.new_port_forwarding.floatingip_id), ('internal_ip_address', self.new_port_forwarding. @@ -253,7 +425,9 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): 'Internal Port ID', 'Internal IP Address', 'Internal Port', + 'Internal Port Range', 'External Port', + 'External Port Range', 'Protocol', 'Description', ) @@ -275,7 +449,9 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): port_forwarding.internal_port_id, port_forwarding.internal_ip_address, port_forwarding.internal_port, + port_forwarding.internal_port_range, port_forwarding.external_port, + port_forwarding.external_port_range, port_forwarding.protocol, port_forwarding.description, )) @@ -330,7 +506,7 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): query = { 'internal_port_id': self.port_forwardings[0].internal_port_id, - 'external_port': str(self.port_forwardings[0].external_port), + 'external_port': self.port_forwardings[0].external_port, 'protocol': self.port_forwardings[0].protocol, } @@ -392,7 +568,7 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): self.assertIsNone(result) def test_set_all_thing(self): - arglist = [ + arglist_single = [ '--port', self.port.id, '--internal-ip-address', 'new_internal_ip_address', '--internal-protocol-port', '100', @@ -402,21 +578,23 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): self._port_forwarding.floatingip_id, self._port_forwarding.id, ] - verifylist = [ + arglist_range = list(arglist_single) + arglist_range[5] = '100:110' + arglist_range[7] = '200:210' + verifylist_single = [ ('port', self.port.id), ('internal_ip_address', 'new_internal_ip_address'), - ('internal_protocol_port', 100), - ('external_protocol_port', 200), + ('internal_protocol_port', '100'), + ('external_protocol_port', '200'), ('protocol', 'tcp'), ('description', 'some description'), ('floating_ip', self._port_forwarding.floatingip_id), ('port_forwarding_id', self._port_forwarding.id), ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - attrs = { + verifylist_range = list(verifylist_single) + verifylist_range[2] = ('internal_protocol_port', '100:110') + verifylist_range[3] = ('external_protocol_port', '200:210') + attrs_single = { 'internal_port_id': self.port.id, 'internal_ip_address': 'new_internal_ip_address', 'internal_port': 100, @@ -424,12 +602,25 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): 'protocol': 'tcp', 'description': 'some description', } - self.network.update_floating_ip_port_forwarding.assert_called_with( - self._port_forwarding.floatingip_id, - self._port_forwarding.id, - **attrs - ) - self.assertIsNone(result) + attrs_range = dict(attrs_single, internal_port_range='100:110', + external_port_range='200:210') + attrs_range.pop('internal_port') + attrs_range.pop('external_port') + + def run_and_validate(arglist, verifylist, attrs): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.update_floating_ip_port_forwarding.assert_called_with( + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + **attrs + ) + self.assertIsNone(result) + + run_and_validate(arglist_single, verifylist_single, attrs_single) + run_and_validate(arglist_range, verifylist_range, attrs_range) class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): @@ -438,11 +629,13 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): columns = ( 'description', 'external_port', + 'external_port_range', 'floatingip_id', 'id', 'internal_ip_address', 'internal_port', 'internal_port_id', + 'internal_port_range', 'protocol', ) @@ -459,11 +652,13 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): self.data = ( self._port_forwarding.description, self._port_forwarding.external_port, + self._port_forwarding.external_port_range, self._port_forwarding.floatingip_id, self._port_forwarding.id, self._port_forwarding.internal_ip_address, self._port_forwarding.internal_port, self._port_forwarding.internal_port_id, + self._port_forwarding.internal_port_range, self._port_forwarding.protocol, ) self.network.find_floating_ip_port_forwarding = mock.Mock( diff --git a/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml b/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml new file mode 100644 index 0000000000..80e4445e32 --- /dev/null +++ b/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add port ranges support to the ``floating ip port forwarding`` commands.