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:
parent
8da4fd576b
commit
f39c8a29aa
64
README.rst
64
README.rst
|
@ -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
|
||||
~~~~~~~~~~
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue