From 230d38fb4ca0235706a9cd7617628b84413075b7 Mon Sep 17 00:00:00 2001
From: reedip <reedip.banerjee@nectechnologies.in>
Date: Thu, 7 Apr 2016 15:22:11 +0900
Subject: [PATCH] Add command to unset information from ports

This patch introduces the ``port unset`` command to
clear the fixed-ip and binding:profile information
from the ports.
Implements: blueprint network-property-unset

Change-Id: I9dba309234105af477e7618a8a437b7fa3b13cd7
---
 doc/source/command-objects/port.rst           | 29 +++++++
 openstackclient/network/v2/port.py            | 59 ++++++++++++++
 openstackclient/tests/network/v2/test_port.py | 78 +++++++++++++++++++
 ...d-port-unset-command-8bdaf1fa9c593374.yaml |  6 ++
 setup.cfg                                     |  1 +
 5 files changed, 173 insertions(+)
 create mode 100644 releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml

diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst
index 9d93ee62c4..4d7b95b4e3 100644
--- a/doc/source/command-objects/port.rst
+++ b/doc/source/command-objects/port.rst
@@ -215,3 +215,32 @@ Display port details
 .. describe:: <port>
 
     Port to display (name or ID)
+
+port unset
+----------
+
+Unset port properties
+
+.. program:: port unset
+.. code:: bash
+
+    os port unset
+        [--fixed-ip subnet=<subnet>,ip-address=<ip-address> [...]]
+        [--binding-profile <binding-profile-key> [...]]
+        <port>
+
+.. option:: --fixed-ip subnet=<subnet>,ip-address=<ip-address>
+
+    Desired IP and/or subnet (name or ID) which should be removed
+    from this port: subnet=<subnet>,ip-address=<ip-address>
+    (repeat option to unset multiple fixed IP addresses)
+
+.. option:: --binding-profile <binding-profile-key>
+
+    Desired key which should be removed from binding-profile
+    (repeat option to unset multiple binding:profile data)
+
+.. _port_unset-port:
+.. describe:: <port>
+
+    Port to modify (name or ID)
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 2a069f0922..05c5a012f3 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -14,6 +14,7 @@
 """Port action implementations"""
 
 import argparse
+import copy
 import json
 import logging
 
@@ -485,3 +486,61 @@ class ShowPort(command.ShowOne):
         columns = _get_columns(obj)
         data = utils.get_item_properties(obj, columns, formatters=_formatters)
         return (columns, data)
