From 25e0d2ab2794dc68cccec0d1e7b463b7d7f7e65f Mon Sep 17 00:00:00 2001
From: Terry Howe <terrylhowe@gmail.com>
Date: Fri, 11 Jul 2014 09:05:37 -0600
Subject: [PATCH] Add network extension list

Network extension list support

Change-Id: I013f68ef2c3329c8db59e2441dd8d4ffafd4470e
Closes-Bug: #1337685
---
 openstackclient/common/extension.py           | 54 ++++++++++++++-----
 .../tests/common/test_extension.py            | 52 +++++++++++++++++-
 openstackclient/tests/network/v2/fakes.py     | 35 ++++++++++++
 3 files changed, 126 insertions(+), 15 deletions(-)
 create mode 100644 openstackclient/tests/network/v2/fakes.py

diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 91ee228be5..be7426daa0 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -15,6 +15,7 @@
 
 """Extension action implementations"""
 
+import itertools
 import logging
 
 from cliff import lister
@@ -25,28 +26,30 @@ from openstackclient.common import utils
 class ListExtension(lister.Lister):
     """List extension command"""
 
-    # TODO(mfisch): add support for volume and network
-    # when the underlying APIs support it.
-
     log = logging.getLogger(__name__ + '.ListExtension')
 
     def get_parser(self, prog_name):
         parser = super(ListExtension, self).get_parser(prog_name)
         parser.add_argument(
-            '--long',
+            '--compute',
             action='store_true',
             default=False,
-            help='List additional fields in output')
+            help='List extensions for the Compute API')
         parser.add_argument(
             '--identity',
             action='store_true',
             default=False,
             help='List extensions for the Identity API')
         parser.add_argument(
-            '--compute',
+            '--long',
             action='store_true',
             default=False,
-            help='List extensions for the Compute API')
+            help='List additional fields in output')
+        parser.add_argument(
+            '--network',
+            action='store_true',
+            default=False,
+            help='List extensions for the Network API')
         parser.add_argument(
             '--volume',
             action='store_true',
@@ -69,7 +72,7 @@ class ListExtension(lister.Lister):
         # user specifies one or more of the APIs to show
         # for now, only identity and compute are supported.
         show_all = (not parsed_args.identity and not parsed_args.compute
-                    and not parsed_args.volume)
+                    and not parsed_args.volume and not parsed_args.network)
 
         if parsed_args.identity or show_all:
             identity_client = self.app.client_manager.identity
@@ -95,8 +98,33 @@ class ListExtension(lister.Lister):
                 message = "Extensions list not supported by Volume API"
                 self.log.warning(message)
 
-        return (columns,
-                (utils.get_item_properties(
-                    s, columns,
-                    formatters={},
-                ) for s in data))
+        # Resource classes for the above
+        extension_tuples = (
+            utils.get_item_properties(
+                s,
+                columns,
+                formatters={},
+            ) for s in data
+        )
+
+        # Dictionaries for the below
+        if parsed_args.network or show_all:
+            network_client = self.app.client_manager.network
+            try:
+                data = network_client.list_extensions()['extensions']
+                dict_tuples = (
+                    utils.get_dict_properties(
+                        s,
+                        columns,
+                        formatters={},
+                    ) for s in data
+                )
+                extension_tuples = itertools.chain(
+                    extension_tuples,
+                    dict_tuples
+                )
+            except Exception:
+                message = "Extensions list not supported by Network API"
+                self.log.warning(message)
+
+        return (columns, extension_tuples)
diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py
index 2e6e7050f8..5561345b50 100644
--- a/openstackclient/tests/common/test_extension.py
+++ b/openstackclient/tests/common/test_extension.py
@@ -18,6 +18,7 @@ from openstackclient.tests import fakes
 from openstackclient.tests import utils
 
 from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.network.v2 import fakes as network_fakes
 
 
 class TestExtension(utils.TestCommand):
@@ -29,12 +30,15 @@ class TestExtension(utils.TestCommand):
             endpoint=fakes.AUTH_URL,
             token=fakes.AUTH_TOKEN,
         )
-
-        # Get shortcuts to the ExtensionManager Mocks
         self.identity_extensions_mock = (
             self.app.client_manager.identity.extensions)
         self.identity_extensions_mock.reset_mock()
 
+        network = network_fakes.FakeNetworkV2Client()
+        self.app.client_manager.network = network
+        self.network_extensions_mock = network.list_extensions
+        self.network_extensions_mock.reset_mock()
+
 
 class TestExtensionList(TestExtension):
 
@@ -48,6 +52,13 @@ class TestExtensionList(TestExtension):
                 loaded=True,
             ),
         ]
+        self.network_extensions_mock.list.return_value = [
+            fakes.FakeResource(
+                None,
+                copy.deepcopy(identity_fakes.EXTENSION),
+                loaded=True,
+            ),
+        ]
 
         # Get the command object to test
         self.cmd = extension.ListExtension(self.app, None)
@@ -71,6 +82,11 @@ class TestExtensionList(TestExtension):
                 identity_fakes.extension_alias,
                 identity_fakes.extension_description,
             ),
+            (
+                network_fakes.extension_name,
+                network_fakes.extension_alias,
+                network_fakes.extension_description,
+            ),
         )
         self.assertEqual(tuple(data), datalist)
 
@@ -101,6 +117,14 @@ class TestExtensionList(TestExtension):
                 identity_fakes.extension_updated,
                 identity_fakes.extension_links,
             ),
+            (
+                network_fakes.extension_name,
+                network_fakes.extension_namespace,
+                network_fakes.extension_description,
+                network_fakes.extension_alias,
+                network_fakes.extension_updated,
+                network_fakes.extension_links,
+            ),
         )
         self.assertEqual(tuple(data), datalist)
 
@@ -126,3 +150,27 @@ class TestExtensionList(TestExtension):
             identity_fakes.extension_description,
         ), )
         self.assertEqual(tuple(data), datalist)
+
+    def test_extension_list_network(self):
+        arglist = [
+            '--network',
+        ]
+        verifylist = [
+            ('network', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network_extensions_mock.assert_called_with()
+
+        collist = ('Name', 'Alias', 'Description')
+        self.assertEqual(columns, collist)
+        datalist = (
+            (
+                network_fakes.extension_name,
+                network_fakes.extension_alias,
+                network_fakes.extension_description,
+            ),
+        )
+        self.assertEqual(tuple(data), datalist)
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
new file mode 100644
index 0000000000..ea191c8e8c
--- /dev/null
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -0,0 +1,35 @@
+#   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 mock
+
+extension_name = 'Matrix'
+extension_namespace = 'http://docs.openstack.org/network/'
+extension_description = 'Simulated reality'
+extension_updated = '2013-07-09T12:00:0-00:00'
+extension_alias = 'Dystopian'
+extension_links = '[{"href":''"https://github.com/os/network", "type"}]'
+
+NETEXT = {
+    'name': extension_name,
+    'namespace': extension_namespace,
+    'description': extension_description,
+    'updated': extension_updated,
+    'alias': extension_alias,
+    'links': extension_links,
+}
+
+
+class FakeNetworkV2Client(object):
+    def __init__(self, **kwargs):
+        self.list_extensions = mock.Mock(return_value={'extensions': [NETEXT]})