Add support for new flavor properties
Partially Implements: bp new-flavor Depends-On: I5fa154dbf8bc96d15c3cdd3699c671e5eccc1cdd Change-Id: I5f56b5975f0d48ae0a9a1c70598604494901189b
This commit is contained in:
parent
411076c388
commit
3578c88ba6
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
Loading…
x
Reference in New Issue
Block a user