Merge "Add volume_type extra_specs support to client"
This commit is contained in:
@@ -17,9 +17,7 @@ class Client(object):
|
||||
|
||||
Then call methods on its managers::
|
||||
|
||||
>>> client.servers.list()
|
||||
...
|
||||
>>> client.flavors.list()
|
||||
>>> client.volumes.list()
|
||||
...
|
||||
|
||||
"""
|
||||
|
@@ -20,6 +20,7 @@ import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from cinderclient import exceptions
|
||||
from cinderclient import utils
|
||||
|
||||
|
||||
@@ -91,14 +92,17 @@ def _translate_volume_snapshot_keys(collection):
|
||||
setattr(item, to_key, item._info[from_key])
|
||||
|
||||
|
||||
def _extract_metadata(arg_list):
|
||||
def _extract_metadata(args):
|
||||
metadata = {}
|
||||
for metadatum in arg_list:
|
||||
assert(metadatum.find('=') > -1), "Improperly formatted metadata "\
|
||||
"input (%s)" % metadatum
|
||||
(key, value) = metadatum.split('=', 1)
|
||||
metadata[key] = value
|
||||
for metadatum in args.metadata[0]:
|
||||
# unset doesn't require a val, so we have the if/else
|
||||
if '=' in metadatum:
|
||||
(key, value) = metadatum.split('=', 1)
|
||||
else:
|
||||
key = metadatum
|
||||
value = None
|
||||
|
||||
metadata[key] = value
|
||||
return metadata
|
||||
|
||||
|
||||
@@ -219,7 +223,7 @@ def do_create(cs, args):
|
||||
|
||||
volume_metadata = None
|
||||
if args.metadata is not None:
|
||||
volume_metadata = _extract_metadata(args.metadata)
|
||||
volume_metadata = _extract_metadata(args)
|
||||
|
||||
volume = cs.volumes.create(args.size,
|
||||
args.snapshot_id,
|
||||
@@ -375,7 +379,9 @@ def do_snapshot_rename(cs, args):
|
||||
|
||||
|
||||
def _print_volume_type_list(vtypes):
|
||||
utils.print_list(vtypes, ['ID', 'Name'])
|
||||
#_translate_type_keys(vtypes)
|
||||
formatters = {'extra_specs': _print_type_extra_specs}
|
||||
utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters)
|
||||
|
||||
|
||||
@utils.service_type('volume')
|
||||
@@ -387,7 +393,7 @@ def do_type_list(cs, args):
|
||||
|
||||
@utils.arg('name',
|
||||
metavar='<name>',
|
||||
help="Name of the new flavor")
|
||||
help="Name of the new volume type")
|
||||
@utils.service_type('volume')
|
||||
def do_type_create(cs, args):
|
||||
"""Create a new volume type."""
|
||||
@@ -400,10 +406,35 @@ def do_type_create(cs, args):
|
||||
help="Unique ID of the volume type to delete")
|
||||
@utils.service_type('volume')
|
||||
def do_type_delete(cs, args):
|
||||
"""Delete a specific flavor"""
|
||||
"""Delete a specific volume type"""
|
||||
cs.volume_types.delete(args.id)
|
||||
|
||||
|
||||
@utils.arg('vtype',
|
||||
metavar='<vtype>',
|
||||
help="Name or ID of the volume type")
|
||||
@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)')
|
||||
@utils.service_type('volume')
|
||||
def do_type_key(cs, args):
|
||||
"Set or unset extra_spec for a volume type."""
|
||||
vtype = _find_volume_type(cs, args.vtype)
|
||||
keypair = _extract_metadata(args)
|
||||
|
||||
if args.action == 'set':
|
||||
vtype.set_keys(keypair)
|
||||
elif args.action == 'unset':
|
||||
vtype.unset_keys(keypair.keys())
|
||||
|
||||
|
||||
def do_endpoints(cs, args):
|
||||
"""Discover endpoints that get returned from the authenticate services"""
|
||||
catalog = cs.client.service_catalog.catalog
|
||||
@@ -513,3 +544,15 @@ def do_rate_limits(cs, args):
|
||||
limits = cs.limits.get().rate
|
||||
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
|
||||
utils.print_list(limits, columns)
|
||||
|
||||
|
||||
def _print_type_extra_specs(vol_type):
|
||||
try:
|
||||
return vol_type.get_keys()
|
||||
except exceptions.NotFound:
|
||||
return "N/A"
|
||||
|
||||
|
||||
def _find_volume_type(cs, vtype):
|
||||
"""Get a volume type by name or ID."""
|
||||
return utils.find_resource(cs.volume_types, vtype)
|
||||
|
@@ -26,7 +26,52 @@ class VolumeType(base.Resource):
|
||||
A Volume Type is the type of volume to be created
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "<Volume Type: %s>" % self.name
|
||||
return "<VolumeType: %s>" % self.name
|
||||
|
||||
def get_keys(self):
|
||||
"""
|
||||
Get extra specs from a volume type.
|
||||
|
||||
:param vol_type: The :class:`VolumeType` to get extra specs from
|
||||
"""
|
||||
_resp, body = self.manager.api.client.get(
|
||||
"/types/%s/extra_specs" %
|
||||
base.getid(self))
|
||||
return body["extra_specs"]
|
||||
|
||||
def set_keys(self, metadata):
|
||||
"""
|
||||
Set extra specs on a volume type.
|
||||
|
||||
:param type : The :class:`VolumeType` to set extra spec on
|
||||
:param metadata: A dict of key/value pairs to be set
|
||||
"""
|
||||
body = {'extra_specs': metadata}
|
||||
return self.manager._create(
|
||||
"/types/%s/extra_specs" % base.getid(self),
|
||||
body,
|
||||
"extra_specs",
|
||||
return_raw=True)
|
||||
|
||||
def unset_keys(self, keys):
|
||||
"""
|
||||
Unset extra specs on a volue type.
|
||||
|
||||
:param type_id: The :class:`VolumeType` to unset extra spec on
|
||||
:param keys: A list of keys to be unset
|
||||
"""
|
||||
|
||||
# NOTE(jdg): This wasn't actually doing all of the keys before
|
||||
# the return in the loop resulted in ony ONE key being unset.
|
||||
# since on success the return was NONE, we'll only interrupt the loop
|
||||
# and return if there's an error
|
||||
result = None
|
||||
for k in keys:
|
||||
resp = self.manager._delete(
|
||||
"/types/%s/extra_specs/%s" % (
|
||||
base.getid(self), k))
|
||||
if resp is not None:
|
||||
return resp
|
||||
|
||||
|
||||
class VolumeTypeManager(base.ManagerWithFind):
|
||||
|
@@ -227,3 +227,35 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'metadata_items': [],
|
||||
'volumes': 2,
|
||||
'gigabytes': 1}})
|
||||
|
||||
#
|
||||
# VolumeTypes
|
||||
#
|
||||
def get_types(self, **kw):
|
||||
return (200, {
|
||||
'volume_types': [{'id': 1,
|
||||
'name': 'test-type-1',
|
||||
'extra_specs':{}},
|
||||
{'id': 2,
|
||||
'name': 'test-type-2',
|
||||
'extra_specs':{}}]})
|
||||
|
||||
def get_types_1(self, **kw):
|
||||
return (200, {'volume_type': {'id': 1,
|
||||
'name': 'test-type-1',
|
||||
'extra_specs': {}}})
|
||||
|
||||
def post_types(self, body, **kw):
|
||||
return (202, {'volume_type': {'id': 3,
|
||||
'name': 'test-type-3',
|
||||
'extra_specs': {}}})
|
||||
|
||||
def post_types_1_extra_specs(self, body, **kw):
|
||||
assert body.keys() == ['extra_specs']
|
||||
return (200, {'extra_specs': {'k': 'v'}})
|
||||
|
||||
def delete_types_1_extra_specs_k(self, **kw):
|
||||
return(204, None)
|
||||
|
||||
def delete_types_1(self, **kw):
|
||||
return (202, None)
|
||||
|
35
tests/v1/test_types.py
Normal file
35
tests/v1/test_types.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from cinderclient import exceptions
|
||||
from cinderclient.v1 import volume_types
|
||||
from tests import utils
|
||||
from tests.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
||||
|
||||
class TypesTest(utils.TestCase):
|
||||
def test_list_types(self):
|
||||
tl = cs.volume_types.list()
|
||||
cs.assert_called('GET', '/types')
|
||||
for t in tl:
|
||||
self.assertTrue(isinstance(t, volume_types.VolumeType))
|
||||
|
||||
def test_create(self):
|
||||
t = cs.volume_types.create('test-type-3')
|
||||
cs.assert_called('POST', '/types')
|
||||
self.assertTrue(isinstance(t, volume_types.VolumeType))
|
||||
|
||||
def test_set_key(self):
|
||||
t = cs.volume_types.get(1)
|
||||
t.set_keys({'k': 'v'})
|
||||
cs.assert_called('POST',
|
||||
'/types/1/extra_specs',
|
||||
{'extra_specs': {'k': 'v'}})
|
||||
|
||||
def test_unsset_keys(self):
|
||||
t = cs.volume_types.get(1)
|
||||
t.unset_keys(['k'])
|
||||
cs.assert_called('DELETE', '/types/1/extra_specs/k')
|
||||
|
||||
def test_delete(self):
|
||||
cs.volume_types.delete(1)
|
||||
cs.assert_called('DELETE', '/types/1')
|
Reference in New Issue
Block a user