From eab6cdebdc28c7eaaf330bbf6c9261c4eedf4dce Mon Sep 17 00:00:00 2001
From: Manjeet Singh Bhatia <manjeet.s.bhatia@intel.com>
Date: Thu, 14 Apr 2016 20:45:27 +0000
Subject: [PATCH] Add network availability for osc

This patch implements openstack client for network ip availability.

Implements: blueprint neutron-ip-capacity

Depends-On: I3b40d8edea87c068c4e8133e436511765064d5f8
Change-Id: Iffaa2e20ff495fbd205d3397e027e8141d04385e
---
 .../ip-availability.rst                       |   7 +-
 doc/source/commands.rst                       |   1 +
 openstackclient/network/v2/ip_availability.py | 109 +++++++++++
 openstackclient/tests/network/v2/fakes.py     |  44 +++++
 .../tests/network/v2/test_ip_availability.py  | 180 ++++++++++++++++++
 .../ip-availability-ca1cf440f6c70afc.yaml     |   5 +
 setup.cfg                                     |   3 +
 7 files changed, 345 insertions(+), 4 deletions(-)
 rename doc/source/{specs => command-objects}/ip-availability.rst (88%)
 create mode 100644 openstackclient/network/v2/ip_availability.py
 create mode 100644 openstackclient/tests/network/v2/test_ip_availability.py
 create mode 100644 releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml

diff --git a/doc/source/specs/ip-availability.rst b/doc/source/command-objects/ip-availability.rst
similarity index 88%
rename from doc/source/specs/ip-availability.rst
rename to doc/source/command-objects/ip-availability.rst
index cf0c71ff38..55b7842775 100644
--- a/doc/source/specs/ip-availability.rst
+++ b/doc/source/command-objects/ip-availability.rst
@@ -24,12 +24,12 @@ number of allocated IP addresses from that pool.
 
 .. option:: --ip-version {4,6}
 
-    List IP availability for specific version
+    List IP availability of given IP version networks
     (Default is 4)
 
 .. option:: --project <project>
 
-    List IP availability for specific project
+    List IP availability of given project
     (name or ID)
 
 ip availability show
@@ -57,5 +57,4 @@ subnet within the network as well.
 .. _ip_availability_show-network
 .. describe:: <network>
 
-    Show network IP availability for specific
-    network (name or ID)
+    Show IP availability for a specific network (name or ID)
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 12542d1c73..a165fbfcd2 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -96,6 +96,7 @@ referring to both Compute and Volume quotas.
 * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes
 * ``identity provider``: (**Identity**) a source of users and authentication
 * ``image``: (**Image**) a disk image
+* ``ip availability``: (**Network**) - details of IP usage of a network
 * ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server
 * ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server
 * ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses
diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py
new file mode 100644
index 0000000000..cc240338f3
--- /dev/null
+++ b/openstackclient/network/v2/ip_availability.py
@@ -0,0 +1,109 @@
+#   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.
+#
+
+"""IP Availability Info implementations"""
+
+from openstackclient.common import command
+from openstackclient.common import utils
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+
+
+_formatters = {
+    'subnet_ip_availability': utils.format_list_of_dicts,
+}
+
+
+def _get_columns(item):
+    columns = list(item.keys())
+    if 'tenant_id' in columns:
+        columns.remove('tenant_id')
+        columns.append('project_id')
+    return tuple(sorted(columns))
+
+
+class ListIPAvailability(command.Lister):
+    """List IP availability for network"""
+
+    def get_parser(self, prog_name):
+        parser = super(ListIPAvailability, self).get_parser(prog_name)
+        parser.add_argument(
+            '--ip-version',
+            type=int,
+            choices=[4, 6],
+            metavar='<ip-version>',
+            dest='ip_version',
+            help=_("List IP availability of given IP version networks"),
+        )
+        parser.add_argument(
+            '--project',
+            metavar='<project>',
+            help=_("List IP availability of given project"),
+        )
+        identity_common.add_project_domain_option_to_parser(parser)
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+
+        columns = (
+            'network_id',
+            'network_name',
+            'total_ips',
+            'used_ips',
+        )
+        column_headers = (
+            'Network ID',
+            'Network Name',
+            'Total IPs',
+            'Used IPs',
+        )
+
+        filters = {}
+        if parsed_args.ip_version:
+            filters['ip_version'] = parsed_args.ip_version
+
+        if parsed_args.project:
+            identity_client = self.app.client_manager.identity
+            project_id = identity_common.find_project(
+                identity_client,
+                parsed_args.project,
+                parsed_args.project_domain,
+            ).id
+            filters['tenant_id'] = project_id
+        data = client.network_ip_availabilities(**filters)
+        return (column_headers,
+                (utils.get_item_properties(
+                    s, columns,
+                ) for s in data))
+
+
+class ShowIPAvailability(command.ShowOne):
+    """Show network IP availability details"""
+
+    def get_parser(self, prog_name):
+        parser = super(ShowIPAvailability, self).get_parser(prog_name)
+        parser.add_argument(
+            'network',
+            metavar="<network>",
+            help=_("Show IP availability for a specific network (name or ID)"),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        obj = client.find_network_ip_availability(parsed_args.network,
+                                                  ignore_missing=False)
+        columns = _get_columns(obj)
+        data = utils.get_item_properties(obj, columns, formatters=_formatters)
+        return columns, data
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index 84ede381bf..ee63a0e0f5 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -174,6 +174,50 @@ class FakeAvailabilityZone(object):
         return availability_zones
 
 
+class FakeIPAvailability(object):
+    """Fake one or more network ip availabilities."""
+
+    @staticmethod
+    def create_one_ip_availability():
+        """Create a fake list with ip availability stats of a network.
+
+        :return:
+            A FakeResource object with network_name, network_id, etc.
+        """
+
+        # Set default attributes.
+        network_ip_availability = {
+            'network_id': 'network-id-' + uuid.uuid4().hex,
+            'network_name': 'network-name-' + uuid.uuid4().hex,
+            'tenant_id': '',
+            'subnet_ip_availability': [],
+            'total_ips': 254,
+            'used_ips': 6,
+        }
+
+        network_ip_availability = fakes.FakeResource(
+            info=copy.deepcopy(network_ip_availability),
+            loaded=True)
+        return network_ip_availability
+
+    @staticmethod
+    def create_ip_availability(count=2):
+        """Create fake list of ip availability stats of multiple networks.
+
+        :param int count:
+            The number of networks to fake
+        :return:
+            A list of FakeResource objects faking network ip availability stats
+        """
+        network_ip_availabilities = []
+        for i in range(0, count):
+            network_ip_availability = \
+                FakeIPAvailability.create_one_ip_availability()
+            network_ip_availabilities.append(network_ip_availability)
+
+        return network_ip_availabilities
+
+
 class FakeNetwork(object):
     """Fake one or more networks."""
 
diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py
new file mode 100644
index 0000000000..04979e7710
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_ip_availability.py
@@ -0,0 +1,180 @@
+#   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.
+#
+
+import copy
+import mock
+
+from openstackclient.common import utils as osc_utils
+from openstackclient.network.v2 import ip_availability
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
+
+
+class TestIPAvailability(network_fakes.TestNetworkV2):
+
+    def setUp(self):
+        super(TestIPAvailability, self).setUp()
+
+        # Get a shortcut to the network client
+        self.network = self.app.client_manager.network
+
+        # Set identity client v3. And get a shortcut to Identity client.
+        identity_client = identity_fakes.FakeIdentityv3Client(
+            endpoint=fakes.AUTH_URL,
+            token=fakes.AUTH_TOKEN,
+        )
+        self.app.client_manager.identity = identity_client
+        self.identity = self.app.client_manager.identity
+
+        # Get a shortcut to the ProjectManager Mock
+        self.projects_mock = self.identity.projects
+        self.projects_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.PROJECT),
+            loaded=True,
+        )
+
+
+class TestListIPAvailability(TestIPAvailability):
+
+    _ip_availability = \
+        network_fakes.FakeIPAvailability.create_ip_availability(count=3)
+    columns = (
+        'Network ID',
+        'Network Name',
+        'Total IPs',
+        'Used IPs',
+    )
+    data = []
+    for net in _ip_availability:
+        data.append((
+            net.network_id,
+            net.network_name,
+            net.total_ips,
+            net.used_ips,
+        ))
+
+    def setUp(self):
+        super(TestListIPAvailability, self).setUp()
+
+        self.cmd = ip_availability.ListIPAvailability(
+            self.app, self.namespace)
+        self.network.network_ip_availabilities = mock.Mock(
+            return_value=self._ip_availability)
+
+    def test_list_no_options(self):
+        arglist = []
+        verifylist = []
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.network_ip_availabilities.assert_called_once_with()
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, list(data))
+
+    def test_list_ip_version(self):
+        arglist = [
+            '--ip-version', str(4),
+        ]
+        verifylist = [
+            ('ip_version', 4)
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+        filters = {'ip_version': 4}
+
+        self.network.network_ip_availabilities.assert_called_once_with(
+            **filters)
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, list(data))
+
+    def test_list_project(self):
+        arglist = [
+            '--project', identity_fakes.project_name
+        ]
+        verifylist = [
+            ('project', identity_fakes.project_name)
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+        filters = {'tenant_id': identity_fakes.project_id}
+
+        self.network.network_ip_availabilities.assert_called_once_with(
+            **filters)
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, list(data))
+
+
+class TestShowIPAvailability(TestIPAvailability):
+
+    _ip_availability = \
+        network_fakes.FakeIPAvailability.create_one_ip_availability()
+
+    columns = (
+        'network_id',
+        'network_name',
+        'project_id',
+        'subnet_ip_availability',
+        'total_ips',
+        'used_ips',
+    )
+    data = (
+        _ip_availability.network_id,
+        _ip_availability.network_name,
+        _ip_availability.tenant_id,
+        osc_utils.format_list(
+            _ip_availability.subnet_ip_availability),
+        _ip_availability.total_ips,
+        _ip_availability.used_ips,
+    )
+
+    def setUp(self):
+        super(TestShowIPAvailability, self).setUp()
+
+        self.network.find_network_ip_availability = mock.Mock(
+            return_value=self._ip_availability)
+
+        # Get the command object to test
+        self.cmd = ip_availability.ShowIPAvailability(
+            self.app, self.namespace)
+
+    def test_show_no_option(self):
+        arglist = []
+        verifylist = []
+
+        self.assertRaises(tests_utils.ParserException,
+                          self.check_parser, self.cmd, arglist, verifylist)
+
+    def test_show_all_options(self):
+        arglist = [
+            self._ip_availability.network_name,
+        ]
+        verifylist = [
+            ('network', self._ip_availability.network_name)
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+        columns, data = self.cmd.take_action(parsed_args)
+        self.network.find_network_ip_availability.assert_called_once_with(
+            self._ip_availability.network_name,
+            ignore_missing=False)
+
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
diff --git a/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml
new file mode 100644
index 0000000000..81b217c092
--- /dev/null
+++ b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add support for the ``ip availability list`` and  ``ip availability show`` commands.
+    [Blueprint `neutron-ip-capacity <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-ip-capacity>`_]
diff --git a/setup.cfg b/setup.cfg
index a62f5d25cd..97a47f31c0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -330,6 +330,9 @@ openstack.network.v2 =
     address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope
     address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope
 
+    ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability
+    ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability
+
     ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP
     ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP
     ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP