From 07515cd1602841851d72dac30c7b12affee27bbe Mon Sep 17 00:00:00 2001
From: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
Date: Fri, 21 Mar 2025 06:26:36 +0000
Subject: [PATCH] [Neutron] Add "qos-policy" parameter to router creation
 command

This patch adds the parameter "qos-policy" to the router creation
command.

Closes-Bug: #2103774
Change-Id: I742b3273c5e9d3ec16e8018beddc8cdace8a57c6
---
 openstackclient/network/v2/router.py          | 12 ++++
 .../functional/network/v2/test_router.py      | 38 +++++++++++
 .../tests/unit/network/v2/test_router.py      | 63 +++++++++++++++++++
 ...eate-with-qos-policy-b94967a35351cddd.yaml |  7 +++
 4 files changed, 120 insertions(+)
 create mode 100644 releasenotes/notes/router-create-with-qos-policy-b94967a35351cddd.yaml

diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 0deae3600d..da35d367eb 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -581,6 +581,11 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
             help=argparse.SUPPRESS,
         )
         _parser_add_bfd_ecmp_arguments(parser)
+        parser.add_argument(
+            '--qos-policy',
+            metavar='<qos-policy>',
+            help=_('Attach QoS policy to router gateway IPs'),
+        )
 
         return parser
 
@@ -603,6 +608,13 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
             )
             raise exceptions.CommandError(msg)
 
+        if parsed_args.qos_policy and not parsed_args.external_gateways:
+            msg = _(
+                "You must specify '--external-gateway' in order "
+                "to define a QoS policy"
+            )
+            raise exceptions.CommandError(msg)
+
         if parsed_args.enable_ndp_proxy is not None:
             attrs['enable_ndp_proxy'] = parsed_args.enable_ndp_proxy
 
diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py
index 89d0652353..90708324b1 100644
--- a/openstackclient/tests/functional/network/v2/test_router.py
+++ b/openstackclient/tests/functional/network/v2/test_router.py
@@ -44,6 +44,44 @@ class RouterTests(common.NetworkTagTests):
         del_output = self.openstack('router delete ' + name1 + ' ' + name2)
         self.assertOutput('', del_output)
 
+    def test_router_create_with_external_gateway(self):
+        network_name = uuid.uuid4().hex
+        subnet_name = uuid.uuid4().hex
+        qos_policy = uuid.uuid4().hex
+        router_name = uuid.uuid4().hex
+
+        cmd_net = self.openstack(
+            f'network create --external {network_name}', parse_output=True
+        )
+        self.addCleanup(self.openstack, f'network delete {network_name}')
+        network_id = cmd_net['id']
+
+        self.openstack(
+            f'subnet create {subnet_name} '
+            f'--network {network_name} --subnet-range 10.0.0.0/24'
+        )
+
+        cmd_qos = self.openstack(
+            f'network qos policy create {qos_policy}', parse_output=True
+        )
+        self.addCleanup(
+            self.openstack, f'network qos policy delete {qos_policy}'
+        )
+        qos_id = cmd_qos['id']
+
+        self.openstack(
+            f'router create --external-gateway {network_name} '
+            f'--qos-policy {qos_policy} {router_name}'
+        )
+        self.addCleanup(self.openstack, f'router delete {router_name}')
+
+        cmd_output = self.openstack(
+            f'router show {router_name}', parse_output=True
+        )
+        gw_info = cmd_output['external_gateway_info']
+        self.assertEqual(network_id, gw_info['network_id'])
+        self.assertEqual(qos_id, gw_info['qos_policy_id'])
+
     def test_router_list(self):
         """Test create, list filter"""
         # Get project IDs
diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py
index aa6de7d2ef..70993639c2 100644
--- a/openstackclient/tests/unit/network/v2/test_router.py
+++ b/openstackclient/tests/unit/network/v2/test_router.py
@@ -552,6 +552,69 @@ class TestCreateRouter(TestRouter):
             parsed_args,
         )
 
+    def test_create_with_qos_policy(self):
+        _network = network_fakes.create_one_network()
+        self.network_client.find_network = mock.Mock(return_value=_network)
+        _qos_policy = (
+            network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
+        )
+        self.network_client.find_qos_policy = mock.Mock(
+            return_value=_qos_policy
+        )
+        arglist = [
+            self.new_router.name,
+            '--external-gateway',
+            _network.id,
+            '--qos-policy',
+            _qos_policy.id,
+        ]
+        verifylist = [
+            ('name', self.new_router.name),
+            ('enable', True),
+            ('distributed', False),
+            ('ha', False),
+            ('qos_policy', _qos_policy.id),
+            ('external_gateways', [_network.id]),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = self.cmd.take_action(parsed_args)
+        gw_info = {'network_id': _network.id, 'qos_policy_id': _qos_policy.id}
+        self.network_client.create_router.assert_called_once_with(
+            **{
+                'admin_state_up': True,
+                'name': self.new_router.name,
+                **{'external_gateway_info': gw_info},
+            }
+        )
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_create_with_qos_policy_no_external_gateway(self):
+        _qos_policy = (
+            network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
+        )
+        self.network_client.find_qos_policy = mock.Mock(
+            return_value=_qos_policy
+        )
+        arglist = [
+            self.new_router.name,
+            '--qos-policy',
+            _qos_policy.id,
+        ]
+        verifylist = [
+            ('name', self.new_router.name),
+            ('enable', True),
+            ('distributed', False),
+            ('ha', False),
+            ('qos_policy', _qos_policy.id),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args,
+        )
+
 
 class TestDeleteRouter(TestRouter):
     # The routers to delete.
diff --git a/releasenotes/notes/router-create-with-qos-policy-b94967a35351cddd.yaml b/releasenotes/notes/router-create-with-qos-policy-b94967a35351cddd.yaml
new file mode 100644
index 0000000000..67150ece9d
--- /dev/null
+++ b/releasenotes/notes/router-create-with-qos-policy-b94967a35351cddd.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    The router creation command now has the parameter ``--qos-policy``, that
+    allows to set a QoS policy for the provided external gateways (one or
+    many). It is mandatory to define an external gateway if the QoS policy is
+    set.