Implement *-update commands to resrouces

Added node-update, chassis-update and port-update commands for v1.

Change-Id: I051841fce2872f1e19f95117e8827d7a2b1b521c
This commit is contained in:
Lucas Alvares Gomes
2013-09-30 14:22:38 +01:00
parent 6752ae8307
commit cc66495430
12 changed files with 222 additions and 17 deletions

View File

@@ -71,8 +71,8 @@ class Manager(object):
return [obj_class(self, res, loaded=True) for res in data if res]
def _update(self, url, body, response_key=None):
resp, body = self.api.json_request('PUT', url, body=body)
# PUT requests may not return a body
resp, body = self.api.json_request('PATCH', url, body=body)
# PATCH requests may not return a body
if body:
return self.resource_class(self, body)

View File

@@ -177,11 +177,33 @@ def args_array_to_dict(kwargs, key_to_convert):
for v in values_to_convert)
except ValueError:
raise exc.CommandError(
'%s must be a list of key=value not "%s"' % (
'%s must be a list of KEY=VALUE not "%s"' % (
key_to_convert, values_to_convert))
return kwargs
def args_array_to_patch(op, attributes):
patch = []
for attr in attributes:
# Sanitize
if not attr.startswith('/'):
attr = '/' + attr
if op in ['add', 'replace']:
try:
path, value = attr.split("=", 1)
patch.append({'op': op, 'path': path, 'value': value})
except ValueError:
raise exc.CommandError('Attributes must be a list of '
'PATH=VALUE not "%s"' % attr)
elif op == "remove":
# For remove only the key is needed
patch.append({'op': op, 'path': attr})
else:
raise exc.CommandError('Unknown PATCH operation: %s' % op)
return patch
def exit(msg=''):
if msg:
print >> sys.stderr, msg

View File

@@ -18,6 +18,7 @@ import cStringIO
import sys
from ironicclient.common import utils
from ironicclient import exc
from ironicclient.tests import utils as test_utils
@@ -57,3 +58,35 @@ class UtilsTest(test_utils.BaseTestCase):
'matching_metadata': {'metadata.key': 'metadata_value'},
'other': 'value'
})
def test_args_array_to_patch(self):
my_args = {
'attributes': ['foo=bar', '/extra/bar=baz'],
'op': 'add',
}
patch = utils.args_array_to_patch(my_args['op'],
my_args['attributes'])
self.assertEqual(patch, [{'op': 'add',
'value': 'bar',
'path': '/foo'},
{'op': 'add',
'value': 'baz',
'path': '/extra/bar'}])
def test_args_array_to_patch_format_error(self):
my_args = {
'attributes': ['foobar'],
'op': 'add',
}
self.assertRaises(exc.CommandError, utils.args_array_to_patch,
my_args['op'], my_args['attributes'])
def test_args_array_to_patch_remove(self):
my_args = {
'attributes': ['/foo', 'extra/bar'],
'op': 'remove',
}
patch = utils.args_array_to_patch(my_args['op'],
my_args['attributes'])
self.assertEqual(patch, [{'op': 'remove', 'path': '/foo'},
{'op': 'remove', 'path': '/extra/bar'}])

View File

