Implement driver vendor passthrough

* Define 'list' and 'call' vendor_passthru methods in Driver
* Add corresponding proxy methods
* Implement unit tests
  - verify result properly returned for list_vendor_passthru
  - verify session is being called properly for both list and call

Story: 2008193
Task: 40960
Change-Id: Id7e5f6d3f651c371efca29002de63c973e8f33d7
This commit is contained in:
cenne
2021-05-02 00:23:04 +02:00
parent dc47f23697
commit c7e3081bdb
3 changed files with 155 additions and 0 deletions

View File

@@ -194,6 +194,35 @@ class Proxy(proxy.Proxy):
"""
return self._get(_driver.Driver, driver)
def list_driver_vendor_passthru(self, driver):
"""Get driver's vendor_passthru methods.
:param driver: The value can be the name of a driver or a
:class:`~openstack.baremetal.v1.driver.Driver` instance.
:returns: One :dict: of vendor methods with corresponding usages
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
driver matching the name could be found.
"""
driver = self.get_driver(driver)
return driver.list_vendor_passthru(self)
def call_driver_vendor_passthru(self, driver,
verb: str, method: str, body=None):
"""Call driver's vendor_passthru method.
:param driver: The value can be the name of a driver or a
:class:`~openstack.baremetal.v1.driver.Driver` instance.
:param verb: One of GET, POST, PUT, DELETE,
depending on the driver and method.
:param method: Name of vendor method.
:param body: passed to the vendor function as json body.
:returns: Server response
"""
driver = self.get_driver(driver)
return driver.call_vendor_passthru(self, verb, method, body)
def nodes(self, details=False, **query):
"""Retrieve a generator of nodes.

View File

@@ -10,7 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack.baremetal.v1 import _common
from openstack import exceptions
from openstack import resource
from openstack import utils
class Driver(resource.Resource):
@@ -117,3 +120,60 @@ class Driver(resource.Resource):
#: Enabled vendor interface implementations.
#: Introduced in API microversion 1.30.
enabled_vendor_interfaces = resource.Body("enabled_vendor_interfaces")
def list_vendor_passthru(self, session):
"""Fetch vendor specific methods exposed by driver
:param session: The session to use for making this request.
:returns: A dict of the available vendor passthru methods for driver.
Method names keys and corresponding usages in dict form as values
Usage dict properties:
* ``async``: bool # Is passthru function invoked asynchronously
* ``attach``: bool # Is return value attached to response object
* ``description``: str # Description of what the method does
* ``http_methods``: list # List of HTTP methods supported
"""
session = self._get_session(session)
request = self._prepare_request()
request.url = utils.urljoin(
request.url, 'vendor_passthru', 'methods')
response = session.get(request.url, headers=request.headers)
msg = ("Failed to list list vendor_passthru methods for {driver_name}"
.format(driver_name=self.name))
exceptions.raise_from_response(response, error_message=msg)
return response.json()
def call_vendor_passthru(self, session,
verb: str, method: str, body: dict = None):
"""Call a vendor specific passthru method
Contents of body are params passed to the hardware driver
function. Validation happens there. Missing parameters, or
excess parameters will cause the request to be rejected
:param session: The session to use for making this request.
:param method: Vendor passthru method name.
:param verb: One of GET, POST, PUT, DELETE,
depending on the driver and method.
:param body: passed to the vendor function as json body.
:raises: :exc:`ValueError` if :data:`verb` is not one of
GET, POST, PUT, DELETE
:returns: response of method call.
"""
if verb.upper() not in ['GET', 'PUT', 'POST', 'DELETE']:
raise ValueError('Invalid verb: {}'.format(verb))
session = self._get_session(session)
request = self._prepare_request()
request.url = utils.urljoin(
request.url, f'vendor_passthru?method={method}')
call = getattr(session, verb.lower())
response = call(
request.url, json=body, headers=request.headers,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
msg = ("Failed call to method {method} on driver {driver_name}"
.format(method=method, driver_name=self.name))
exceptions.raise_from_response(response, error_message=msg)
return response

View File

@@ -10,7 +10,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from keystoneauth1 import adapter
from openstack.baremetal.v1 import _common
from openstack.baremetal.v1 import driver
from openstack import exceptions
from openstack.tests.unit import base
@@ -63,3 +69,63 @@ class TestDriver(base.TestCase):
self.assertEqual(FAKE['hosts'], sot.hosts)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['properties'], sot.properties)
@mock.patch.object(exceptions, 'raise_from_response', mock.Mock())
def test_list_vendor_passthru(self):
self.session = mock.Mock(spec=adapter.Adapter)
sot = driver.Driver(**FAKE)
fake_vendor_passthru_info = {
'fake_vendor_method': {
'async': True,
'attach': False,
'description': "Fake function that does nothing in background",
'http_methods': ['GET', 'PUT', 'POST', 'DELETE']
}
}
self.session.get.return_value.json.return_value = (
fake_vendor_passthru_info)
result = sot.list_vendor_passthru(self.session)
self.session.get.assert_called_once_with(
'drivers/{driver_name}/vendor_passthru/methods'.format(
driver_name=FAKE["name"]),
headers=mock.ANY)
self.assertEqual(result, fake_vendor_passthru_info)
@mock.patch.object(exceptions, 'raise_from_response', mock.Mock())
def test_call_vendor_passthru(self):
self.session = mock.Mock(spec=adapter.Adapter)
sot = driver.Driver(**FAKE)
# GET
sot.call_vendor_passthru(self.session, 'GET', 'fake_vendor_method')
self.session.get.assert_called_once_with(
'drivers/{}/vendor_passthru?method={}'.format(
FAKE["name"], 'fake_vendor_method'),
json=None,
headers=mock.ANY,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
# PUT
sot.call_vendor_passthru(self.session, 'PUT', 'fake_vendor_method',
body={"fake_param_key": "fake_param_value"})
self.session.put.assert_called_once_with(
'drivers/{}/vendor_passthru?method={}'.format(
FAKE["name"], 'fake_vendor_method'),
json={"fake_param_key": "fake_param_value"},
headers=mock.ANY,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
# POST
sot.call_vendor_passthru(self.session, 'POST', 'fake_vendor_method',
body={"fake_param_key": "fake_param_value"})
self.session.post.assert_called_once_with(
'drivers/{}/vendor_passthru?method={}'.format(
FAKE["name"], 'fake_vendor_method'),
json={"fake_param_key": "fake_param_value"},
headers=mock.ANY,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
# DELETE
sot.call_vendor_passthru(self.session, 'DELETE', 'fake_vendor_method')
self.session.delete.assert_called_once_with(
'drivers/{}/vendor_passthru?method={}'.format(
FAKE["name"], 'fake_vendor_method'),
json=None,
headers=mock.ANY,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)