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::
|
Then call methods on its managers::
|
||||||
|
|
||||||
>>> client.servers.list()
|
>>> client.volumes.list()
|
||||||
...
|
|
||||||
>>> client.flavors.list()
|
|
||||||
...
|
...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@@ -20,6 +20,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from cinderclient import exceptions
|
||||||
from cinderclient import utils
|
from cinderclient import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -91,14 +92,17 @@ def _translate_volume_snapshot_keys(collection):
|
|||||||
setattr(item, to_key, item._info[from_key])
|
setattr(item, to_key, item._info[from_key])
|
||||||
|
|
||||||
|
|
||||||
def _extract_metadata(arg_list):
|
def _extract_metadata(args):
|
||||||
metadata = {}
|
metadata = {}
|
||||||
for metadatum in arg_list:
|
for metadatum in args.metadata[0]:
|
||||||
assert(metadatum.find('=') > -1), "Improperly formatted metadata "\
|
# unset doesn't require a val, so we have the if/else
|
||||||
"input (%s)" % metadatum
|
if '=' in metadatum:
|
||||||
(key, value) = metadatum.split('=', 1)
|
(key, value) = metadatum.split('=', 1)
|
||||||
metadata[key] = value
|
else:
|
||||||
|
key = metadatum
|
||||||
|
value = None
|
||||||
|
|
||||||
|
metadata[key] = value
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
@@ -219,7 +223,7 @@ def do_create(cs, args):
|
|||||||
|
|
||||||
volume_metadata = None
|
volume_metadata = None
|
||||||
if args.metadata is not None:
|
if args.metadata is not None:
|
||||||
volume_metadata = _extract_metadata(args.metadata)
|
volume_metadata = _extract_metadata(args)
|
||||||
|
|
||||||
volume = cs.volumes.create(args.size,
|
volume = cs.volumes.create(args.size,
|
||||||
args.snapshot_id,
|
args.snapshot_id,
|
||||||
@@ -375,7 +379,9 @@ def do_snapshot_rename(cs, args):
|
|||||||
|
|
||||||
|
|
||||||
def _print_volume_type_list(vtypes):
|
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')
|
@utils.service_type('volume')
|
||||||
@@ -387,7 +393,7 @@ def do_type_list(cs, args):
|
|||||||
|
|
||||||
@utils.arg('name',
|
@utils.arg('name',
|
||||||
metavar='<name>',
|
metavar='<name>',
|
||||||
help="Name of the new flavor")
|
help="Name of the new volume type")
|
||||||
@utils.service_type('volume')
|
@utils.service_type('volume')
|
||||||
def do_type_create(cs, args):
|
def do_type_create(cs, args):
|
||||||
"""Create a new volume type."""
|
"""Create a new volume type."""
|
||||||
@@ -400,10 +406,35 @@ def do_type_create(cs, args):
|
|||||||
help="Unique ID of the volume type to delete")
|
help="Unique ID of the volume type to delete")
|
||||||
@utils.service_type('volume')
|
@utils.service_type('volume')
|
||||||
def do_type_delete(cs, args):
|
def do_type_delete(cs, args):
|
||||||
"""Delete a specific flavor"""
|
"""Delete a specific volume type"""
|
||||||
cs.volume_types.delete(args.id)
|
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):
|
def do_endpoints(cs, args):
|
||||||
"""Discover endpoints that get returned from the authenticate services"""
|
"""Discover endpoints that get returned from the authenticate services"""
|
||||||
catalog = cs.client.service_catalog.catalog
|
catalog = cs.client.service_catalog.catalog
|
||||||
@@ -513,3 +544,15 @@ def do_rate_limits(cs, args):
|
|||||||
limits = cs.limits.get().rate
|
limits = cs.limits.get().rate
|
||||||
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
|
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
|
||||||
utils.print_list(limits, columns)
|
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
|
A Volume Type is the type of volume to be created
|
||||||
"""
|
"""
|
||||||
def __repr__(self):
|
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):
|
class VolumeTypeManager(base.ManagerWithFind):
|
||||||
|
@@ -227,3 +227,35 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
'metadata_items': [],
|
'metadata_items': [],
|
||||||
'volumes': 2,
|
'volumes': 2,
|
||||||
'gigabytes': 1}})
|
'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