Add introspect client call and switch functest to it

Also make README reflect the current reality, as it wasn't updated
in the previous (already merged) patch.

Change-Id: I32a95a7f428f08fec564c3e3bc2b6413bd38bf3a
Implements: blueprint v1-api-reform
This commit is contained in:
Dmitry Tantsur 2015-01-12 15:47:09 +01:00
parent 8da4fd576b
commit f39c8a29aa
4 changed files with 113 additions and 61 deletions

View File

@ -142,7 +142,7 @@ of UUID's, ``base_url`` --- optional *ironic-discoverd* service URL and
You can also use it from CLI::
python -m ironic_discoverd.client --auth-token TOKEN UUID1 UUID2
python -m ironic_discoverd.client --auth-token TOKEN introspect UUID
.. note::
This CLI interface is not stable and may be changes without prior notice.
@ -153,26 +153,42 @@ API
By default *ironic-discoverd* listens on ``0.0.0.0:5050``, this can be changed
in configuration. Protocol is JSON over HTTP.
HTTP API consist of 2 endpoints:
HTTP API consist of these endpoints:
* ``POST /v1/discover`` initiate hardware discovery. Request body: JSON - list
of UUID's of nodes to discover. All power management configuration for these
nodes needs to be done prior to calling the endpoint. Requires X-Auth-Token
header with Keystone token for authentication.
* ``POST /v1/introspection/<UUID>`` initiate hardware discovery for node
``<UUID>``. All power management configuration for this node needs to be done
prior to calling the endpoint.
Nodes will be put into maintenance mode during discovery. It's up to caller
to put them back into use after discovery is done.
.. note::
Before version 0.2.0 this endpoint was not authenticated. Now it is,
but check for admin role is not implemented yet - see `bug #1391866`_.
Requires X-Auth-Token header with Keystone token for authentication.
Response:
* 202 - accepted discovery request
* 400 - bad request
* 401, 403 - missing or invalid authentication
* 404 - node cannot be found
Client library function: ``ironic_discoverd.client.introspect`` for node
``<UUID>``.
* ``GET /v1/introspection/<UUID>`` get hardware discovery status.
Requires X-Auth-Token header with Keystone token for authentication.
Response:
* 200 - OK
* 400 - bad request
* 401, 403 - missing or invalid authentication
* 404 - node cannot be found
Response body: JSON dictionary with keys:
* ``finished`` (boolean) whether discovery is finished
* ``error`` error string or ``null``
Client library function: ``ironic_discoverd.client.get_status``.
* ``POST /v1/continue`` internal endpoint for the discovery ramdisk to post
back discovered data. Should not be used for anything other than implementing
the ramdisk. Request body: JSON dictionary with keys:
@ -194,12 +210,6 @@ HTTP API consist of 2 endpoints:
* 403 - node is not on discovery
* 404 - node cannot be found or multiple nodes found
Successful response body is a JSON dictionary with keys:
* ``node`` node as returned by Ironic
.. _bug #1391866: https://bugs.launchpad.net/ironic-discoverd/+bug/1391866
Release Notes
-------------
@ -213,10 +223,20 @@ See `1.0.0 release tracking page`_ for details.
**API**
* New API ``GET /v1/introspection/<uuid>`` and ``client.get_status`` for
getting discovery status.
See `get-status-api blueprint`_ for details.
* New API ``POST /v1/introspection/<uuid>`` and ``client.introspect``
is now used to initiate discovery, ``/v1/discover`` is deprecated.
See `v1 API reform blueprint`_ for details.
* ``/v1/continue`` is now sync:
* Errors are properly returned to the caller
* This call now returns value as a JSON dict
* This call now returns value as a JSON dict (currently empty)
* Experimental support for updating IPMI credentials from within ramdisk.
@ -230,11 +250,6 @@ See `1.0.0 release tracking page`_ for details.
* Add support for plugins that hook into data processing pipeline, see
`plugin-architecture blueprint`_ for details.
* Add new API ``GET /v1/introspection/<uuid>`` and ``client.get_status`` for
getting discovery status.
See `get-status-api blueprint`_ for details.
**Configuration**
* Cache nodes under discovery in a local SQLite database. Set ``database``
@ -255,6 +270,7 @@ See `1.0.0 release tracking page`_ for details.
.. _plugin-architecture blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/plugin-architecture
.. _get-status-api blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/get-status-api
.. _Kilo state machine blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/kilo-state-machine
.. _v1 API reform blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/v1-api-reform
0.2 Series
~~~~~~~~~~

View File

@ -98,7 +98,7 @@ class Test(base.NodeTest):
def test_bmc(self):
self.node.power_state = 'power off'
client.discover([self.uuid], auth_token='token')
client.introspect(self.uuid, auth_token='token')
eventlet.greenthread.sleep(1)
status = client.get_status(self.uuid, auth_token='token')

View File

