Support flavor extra specs in nova client

Add flavor extra specs so that user can list/set/unset
extra specs in nova client

blueprint extra-specs-in-nova-client

Change-Id: I6ad7293e29764648c79943c4d05f3a09931af411
Signed-off-by: Yunhong, Jiang <yunhong.jiang@intel.com>
This commit is contained in:
Yunhong, Jiang 2012-09-11 19:58:32 +08:00
parent 247b53022b
commit 3dd0393fbb
5 changed files with 112 additions and 9 deletions

View File

@ -4,6 +4,7 @@ Flavor interface.
"""
from novaclient import base
from novaclient import exceptions
class Flavor(base.Resource):
@ -29,6 +30,43 @@ class Flavor(base.Resource):
"""
return self._info.get("os-flavor-access:is_public", 'N/A')
def get_keys(self):
"""
Get extra specs from a flavor.
:param flavor: The :class:`Flavor` to get extra specs from
"""
_resp, body = self.manager.api.client.get(
"/flavors/%s/os-extra_specs" %
base.getid(self))
return body["extra_specs"]
def set_keys(self, metadata):
"""
Set extra specs on a flavor.
:param flavor: The :class:`Flavor` to set extra spec on
:param metadata: A dict of key/value pairs to be set
"""
body = {'extra_specs': metadata}
return self.manager._create(
"/flavors/%s/os-extra_specs" % base.getid(self),
body,
"extra_specs",
return_raw=True)
def unset_keys(self, keys):
"""
Unset extra specs on a flavor.
:param flavor: The :class:`Flavor` to unset extra spec on
:param keys: A list of keys to be unset
"""
for k in keys:
return self.manager._delete(
"/flavors/%s/os-extra_specs/%s" % (
base.getid(self), k))
class FlavorManager(base.ManagerWithFind):
"""

View File

@ -314,8 +314,16 @@ def _translate_flavor_keys(collection):
setattr(item, to_key, item._info[from_key])
def _print_flavor_list(flavors):
def _print_flavor_extra_specs(flavor):
try:
return flavor.get_keys()
except exceptions.NotFound:
return "N/A"
def _print_flavor_list(cs, flavors):
_translate_flavor_keys(flavors)
formatters = {'extra_specs': _print_flavor_extra_specs}
utils.print_list(flavors, [
'ID',
'Name',
@ -325,7 +333,8 @@ def _print_flavor_list(flavors):
'Swap',
'VCPUs',
'RXTX_Factor',
'Is_Public'])
'Is_Public',
'extra_specs'], formatters)
def do_flavor_list(cs, _args):
@ -334,7 +343,7 @@ def do_flavor_list(cs, _args):
for flavor in flavors:
# int needed for numerical sort
flavor.id = int(flavor.id)
_print_flavor_list(flavors)
_print_flavor_list(cs, flavors)
@utils.arg('id',
@ -351,7 +360,7 @@ def do_flavor_delete(cs, args):
def do_flavor_show(cs, args):
"""Show details about the given flavor."""
flavor = _find_flavor(cs, args.flavor)
_print_flavor(flavor)
_print_flavor(cs, flavor)
@utils.arg('name',
@ -391,7 +400,31 @@ def do_flavor_create(cs, args):
f = cs.flavors.create(args.name, args.ram, args.vcpus, args.disk, args.id,
args.ephemeral, args.swap, args.rxtx_factor,
args.is_public)
_print_flavor_list([f])
_print_flavor_list(cs, [f])
@utils.arg('flavor',
metavar='<flavor>',
help="Name or ID of flavor")
@utils.arg('action',
metavar='<action>',
choices=['set', 'unset'],
help="Actions: 'set' or 'unset'")
@utils.arg('metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help='Extra_specs to set/unset (only key is necessary on unset)')
def do_flavor_key(cs, args):
"""Set or unset extra_spec for a flavor."""
flavor = _find_flavor(cs, args.flavor)
keypair = _extract_metadata(args)
if args.action == 'set':
flavor.set_keys(keypair)
elif args.action == 'unset':
flavor.unset_keys(keypair.keys())
@utils.arg('--flavor',
@ -530,10 +563,11 @@ def _print_image(image):
utils.print_dict(info)
def _print_flavor(flavor):
def _print_flavor(cs, flavor):
info = flavor._info.copy()
# ignore links, we don't need to present those
info.pop('links')
info.update({"extra_specs": _print_flavor_extra_specs(flavor)})
utils.print_dict(info)

View File

@ -400,6 +400,24 @@ class FakeHTTPClient(base_client.HTTPClient):
def post_flavors(self, body, **kw):
return (202, {'flavor': self.get_flavors_detail()[1]['flavors'][0]})
def get_flavors_1_os_extra_specs(self, **kw):
return (200,
{'extra_specs': {"k1": "v1"}})
def get_flavors_2_os_extra_specs(self, **kw):
return (200,
{'extra_specs': {"k2": "v2"}})
def post_flavors_1_os_extra_specs(self, body, **kw):
assert body.keys() == ['extra_specs']
fakes.assert_has_keys(body['extra_specs'],
required=['k1'])
return (200,
{'extra_specs': {"k1": "v1"}})
def delete_flavors_1_os_extra_specs_k1(self, **kw):
return (204, None)
#
# Flavor access
#

View File

@ -91,3 +91,14 @@ class FlavorsTest(utils.TestCase):
def test_delete(self):
cs.flavors.delete("flavordelete")
cs.assert_called('DELETE', '/flavors/flavordelete')
def test_set_keys(self):
f = cs.flavors.get(1)
f.set_keys({'k1': 'v1'})
cs.assert_called('POST', '/flavors/1/os-extra_specs',
{"extra_specs": {'k1': 'v1'}})
def test_unset_keys(self):
f = cs.flavors.get(1)
f.unset_keys(['k1'])
cs.assert_called('DELETE', '/flavors/1/os-extra_specs/k1')

View File

@ -167,11 +167,12 @@ class ShellTest(utils.TestCase):
def test_flavor_list(self):
self.run_command('flavor-list')
self.assert_called('GET', '/flavors/2/os-extra_specs')
self.assert_called_anytime('GET', '/flavors/detail')
def test_flavor_show(self):
self.run_command('flavor-show 1')
self.assert_called('GET', '/flavors/1')
self.assert_called_anytime('GET', '/flavors/1')
def test_image_show(self):
self.run_command('image-show 1')
@ -389,8 +390,9 @@ class ShellTest(utils.TestCase):
}
}
self.assert_called('POST', '/flavors', body, pos=-2)
self.assert_called('GET', '/flavors/1')
self.assert_called('POST', '/flavors', pos=-3)
self.assert_called('GET', '/flavors/1', pos=-2)
self.assert_called('GET', '/flavors/1/os-extra_specs', pos=-1)
def test_aggregate_list(self):
self.run_command('aggregate-list')