Add support for new flavor properties

Partially Implements: bp new-flavor

Depends-On: I5fa154dbf8bc96d15c3cdd3699c671e5eccc1cdd
Change-Id: I5f56b5975f0d48ae0a9a1c70598604494901189b
This commit is contained in:
Zhenguo Niu 2017-06-23 14:58:23 +08:00
parent 411076c388
commit 3578c88ba6
4 changed files with 59 additions and 226 deletions

View File

@ -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='<disabled|True>',
default=False,
help=_("Flavor is disabled for users.")
)
parser.add_argument(
"--description",
metavar="<description>",
help=_("Flavor description"),
)
parser.add_argument(
"--property",
"--resources",
metavar="<key=value>",
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="<key=value>",
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='<flavor>',
help=_("Flavor to modify (name or UUID)")
)
parser.add_argument(
"--property",
metavar="<key=value>",
action=parseractions.KeyValueAction,
help=_("Property to set on <flavor> "
"(repeat option to set multiple properties)")
)
parser.add_argument(
"--no-property",
dest="no_property",
action="store_true",
help=_("Remove all properties from <flavor> "
"(specify both --property and --no-property to "
"overwrite the current properties)"),
)
parser.add_argument(
'--project',
metavar='<project>',
@ -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='<flavor>',
help=_("Flavor to modify (name or UUID)")
)
parser.add_argument(
"--property",
metavar="<key>",
action='append',
help=_("Property to remove from <flavor> "
"(repeat option to remove multiple properties)")
)
parser.add_argument(
'--project',
metavar='<project>',
@ -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:

View File

@ -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),

View File

@ -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)

View File

@ -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})