From b0c317ebdd5e5f2626ce2fbd495149336fe5df7e Mon Sep 17 00:00:00 2001
From: Huanxuan Ao <huanxuan.ao@easystack.cn>
Date: Thu, 7 Jul 2016 21:27:22 +0800
Subject: [PATCH] Add Support for showing flavor access list

Add a attribute "access_project_id" for flavor object to
display the access project id list by using "flavor show"
command.

Change-Id: I7f0c152b816e0ca2e32e47f9b5c1aa7663d33b6d
Closes-Bug:#1575461
---
 openstackclient/compute/v2/flavor.py          | 20 +++++++++
 openstackclient/tests/compute/v2/fakes.py     | 29 ++++++++++++
 .../tests/compute/v2/test_flavor.py           | 45 ++++++++++++++++++-
 .../notes/bug-1575461-3fed33e53795684a.yaml   |  5 +++
 4 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 releasenotes/notes/bug-1575461-3fed33e53795684a.yaml

diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index 000df59899..b3f09ce586 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -380,7 +380,27 @@ class ShowFlavor(command.ShowOne):
     def take_action(self, parsed_args):
         compute_client = self.app.client_manager.compute
         resource_flavor = _find_flavor(compute_client, parsed_args.flavor)
+
+        access_projects = None
+        # get access projects list of this flavor
+        if not resource_flavor.is_public:
+            try:
+                flavor_access = compute_client.flavor_access.list(
+                    flavor=resource_flavor.id)
+                projects = [utils.get_field(access, 'tenant_id')
+                            for access in flavor_access]
+                # TODO(Huanxuan Ao): This format case can be removed after
+                # patch https://review.openstack.org/#/c/330223/ merged.
+                access_projects = utils.format_list(projects)
+            except Exception as e:
+                msg = _("Failed to get access projects list "
+                        "for flavor '%(flavor)s': %(e)s")
+                LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e})
+
         flavor = resource_flavor._info.copy()
+        flavor.update({
+            'access_project_ids': access_projects
+        })
         flavor.pop("links", None)
 
         flavor['properties'] = utils.format_dict(resource_flavor.get_keys())
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index 7640247688..e40e62a758 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -787,6 +787,35 @@ class FakeFlavor(object):
         return mock.MagicMock(side_effect=flavors)
 
 
+class FakeFlavorAccess(object):
+    """Fake one or more flavor accesses."""
+
+    @staticmethod
+    def create_one_flavor_access(attrs=None):
+        """Create a fake flavor access.
+
+        :param Dictionary attrs:
+            A dictionary with all attributes
+        :return:
+            A FakeResource object, with flavor_id, tenat_id
+        """
+        attrs = attrs or {}
+
+        # Set default attributes.
+        flavor_access_info = {
+            'flavor_id': 'flavor-id-' + uuid.uuid4().hex,
+            'tenant_id': 'tenant-id-' + uuid.uuid4().hex,
+        }
+
+        # Overwrite default attributes.
+        flavor_access_info.update(attrs)
+
+        flavor_access = fakes.FakeResource(
+            info=copy.deepcopy(flavor_access_info), loaded=True)
+
+        return flavor_access
+
+
 class FakeKeypair(object):
     """Fake one or more keypairs."""
 
diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py
index 20ae8706f6..40cd17c118 100644
--- a/openstackclient/tests/compute/v2/test_flavor.py
+++ b/openstackclient/tests/compute/v2/test_flavor.py
@@ -619,11 +619,13 @@ class TestFlavorSet(TestFlavor):
 class TestFlavorShow(TestFlavor):
 
     # Return value of self.flavors_mock.find().
+    flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access()
     flavor = compute_fakes.FakeFlavor.create_one_flavor()
 
     columns = (
         'OS-FLV-DISABLED:disabled',
         'OS-FLV-EXT-DATA:ephemeral',
+        'access_project_ids',
         'disk',
         'id',
         'name',
@@ -638,6 +640,7 @@ class TestFlavorShow(TestFlavor):
     data = (
         flavor.disabled,
         flavor.ephemeral,
+        None,
         flavor.disk,
         flavor.id,
         flavor.name,
@@ -655,6 +658,7 @@ class TestFlavorShow(TestFlavor):
         # Return value of _find_resource()
         self.flavors_mock.find.return_value = self.flavor
         self.flavors_mock.get.side_effect = exceptions.NotFound(None)
+        self.flavor_access_mock.list.return_value = [self.flavor_access]
         self.cmd = flavor.ShowFlavor(self.app, None)
 
     def test_show_no_options(self):
@@ -665,7 +669,7 @@ class TestFlavorShow(TestFlavor):
         self.assertRaises(tests_utils.ParserException, self.check_parser,
                           self.cmd, arglist, verifylist)
 
-    def test_flavor_show(self):
+    def test_public_flavor_show(self):
         arglist = [
             self.flavor.name,
         ]
@@ -680,6 +684,45 @@ class TestFlavorShow(TestFlavor):
         self.assertEqual(self.columns, columns)
         self.assertEqual(self.data, data)
 
+    def test_private_flavor_show(self):
+        private_flavor = compute_fakes.FakeFlavor.create_one_flavor(
+            attrs={
+                'os-flavor-access:is_public': False,
+            }
+        )
+        self.flavors_mock.find.return_value = private_flavor
+
+        arglist = [
+            private_flavor.name,
+        ]
+        verifylist = [
+            ('flavor', private_flavor.name),
+        ]
+
+        data_with_project = (
+            private_flavor.disabled,
+            private_flavor.ephemeral,
+            self.flavor_access.tenant_id,
+            private_flavor.disk,
+            private_flavor.id,
+            private_flavor.name,
+            private_flavor.is_public,
+            utils.format_dict(private_flavor.get_keys()),
+            private_flavor.ram,
+            private_flavor.rxtx_factor,
+            private_flavor.swap,
+            private_flavor.vcpus,
+        )
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.flavor_access_mock.list.assert_called_with(
+            flavor=private_flavor.id)
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(data_with_project, data)
+
 
 class TestFlavorUnset(TestFlavor):
 
diff --git a/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml
new file mode 100644
index 0000000000..cd319454fa
--- /dev/null
+++ b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add support for showing flavor access list by using ``flavor show`` command.
+    [Bug `1575461 <https://bugs.launchpad.net/bugs/1575461>`_]