Add support for removing flavor-access
Add "--project" and "--project-domain" options in "flavor unset" command to remove flavor access. Change-Id: Ia5c5a80d9890d5af066b75b4e202647c18c7d915 Partial-Bug: #1575461
This commit is contained in:
		| @@ -175,12 +175,23 @@ Unset flavor properties | |||||||
|  |  | ||||||
|     os flavor unset |     os flavor unset | ||||||
|         [--property <key> [...] ] |         [--property <key> [...] ] | ||||||
|  |         [--project <project>] | ||||||
|  |         [--project-domain <project-domain>] | ||||||
|         <flavor> |         <flavor> | ||||||
|  |  | ||||||
| .. option:: --property <key> | .. option:: --property <key> | ||||||
|  |  | ||||||
|     Property to remove from flavor (repeat option to remove multiple properties) |     Property to remove from flavor (repeat option to remove multiple properties) | ||||||
|  |  | ||||||
|  | .. option:: --project <project> | ||||||
|  |  | ||||||
|  |     Remove flavor access from project (name or ID) (admin only) | ||||||
|  |  | ||||||
|  | .. option:: --project-domain <project-domain> | ||||||
|  |  | ||||||
|  |     Domain the project belongs to (name or ID). | ||||||
|  |     This can be used in case collisions between project names exist. | ||||||
|  |  | ||||||
| .. describe:: <flavor> | .. describe:: <flavor> | ||||||
|  |  | ||||||
|     Flavor to modify (name or ID) |     Flavor to modify (name or ID) | ||||||
|   | |||||||
| @@ -337,22 +337,64 @@ class UnsetFlavor(command.Command): | |||||||
|  |  | ||||||
|     def get_parser(self, prog_name): |     def get_parser(self, prog_name): | ||||||
|         parser = super(UnsetFlavor, self).get_parser(prog_name) |         parser = super(UnsetFlavor, self).get_parser(prog_name) | ||||||
|         parser.add_argument( |  | ||||||
|             "--property", |  | ||||||
|             metavar="<key>", |  | ||||||
|             action='append', |  | ||||||
|             required=True, |  | ||||||
|             help=_("Property to remove from flavor " |  | ||||||
|                    "(repeat option to unset multiple properties)") |  | ||||||
|         ) |  | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             "flavor", |             "flavor", | ||||||
|             metavar="<flavor>", |             metavar="<flavor>", | ||||||
|             help=_("Flavor to modify (name or ID)") |             help=_("Flavor to modify (name or ID)") | ||||||
|         ) |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             "--property", | ||||||
|  |             metavar="<key>", | ||||||
|  |             action='append', | ||||||
|  |             help=_("Property to remove from flavor " | ||||||
|  |                    "(repeat option to unset multiple properties)") | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--project', | ||||||
|  |             metavar='<project>', | ||||||
|  |             help=_('Remove flavor access from project (name or ID) ' | ||||||
|  |                    '(admin only)'), | ||||||
|  |         ) | ||||||
|  |         identity_common.add_project_domain_option_to_parser(parser) | ||||||
|  |  | ||||||
|         return parser |         return parser | ||||||
|  |  | ||||||
|     def take_action(self, parsed_args): |     def take_action(self, parsed_args): | ||||||
|         compute_client = self.app.client_manager.compute |         compute_client = self.app.client_manager.compute | ||||||
|  |         identity_client = self.app.client_manager.identity | ||||||
|  |  | ||||||
|         flavor = _find_flavor(compute_client, parsed_args.flavor) |         flavor = _find_flavor(compute_client, parsed_args.flavor) | ||||||
|         flavor.unset_keys(parsed_args.property) |  | ||||||
|  |         if not parsed_args.property and not parsed_args.project: | ||||||
|  |             raise exceptions.CommandError(_("Nothing specified to be unset.")) | ||||||
|  |  | ||||||
|  |         result = 0 | ||||||
|  |         if parsed_args.property: | ||||||
|  |             try: | ||||||
|  |                 flavor.unset_keys(parsed_args.property) | ||||||
|  |             except Exception as e: | ||||||
|  |                 self.app.log.error( | ||||||
|  |                     _("Failed to unset flavor property: %s") % str(e)) | ||||||
|  |                 result += 1 | ||||||
|  |  | ||||||
|  |         if parsed_args.project: | ||||||
|  |             try: | ||||||
|  |                 if flavor.is_public: | ||||||
|  |                     msg = _("Cannot remove access for a public flavor") | ||||||
|  |                     raise exceptions.CommandError(msg) | ||||||
|  |                 else: | ||||||
|  |                     project_id = identity_common.find_project( | ||||||
|  |                         identity_client, | ||||||
|  |                         parsed_args.project, | ||||||
|  |                         parsed_args.project_domain, | ||||||
|  |                     ).id | ||||||
|  |                     compute_client.flavor_access.remove_tenant_access( | ||||||
|  |                         flavor.id, project_id) | ||||||
|  |             except Exception as e: | ||||||
|  |                 self.app.log.error(_("Failed to remove flavor access from" | ||||||
|  |                                      " project: %s") % str(e)) | ||||||
|  |                 result += 1 | ||||||
|  |  | ||||||
|  |         if result > 0: | ||||||
|  |             raise exceptions.CommandError(_("Command Failed: One or more of" | ||||||
|  |                                           " the operations failed")) | ||||||
|   | |||||||
| @@ -616,16 +616,23 @@ class TestFlavorShow(TestFlavor): | |||||||
| class TestFlavorUnset(TestFlavor): | class TestFlavorUnset(TestFlavor): | ||||||
|  |  | ||||||
|     # Return value of self.flavors_mock.find(). |     # Return value of self.flavors_mock.find(). | ||||||
|     flavor = compute_fakes.FakeFlavor.create_one_flavor() |     flavor = compute_fakes.FakeFlavor.create_one_flavor( | ||||||
|  |         attrs={'os-flavor-access:is_public': False}) | ||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super(TestFlavorUnset, self).setUp() |         super(TestFlavorUnset, self).setUp() | ||||||
|  |  | ||||||
|         self.flavors_mock.find.return_value = self.flavor |         self.flavors_mock.find.return_value = self.flavor | ||||||
|         self.flavors_mock.get.side_effect = exceptions.NotFound(None) |         self.flavors_mock.get.side_effect = exceptions.NotFound(None) | ||||||
|  |         # Return a project | ||||||
|  |         self.projects_mock.get.return_value = fakes.FakeResource( | ||||||
|  |             None, | ||||||
|  |             copy.deepcopy(identity_fakes.PROJECT), | ||||||
|  |             loaded=True, | ||||||
|  |         ) | ||||||
|         self.cmd = flavor.UnsetFlavor(self.app, None) |         self.cmd = flavor.UnsetFlavor(self.app, None) | ||||||
|  |  | ||||||
|     def test_flavor_unset(self): |     def test_flavor_unset_property(self): | ||||||
|         arglist = [ |         arglist = [ | ||||||
|             '--property', 'property', |             '--property', 'property', | ||||||
|             'baremetal' |             'baremetal' | ||||||
| @@ -640,3 +647,80 @@ class TestFlavorUnset(TestFlavor): | |||||||
|         self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, |         self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, | ||||||
|                                                   is_public=None) |                                                   is_public=None) | ||||||
|         self.assertIsNone(result) |         self.assertIsNone(result) | ||||||
|  |  | ||||||
|  |     def test_flavor_unset_project(self): | ||||||
|  |         arglist = [ | ||||||
|  |             '--project', identity_fakes.project_id, | ||||||
|  |             self.flavor.id, | ||||||
|  |         ] | ||||||
|  |         verifylist = [ | ||||||
|  |             ('project', identity_fakes.project_id), | ||||||
|  |             ('flavor', self.flavor.id), | ||||||
|  |         ] | ||||||
|  |         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||||
|  |  | ||||||
|  |         result = self.cmd.take_action(parsed_args) | ||||||
|  |         self.assertIsNone(result) | ||||||
|  |  | ||||||
|  |         self.flavor_access_mock.remove_tenant_access.assert_called_with( | ||||||
|  |             self.flavor.id, | ||||||
|  |             identity_fakes.project_id, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def test_flavor_unset_no_project(self): | ||||||
|  |         arglist = [ | ||||||
|  |             '--project', '', | ||||||
|  |             self.flavor.id, | ||||||
|  |         ] | ||||||
|  |         verifylist = [ | ||||||
|  |             ('project', ''), | ||||||
|  |             ('flavor', self.flavor.id), | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||||
|  |         self.assertRaises(exceptions.CommandError, self.cmd.take_action, | ||||||
|  |                           parsed_args) | ||||||
|  |  | ||||||
|  |     def test_flavor_unset_no_flavor(self): | ||||||
|  |         arglist = [ | ||||||
|  |             '--project', identity_fakes.project_id, | ||||||
|  |         ] | ||||||
|  |         verifylist = [ | ||||||
|  |             ('project', identity_fakes.project_id), | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         self.assertRaises(tests_utils.ParserException, | ||||||
|  |                           self.check_parser, | ||||||
|  |                           self.cmd, | ||||||
|  |                           arglist, | ||||||
|  |                           verifylist) | ||||||
|  |  | ||||||
|  |     def test_flavor_unset_with_unexist_flavor(self): | ||||||
|  |         self.flavors_mock.get.side_effect = exceptions.NotFound(None) | ||||||
|  |         self.flavors_mock.find.side_effect = exceptions.NotFound(None) | ||||||
|  |  | ||||||
|  |         arglist = [ | ||||||
|  |             '--project', identity_fakes.project_id, | ||||||
|  |             'unexist_flavor', | ||||||
|  |         ] | ||||||
|  |         verifylist = [ | ||||||
|  |             ('project', identity_fakes.project_id), | ||||||
|  |             ('flavor', 'unexist_flavor'), | ||||||
|  |         ] | ||||||
|  |         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||||
|  |  | ||||||
|  |         self.assertRaises(exceptions.CommandError, | ||||||
|  |                           self.cmd.take_action, | ||||||
|  |                           parsed_args) | ||||||
|  |  | ||||||
|  |     def test_flavor_unset_nothing(self): | ||||||
|  |         arglist = [ | ||||||
|  |             self.flavor.id, | ||||||
|  |         ] | ||||||
|  |         verifylist = [ | ||||||
|  |             ('flavor', self.flavor.id), | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||||
|  |         self.assertRaises(exceptions.CommandError, self.cmd.take_action, | ||||||
|  |                           parsed_args) | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								releasenotes/notes/bug-1575461-54a23327081cbec7.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								releasenotes/notes/bug-1575461-54a23327081cbec7.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | --- | ||||||
|  | features: | ||||||
|  |   - | | ||||||
|  |     Added support for removing flavor access from project by using below command | ||||||
|  |     ``flavor unset <flavor> --project <project> --project-domain <project-domain>`` | ||||||
|  |     [Bug `1575461 <https://bugs.launchpad.net/python-openstackclient/+bug/1575461>`_] | ||||||
		Reference in New Issue
	
	Block a user
	 Huanxuan Ao
					Huanxuan Ao