From 4cbcd02a5787f402a95c84da613d3f8cd7d09312 Mon Sep 17 00:00:00 2001
From: Rui Chen <chenrui.momo@gmail.com>
Date: Mon, 21 Mar 2016 16:29:14 +0800
Subject: [PATCH] Add "aggregate unset" to osc

Support "aggregate unset" command in order to
remove the property of aggregate object in OSC.

Change-Id: I49645135586362f0fd251f5e4a4c03eff273d9e9
Closes-Bug: #1559866
---
 doc/source/command-objects/aggregate.rst      | 23 +++++++
 openstackclient/compute/v2/aggregate.py       | 31 +++++++++
 openstackclient/tests/compute/v2/fakes.py     | 32 +++++++++
 .../tests/compute/v2/test_aggregate.py        | 66 +++++++++++++++++++
 .../notes/bug-1559866-733988f5dd5b07bb.yaml   |  4 ++
 setup.cfg                                     |  1 +
 6 files changed, 157 insertions(+)
 create mode 100644 openstackclient/tests/compute/v2/test_aggregate.py
 create mode 100644 releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml

diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst
index 6a1dd9089e..be403179ea 100644
--- a/doc/source/command-objects/aggregate.rst
+++ b/doc/source/command-objects/aggregate.rst
@@ -150,3 +150,26 @@ Display aggregate details
 .. describe:: <aggregate>
 
     Aggregate to display (name or ID)
+
+aggregate unset
+---------------
+
+Unset aggregate properties
+
+.. program:: aggregate unset
+.. code-block:: bash
+
+    os aggregate unset
+        --property <key>
+        [--property <key>] ...
+        <aggregate>
+
+.. option:: --property <key>
+
+    Property to remove from :ref:`\<aggregate\> <aggregate_unset-aggregate>`
+    (repeat option to remove multiple properties)
+
+.. _aggregate_unset-aggregate:
+.. describe:: <aggregate>
+
+    Aggregate to modify (name or ID)
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index e47c13a7b2..1a02a3886d 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -290,3 +290,34 @@ class ShowAggregate(command.ShowOne):
         info = {}
         info.update(data._info)
         return zip(*sorted(six.iteritems(info)))
+
+
+class UnsetAggregate(command.Command):
+    """Unset aggregate properties"""
+
+    def get_parser(self, prog_name):
+        parser = super(UnsetAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            "aggregate",
+            metavar="<aggregate>",
+            help="Aggregate to modify (name or ID)",
+        )
+        parser.add_argument(
+            "--property",
+            metavar="<key>",
+            action='append',
+            help='Property to remove from aggregate '
+                 '(repeat option to remove multiple properties)',
+            required=True,
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        compute_client = self.app.client_manager.compute
+        aggregate = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate)
+
+        unset_property = {key: None for key in parsed_args.property}
+        compute_client.aggregates.set_metadata(aggregate,
+                                               unset_property)
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index 809825d0a5..2bb0fbfc9b 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -88,6 +88,38 @@ SERVICE = {
 }
 
 
+class FakeAggregate(object):
+    """Fake one aggregate."""
+
+    @staticmethod
+    def create_one_aggregate(attrs=None):
+        """Create a fake aggregate.
+
+        :param Dictionary attrs:
+            A dictionary with all attributes
+        :return:
+            A FakeResource object, with id and other attributes
+        """
+        if attrs is None:
+            attrs = {}
+
+        # Set default attribute
+        aggregate_info = {
+            "name": "aggregate-name-" + uuid.uuid4().hex,
+            "availability_zone": "ag_zone",
+            "hosts": [],
+            "id": "aggregate-id-" + uuid.uuid4().hex,
+            "metadata": {
+                "availability_zone": "ag_zone",
+            }
+        }
+        aggregate_info.update(attrs)
+        aggregate = fakes.FakeResource(
+            info=copy.deepcopy(aggregate_info),
+            loaded=True)
+        return aggregate
+
+
 class FakeComputev2Client(object):
 
     def __init__(self, **kwargs):
diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py
new file mode 100644
index 0000000000..1e8732110f
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_aggregate.py
@@ -0,0 +1,66 @@
+#   Copyright 2016 Huawei, Inc. All rights reserved.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+from openstackclient.compute.v2 import aggregate
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import utils as tests_utils
+
+
+class TestAggregate(compute_fakes.TestComputev2):
+
+    def setUp(self):
+        super(TestAggregate, self).setUp()
+
+        # Get a shortcut to the AggregateManager Mock
+        self.aggregate_mock = self.app.client_manager.compute.aggregates
+        self.aggregate_mock.reset_mock()
+
+
+class TestAggregateUnset(TestAggregate):
+
+    fake_ag = compute_fakes.FakeAggregate.create_one_aggregate()
+
+    def setUp(self):
+        super(TestAggregateUnset, self).setUp()
+
+        self.aggregate_mock.get.return_value = self.fake_ag
+        self.cmd = aggregate.UnsetAggregate(self.app, None)
+
+    def test_aggregate_unset(self):
+        arglist = [
+            '--property', 'unset_key',
+            'ag1'
+        ]
+        verifylist = [
+            ('property', ['unset_key']),
+            ('aggregate', 'ag1')
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        result = self.cmd.take_action(parsed_args)
+        self.aggregate_mock.set_metadata.assert_called_once_with(
+            self.fake_ag, {'unset_key': None})
+        self.assertIsNone(result)
+
+    def test_aggregate_unset_no_property(self):
+        arglist = [
+            'ag1'
+        ]
+        verifylist = None
+        self.assertRaises(tests_utils.ParserException,
+                          self.check_parser,
+                          self.cmd,
+                          arglist,
+                          verifylist)
diff --git a/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml
new file mode 100644
index 0000000000..26bd9a0334
--- /dev/null
+++ b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - Add ``aggregate unset`` command for compute v2
+    [Bug `1559866 <https://bugs.launchpad.net/python-openstackclient/+bug/1559866>`_]
diff --git a/setup.cfg b/setup.cfg
index fcfd4e47bf..b3e09380ac 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -63,6 +63,7 @@ openstack.compute.v2 =
     aggregate_remove_host = openstackclient.compute.v2.aggregate:RemoveAggregateHost
     aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate
     aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate
+    aggregate_unset = openstackclient.compute.v2.aggregate:UnsetAggregate
 
     compute_service_delete = openstackclient.compute.v2.service:DeleteService
     compute_service_list = openstackclient.compute.v2.service:ListService