Verify input uuid

In introspection and discover API, verify input uuid.

Change-Id: Ib49adf8b28a3d6c4a5f2c04d7757468c984bd184
Closes-Bug: #1424598
This commit is contained in:
YuikoTakada 2015-02-27 06:49:06 +00:00
parent 3d1715a8ab
commit 04e060ef45
8 changed files with 81 additions and 41 deletions

View File

@ -18,6 +18,7 @@ import argparse
import functools import functools
import json import json
import logging import logging
from oslo_utils import uuidutils
import sys import sys
import flask import flask
@ -77,6 +78,9 @@ def api_continue():
def api_introspection(uuid): def api_introspection(uuid):
check_auth() check_auth()
if not uuidutils.is_uuid_like(uuid):
raise utils.Error(_('Invalid UUID value'), code=400)
if flask.request.method == 'POST': if flask.request.method == 'POST':
new_ipmi_password = flask.request.args.get('new_ipmi_password', new_ipmi_password = flask.request.args.get('new_ipmi_password',
type=str, type=str,
@ -105,6 +109,10 @@ def api_discover():
data = flask.request.get_json(force=True) data = flask.request.get_json(force=True)
LOG.debug("/v1/discover got JSON %s", data) LOG.debug("/v1/discover got JSON %s", data)
for uuid in data:
if not uuidutils.is_uuid_like(uuid):
raise utils.Error(_('Invalid UUID value'), code=400)
for uuid in data: for uuid in data:
introspect.introspect(uuid) introspect.introspect(uuid)
return "", 202 return "", 202

View File

@ -48,7 +48,7 @@ class BaseTest(unittest.TestCase):
class NodeTest(BaseTest): class NodeTest(BaseTest):
def setUp(self): def setUp(self):
super(NodeTest, self).setUp() super(NodeTest, self).setUp()
self.uuid = 'uuid' self.uuid = '1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e'
self.bmc_address = '1.2.3.4' self.bmc_address = '1.2.3.4'
self.macs = ['11:22:33:44:55:66', '66:55:44:33:22:11'] self.macs = ['11:22:33:44:55:66', '66:55:44:33:22:11']
self.node = mock.Mock(driver='pxe_ipmitool', self.node = mock.Mock(driver='pxe_ipmitool',

View File

@ -14,17 +14,22 @@
import unittest import unittest
import mock import mock
from oslo_utils import uuidutils
from ironic_discoverd import client from ironic_discoverd import client
@mock.patch.object(client.requests, 'post', autospec=True) @mock.patch.object(client.requests, 'post', autospec=True)
class TestIntrospect(unittest.TestCase): class TestIntrospect(unittest.TestCase):
def setUp(self):
super(TestIntrospect, self).setUp()
self.uuid = uuidutils.generate_uuid()
def test(self, mock_post): def test(self, mock_post):
client.introspect('uuid1', base_url="http://host:port", client.introspect(self.uuid, base_url="http://host:port",
auth_token="token") auth_token="token")
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://host:port/v1/introspection/uuid1", "http://host:port/v1/introspection/%s" % self.uuid,
headers={'X-Auth-Token': 'token'}, headers={'X-Auth-Token': 'token'},
params={'new_ipmi_username': None, 'new_ipmi_password': None} params={'new_ipmi_username': None, 'new_ipmi_password': None}
) )
@ -35,28 +40,28 @@ class TestIntrospect(unittest.TestCase):
new_ipmi_username='user') new_ipmi_username='user')
def test_full_url(self, mock_post): def test_full_url(self, mock_post):
client.introspect('uuid1', base_url="http://host:port/v1/", client.introspect(self.uuid, base_url="http://host:port/v1/",
auth_token="token") auth_token="token")
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://host:port/v1/introspection/uuid1", "http://host:port/v1/introspection/%s" % self.uuid,
headers={'X-Auth-Token': 'token'}, headers={'X-Auth-Token': 'token'},
params={'new_ipmi_username': None, 'new_ipmi_password': None} params={'new_ipmi_username': None, 'new_ipmi_password': None}
) )
def test_default_url(self, mock_post): def test_default_url(self, mock_post):
client.introspect('uuid1', auth_token="token") client.introspect(self.uuid, auth_token="token")
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://127.0.0.1:5050/v1/introspection/uuid1", "http://127.0.0.1:5050/v1/introspection/%s" % self.uuid,
headers={'X-Auth-Token': 'token'}, headers={'X-Auth-Token': 'token'},
params={'new_ipmi_username': None, 'new_ipmi_password': None} params={'new_ipmi_username': None, 'new_ipmi_password': None}
) )
def test_set_ipmi_credentials(self, mock_post): def test_set_ipmi_credentials(self, mock_post):
client.introspect('uuid1', base_url="http://host:port", client.introspect(self.uuid, base_url="http://host:port",
auth_token="token", new_ipmi_password='p', auth_token="token", new_ipmi_password='p',
new_ipmi_username='u') new_ipmi_username='u')
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://host:port/v1/introspection/uuid1", "http://host:port/v1/introspection/%s" % self.uuid,
headers={'X-Auth-Token': 'token'}, headers={'X-Auth-Token': 'token'},
params={'new_ipmi_username': 'u', 'new_ipmi_password': 'p'} params={'new_ipmi_username': 'u', 'new_ipmi_password': 'p'}
) )
@ -64,12 +69,18 @@ class TestIntrospect(unittest.TestCase):
@mock.patch.object(client.requests, 'post', autospec=True) @mock.patch.object(client.requests, 'post', autospec=True)
class TestDiscover(unittest.TestCase): class TestDiscover(unittest.TestCase):
def setUp(self):
super(TestDiscover, self).setUp()
self.uuid = uuidutils.generate_uuid()
def test_old_discover(self, mock_post): def test_old_discover(self, mock_post):
client.discover(['uuid1', 'uuid2'], base_url="http://host:port", uuid2 = uuidutils.generate_uuid()
client.discover([self.uuid, uuid2], base_url="http://host:port",
auth_token="token") auth_token="token")
mock_post.assert_called_once_with( mock_post.assert_called_once_with(
"http://host:port/v1/discover", "http://host:port/v1/discover",
data='["uuid1", "uuid2"]', data='["%(uuid1)s", "%(uuid2)s"]' % {'uuid1': self.uuid,
'uuid2': uuid2},
headers={'Content-Type': 'application/json', headers={'Content-Type': 'application/json',
'X-Auth-Token': 'token'} 'X-Auth-Token': 'token'}
) )
@ -81,13 +92,17 @@ class TestDiscover(unittest.TestCase):
@mock.patch.object(client.requests, 'get', autospec=True) @mock.patch.object(client.requests, 'get', autospec=True)
class TestGetStatus(unittest.TestCase): class TestGetStatus(unittest.TestCase):
def setUp(self):
super(TestGetStatus, self).setUp()
self.uuid = uuidutils.generate_uuid()
def test(self, mock_get): def test(self, mock_get):
mock_get.return_value.json.return_value = 'json' mock_get.return_value.json.return_value = 'json'
client.get_status('uuid', auth_token='token') client.get_status(self.uuid, auth_token='token')
mock_get.assert_called_once_with( mock_get.assert_called_once_with(
"http://127.0.0.1:5050/v1/introspection/uuid", "http://127.0.0.1:5050/v1/introspection/%s" % self.uuid,
headers={'X-Auth-Token': 'token'} headers={'X-Auth-Token': 'token'}
) )

