diff --git a/moganclient/osc/v1/flavor.py b/moganclient/osc/v1/flavor.py index 0bdc5a8..fb5f654 100644 --- a/moganclient/osc/v1/flavor.py +++ b/moganclient/osc/v1/flavor.py @@ -16,7 +16,6 @@ """Mogan v1 Baremetal flavor action implementations""" -import copy import logging from osc_lib.cli import parseractions @@ -50,17 +49,30 @@ class CreateFlavor(command.ShowOne): action="store_true", help=_("Flavor is not available to other projects") ) + public_group.add_argument( + "--disabled", + metavar='', + default=False, + help=_("Flavor is disabled for users.") + ) parser.add_argument( "--description", metavar="", help=_("Flavor description"), ) parser.add_argument( - "--property", + "--resources", metavar="", action=parseractions.KeyValueAction, - help=_("Property to add to this flavor " - "(repeat option to set multiple properties)") + help=_("Resources to add to this flavor " + "(repeat option to set multiple resources)") + ) + parser.add_argument( + "--resource-traits", + metavar="", + action=parseractions.KeyValueAction, + help=_("Resource traits to add to this flavor " + "(repeat option to set multiple resource traits)") ) return parser @@ -76,15 +88,13 @@ class CreateFlavor(command.ShowOne): data = bc_client.flavor.create( name=parsed_args.name, - is_public=is_public, description=parsed_args.description, + resources=parsed_args.resources, + resource_traits=parsed_args.resource_traits, + is_public=is_public, + disabled=parsed_args.disabled, ) info.update(data._info) - if parsed_args.property: - bc_client.flavor.update_extra_specs(data, - parsed_args.property) - extra_specs = bc_client.flavor.get_extra_specs(data) - info.update(extra_specs) return zip(*sorted(info.items())) @@ -136,14 +146,16 @@ class ListFlavor(command.Lister): "Name", "Is Public", "Description", - "Properties", + "Resources", + "Resource Traits", ) columns = ( "UUID", "Name", "Is Public", "Description", - "Extra Specs", + "Resources", + "Resource Traits", ) return (column_headers, @@ -162,21 +174,6 @@ class SetFlavor(command.Command): metavar='', help=_("Flavor to modify (name or UUID)") ) - parser.add_argument( - "--property", - metavar="", - action=parseractions.KeyValueAction, - help=_("Property to set on " - "(repeat option to set multiple properties)") - ) - parser.add_argument( - "--no-property", - dest="no_property", - action="store_true", - help=_("Remove all properties from " - "(specify both --property and --no-property to " - "overwrite the current properties)"), - ) parser.add_argument( '--project', metavar='', @@ -193,51 +190,7 @@ class SetFlavor(command.Command): parsed_args.flavor, ) - set_property = None - del_property_key = None - # NOTE(RuiChen): extra specs update API is append mode, so if the - # options is overwrite mode, the update and delete - # properties need to be handled in client side. - if parsed_args.no_property and parsed_args.property: - # override - del_property_key = data.extra_specs.keys() - set_property = copy.deepcopy(parsed_args.property) - elif parsed_args.property: - # append - set_property = copy.deepcopy(parsed_args.property) - elif parsed_args.no_property: - # clean - del_property_key = data.extra_specs.keys() - result = 0 - if del_property_key is not None: - for each_key in del_property_key: - try: - # If the key is in the set_property, it will be updated - # in the follow logic. - if (set_property is None or - each_key not in set_property): - bc_client.flavor.delete_extra_specs( - data, - each_key - ) - except Exception as e: - result += 1 - LOG.error(_("Failed to remove flavor property with key " - "'%(key)s': %(e)s") % {'key': each_key, - 'e': e}) - if set_property is not None: - try: - bc_client.flavor.update_extra_specs( - data, - set_property - ) - except Exception as e: - result += 1 - LOG.error(_("Failed to update flavor property with key/value " - "'%(key)s': %(e)s") % {'key': set_property, - 'e': e}) - if parsed_args.project: try: if data.is_public: @@ -289,13 +242,6 @@ class UnsetFlavor(command.Command): metavar='', help=_("Flavor to modify (name or UUID)") ) - parser.add_argument( - "--property", - metavar="", - action='append', - help=_("Property to remove from " - "(repeat option to remove multiple properties)") - ) parser.add_argument( '--project', metavar='', @@ -312,21 +258,7 @@ class UnsetFlavor(command.Command): parsed_args.flavor, ) - unset_property_key = [] - if parsed_args.property: - unset_property_key = list( - set(data.extra_specs.keys()).intersection( - set(parsed_args.property))) - result = 0 - for each_key in unset_property_key: - try: - bc_client.flavor.delete_extra_specs(data, each_key) - except Exception as e: - result += 1 - LOG.error(_("Failed to remove flavor property with key " - "'%(key)s': %(e)s") % {'key': each_key, 'e': e}) - if parsed_args.project: try: if data.is_public: diff --git a/moganclient/tests/unit/fakes.py b/moganclient/tests/unit/fakes.py index c7116a3..6fa0080 100644 --- a/moganclient/tests/unit/fakes.py +++ b/moganclient/tests/unit/fakes.py @@ -169,8 +169,10 @@ class FakeFlavor(object): flavor_info = { "created_at": "2016-09-27T02:37:21.966342+00:00", "description": "fake_description", - "extra_specs": {"key0": "value0"}, + "resources": {"BAREMETAL_GOLD": 1}, + "resource_traits": {"BAREMETAL_GOLD": "FPGA"}, "is_public": True, + "disabled": False, "name": "flavor-name-" + uuidutils.generate_uuid(dashed=False), "updated_at": None, "uuid": "flavor-id-" + uuidutils.generate_uuid(dashed=False), diff --git a/moganclient/tests/unit/osc/v1/test_flavor.py b/moganclient/tests/unit/osc/v1/test_flavor.py index 492b838..8bd739c 100644 --- a/moganclient/tests/unit/osc/v1/test_flavor.py +++ b/moganclient/tests/unit/osc/v1/test_flavor.py @@ -31,9 +31,11 @@ class TestFlavor(test_base.TestBaremetalComputeV1): columns = ( 'created_at', 'description', - 'extra_specs', + 'disabled', 'is_public', 'name', + 'resource_traits', + 'resources', 'updated_at', 'uuid', ) @@ -41,9 +43,11 @@ class TestFlavor(test_base.TestBaremetalComputeV1): data = ( fake_flavor.created_at, fake_flavor.description, - fake_flavor.extra_specs, + fake_flavor.disabled, fake_flavor.is_public, fake_flavor.name, + fake_flavor.resource_traits, + fake_flavor.resources, fake_flavor.updated_at, fake_flavor.uuid, ) @@ -58,9 +62,11 @@ class TestFlavorCreate(TestFlavor): def test_flavor_create(self, mock_create): arglist = [ 'flavor1', + '--resources', 'k1=v1' ] verifylist = [ ('name', 'flavor1'), + ('resources', {'k1': 'v1'}), ] mock_create.return_value = self.fake_flavor parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -69,7 +75,9 @@ class TestFlavorCreate(TestFlavor): data={ 'name': 'flavor1', 'is_public': True, + 'disabled': False, 'description': None, + 'resources': {'k1': 'v1'}, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -90,6 +98,7 @@ class TestFlavorCreate(TestFlavor): data={ 'name': 'flavor1', 'is_public': True, + 'disabled': False, 'description': None, }) self.assertEqual(self.columns, columns) @@ -111,6 +120,7 @@ class TestFlavorCreate(TestFlavor): data={ 'name': 'flavor1', 'is_public': False, + 'disabled': False, 'description': None, }) self.assertEqual(self.columns, columns) @@ -133,6 +143,7 @@ class TestFlavorCreate(TestFlavor): data={ 'name': 'flavor1', 'is_public': True, + 'disabled': False, 'description': 'test description.', }) self.assertEqual(self.columns, columns) @@ -140,36 +151,30 @@ class TestFlavorCreate(TestFlavor): @mock.patch.object(flavor_mgr.FlavorManager, '_get') @mock.patch.object(flavor_mgr.FlavorManager, '_update') - def test_flavor_create_with_property(self, mock_update, mock_get, - mock_create): + def test_flavor_create_with_resources(self, mock_update, mock_get, + mock_create): arglist = [ - '--property', 'key1=value1', + '--resources', 'k1=v1', 'flavor1', ] verifylist = [ - ('property', {'key1': 'value1'}), + ('resources', {'k1': 'v1'}), ('name', 'flavor1'), ] mock_create.return_value = self.fake_flavor - mock_get.return_value = {'extra_specs': {'key1': 'value1'}} + mock_get.return_value = {'resources': {'k1': 'v1'}} parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) mock_create.assert_called_once_with('/flavors', data={ 'name': 'flavor1', 'is_public': True, + 'disabled': False, 'description': None, + 'resources': {'k1': 'v1'}, }) - expected_url = '/flavors/%s/extraspecs' % base.getid(self.fake_flavor) - mock_update.assert_called_once_with(expected_url, - data=parsed_args.property, - return_raw=True) - mock_get.assert_called_once_with(expected_url, return_raw=True) self.assertEqual(self.columns, columns) expected_data = copy.deepcopy(self.data) - # update extra specs - expected_data[2].pop('key0') - expected_data[2].update({'key1': 'value1'}) self.assertEqual(expected_data, data) @@ -220,7 +225,8 @@ class TestFlavorList(TestFlavor): "Name", "Is Public", "Description", - "Properties", + "Resources", + "Resource Traits", ) list_data = (( @@ -228,7 +234,8 @@ class TestFlavorList(TestFlavor): TestFlavor.fake_flavor.name, TestFlavor.fake_flavor.is_public, TestFlavor.fake_flavor.description, - TestFlavor.fake_flavor.extra_specs, + TestFlavor.fake_flavor.resources, + TestFlavor.fake_flavor.resource_traits, ),) def setUp(self): @@ -246,80 +253,6 @@ class TestFlavorList(TestFlavor): self.assertEqual(self.list_data, tuple(data)) -@mock.patch.object(utils, 'find_resource') -@mock.patch.object(flavor_mgr.FlavorManager, '_delete') -@mock.patch.object(flavor_mgr.FlavorManager, '_update') -class TestFlavorSet(TestFlavor): - def setUp(self): - super(TestFlavorSet, self).setUp() - self.cmd = flavor.SetFlavor(self.app, None) - - def test_flavor_set_property(self, mock_update, mock_delete, mock_find): - arglist = [ - '--property', 'key1=value1', - '--property', 'key2=value2', - 'flavor1', - ] - verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), - ('flavor', 'flavor1'), - ] - mock_find.return_value = self.fake_flavor - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - expected_url = '/flavors/%s/extraspecs' % base.getid(self.fake_flavor) - expected_data = {'key1': 'value1', 'key2': 'value2'} - mock_update.assert_called_once_with(expected_url, - data=expected_data, - return_raw=True) - self.assertNotCalled(mock_delete) - self.assertIsNone(result) - - def test_flavor_set_clean_property(self, mock_update, mock_delete, - mock_find): - arglist = [ - '--no-property', - 'flavor1', - ] - verifylist = [ - ('no_property', True), - ('flavor', 'flavor1'), - ] - mock_find.return_value = self.fake_flavor - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - expected_url = '/flavors/%s/extraspecs/key0' % base.getid( - self.fake_flavor) - self.assertNotCalled(mock_update) - mock_delete.assert_called_once_with(expected_url) - self.assertIsNone(result) - - def test_flavor_set_overrider_property(self, mock_update, mock_delete, - mock_find): - arglist = [ - '--property', 'key1=value1', - '--no-property', - 'flavor1', - ] - verifylist = [ - ('property', {'key1': 'value1'}), - ('no_property', True), - ('flavor', 'flavor1'), - ] - mock_find.return_value = self.fake_flavor - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - expected_url = '/flavors/%s/extraspecs' % base.getid(self.fake_flavor) - expected_data = {'key1': 'value1'} - mock_update.assert_called_once_with(expected_url, - data=expected_data, - return_raw=True) - expected_url = '/flavors/%s/extraspecs/key0' % base.getid( - self.fake_flavor) - mock_delete.assert_called_once_with(expected_url) - self.assertIsNone(result) - - @mock.patch.object(flavor_mgr.FlavorManager, '_get') class TestFlavorShow(TestFlavor): def setUp(self): @@ -340,29 +273,3 @@ class TestFlavorShow(TestFlavor): mock_get.assert_called_once_with(expected_url) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - - -@mock.patch.object(utils, 'find_resource') -@mock.patch.object(flavor_mgr.FlavorManager, '_delete') -class TestFlavorUnset(TestFlavor): - def setUp(self): - super(TestFlavorUnset, self).setUp() - self.cmd = flavor.UnsetFlavor(self.app, None) - - def test_flavor_unset_property(self, mock_delete, mock_find): - arglist = [ - '--property', 'key0', - '--property', 'key2', - 'flavor1', - ] - verifylist = [ - ('property', ['key0', 'key2']), - ('flavor', 'flavor1'), - ] - mock_find.return_value = self.fake_flavor - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - expected_url = '/flavors/%s/extraspecs/key0' % base.getid( - self.fake_flavor) - mock_delete.assert_called_once_with(expected_url) - self.assertIsNone(result) diff --git a/moganclient/v1/flavor.py b/moganclient/v1/flavor.py index e4e1f99..1750dbb 100644 --- a/moganclient/v1/flavor.py +++ b/moganclient/v1/flavor.py @@ -23,13 +23,19 @@ class Flavor(base.Resource): class FlavorManager(base.ManagerWithFind): resource_class = Flavor - def create(self, name, is_public, description=None): + def create(self, name, resources, resource_traits, is_public, disabled, + description=None): url = '/flavors' data = { 'name': name, - 'is_public': is_public, 'description': description, + 'is_public': is_public, + 'disabled': disabled, } + if resources: + data['resources'] = resources + if resource_traits: + data['resource_traits'] = resource_traits return self._create(url, data=data) def delete(self, flavor): @@ -44,20 +50,6 @@ class FlavorManager(base.ManagerWithFind): url = '/flavors' return self._list(url, response_key='flavors') - def get_extra_specs(self, flavor): - url = '/flavors/%s/extraspecs' % base.getid(flavor) - return self._get(url, return_raw=True) - - def update_extra_specs(self, flavor, extra_specs): - url = '/flavors/%s/extraspecs' % base.getid(flavor) - data = extra_specs - return self._update(url, data=data, return_raw=True) - - def delete_extra_specs(self, flavor, key): - url = '/flavors/%(id)s/extraspecs/%(key)s' % { - 'id': base.getid(flavor), 'key': key} - return self._delete(url) - def add_tenant_access(self, flavor, project): url = '/flavors/%s/access' % base.getid(flavor) return self._create(url, data={'tenant_id': project})