Implement *-update commands to resrouces
Added node-update, chassis-update and port-update commands for v1. Change-Id: I051841fce2872f1e19f95117e8827d7a2b1b521c
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'}])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user