View File

@ -248,7 +248,7 @@ class TestIntrospect(BaseTest):
self.assertRaisesRegexp( self.assertRaisesRegexp(
utils.Error, utils.Error,
'node uuid with provision state "active"', 'node %s with provision state "active"' % self.uuid,
introspect.introspect, self.uuid) introspect.introspect, self.uuid)
self.assertEqual(0, cli.node.list_ports.call_count) self.assertEqual(0, cli.node.list_ports.call_count)
@ -264,7 +264,7 @@ class TestIntrospect(BaseTest):
self.assertRaisesRegexp( self.assertRaisesRegexp(
utils.Error, utils.Error,
'node uuid with power state "power on"', 'node %s with power state "power on"' % self.uuid,
introspect.introspect, self.uuid) introspect.introspect, self.uuid)
self.assertEqual(0, cli.node.list_ports.call_count) self.assertEqual(0, cli.node.list_ports.call_count)

View File

@ -17,6 +17,7 @@ import unittest
import eventlet import eventlet
from keystoneclient import exceptions as keystone_exc from keystoneclient import exceptions as keystone_exc
import mock import mock
from oslo_utils import uuidutils
from ironic_discoverd import conf from ironic_discoverd import conf
from ironic_discoverd import introspect from ironic_discoverd import introspect
@ -35,48 +36,50 @@ class TestApi(test_base.BaseTest):
main.app.config['TESTING'] = True main.app.config['TESTING'] = True
self.app = main.app.test_client() self.app = main.app.test_client()
conf.CONF.set('discoverd', 'authenticate', 'false') conf.CONF.set('discoverd', 'authenticate', 'false')
self.uuid = uuidutils.generate_uuid()
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_no_authentication(self, introspect_mock): def test_introspect_no_authentication(self, introspect_mock):
conf.CONF.set('discoverd', 'authenticate', 'false') conf.CONF.set('discoverd', 'authenticate', 'false')
res = self.app.post('/v1/introspection/uuid1') res = self.app.post('/v1/introspection/%s' % self.uuid)
self.assertEqual(202, res.status_code) self.assertEqual(202, res.status_code)
introspect_mock.assert_called_once_with("uuid1", introspect_mock.assert_called_once_with(self.uuid,
new_ipmi_credentials=None) new_ipmi_credentials=None)
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_set_ipmi_credentials(self, introspect_mock): def test_introspect_set_ipmi_credentials(self, introspect_mock):
conf.CONF.set('discoverd', 'authenticate', 'false') conf.CONF.set('discoverd', 'authenticate', 'false')
res = self.app.post('/v1/introspection/uuid1?new_ipmi_username=user&' res = self.app.post('/v1/introspection/%s?new_ipmi_username=user&'
'new_ipmi_password=password') 'new_ipmi_password=password' % self.uuid)
self.assertEqual(202, res.status_code) self.assertEqual(202, res.status_code)
introspect_mock.assert_called_once_with( introspect_mock.assert_called_once_with(
"uuid1", self.uuid,
new_ipmi_credentials=('user', 'password')) new_ipmi_credentials=('user', 'password'))
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_set_ipmi_credentials_no_user(self, introspect_mock): def test_introspect_set_ipmi_credentials_no_user(self, introspect_mock):
conf.CONF.set('discoverd', 'authenticate', 'false') conf.CONF.set('discoverd', 'authenticate', 'false')
res = self.app.post('/v1/introspection/uuid1?' res = self.app.post('/v1/introspection/%s?'
'new_ipmi_password=password') 'new_ipmi_password=password' % self.uuid)
self.assertEqual(202, res.status_code) self.assertEqual(202, res.status_code)
introspect_mock.assert_called_once_with( introspect_mock.assert_called_once_with(
"uuid1", self.uuid,
new_ipmi_credentials=(None, 'password')) new_ipmi_credentials=(None, 'password'))
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_intospect_failed(self, introspect_mock): def test_intospect_failed(self, introspect_mock):
introspect_mock.side_effect = utils.Error("boom") introspect_mock.side_effect = utils.Error("boom")
res = self.app.post('/v1/introspection/uuid1') res = self.app.post('/v1/introspection/%s' % self.uuid)
self.assertEqual(400, res.status_code) self.assertEqual(400, res.status_code)
self.assertEqual(b"boom", res.data) self.assertEqual(b"boom", res.data)
introspect_mock.assert_called_once_with("uuid1", introspect_mock.assert_called_once_with(
self.uuid,
new_ipmi_credentials=None) new_ipmi_credentials=None)
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_missing_authentication(self, introspect_mock): def test_introspect_missing_authentication(self, introspect_mock):
conf.CONF.set('discoverd', 'authenticate', 'true') conf.CONF.set('discoverd', 'authenticate', 'true')
res = self.app.post('/v1/introspection/uuid1') res = self.app.post('/v1/introspection/%s' % self.uuid)
self.assertEqual(401, res.status_code) self.assertEqual(401, res.status_code)
self.assertFalse(introspect_mock.called) self.assertFalse(introspect_mock.called)
@ -86,17 +89,29 @@ class TestApi(test_base.BaseTest):
keystone_mock): keystone_mock):
conf.CONF.set('discoverd', 'authenticate', 'true') conf.CONF.set('discoverd', 'authenticate', 'true')
keystone_mock.side_effect = keystone_exc.Unauthorized() keystone_mock.side_effect = keystone_exc.Unauthorized()
res = self.app.post('/v1/introspection/uuid1', res = self.app.post('/v1/introspection/%s' % self.uuid,
headers={'X-Auth-Token': 'token'}) headers={'X-Auth-Token': 'token'})
self.assertEqual(403, res.status_code) self.assertEqual(403, res.status_code)
self.assertFalse(introspect_mock.called) self.assertFalse(introspect_mock.called)
keystone_mock.assert_called_once_with(token='token') keystone_mock.assert_called_once_with(token='token')
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_invalid_uuid(self, introspect_mock):
uuid_dummy = 'uuid1'
res = self.app.post('/v1/introspection/%s' % uuid_dummy)
self.assertEqual(400, res.status_code)
@mock.patch.object(introspect, 'introspect', autospec=True) @mock.patch.object(introspect, 'introspect', autospec=True)
def test_discover(self, discover_mock): def test_discover(self, discover_mock):
res = self.app.post('/v1/discover', data='["uuid1"]') res = self.app.post('/v1/discover', data='["%s"]' % self.uuid)
self.assertEqual(202, res.status_code) self.assertEqual(202, res.status_code)
discover_mock.assert_called_once_with("uuid1") discover_mock.assert_called_once_with(self.uuid)
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_discover_invalid_uuid(self, discover_mock):
uuid_dummy = 'uuid1'
res = self.app.post('/v1/discover', data='["%s"]' % uuid_dummy)
self.assertEqual(400, res.status_code)
@mock.patch.object(process, 'process', autospec=True) @mock.patch.object(process, 'process', autospec=True)
def test_continue(self, process_mock): def test_continue(self, process_mock):
@ -117,20 +132,20 @@ class TestApi(test_base.BaseTest):
@mock.patch.object(node_cache, 'get_node', autospec=True) @mock.patch.object(node_cache, 'get_node', autospec=True)
def test_get_introspection_in_progress(self, get_mock): def test_get_introspection_in_progress(self, get_mock):
get_mock.return_value = node_cache.NodeInfo(uuid='uuid', get_mock.return_value = node_cache.NodeInfo(uuid=self.uuid,
started_at=42.0) started_at=42.0)
res = self.app.get('/v1/introspection/uuid') res = self.app.get('/v1/introspection/%s' % self.uuid)
self.assertEqual(200, res.status_code) self.assertEqual(200, res.status_code)
self.assertEqual({'finished': False, 'error': None}, self.assertEqual({'finished': False, 'error': None},
json.loads(res.data.decode('utf-8'))) json.loads(res.data.decode('utf-8')))
@mock.patch.object(node_cache, 'get_node', autospec=True) @mock.patch.object(node_cache, 'get_node', autospec=True)
def test_get_introspection_finished(self, get_mock): def test_get_introspection_finished(self, get_mock):
get_mock.return_value = node_cache.NodeInfo(uuid='uuid', get_mock.return_value = node_cache.NodeInfo(uuid=self.uuid,
started_at=42.0, started_at=42.0,
finished_at=100.1, finished_at=100.1,
error='boom') error='boom')
res = self.app.get('/v1/introspection/uuid') res = self.app.get('/v1/introspection/%s' % self.uuid)
self.assertEqual(200, res.status_code) self.assertEqual(200, res.status_code)
self.assertEqual({'finished': True, 'error': 'boom'}, self.assertEqual({'finished': True, 'error': 'boom'},
json.loads(res.data.decode('utf-8'))) json.loads(res.data.decode('utf-8')))

