diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 000df5989..b3f09ce58 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 764024768..e40e62a75 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 20ae8706f..40cd17c11 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 000000000..cd319454f --- /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 `_]