@ -11,6 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import argparse
import json
@ -29,23 +31,20 @@ def _prepare(base_url, auth_token):
return base_url, headers
def discover(uuids, base_url=_DEFAULT_URL, auth_token=''):
"""Post node UUID's for discovery.
def introspect(uuid, base_url=_DEFAULT_URL, auth_token=''):
"""Start introspection for a node.
:param uuids: list of UUID's.
:param uuid: node uuid
:param base_url: *ironic-discoverd* URL in form: http://host:port[/ver],
defaults to ``http://127.0.0.1:5050/v1``.
:param auth_token: Keystone authentication token.
:raises: *requests* library HTTP errors.
"""
if not all(isinstance(s, six.string_types) for s in uuids):
raise TypeError("Expected list of strings for uuids argument, got %s" %
uuids)
if not isinstance(uuid, six.string_types):
raise TypeError("Expected string for uuid argument, got %r" % uuid)
base_url, headers = _prepare(base_url, auth_token)
headers['Content-Type'] = 'application/json'
res = requests.post(base_url + "/discover",
data=json.dumps(uuids), headers=headers)
res = requests.post("%s/introspection/%s" % (base_url, uuid),
headers=headers)
res.raise_for_status()
@ -63,15 +62,35 @@ def get_status(uuid, base_url=_DEFAULT_URL, auth_token=''):
raise TypeError("Expected string for uuid argument, got %r" % uuid)
base_url, headers = _prepare(base_url, auth_token)
res = requests.get(base_url + "/introspection/%s" % uuid, headers=headers)
res = requests.get("%s/introspection/%s" % (base_url, uuid),
headers=headers)
res.raise_for_status()
return res.json()
if __name__ == '__main__':
def discover(uuids, base_url=_DEFAULT_URL, auth_token=''):
"""Post node UUID's for discovery.
DEPRECATED. Use introspect instead.
"""
if not all(isinstance(s, six.string_types) for s in uuids):
raise TypeError("Expected list of strings for uuids argument, got %s" %
uuids)
base_url, headers = _prepare(base_url, auth_token)
headers['Content-Type'] = 'application/json'
res = requests.post(base_url + "/discover",
data=json.dumps(uuids), headers=headers)
res.raise_for_status()
if __name__ == '__main__': # pragma: no cover
parser = argparse.ArgumentParser(description='Discover nodes.')
parser.add_argument('uuids', metavar='UUID', type=str, nargs='+',
help='node UUID\'s.')
parser.add_argument('cmd', metavar='cmd',
choices=['introspect', 'get_status'],
help='command: introspect or get_status.')
parser.add_argument('uuid', metavar='UUID', type=str,
help='node UUID.')
parser.add_argument('--base-url', dest='base_url', action='store',
default=_DEFAULT_URL,
help='base URL, default to localhost.')
@ -79,4 +98,12 @@ if __name__ == '__main__':
default='',
help='Keystone token.')
args = parser.parse_args()
discover(args.uuids, base_url=args.base_url, auth_token=args.auth_token)
func = globals()[args.cmd]
try:
res = func(uuid=args.uuid, base_url=args.base_url,
auth_token=args.auth_token)
except Exception as exc:
print('Error:', exc)
else:
if res:
print(res)

View File

@ -19,8 +19,37 @@ from ironic_discoverd import client
@mock.patch.object(client.requests, 'post', autospec=True)
class TestDiscover(unittest.TestCase):
class TestIntrospect(unittest.TestCase):
def test(self, mock_post):
client.introspect('uuid1', base_url="http://host:port",
auth_token="token")
mock_post.assert_called_once_with(
"http://host:port/v1/introspection/uuid1",
headers={'X-Auth-Token': 'token'}
)
def test_invalid_input(self, _):
self.assertRaises(TypeError, client.introspect, 42)
def test_full_url(self, mock_post):
client.introspect('uuid1', base_url="http://host:port/v1/",
auth_token="token")
mock_post.assert_called_once_with(
"http://host:port/v1/introspection/uuid1",
headers={'X-Auth-Token': 'token'}
)
def test_default_url(self, mock_post):
client.introspect('uuid1', auth_token="token")
mock_post.assert_called_once_with(
"http://127.0.0.1:5050/v1/introspection/uuid1",
headers={'X-Auth-Token': 'token'}
)
@mock.patch.object(client.requests, 'post', autospec=True)
class TestDiscover(unittest.TestCase):
def test_old_discover(self, mock_post):
client.discover(['uuid1', 'uuid2'], base_url="http://host:port",
auth_token="token")
mock_post.assert_called_once_with(
@ -30,26 +59,6 @@ class TestDiscover(unittest.TestCase):
'X-Auth-Token': 'token'}
)
def test_full_url(self, mock_post):
client.discover(['uuid1', 'uuid2'], base_url="http://host:port/v1/",
auth_token="token")
mock_post.assert_called_once_with(
"http://host:port/v1/discover",
data='["uuid1", "uuid2"]',
headers={'Content-Type': 'application/json',
'X-Auth-Token': 'token'}
)
def test_default_url(self, mock_post):
client.discover(['uuid1', 'uuid2'],
auth_token="token")
mock_post.assert_called_once_with(
"http://127.0.0.1:5050/v1/discover",
data='["uuid1", "uuid2"]',
headers={'Content-Type': 'application/json',
'X-Auth-Token': 'token'}
)
@mock.patch.object(client.requests, 'get', autospec=True)
class TestGetStatus(unittest.TestCase):