View File

@ -42,7 +42,8 @@ class TestNodeCache(test_base.NodeTest):
res = self.db.execute("select uuid, started_at " res = self.db.execute("select uuid, started_at "
"from nodes order by uuid").fetchall() "from nodes order by uuid").fetchall()
self.assertEqual(['uuid', 'uuid2'], [t[0] for t in res]) self.assertEqual(['1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e',
'uuid2'], [t[0] for t in res])
self.assertTrue(time.time() - 60 < res[0][1] < time.time() + 60) self.assertTrue(time.time() - 60 < res[0][1] < time.time() + 60)
res = self.db.execute("select name, value, uuid from attributes " res = self.db.execute("select name, value, uuid from attributes "

View File

@ -410,8 +410,8 @@ class TestProcessNode(BaseTest):
self.patch_before) self.patch_before)
finished_mock.assert_called_once_with( finished_mock.assert_called_once_with(
mock.ANY, mock.ANY,
error='Timeout waiting for node uuid to power off ' error='Timeout waiting for node %s to power off '
'after introspection') 'after introspection' % self.uuid)
def test_port_failed(self, filters_mock, post_hook_mock): def test_port_failed(self, filters_mock, post_hook_mock):
self.ports[0] = exceptions.Conflict() self.ports[0] = exceptions.Conflict()
@ -486,8 +486,8 @@ class TestProcessNode(BaseTest):
self.assertFalse(self.cli.node.set_power_state.called) self.assertFalse(self.cli.node.set_power_state.called)
finished_mock.assert_called_once_with( finished_mock.assert_called_once_with(
mock.ANY, mock.ANY,
error='Failed to validate updated IPMI credentials for node uuid, ' error='Failed to validate updated IPMI credentials for node %s, '
'node might require maintenance') 'node might require maintenance' % self.uuid)
@mock.patch.object(node_cache.NodeInfo, 'finished', autospec=True) @mock.patch.object(node_cache.NodeInfo, 'finished', autospec=True)
def test_power_off_failed(self, finished_mock, filters_mock, def test_power_off_failed(self, finished_mock, filters_mock,
@ -502,5 +502,5 @@ class TestProcessNode(BaseTest):
self.patch_before) self.patch_before)
finished_mock.assert_called_once_with( finished_mock.assert_called_once_with(
mock.ANY, mock.ANY,
error='Failed to power off node uuid, check it\'s power management' error='Failed to power off node %s, check it\'s power management'
' configuration: boom') ' configuration: boom' % self.uuid)

View File

@ -7,5 +7,6 @@ python-ironicclient>=0.2.1
python-keystoneclient>=1.1.0 python-keystoneclient>=1.1.0
requests>=2.2.0,!=2.4.0 requests>=2.2.0,!=2.4.0
oslo.i18n>=1.3.0 # Apache-2.0 oslo.i18n>=1.3.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0
six>=1.7.0 six>=1.7.0
stevedore>=1.1.0 stevedore>=1.1.0