Support for the baremetal introspection service
This change adds support for the baremetal introspection (aka ironic-inspector) API. The initial patch includes starting, stopping introspection, retrieving introspection statuses and introspection data. Change-Id: I4b8448316b1b6f6777ed0044374175ed794937f1
This commit is contained in:
parent
d8db601f21
commit
598c994e11
@ -95,6 +95,7 @@ control which services can be used.
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
Baremetal <proxies/baremetal>
|
Baremetal <proxies/baremetal>
|
||||||
|
Baremetal Introspection <proxies/baremetal_introspection>
|
||||||
Block Storage <proxies/block_storage>
|
Block Storage <proxies/block_storage>
|
||||||
Clustering <proxies/clustering>
|
Clustering <proxies/clustering>
|
||||||
Compute <proxies/compute>
|
Compute <proxies/compute>
|
||||||
@ -128,6 +129,7 @@ The following services have exposed *Resource* classes.
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
Baremetal <resources/baremetal/index>
|
Baremetal <resources/baremetal/index>
|
||||||
|
Baremetal Introspection <resources/baremetal_introspection/index>
|
||||||
Block Storage <resources/block_storage/index>
|
Block Storage <resources/block_storage/index>
|
||||||
Clustering <resources/clustering/index>
|
Clustering <resources/clustering/index>
|
||||||
Compute <resources/compute/index>
|
Compute <resources/compute/index>
|
||||||
|
25
doc/source/user/proxies/baremetal_introspection.rst
Normal file
25
doc/source/user/proxies/baremetal_introspection.rst
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Baremetal Introspection API
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: openstack.baremetal_introspection.v1._proxy
|
||||||
|
|
||||||
|
The Baremetal Introspection Proxy
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
The baremetal introspection high-level interface is available through
|
||||||
|
the ``baremetal_introspection`` member of a
|
||||||
|
:class:`~openstack.connection.Connection` object.
|
||||||
|
The ``baremetal_introspection`` member will only be added if the service is
|
||||||
|
detected.
|
||||||
|
|
||||||
|
Introspection Process Operations
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. autoclass:: openstack.baremetal_introspection.v1._proxy.Proxy
|
||||||
|
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.introspections
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.get_introspection
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.get_introspection_data
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.start_introspection
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.wait_for_introspection
|
||||||
|
.. automethod:: openstack.baremetal_introspection.v1._proxy.Proxy.abort_introspection
|
@ -0,0 +1,7 @@
|
|||||||
|
Baremetal Introspection Resources
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
v1/introspection
|
@ -0,0 +1,13 @@
|
|||||||
|
openstack.baremetal_introspection.v1.Introspection
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
.. automodule:: openstack.baremetal_introspection.v1.introspection
|
||||||
|
|
||||||
|
The Introspection Class
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The ``Introspection`` class inherits from
|
||||||
|
:class:`~openstack.resource.Resource`.
|
||||||
|
|
||||||
|
.. autoclass:: openstack.baremetal_introspection.v1.introspection.Introspection
|
||||||
|
:members:
|
0
openstack/baremetal_introspection/__init__.py
Normal file
0
openstack/baremetal_introspection/__init__.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from openstack import service_description
|
||||||
|
from openstack.baremetal_introspection.v1 import _proxy
|
||||||
|
|
||||||
|
|
||||||
|
class BaremetalIntrospectionService(service_description.ServiceDescription):
|
||||||
|
"""The bare metal introspection service."""
|
||||||
|
|
||||||
|
supported_versions = {
|
||||||
|
'1': _proxy.Proxy,
|
||||||
|
}
|
0
openstack/baremetal_introspection/v1/__init__.py
Normal file
0
openstack/baremetal_introspection/v1/__init__.py
Normal file
130
openstack/baremetal_introspection/v1/_proxy.py
Normal file
130
openstack/baremetal_introspection/v1/_proxy.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from openstack import _log
|
||||||
|
from openstack.baremetal_introspection.v1 import introspection as _introspect
|
||||||
|
from openstack import exceptions
|
||||||
|
from openstack import proxy
|
||||||
|
|
||||||
|
|
||||||
|
_logger = _log.setup_logging('openstack')
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(proxy.Proxy):
|
||||||
|
|
||||||
|
def introspections(self, **query):
|
||||||
|
"""Retrieve a generator of introspection records.
|
||||||
|
|
||||||
|
:param dict query: Optional query parameters to be sent to restrict
|
||||||
|
the records to be returned. Available parameters include:
|
||||||
|
|
||||||
|
* ``fields``: A list containing one or more fields to be returned
|
||||||
|
in the response. This may lead to some performance gain
|
||||||
|
because other fields of the resource are not refreshed.
|
||||||
|
* ``limit``: Requests at most the specified number of items be
|
||||||
|
returned from the query.
|
||||||
|
* ``marker``: Specifies the ID of the last-seen introspection. Use
|
||||||
|
the ``limit`` parameter to make an initial limited request and
|
||||||
|
use the ID of the last-seen introspection from the response as
|
||||||
|
the ``marker`` value in a subsequent limited request.
|
||||||
|
* ``sort_dir``: Sorts the response by the requested sort direction.
|
||||||
|
A valid value is ``asc`` (ascending) or ``desc``
|
||||||
|
(descending). Default is ``asc``. You can specify multiple
|
||||||
|
pairs of sort key and sort direction query parameters. If
|
||||||
|
you omit the sort direction in a pair, the API uses the
|
||||||
|
natural sorting direction of the server attribute that is
|
||||||
|
provided as the ``sort_key``.
|
||||||
|
* ``sort_key``: Sorts the response by the this attribute value.
|
||||||
|
Default is ``id``. You can specify multiple pairs of sort
|
||||||
|
key and sort direction query parameters. If you omit the
|
||||||
|
sort direction in a pair, the API uses the natural sorting
|
||||||
|
direction of the server attribute that is provided as the
|
||||||
|
``sort_key``.
|
||||||
|
|
||||||
|
:returns: A generator of :class:`~.introspection.Introspection`
|
||||||
|
objects
|
||||||
|
"""
|
||||||
|
return _introspect.Introspection.list(self, **query)
|
||||||
|
|
||||||
|
def start_introspection(self, node):
|
||||||
|
"""Create a new introspection from attributes.
|
||||||
|
|
||||||
|
:param node: The value can be either the name or ID of a node or
|
||||||
|
a :class:`~openstack.baremetal.v1.node.Node` instance.
|
||||||
|
|
||||||
|
:returns: :class:`~.introspection.Introspection` instance.
|
||||||
|
"""
|
||||||
|
return self._create(_introspect.Introspection, id=node)
|
||||||
|
|
||||||
|
def get_introspection(self, introspection):
|
||||||
|
"""Get a specific introspection.
|
||||||
|
|
||||||
|
:param introspection: The value can be the name or ID of an
|
||||||
|
introspection (matching bare metal node name or ID) or
|
||||||
|
an :class:`~.introspection.Introspection` instance.
|
||||||
|
:returns: :class:`~.introspection.Introspection` instance.
|
||||||
|
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
|
||||||
|
introspection matching the name or ID could be found.
|
||||||
|
"""
|
||||||
|
return self._get(_introspect.Introspection, introspection)
|
||||||
|
|
||||||
|
def get_introspection_data(self, introspection):
|
||||||
|
"""Get introspection data.
|
||||||
|
|
||||||
|
:param introspection: The value can be the name or ID of an
|
||||||
|
introspection (matching bare metal node name or ID) or
|
||||||
|
an :class:`~.introspection.Introspection` instance.
|
||||||
|
:returns: introspection data from the most recent successful run.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_introspect.Introspection, introspection)
|
||||||
|
return res.get_data(self)
|
||||||
|
|
||||||
|
def abort_introspection(self, introspection, ignore_missing=True):
|
||||||
|
"""Abort an introspection.
|
||||||
|
|
||||||
|
Note that the introspection is not aborted immediately, you may use
|
||||||
|
`wait_for_introspection` with `ignore_error=True`.
|
||||||
|
|
||||||
|
:param introspection: The value can be the name or ID of an
|
||||||
|
introspection (matching bare metal node name or ID) or
|
||||||
|
an :class:`~.introspection.Introspection` instance.
|
||||||
|
:param bool ignore_missing: When set to ``False``, an exception
|
||||||
|
:class:`~openstack.exceptions.ResourceNotFound` will be raised
|
||||||
|
when the introspection could not be found. When set to ``True``, no
|
||||||
|
exception will be raised when attempting to abort a non-existent
|
||||||
|
introspection.
|
||||||
|
:returns: nothing
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_introspect.Introspection, introspection)
|
||||||
|
try:
|
||||||
|
res.abort(self)
|
||||||
|
except exceptions.ResourceNotFound:
|
||||||
|
if not ignore_missing:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def wait_for_introspection(self, introspection, timeout=None,
|
||||||
|
ignore_error=False):
|
||||||
|
"""Wait for the introspection to finish.
|
||||||
|
|
||||||
|
:param introspection: The value can be the name or ID of an
|
||||||
|
introspection (matching bare metal node name or ID) or
|
||||||
|
an :class:`~.introspection.Introspection` instance.
|
||||||
|
:param timeout: How much (in seconds) to wait for the introspection.
|
||||||
|
The value of ``None`` (the default) means no client-side timeout.
|
||||||
|
:param ignore_error: If ``True``, this call will raise an exception
|
||||||
|
if the introspection reaches the ``error`` state. Otherwise the
|
||||||
|
error state is considered successful and the call returns.
|
||||||
|
:returns: :class:`~.introspection.Introspection` instance.
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_introspect.Introspection, introspection)
|
||||||
|
return res.wait(self, timeout=timeout, ignore_error=ignore_error)
|
131
openstack/baremetal_introspection/v1/introspection.py
Normal file
131
openstack/baremetal_introspection/v1/introspection.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from openstack import _log
|
||||||
|
from openstack.baremetal.v1 import _common
|
||||||
|
from openstack import exceptions
|
||||||
|
from openstack import resource
|
||||||
|
from openstack import utils
|
||||||
|
|
||||||
|
|
||||||
|
_logger = _log.setup_logging('openstack')
|
||||||
|
|
||||||
|
|
||||||
|
class Introspection(resource.Resource):
|
||||||
|
|
||||||
|
resources_key = 'introspection'
|
||||||
|
base_path = '/introspection'
|
||||||
|
|
||||||
|
# capabilities
|
||||||
|
allow_create = True
|
||||||
|
allow_fetch = True
|
||||||
|
allow_commit = False
|
||||||
|
allow_delete = True
|
||||||
|
allow_list = True
|
||||||
|
|
||||||
|
# created via POST with ID
|
||||||
|
create_method = 'POST'
|
||||||
|
create_requires_id = True
|
||||||
|
create_returns_body = False
|
||||||
|
|
||||||
|
#: Timestamp at which the introspection was finished.
|
||||||
|
finished_at = resource.Body('finished_at')
|
||||||
|
#: The last error message (if any).
|
||||||
|
error = resource.Body('error')
|
||||||
|
#: The UUID of the introspection (matches the node UUID).
|
||||||
|
id = resource.Body('uuid', alternate_id=True)
|
||||||
|
#: Whether introspection is finished.
|
||||||
|
is_finished = resource.Body('finished', type=bool)
|
||||||
|
#: A list of relative links, including the self and bookmark links.
|
||||||
|
links = resource.Body('links', type=list)
|
||||||
|
#: Timestamp at which the introspection was started.
|
||||||
|
started_at = resource.Body('started_at')
|
||||||
|
#: The current introspection state.
|
||||||
|
state = resource.Body('state')
|
||||||
|
|
||||||
|
def abort(self, session):
|
||||||
|
"""Abort introspection.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||||
|
"""
|
||||||
|
if self.is_finished:
|
||||||
|
return
|
||||||
|
|
||||||
|
session = self._get_session(session)
|
||||||
|
|
||||||
|
version = self._get_microversion_for(session, 'delete')
|
||||||
|
request = self._prepare_request(requires_id=True)
|
||||||
|
request.url = utils.urljoin(request.url, 'abort')
|
||||||
|
response = session.post(
|
||||||
|
request.url, headers=request.headers, microversion=version,
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
msg = ("Failed to abort introspection for node {id}"
|
||||||
|
.format(id=self.id))
|
||||||
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
|
||||||
|
def get_data(self, session):
|
||||||
|
"""Get introspection data.
|
||||||
|
|
||||||
|
Note that the introspection data format is not stable and can vary
|
||||||
|
from environment to environment.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||||
|
:returns: introspection data from the most recent successful run.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
session = self._get_session(session)
|
||||||
|
|
||||||
|
version = self._get_microversion_for(session, 'fetch')
|
||||||
|
request = self._prepare_request(requires_id=True)
|
||||||
|
request.url = utils.urljoin(request.url, 'data')
|
||||||
|
response = session.get(
|
||||||
|
request.url, headers=request.headers, microversion=version)
|
||||||
|
msg = ("Failed to fetch introspection data for node {id}"
|
||||||
|
.format(id=self.id))
|
||||||
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def wait(self, session, timeout=None, ignore_error=False):
|
||||||
|
"""Wait for the node to reach the expected state.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||||
|
:param timeout: How much (in seconds) to wait for the introspection.
|
||||||
|
The value of ``None`` (the default) means no client-side timeout.
|
||||||
|
:param ignore_error: If ``True``, this call will raise an exception
|
||||||
|
if the introspection reaches the ``error`` state. Otherwise the
|
||||||
|
error state is considered successful and the call returns.
|
||||||
|
:return: This :class:`Introspection` instance.
|
||||||
|
"""
|
||||||
|
if self._check_state(ignore_error):
|
||||||
|
return self
|
||||||
|
|
||||||
|
for count in utils.iterate_timeout(
|
||||||
|
timeout,
|
||||||
|
"Timeout waiting for introspection on node %s" % self.id):
|
||||||
|
self.fetch(session)
|
||||||
|
if self._check_state(ignore_error):
|
||||||
|
return self
|
||||||
|
|
||||||
|
_logger.debug('Still waiting for introspection of node %(node)s, '
|
||||||
|
'the current state is "%(state)s"',
|
||||||
|
{'node': self.id, 'state': self.state})
|
||||||
|
|
||||||
|
def _check_state(self, ignore_error):
|
||||||
|
if self.state == 'error' and not ignore_error:
|
||||||
|
raise exceptions.SDKException(
|
||||||
|
"Introspection of node %(node)s failed: %(error)s" %
|
||||||
|
{'node': self.id, 'error': self.error})
|
||||||
|
else:
|
||||||
|
return self.is_finished
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"auth_type": "password",
|
"auth_type": "password",
|
||||||
"baremetal_status_code_retries": 5,
|
"baremetal_status_code_retries": 5,
|
||||||
|
"baremetal_introspection_status_code_retries": 5,
|
||||||
"image_status_code_retries": 5,
|
"image_status_code_retries": 5,
|
||||||
"disable_vendor_agent": {},
|
"disable_vendor_agent": {},
|
||||||
"interface": "public",
|
"interface": "public",
|
||||||
|
@ -406,8 +406,12 @@ class Resource(dict):
|
|||||||
|
|
||||||
#: Do calls for this resource require an id
|
#: Do calls for this resource require an id
|
||||||
requires_id = True
|
requires_id = True
|
||||||
|
#: Whether create requires an ID (determined from method if None).
|
||||||
|
create_requires_id = None
|
||||||
#: Do responses for this resource have bodies
|
#: Do responses for this resource have bodies
|
||||||
has_body = True
|
has_body = True
|
||||||
|
#: Does create returns a body (if False requires ID), defaults to has_body
|
||||||
|
create_returns_body = None
|
||||||
|
|
||||||
#: Maximum microversion to use for getting/creating/updating the Resource
|
#: Maximum microversion to use for getting/creating/updating the Resource
|
||||||
_max_microversion = None
|
_max_microversion = None
|
||||||
@ -1104,15 +1108,18 @@ class Resource(dict):
|
|||||||
|
|
||||||
session = self._get_session(session)
|
session = self._get_session(session)
|
||||||
microversion = self._get_microversion_for(session, 'create')
|
microversion = self._get_microversion_for(session, 'create')
|
||||||
|
requires_id = (self.create_requires_id
|
||||||
|
if self.create_requires_id is not None
|
||||||
|
else self.create_method == 'PUT')
|
||||||
if self.create_method == 'PUT':
|
if self.create_method == 'PUT':
|
||||||
request = self._prepare_request(requires_id=True,
|
request = self._prepare_request(requires_id=requires_id,
|
||||||
prepend_key=prepend_key,
|
prepend_key=prepend_key,
|
||||||
base_path=base_path)
|
base_path=base_path)
|
||||||
response = session.put(request.url,
|
response = session.put(request.url,
|
||||||
json=request.body, headers=request.headers,
|
json=request.body, headers=request.headers,
|
||||||
microversion=microversion)
|
microversion=microversion)
|
||||||
elif self.create_method == 'POST':
|
elif self.create_method == 'POST':
|
||||||
request = self._prepare_request(requires_id=False,
|
request = self._prepare_request(requires_id=requires_id,
|
||||||
prepend_key=prepend_key,
|
prepend_key=prepend_key,
|
||||||
base_path=base_path)
|
base_path=base_path)
|
||||||
response = session.post(request.url,
|
response = session.post(request.url,
|
||||||
@ -1122,8 +1129,14 @@ class Resource(dict):
|
|||||||
raise exceptions.ResourceFailure(
|
raise exceptions.ResourceFailure(
|
||||||
msg="Invalid create method: %s" % self.create_method)
|
msg="Invalid create method: %s" % self.create_method)
|
||||||
|
|
||||||
|
has_body = (self.has_body if self.create_returns_body is None
|
||||||
|
else self.create_returns_body)
|
||||||
self.microversion = microversion
|
self.microversion = microversion
|
||||||
self._translate_response(response)
|
self._translate_response(response, has_body=has_body)
|
||||||
|
# direct comparision to False since we need to rule out None
|
||||||
|
if self.has_body and self.create_returns_body is False:
|
||||||
|
# fetch the body if it's required but not returned by create
|
||||||
|
return self.fetch(session)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def fetch(self, session, requires_id=True,
|
def fetch(self, session, requires_id=True,
|
||||||
|
142
openstack/tests/unit/baremetal_introspection/v1/test_proxy.py
Normal file
142
openstack/tests/unit/baremetal_introspection/v1/test_proxy.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from keystoneauth1 import adapter
|
||||||
|
|
||||||
|
from openstack.baremetal_introspection.v1 import _proxy
|
||||||
|
from openstack.baremetal_introspection.v1 import introspection
|
||||||
|
from openstack import exceptions
|
||||||
|
from openstack.tests.unit import base
|
||||||
|
from openstack.tests.unit import test_proxy_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalIntrospectionProxy(test_proxy_base.TestProxyBase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalIntrospectionProxy, self).setUp()
|
||||||
|
self.proxy = _proxy.Proxy(self.session)
|
||||||
|
|
||||||
|
def test_create_introspection(self):
|
||||||
|
self.verify_create(self.proxy.start_introspection,
|
||||||
|
introspection.Introspection,
|
||||||
|
method_kwargs={'node': 'abcd'},
|
||||||
|
expected_kwargs={'id': 'abcd'})
|
||||||
|
|
||||||
|
def test_get_introspection(self):
|
||||||
|
self.verify_get(self.proxy.get_introspection,
|
||||||
|
introspection.Introspection)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('time.sleep', lambda _sec: None)
|
||||||
|
@mock.patch.object(introspection.Introspection, 'fetch', autospec=True)
|
||||||
|
class TestWaitForIntrospection(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestWaitForIntrospection, self).setUp()
|
||||||
|
self.session = mock.Mock(spec=adapter.Adapter)
|
||||||
|
self.proxy = _proxy.Proxy(self.session)
|
||||||
|
self.fake = {'state': 'waiting', 'error': None, 'finished': False}
|
||||||
|
self.introspection = introspection.Introspection(**self.fake)
|
||||||
|
|
||||||
|
def test_already_finished(self, mock_fetch):
|
||||||
|
self.introspection.is_finished = True
|
||||||
|
self.introspection.state = 'finished'
|
||||||
|
result = self.proxy.wait_for_introspection(self.introspection)
|
||||||
|
self.assertIs(result, self.introspection)
|
||||||
|
self.assertFalse(mock_fetch.called)
|
||||||
|
|
||||||
|
def test_wait(self, mock_fetch):
|
||||||
|
marker = [False] # mutable object to modify in the closure
|
||||||
|
|
||||||
|
def _side_effect(allocation, session):
|
||||||
|
if marker[0]:
|
||||||
|
self.introspection.state = 'finished'
|
||||||
|
self.introspection.is_finished = True
|
||||||
|
else:
|
||||||
|
self.introspection.state = 'processing'
|
||||||
|
marker[0] = True
|
||||||
|
|
||||||
|
mock_fetch.side_effect = _side_effect
|
||||||
|
result = self.proxy.wait_for_introspection(self.introspection)
|
||||||
|
self.assertIs(result, self.introspection)
|
||||||
|
self.assertEqual(2, mock_fetch.call_count)
|
||||||
|
|
||||||
|
def test_timeout(self, mock_fetch):
|
||||||
|
self.assertRaises(exceptions.ResourceTimeout,
|
||||||
|
self.proxy.wait_for_introspection,
|
||||||
|
self.introspection,
|
||||||
|
timeout=0.001)
|
||||||
|
mock_fetch.assert_called_with(self.introspection, self.proxy)
|
||||||
|
|
||||||
|
def test_failure(self, mock_fetch):
|
||||||
|
def _side_effect(allocation, session):
|
||||||
|
self.introspection.state = 'error'
|
||||||
|
self.introspection.is_finished = True
|
||||||
|
self.introspection.error = 'boom'
|
||||||
|
|
||||||
|
mock_fetch.side_effect = _side_effect
|
||||||
|
self.assertRaisesRegex(exceptions.SDKException, 'boom',
|
||||||
|
self.proxy.wait_for_introspection,
|
||||||
|
self.introspection)
|
||||||
|
mock_fetch.assert_called_once_with(self.introspection, self.proxy)
|
||||||
|
|
||||||
|
def test_failure_ignored(self, mock_fetch):
|
||||||
|
def _side_effect(allocation, session):
|
||||||
|
self.introspection.state = 'error'
|
||||||
|
self.introspection.is_finished = True
|
||||||
|
self.introspection.error = 'boom'
|
||||||
|
|
||||||
|
mock_fetch.side_effect = _side_effect
|
||||||
|
result = self.proxy.wait_for_introspection(self.introspection,
|
||||||
|
ignore_error=True)
|
||||||
|
self.assertIs(result, self.introspection)
|
||||||
|
mock_fetch.assert_called_once_with(self.introspection, self.proxy)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(_proxy.Proxy, 'request', autospec=True)
|
||||||
|
class TestAbortIntrospection(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAbortIntrospection, self).setUp()
|
||||||
|
self.session = mock.Mock(spec=adapter.Adapter)
|
||||||
|
self.proxy = _proxy.Proxy(self.session)
|
||||||
|
self.fake = {'id': '1234', 'finished': False}
|
||||||
|
self.introspection = introspection.Introspection(**self.fake)
|
||||||
|
|
||||||
|
def test_abort(self, mock_request):
|
||||||
|
mock_request.return_value.status_code = 202
|
||||||
|
self.proxy.abort_introspection(self.introspection)
|
||||||
|
mock_request.assert_called_once_with(
|
||||||
|
self.proxy, 'introspection/1234/abort', 'POST',
|
||||||
|
headers=mock.ANY, microversion=mock.ANY,
|
||||||
|
retriable_status_codes=[409, 503])
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(_proxy.Proxy, 'request', autospec=True)
|
||||||
|
class TestGetData(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestGetData, self).setUp()
|
||||||
|
self.session = mock.Mock(spec=adapter.Adapter)
|
||||||
|
self.proxy = _proxy.Proxy(self.session)
|
||||||
|
self.fake = {'id': '1234', 'finished': False}
|
||||||
|
self.introspection = introspection.Introspection(**self.fake)
|
||||||
|
|
||||||
|
def test_get_data(self, mock_request):
|
||||||
|
mock_request.return_value.status_code = 200
|
||||||
|
data = self.proxy.get_introspection_data(self.introspection)
|
||||||
|
mock_request.assert_called_once_with(
|
||||||
|
self.proxy, 'introspection/1234/data', 'GET',
|
||||||
|
headers=mock.ANY, microversion=mock.ANY)
|
||||||
|
self.assertIs(data, mock_request.return_value.json.return_value)
|
@ -1120,7 +1120,8 @@ class TestResourceActions(base.TestCase):
|
|||||||
microversion=microversion)
|
microversion=microversion)
|
||||||
|
|
||||||
self.assertEqual(sot.microversion, microversion)
|
self.assertEqual(sot.microversion, microversion)
|
||||||
sot._translate_response.assert_called_once_with(self.response)
|
sot._translate_response.assert_called_once_with(self.response,
|
||||||
|
has_body=sot.has_body)
|
||||||
self.assertEqual(result, sot)
|
self.assertEqual(result, sot)
|
||||||
|
|
||||||
def test_put_create(self):
|
def test_put_create(self):
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds support for the bare metal introspection service.
|
Loading…
x
Reference in New Issue
Block a user