Merge "VendorPassthru commands to support different HTTP methods"

This commit is contained in:
Jenkins 2014-12-08 18:23:00 +00:00 committed by Gerrit Code Review
commit 8a07fdffb2
8 changed files with 185 additions and 48 deletions

View File

@ -19,9 +19,9 @@ import mock
import testtools
from testtools import matchers
from ironicclient.common import base
from ironicclient import exc
from ironicclient.tests import utils
import ironicclient.v1.driver
from ironicclient.v1 import driver
DRIVER1 = {'name': 'fake', 'hosts': ['fake-host1', 'fake-host2']}
@ -63,7 +63,7 @@ class DriverManagerTest(testtools.TestCase):
def setUp(self):
super(DriverManagerTest, self).setUp()
self.api = utils.FakeAPI(fake_responses)
self.mgr = ironicclient.v1.driver.DriverManager(self.api)
self.mgr = driver.DriverManager(self.api)
def test_driver_list(self):
drivers = self.mgr.list()
@ -74,13 +74,13 @@ class DriverManagerTest(testtools.TestCase):
self.assertThat(drivers, matchers.HasLength(1))
def test_driver_show(self):
driver = self.mgr.get(DRIVER1['name'])
driver_ = self.mgr.get(DRIVER1['name'])
expect = [
('GET', '/v1/drivers/%s' % DRIVER1['name'], {}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(DRIVER1['name'], driver.name)
self.assertEqual(DRIVER1['hosts'], driver.hosts)
self.assertEqual(DRIVER1['name'], driver_.name)
self.assertEqual(DRIVER1['hosts'], driver_.hosts)
def test_driver_properties(self):
properties = self.mgr.properties(DRIVER2['name'])
@ -90,8 +90,8 @@ class DriverManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(DRIVER2_PROPERTIES, properties)
@mock.patch.object(base.Manager, '_update')
def test_vendor_passthru(self, update_mock):
@mock.patch.object(driver.DriverManager, 'update')
def test_vendor_passthru_update(self, update_mock):
# For now just mock the tests because vendor-passthru doesn't return
# anything to verify.
vendor_passthru_args = {'arg1': 'val1'}
@ -100,9 +100,46 @@ class DriverManagerTest(testtools.TestCase):
'method': 'method',
'args': vendor_passthru_args
}
self.mgr.vendor_passthru(**kwargs)
final_path = '/v1/drivers/driver_name/vendor_passthru/method'
update_mock.assert_once_called_with(final_path,
vendor_passthru_args,
method='POST')
final_path = 'driver_name/vendor_passthru/method'
for http_method in ('POST', 'PUT', 'PATCH'):
kwargs['http_method'] = http_method
self.mgr.vendor_passthru(**kwargs)
update_mock.assert_called_once_with(final_path,
vendor_passthru_args,
http_method=http_method)
update_mock.reset_mock()
@mock.patch.object(driver.DriverManager, 'get')
def test_vendor_passthru_get(self, get_mock):
kwargs = {
'driver_name': 'driver_name',
'method': 'method',
'http_method': 'GET',
}
final_path = 'driver_name/vendor_passthru/method'
self.mgr.vendor_passthru(**kwargs)
get_mock.assert_called_once_with(final_path)
@mock.patch.object(driver.DriverManager, 'delete')
def test_vendor_passthru_delete(self, delete_mock):
kwargs = {
'driver_name': 'driver_name',
'method': 'method',
'http_method': 'DELETE',
}
final_path = 'driver_name/vendor_passthru/method'
self.mgr.vendor_passthru(**kwargs)
delete_mock.assert_called_once_with(final_path)
@mock.patch.object(driver.DriverManager, 'delete')
def test_vendor_passthru_unknown_http_method(self, delete_mock):
kwargs = {
'driver_name': 'driver_name',
'method': 'method',
'http_method': 'UNKNOWN',
}
self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru,
**kwargs)

View File

@ -36,21 +36,24 @@ class DriverShellTest(utils.BaseTestCase):
client_mock = mock.MagicMock()
args = mock.MagicMock()
args.driver_name = 'driver_name'
args.http_method = 'POST'
args.method = 'method'
args.arguments = [['arg1=val1', 'arg2=val2']]
d_shell.do_driver_vendor_passthru(client_mock, args)
client_mock.driver.vendor_passthru.assert_called_once_with(
args.driver_name, args.method,
args.driver_name, args.method, http_method=args.http_method,
args={'arg1': 'val1', 'arg2': 'val2'})
def test_do_driver_vendor_passthru_without_args(self):
client_mock = mock.MagicMock()
args = mock.MagicMock()
args.driver_name = 'driver_name'
args.http_method = 'POST'
args.method = 'method'
args.arguments = [[]]
d_shell.do_driver_vendor_passthru(client_mock, args)
client_mock.driver.vendor_passthru.assert_called_once_with(
args.driver_name, args.method, args={})
args.driver_name, args.method, args={},
http_method=args.http_method)

View File

@ -20,7 +20,7 @@ import mock
import testtools
from testtools.matchers import HasLength
from ironicclient.common import base
from ironicclient import exc
from ironicclient.tests import utils
from ironicclient.v1 import node
@ -648,8 +648,8 @@ class NodeManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(CONSOLE_DATA_DISABLED, info)
@mock.patch.object(base.Manager, '_update')
def test_vendor_passthru(self, update_mock):
@mock.patch.object(node.NodeManager, 'update')
def test_vendor_passthru_update(self, update_mock):
# For now just mock the tests because vendor-passthru doesn't return
# anything to verify.
vendor_passthru_args = {'arg1': 'val1'}
@ -658,12 +658,49 @@ class NodeManagerTest(testtools.TestCase):
'method': 'method',
'args': vendor_passthru_args
}
self.mgr.vendor_passthru(**kwargs)
final_path = '/v1/nodes/node_uuid/vendor_passthru/method'
update_mock.assert_once_called_with(final_path,
vendor_passthru_args,
method='POST')
final_path = 'node_uuid/vendor_passthru/method'
for http_method in ('POST', 'PUT', 'PATCH'):
kwargs['http_method'] = http_method
self.mgr.vendor_passthru(**kwargs)
update_mock.assert_called_once_with(final_path,
vendor_passthru_args,
http_method=http_method)
update_mock.reset_mock()
@mock.patch.object(node.NodeManager, 'get')
def test_vendor_passthru_get(self, get_mock):
kwargs = {
'node_id': 'node_uuid',
'method': 'method',
'http_method': 'GET',
}
final_path = 'node_uuid/vendor_passthru/method'
self.mgr.vendor_passthru(**kwargs)
get_mock.assert_called_once_with(final_path)
@mock.patch.object(node.NodeManager, 'delete')
def test_vendor_passthru_delete(self, delete_mock):
kwargs = {
'node_id': 'node_uuid',
'method': 'method',
'http_method': 'DELETE',
}
final_path = 'node_uuid/vendor_passthru/method'
self.mgr.vendor_passthru(**kwargs)
delete_mock.assert_called_once_with(final_path)
@mock.patch.object(node.NodeManager, 'delete')
def test_vendor_passthru_unknown_http_method(self, delete_mock):
kwargs = {
'node_id': 'node_uuid',
'method': 'method',
'http_method': 'UNKNOWN',
}
self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru,
**kwargs)
def _test_node_set_boot_device(self, boot_device, persistent=False):
self.mgr.set_boot_device(NODE1['uuid'], boot_device, persistent)

View File

@ -230,23 +230,26 @@ class NodeShellTest(utils.BaseTestCase):
client_mock = mock.MagicMock()
args = mock.MagicMock()
args.node = 'node_uuid'
args.http_method = 'POST'
args.method = 'method'
args.arguments = [['arg1=val1', 'arg2=val2']]
n_shell.do_node_vendor_passthru(client_mock, args)
client_mock.node.vendor_passthru.assert_called_once_with(
args.node, args.method, args={'arg1': 'val1', 'arg2': 'val2'})
args.node, args.method, args={'arg1': 'val1', 'arg2': 'val2'},
http_method=args.http_method)
def test_do_node_vendor_passthru_without_args(self):
client_mock = mock.MagicMock()
args = mock.MagicMock()
args.node = 'node_uuid'
args.http_method = 'POST'
args.method = 'method'
args.arguments = [[]]
n_shell.do_node_vendor_passthru(client_mock, args)
client_mock.node.vendor_passthru.assert_called_once_with(
args.node, args.method, args={})
args.node, args.method, args={}, http_method=args.http_method)
def test_do_node_set_provision_state_active(self):
client_mock = mock.MagicMock()

View File