+
+
+class UnsetPort(command.Command):
+    """Unset port properties"""
+
+    def get_parser(self, prog_name):
+        parser = super(UnsetPort, self).get_parser(prog_name)
+        parser.add_argument(
+            '--fixed-ip',
+            metavar='subnet=<subnet>,ip-address=<ip-address>',
+            action=parseractions.MultiKeyValueAction,
+            optional_keys=['subnet', 'ip-address'],
+            help=_("Desired IP and/or subnet (name or ID) which should be "
+                   "removed from this port: subnet=<subnet>,"
+                   "ip-address=<ip-address> (repeat option to unset multiple "
+                   "fixed IP addresses)"))
+
+        parser.add_argument(
+            '--binding-profile',
+            metavar='<binding-profile-key>',
+            action='append',
+            help=_("Desired key which should be removed from binding:profile"
+                   "(repeat option to unset multiple binding:profile data)"))
+        parser.add_argument(
+            'port',
+            metavar="<port>",
+            help=_("Port to modify (name or ID)")
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        obj = client.find_port(parsed_args.port, ignore_missing=False)
+        # SDK ignores update() if it recieves a modified obj and attrs
+        # To handle the same tmp_obj is created in all take_action of
+        # Unset* classes
+        tmp_fixed_ips = copy.deepcopy(obj.fixed_ips)
+        tmp_binding_profile = copy.deepcopy(obj.binding_profile)
+        _prepare_fixed_ips(self.app.client_manager, parsed_args)
+        attrs = {}
+        if parsed_args.fixed_ip:
+            try:
+                for ip in parsed_args.fixed_ip:
+                    tmp_fixed_ips.remove(ip)
+            except ValueError:
+                msg = _("Port does not contain fixed-ip %s") % ip
+                raise exceptions.CommandError(msg)
+            attrs['fixed_ips'] = tmp_fixed_ips
+        if parsed_args.binding_profile:
+            try:
+                for key in parsed_args.binding_profile:
+                    del tmp_binding_profile[key]
+            except KeyError:
+                msg = _("Port does not contain binding-profile %s") % key
+                raise exceptions.CommandError(msg)
+            attrs['binding:profile'] = tmp_binding_profile
+        if attrs:
+            client.update_port(obj, **attrs)
diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py
index 871fe87296..a1cecec8e7 100644
--- a/openstackclient/tests/network/v2/test_port.py
+++ b/openstackclient/tests/network/v2/test_port.py
@@ -616,3 +616,81 @@ class TestShowPort(TestPort):
         ref_columns, ref_data = self._get_common_cols_data(self._port)
         self.assertEqual(ref_columns, columns)
         self.assertEqual(ref_data, data)
+
+
+class TestUnsetPort(TestPort):
+
+    def setUp(self):
+        super(TestUnsetPort, self).setUp()
+        self._testport = network_fakes.FakePort.create_one_port(
+            {'fixed_ips': [{'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+                            'ip_address': '0.0.0.1'},
+                           {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+                            'ip_address': '1.0.0.0'}],
+             'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}})
+        self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
+            {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'})
+        self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+        self.network.find_port = mock.Mock(return_value=self._testport)
+        self.network.update_port = mock.Mock(return_value=None)
+        # Get the command object to test
+        self.cmd = port.UnsetPort(self.app, self.namespace)
+
+    def test_unset_port_parameters(self):
+        arglist = [
+            '--fixed-ip',
+            'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0',
+            '--binding-profile', 'Superman',
+            self._testport.name,
+        ]
+        verifylist = [
+            ('fixed_ip', [{
+                'subnet': '042eb10a-3a18-4658-ab-cf47c8d03152',
+                'ip-address': '1.0.0.0'}]),
+            ('binding_profile', ['Superman']),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        result = self.cmd.take_action(parsed_args)
+
+        attrs = {
+            'fixed_ips': [{
+                'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+                'ip_address': '0.0.0.1'}],
+            'binding:profile': {'batman': 'Joker'}
+        }
+        self.network.update_port.assert_called_once_with(
+            self._testport, **attrs)
+        self.assertIsNone(result)
+
+    def test_unset_port_fixed_ip_not_existent(self):
+        arglist = [
+            '--fixed-ip', 'ip-address=1.0.0.1',
+            '--binding-profile', 'Superman',
+            self._testport.name,
+        ]
+        verifylist = [
+            ('fixed_ip', [{'ip-address': '1.0.0.1'}]),
+            ('binding_profile', ['Superman']),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        self.assertRaises(exceptions.CommandError,
+                          self.cmd.take_action,
+                          parsed_args)
+
+    def test_unset_port_binding_profile_not_existent(self):
+        arglist = [
+            '--fixed-ip', 'ip-address=1.0.0.0',
+            '--binding-profile', 'Neo',
+            self._testport.name,
+        ]
+        verifylist = [
+            ('fixed_ip', [{'ip-address': '1.0.0.0'}]),
+            ('binding_profile', ['Neo']),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        self.assertRaises(exceptions.CommandError,
+                          self.cmd.take_action,
+                          parsed_args)
diff --git a/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml
new file mode 100644
index 0000000000..f4e547b9e9
--- /dev/null
+++ b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add a new command ``port unset`` to clear the information
+    of fixed-ip and binding-profile from the port.
+    [ Blueprint `network-property-unset <https://blueprints.launchpad.net/python-openstackclient/+spec/network-property-unset>`_]
diff --git a/setup.cfg b/setup.cfg
index 356400e921..b2569cdd6a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -355,6 +355,7 @@ openstack.network.v2 =
     port_list = openstackclient.network.v2.port:ListPort
     port_set = openstackclient.network.v2.port:SetPort
     port_show = openstackclient.network.v2.port:ShowPort
+    port_unset = openstackclient.network.v2.port:UnsetPort
 
     router_add_port = openstackclient.network.v2.router:AddPortToRouter
     router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter