From 629eb33c4dcb73d44d1a4c6105e40d28f6cebdfc Mon Sep 17 00:00:00 2001
From: Stephen Finucane <stephenfin@redhat.com>
Date: Wed, 17 May 2023 12:27:45 +0100
Subject: [PATCH] volume: Add 'volume qos set --no-property' option

Supporting "--no-property" option will apply user a convenient way to
clean all properties of volume qos in a short command. The patch adds
"--no-property" option in "volume qos set" command and update related
test cases and docs.

Change-Id: I1fb5b4f0a923bbf557a3af3f63809bde9e84ffd4
---
 doc/source/cli/command-objects/volume-qos.rst |  7 ++++
 .../tests/functional/volume/v1/test_qos.py    | 16 ++++++---
 .../tests/functional/volume/v2/test_qos.py    | 16 ++++++---
 .../tests/functional/volume/v3/test_qos.py    | 16 ++++++---
 .../tests/unit/volume/v1/test_qos_specs.py    | 16 ++++++---
 .../tests/unit/volume/v2/test_qos_specs.py    | 15 ++++++---
 openstackclient/volume/v1/qos_specs.py        | 33 ++++++++++++++++++-
 openstackclient/volume/v2/qos_specs.py        | 33 ++++++++++++++++++-
 ...t-no-property-option-348480dfc42a0a64.yaml |  4 +++
 9 files changed, 131 insertions(+), 25 deletions(-)
 create mode 100644 releasenotes/notes/add-volume-qos-set-no-property-option-348480dfc42a0a64.yaml

diff --git a/doc/source/cli/command-objects/volume-qos.rst b/doc/source/cli/command-objects/volume-qos.rst
index 8fdbc12284..52c1f2d484 100644
--- a/doc/source/cli/command-objects/volume-qos.rst
+++ b/doc/source/cli/command-objects/volume-qos.rst
@@ -116,9 +116,16 @@ Set QoS specification properties
 .. code:: bash
 
     openstack volume qos set
+        [--no-property]
         [--property <key=value> [...] ]
         <qos-spec>
 
+.. option:: --no-property
+
+    Remove all properties from :ref:`\<snapshot\> <volume_qos_set-qos-spec>`
+    (specify both :option:`--no-property` and :option:`--property` to
+    remove the current properties before setting new properties.)
+
 .. option:: --property <key=value>
 
     Property to add or modify for this QoS specification (repeat option to set multiple properties)
diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py
index cabcd19e80..5d0aa41f67 100644
--- a/openstackclient/tests/functional/volume/v1/test_qos.py
+++ b/openstackclient/tests/functional/volume/v1/test_qos.py
@@ -52,8 +52,10 @@ class QosTests(common.BaseVolumeTests):
 
         name = uuid.uuid4().hex
         cmd_output = self.openstack(
-            'volume qos create ' + '--consumer front-end '
-            '--property Alpha=a ' + name,
+            'volume qos create '
+            + '--consumer front-end '
+            + '--property Alpha=a '
+            + name,
             parse_output=True,
         )
         self.addCleanup(self.openstack, 'volume qos delete ' + name)
@@ -64,8 +66,9 @@ class QosTests(common.BaseVolumeTests):
         # Test volume qos set
         raw_output = self.openstack(
             'volume qos set '
-            + '--property Alpha=c '
+            + '--no-property '
             + '--property Beta=b '
+            + '--property Charlie=c '
             + name,
         )
         self.assertOutput('', raw_output)
@@ -76,11 +79,14 @@ class QosTests(common.BaseVolumeTests):
             parse_output=True,
         )
         self.assertEqual(name, cmd_output['name'])
-        self.assertEqual({'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'])
+        self.assertEqual(
+            {'Beta': 'b', 'Charlie': 'c'},
+            cmd_output['properties'],
+        )
 
         # Test volume qos unset
         raw_output = self.openstack(
-            'volume qos unset ' + '--property Alpha ' + name,
+            'volume qos unset ' + '--property Charlie ' + name,
         )
         self.assertOutput('', raw_output)
 
diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py
index 4f07084ad7..fc4c52de2e 100644
--- a/openstackclient/tests/functional/volume/v2/test_qos.py
+++ b/openstackclient/tests/functional/volume/v2/test_qos.py
@@ -52,8 +52,10 @@ class QosTests(common.BaseVolumeTests):
 
         name = uuid.uuid4().hex
         cmd_output = self.openstack(
-            'volume qos create ' + '--consumer front-end '
-            '--property Alpha=a ' + name,
+            'volume qos create '
+            + '--consumer front-end '
+            + '--property Alpha=a '
+            + name,
             parse_output=True,
         )
         self.addCleanup(self.openstack, 'volume qos delete ' + name)
@@ -65,8 +67,9 @@ class QosTests(common.BaseVolumeTests):
         # Test volume qos set
         raw_output = self.openstack(
             'volume qos set '
-            + '--property Alpha=c '
+            + '--no-property '
             + '--property Beta=b '
+            + '--property Charlie=c '
             + name,
         )
         self.assertOutput('', raw_output)
@@ -77,11 +80,14 @@ class QosTests(common.BaseVolumeTests):
             parse_output=True,
         )
         self.assertEqual(name, cmd_output['name'])
-        self.assertEqual({'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'])
+        self.assertEqual(
+            {'Beta': 'b', 'Charlie': 'c'},
+            cmd_output['properties'],
+        )
 
         # Test volume qos unset
         raw_output = self.openstack(
-            'volume qos unset ' + '--property Alpha ' + name,
+            'volume qos unset ' + '--property Charlie ' + name,
         )
         self.assertOutput('', raw_output)
 
diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py
index 2a09e549b6..54dffbc0e7 100644
--- a/openstackclient/tests/functional/volume/v3/test_qos.py
+++ b/openstackclient/tests/functional/volume/v3/test_qos.py
@@ -52,8 +52,10 @@ class QosTests(common.BaseVolumeTests):
 
         name = uuid.uuid4().hex
         cmd_output = self.openstack(
-            'volume qos create ' + '--consumer front-end '
-            '--property Alpha=a ' + name,
+            'volume qos create '
+            + '--consumer front-end '
+            + '--property Alpha=a '
+            + name,
             parse_output=True,
         )
         self.addCleanup(self.openstack, 'volume qos delete ' + name)
@@ -65,8 +67,9 @@ class QosTests(common.BaseVolumeTests):
         # Test volume qos set
         raw_output = self.openstack(
             'volume qos set '
-            + '--property Alpha=c '
+            + '--no-property '
             + '--property Beta=b '
+            + '--property Charlie=c '
             + name,
         )
         self.assertOutput('', raw_output)
@@ -77,11 +80,14 @@ class QosTests(common.BaseVolumeTests):
             parse_output=True,
         )
         self.assertEqual(name, cmd_output['name'])
-        self.assertEqual({'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'])
+        self.assertEqual(
+            {'Beta': 'b', 'Charlie': 'c'},
+            cmd_output['properties'],
+        )
 
         # Test volume qos unset
         raw_output = self.openstack(
-            'volume qos unset ' + '--property Alpha ' + name,
+            'volume qos unset ' + '--property Charlie ' + name,
         )
         self.assertOutput('', raw_output)
 
diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
index 7e26e22374..cbfd1fa4ce 100644
--- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
@@ -366,22 +366,30 @@ class TestQosSet(TestQos):
 
     def test_qos_set_with_properties_with_id(self):
         arglist = [
+            '--no-property',
             '--property',
-            'foo=bar',
+            'a=b',
             '--property',
-            'iops=9001',
+            'c=d',
             self.qos_spec.id,
         ]
+        new_property = {"a": "b", "c": "d"}
         verifylist = [
-            ('property', self.qos_spec.specs),
+            ('no_property', True),
+            ('property', new_property),
             ('qos_spec', self.qos_spec.id),
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
         result = self.cmd.take_action(parsed_args)
 
+        self.qos_mock.unset_keys.assert_called_with(
+            self.qos_spec.id,
+            list(self.qos_spec.specs.keys()),
+        )
         self.qos_mock.set_keys.assert_called_with(
-            self.qos_spec.id, self.qos_spec.specs
+            self.qos_spec.id,
+            {"a": "b", "c": "d"},
         )
         self.assertIsNone(result)
 
diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
index 994bcf9646..54a13efcdf 100644
--- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
@@ -362,22 +362,29 @@ class TestQosSet(TestQos):
 
     def test_qos_set_with_properties_with_id(self):
         arglist = [
+            '--no-property',
             '--property',
-            'foo=bar',
+            'a=b',
             '--property',
-            'iops=9001',
+            'c=d',
             self.qos_spec.id,
         ]
+        new_property = {"a": "b", "c": "d"}
         verifylist = [
-            ('property', self.qos_spec.specs),
+            ('no_property', True),
+            ('property', new_property),
             ('qos_spec', self.qos_spec.id),
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
         result = self.cmd.take_action(parsed_args)
 
+        self.qos_mock.unset_keys.assert_called_with(
+            self.qos_spec.id,
+            list(self.qos_spec.specs.keys()),
+        )
         self.qos_mock.set_keys.assert_called_with(
-            self.qos_spec.id, self.qos_spec.specs
+            self.qos_spec.id, {"a": "b", "c": "d"}
         )
         self.assertIsNone(result)
 
diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py
index 87e32ff45d..830d5d8276 100644
--- a/openstackclient/volume/v1/qos_specs.py
+++ b/openstackclient/volume/v1/qos_specs.py
@@ -255,6 +255,16 @@ class SetQos(command.Command):
             metavar='<qos-spec>',
             help=_('QoS specification to modify (name or ID)'),
         )
+        parser.add_argument(
+            '--no-property',
+            dest='no_property',
+            action='store_true',
+            help=_(
+                'Remove all properties from <qos-spec> '
+                '(specify both --no-property and --property to remove the '
+                'current properties before setting new properties)'
+            ),
+        )
         parser.add_argument(
             '--property',
             metavar='<key=value>',
@@ -272,8 +282,29 @@ class SetQos(command.Command):
             volume_client.qos_specs, parsed_args.qos_spec
         )
 
+        result = 0
+        if parsed_args.no_property:
+            try:
+                key_list = list(qos_spec._info['specs'].keys())
+                volume_client.qos_specs.unset_keys(qos_spec.id, key_list)
+            except Exception as e:
+                LOG.error(_("Failed to clean qos properties: %s"), e)
+                result += 1
+
         if parsed_args.property:
-            volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property)
+            try:
+                volume_client.qos_specs.set_keys(
+                    qos_spec.id,
+                    parsed_args.property,
+                )
+            except Exception as e:
+                LOG.error(_("Failed to set qos property: %s"), e)
+                result += 1
+
+        if result > 0:
+            raise exceptions.CommandError(
+                _("One or more of the set operations failed")
+            )
 
 
 class ShowQos(command.ShowOne):
diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py
index 2c06ee3418..0454ecae84 100644
--- a/openstackclient/volume/v2/qos_specs.py
+++ b/openstackclient/volume/v2/qos_specs.py
@@ -257,6 +257,16 @@ class SetQos(command.Command):
             metavar='<qos-spec>',
             help=_('QoS specification to modify (name or ID)'),
         )
+        parser.add_argument(
+            '--no-property',
+            dest='no_property',
+            action='store_true',
+            help=_(
+                'Remove all properties from <qos-spec> '
+                '(specify both --no-property and --property to remove the '
+                'current properties before setting new properties)'
+            ),
+        )
         parser.add_argument(
             '--property',
             metavar='<key=value>',
@@ -274,8 +284,29 @@ class SetQos(command.Command):
             volume_client.qos_specs, parsed_args.qos_spec
         )
 
+        result = 0
+        if parsed_args.no_property:
+            try:
+                key_list = list(qos_spec._info['specs'].keys())
+                volume_client.qos_specs.unset_keys(qos_spec.id, key_list)
+            except Exception as e:
+                LOG.error(_("Failed to clean qos properties: %s"), e)
+                result += 1
+
         if parsed_args.property:
-            volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property)
+            try:
+                volume_client.qos_specs.set_keys(
+                    qos_spec.id,
+                    parsed_args.property,
+                )
+            except Exception as e:
+                LOG.error(_("Failed to set qos property: %s"), e)
+                result += 1
+
+        if result > 0:
+            raise exceptions.CommandError(
+                _("One or more of the set operations failed")
+            )
 
 
 class ShowQos(command.ShowOne):
diff --git a/releasenotes/notes/add-volume-qos-set-no-property-option-348480dfc42a0a64.yaml b/releasenotes/notes/add-volume-qos-set-no-property-option-348480dfc42a0a64.yaml
new file mode 100644
index 0000000000..51b2145068
--- /dev/null
+++ b/releasenotes/notes/add-volume-qos-set-no-property-option-348480dfc42a0a64.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add ``--no-property`` option in ``volume qos set``.