@ -16,6 +16,7 @@
# under the License.
from ironicclient.common import base
from ironicclient import exc
class Driver(base.Resource):
@ -35,6 +36,13 @@ class DriverManager(base.Manager):
except IndexError:
return None
def update(self, driver_name, patch, http_method='PATCH'):
path = '/v1/drivers/%s' % driver_name
return self._update(path, patch, method=http_method)
def delete(self, driver_name):
return self._delete('/v1/drivers/%s' % driver_name)
def properties(self, driver_name):
try:
info = self._list('/v1/drivers/%s/properties' % driver_name)[0]
@ -44,19 +52,31 @@ class DriverManager(base.Manager):
except IndexError:
return {}
def vendor_passthru(self, driver_name, method, args=None):
def vendor_passthru(self, driver_name, method, args=None,
http_method=None):
"""Issue requests for vendor-specific actions on a given driver.
:param driver_name: Name of the driver.
:param method: Name of the vendor method.
:param args: Optional. The arguments to be passed to the method.
:param http_method: The HTTP method to use on the request.
Defaults to POST.
"""
if args is None:
args = {}
path = "/v1/drivers/%(driver_name)s/vendor_passthru/%(method)s" % {
'driver_name': driver_name,
'method': method
}
return self._update(path, args, method='POST')
if http_method is None:
http_method = 'POST'
http_method = http_method.upper()
path = "%s/vendor_passthru/%s" % (driver_name, method)
if http_method in ('POST', 'PUT', 'PATCH'):
return self.update(path, args, http_method=http_method)
elif http_method == 'DELETE':
return self.delete(path)
elif http_method == 'GET':
return self.get(path)
else:
raise exc.InvalidAttribute(
_('Unknown HTTP method: %s') % http_method)

View File

@ -70,6 +70,12 @@ def do_driver_properties(cc, args):
action='append',
default=[],
help="arguments to be passed to vendor-passthru method")
@cliutils.arg('--http_method',
metavar='<http_method>',
choices=['POST', 'PUT', 'GET', 'DELETE', 'PATCH'],
help="The HTTP method to use in the request. Valid HTTP "
"methods are: 'POST', 'PUT', 'GET', 'DELETE', 'PATCH'. "
"Defaults to 'POST'.")
def do_driver_vendor_passthru(cc, args):
"""Call a vendor-passthru extension for a driver."""
arguments = utils.args_array_to_dict({'args': args.arguments[0]},
@ -80,4 +86,9 @@ def do_driver_vendor_passthru(cc, args):
if not arguments:
arguments = {}
cc.driver.vendor_passthru(args.driver_name, args.method, args=arguments)
resp = cc.driver.vendor_passthru(args.driver_name, args.method,
http_method=args.http_method,
args=arguments)
if resp:
# Print the raw response we don't know how it should be formated
print(str(resp.to_dict()))

View File

@ -162,22 +162,37 @@ 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)
def update(self, node_id, patch, http_method='PATCH'):
return self._update(self._path(node_id), patch, method=http_method)
def vendor_passthru(self, node_id, method, args=None):
def vendor_passthru(self, node_id, method, args=None, http_method=None):
"""Issue requests for vendor-specific actions on a given node.
:param node_id: The UUID of the node.
:param method: Name of the vendor method.
:param args: Optional. The arguments to be passed to the method.
:param http_method: The HTTP method to use on the request.
Defaults to POST.
"""
if args is None:
args = {}
path = self._path(node_id) + "/vendor_passthru/%s" % method
return self._update(path, args, method='POST')
if http_method is None:
http_method = 'POST'
http_method = http_method.upper()
path = "%s/vendor_passthru/%s" % (node_id, method)
if http_method in ('POST', 'PUT', 'PATCH'):
return self.update(path, args, http_method=http_method)
elif http_method == 'DELETE':
return self.delete(path)
elif http_method == 'GET':
return self.get(path)
else:
raise exc.InvalidAttribute(
_('Unknown HTTP method: %s') % http_method)
def set_maintenance(self, node_id, state, maint_reason=None):
path = "%s/maintenance" % node_id

View File

@ -183,17 +183,23 @@ def do_node_update(cc, args):
@cliutils.arg('node',
metavar='<node id>',
help="UUID of node")
metavar='<node id>',
help="UUID of node")
@cliutils.arg('method',
metavar='<method>',
help="vendor-passthru method to be called")
metavar='<method>',
help="vendor-passthru method to be called")
@cliutils.arg('arguments',
metavar='<arg=value>',
nargs='*',
action='append',
default=[],
help="arguments to be passed to vendor-passthru method")
metavar='<arg=value>',
nargs='*',
action='append',
default=[],
help="arguments to be passed to vendor-passthru method")
@cliutils.arg('--http_method',
metavar='<http_method>',
choices=['POST', 'PUT', 'GET', 'DELETE', 'PATCH'],
help="The HTTP method to use in the request. Valid HTTP "
"methods are: 'POST', 'PUT', 'GET', 'DELETE', 'PATCH'. "
"Defaults to 'POST'.")
def do_node_vendor_passthru(cc, args):
"""Call a vendor-passthru extension for a node."""
arguments = utils.args_array_to_dict({'args': args.arguments[0]},
@ -204,7 +210,12 @@ def do_node_vendor_passthru(cc, args):
if not arguments:
arguments = {}
cc.node.vendor_passthru(args.node, args.method, args=arguments)
resp = cc.node.vendor_passthru(args.node, args.method,
http_method=args.http_method,
args=arguments)
if resp:
# Print the raw response we don't know how it should be formated
print(str(resp.to_dict()))
@cliutils.arg(