@@ -32,6 +32,10 @@ CREATE_CHASSIS = copy.deepcopy(CHASSIS)
del CREATE_CHASSIS['id']
del CREATE_CHASSIS['uuid']
UPDATED_CHASSIS = copy.deepcopy(CHASSIS)
NEW_DESCR = 'new-description'
UPDATED_CHASSIS['description'] = NEW_DESCR
fixtures = {
'/v1/chassis':
{
@@ -54,6 +58,11 @@ fixtures = {
{},
None,
),
'PATCH': (
{},
UPDATED_CHASSIS,
),
},
}
@@ -97,3 +106,14 @@ class ChassisManagerTest(testtools.TestCase):
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(chassis is None)
def test_update(self):
patch = {'op': 'replace',
'value': NEW_DESCR,
'path': '/description'}
chassis = self.mgr.update(chassis_id=CHASSIS['uuid'], patch=patch)
expect = [
('PATCH', '/v1/chassis/%s' % CHASSIS['uuid'], {}, patch),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(chassis.description, NEW_DESCR)

View File

@@ -34,6 +34,9 @@ CREATE_NODE = copy.deepcopy(NODE)
del CREATE_NODE['id']
del CREATE_NODE['uuid']
UPDATED_NODE = copy.deepcopy(NODE)
NEW_DRIVER = 'new-driver'
UPDATED_NODE['driver'] = NEW_DRIVER
fixtures = {
'/v1/nodes':
@@ -57,6 +60,10 @@ fixtures = {
{},
None,
),
'PATCH': (
{},
UPDATED_NODE,
),
},
}
@@ -99,3 +106,14 @@ class NodeManagerTest(testtools.TestCase):
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(node is None)
def test_update(self):
patch = {'op': 'replace',
'value': NEW_DRIVER,
'path': '/driver'}
node = self.mgr.update(node_id=NODE['uuid'], patch=patch)
expect = [
('PATCH', '/v1/nodes/%s' % NODE['uuid'], {}, patch),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(node.driver, NEW_DRIVER)

View File

@@ -33,6 +33,10 @@ CREATE_PORT = copy.deepcopy(PORT)
del CREATE_PORT['id']
del CREATE_PORT['uuid']
UPDATED_PORT = copy.deepcopy(PORT)
NEW_ADDR = 'AA:AA:AA:AA:AA:AA'
UPDATED_PORT['address'] = NEW_ADDR
fixtures = {
'/v1/ports':
{
@@ -55,6 +59,10 @@ fixtures = {
{},
None,
),
'PATCH': (
{},
UPDATED_PORT,
),
},
}
@@ -98,3 +106,14 @@ class PortManagerTest(testtools.TestCase):
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(port is None)
def test_update(self):
patch = {'op': 'replace',
'value': NEW_ADDR,
'path': '/address'}
port = self.mgr.update(port_id=PORT['uuid'], patch=patch)
expect = [
('PATCH', '/v1/ports/%s' % PORT['uuid'], {}, patch),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(port.address, NEW_ADDR)

View File

@@ -53,3 +53,6 @@ class ChassisManager(base.Manager):
def delete(self, chassis_id):
return self._delete(self._path(chassis_id))
def update(self, chassis_id, patch):
return self._update(self._path(chassis_id), patch)

View File

@@ -20,6 +20,12 @@ from ironicclient.common import utils
from ironicclient import exc
def _print_chassis_show(chassis):
fields = ['uuid', 'description', 'extra']
data = dict([(f, getattr(chassis, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
@utils.arg('chassis', metavar='<chassis>', help="ID of chassis")
def do_chassis_show(cc, args):
"""Show a chassis."""
@@ -28,9 +34,7 @@ def do_chassis_show(cc, args):
except exc.HTTPNotFound:
raise exc.CommandError('Chassis not found: %s' % args.chassis)
else:
fields = ['uuid', 'description', 'extra']
data = dict([(f, getattr(chassis, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
_print_chassis_show(chassis)
def do_chassis_list(cc, args):
@@ -85,3 +89,27 @@ def do_node_show(cc, args):
'created_at', 'updated_at', 'reservation']
data = dict([(f, getattr(node, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
@utils.arg('chassis',
metavar='<CHASSIS>',
help="ID of chassis")
@utils.arg('op',
metavar='<OP>',
choices=['add', 'replace', 'remove'],
help="Operations: 'add', 'replace' or 'remove'")
@utils.arg('attributes',
metavar='<PATH=VALUE>',
nargs='+',
action='append',
default=[],
help="Attributes to add/replace or remove "
"(only PATH is necessary on remove)")
def do_chassis_update(cc, args):
"""Update a chassis."""
patch = utils.args_array_to_patch(args.op, args.attributes[0])
try:
chassis = cc.chassis.update(args.chassis, patch)
except exc.HTTPNotFound:
raise exc.CommandError('Chassis not found: %s' % args.chassis)
_print_chassis_show(chassis)

View File

@@ -54,3 +54,6 @@ class NodeManager(base.Manager):
def delete(self, node_id):
return self._delete(self._path(node_id))
def update(self, node_id, patch):
return self._update(self._path(node_id), patch)

View File

@@ -20,6 +20,15 @@ from ironicclient.common import utils
from ironicclient import exc
def _print_node_show(node):
fields = ['uuid', 'instance_uuid', 'power_state', 'target_power_state',
'provision_state', 'target_provision_state', 'driver',
'driver_info', 'properties', 'extra',
'created_at', 'updated_at', 'reservation']
data = dict([(f, getattr(node, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
@utils.arg('node', metavar='<node>', help="ID of node")
def do_node_show(cc, args):
"""Show a node."""
@@ -28,12 +37,7 @@ def do_node_show(cc, args):
except exc.HTTPNotFound:
raise exc.CommandError('Node not found: %s' % args.node)
else:
fields = ['uuid', 'instance_uuid', 'power_state', 'target_power_state',
'provision_state', 'target_provision_state', 'driver',
'driver_info', 'properties', 'extra',
'created_at', 'updated_at', 'reservation']
data = dict([(f, getattr(node, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
_print_node_show(node)
def do_node_list(cc, args):
@@ -86,3 +90,27 @@ def do_node_delete(cc, args):
cc.node.delete(args.node)
except exc.HTTPNotFound:
raise exc.CommandError('Node not found: %s' % args.node)
@utils.arg('node',
metavar='<NODE>',
help="ID of node")
@utils.arg('op',
metavar='<OP>',
choices=['add', 'replace', 'remove'],
help="Operations: 'add', 'replace' or 'remove'")
@utils.arg('attributes',
metavar='<PATH=VALUE>',
nargs='+',
action='append',
default=[],
help="Attributes to add/replace or remove "
"(only PATH is necessary on remove)")
def do_node_update(cc, args):
"""Update a node."""
patch = utils.args_array_to_patch(args.op, args.attributes[0])
try:
node = cc.node.update(args.node, patch)
except exc.HTTPNotFound:
raise exc.CommandError('Node not found: %s' % args.node)
_print_node_show(node)

View File

@@ -52,3 +52,6 @@ class PortManager(base.Manager):
def delete(self, port_id):
return self._delete(self._path(port_id))
def update(self, port_id, patch):
return self._update(self._path(port_id), patch)

View File

@@ -20,7 +20,13 @@ from ironicclient.common import utils
from ironicclient import exc
@utils.arg('port', metavar='<port>', help="ID of port")
def _print_port_show(port):
fields = ['uuid', 'address', 'extra']
data = dict([(f, getattr(port, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
@utils.arg('port', metavar='<PORT>', help="ID of port")
def do_port_show(cc, args):
"""Show a port."""
try:
@@ -28,9 +34,7 @@ def do_port_show(cc, args):
except exc.HTTPNotFound:
raise exc.CommandError('Port not found: %s' % args.port)
else:
fields = ['uuid', 'address', 'extra']
data = dict([(f, getattr(port, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
_print_port_show(port)
def do_port_list(cc, args):
@@ -48,7 +52,7 @@ def do_port_list(cc, args):
metavar='<NODE_ID>',
help='ID of the node that this port belongs to.')
@utils.arg('--extra',
metavar="<key=value>",
metavar="<KEY=VALUE>",
action='append',
help="Record arbitrary key/value metadata. "
"Can be specified multiple times.")
@@ -72,3 +76,27 @@ def do_port_delete(cc, args):
cc.port.delete(args.port)
except exc.HTTPNotFound:
raise exc.CommandError('Port not found: %s' % args.port)
@utils.arg('port',
metavar='<PORT>',
help="ID of port")
@utils.arg('op',
metavar='<OP>',
choices=['add', 'replace', 'remove'],
help="Operations: 'add', 'replace' or 'remove'")
@utils.arg('attributes',
metavar='<PATH=VALUE>',
nargs='+',
action='append',
default=[],
help="Attributes to add/replace or remove "
"(only PATH is necessary on remove)")
def do_port_update(cc, args):
"""Update a port."""
patch = utils.args_array_to_patch(args.op, args.attributes[0])
try:
port = cc.port.update(args.port, patch)
except exc.HTTPNotFound:
raise exc.CommandError('Port not found: %s' % args.port)
_print_port_show(port)