Add vendor_passthru method for subscriptions

This patch adds two new vendor_passthru methods for Redfish:
- create_subscription (create a sbuscription)
- delete_subscription (delete a subscription)
- get_all_subscriptions (get all subscriptions on the node)
- get_subscription (get a single subscription)

Unit Tests in test_utils split into multiple classes to avoid random
failures due to cache.

Tested in bifrost env using two different HW:
- HPE EL8000 e910
- Dell R640

Story: #2009061
Task: #42854
Change-Id: I5b7fa99b0ee64ccdc0f62d9686df655082db3665
This commit is contained in:
Iury Gregory Melo Ferreira 2021-07-16 16:02:09 +02:00
parent 7b42258ab9
commit 4bc5142df2
6 changed files with 559 additions and 70 deletions

View File

@ -11,7 +11,7 @@ python-dracclient>=5.1.0,<7.0.0
python-xclarityclient>=0.1.6 python-xclarityclient>=0.1.6
# The Redfish hardware type uses the Sushy library # The Redfish hardware type uses the Sushy library
sushy>=3.8.0 sushy>=3.10.0
# Ansible-deploy interface # Ansible-deploy interface
ansible>=2.7 ansible>=2.7

View File

@ -263,6 +263,23 @@ def get_update_service(node):
raise exception.RedfishError(error=e) raise exception.RedfishError(error=e)
def get_event_service(node):
"""Get a node's event service.
:param node: an Ironic node object.
:raises: RedfishConnectionError when it fails to connect to Redfish
:raises: RedfishError when the EventService is not registered in Redfish
"""
try:
return _get_connection(node, lambda conn: conn.get_event_service())
except sushy.exceptions.MissingAttributeError as e:
LOG.error('The Redfish EventService was not found for '
'node %(node)s. Error %(error)s',
{'node': node.uuid, 'error': e})
raise exception.RedfishError(error=e)
def get_system(node): def get_system(node):
"""Get a Redfish System that represents a node. """Get a Redfish System that represents a node.

View File

@ -16,6 +16,9 @@ Vendor Interface for Redfish drivers and its supporting methods.
""" """
from ironic_lib import metrics_utils from ironic_lib import metrics_utils
from oslo_log import log
from oslo_utils import importutils
import rfc3986
from ironic.common import exception from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
@ -23,7 +26,14 @@ from ironic.drivers import base
from ironic.drivers.modules.redfish import boot as redfish_boot from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import utils as redfish_utils from ironic.drivers.modules.redfish import utils as redfish_utils
sushy = importutils.try_import('sushy')
LOG = log.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__) METRICS = metrics_utils.get_metrics_logger(__name__)
SUBSCRIPTION_FIELDS_REMOVE = {
'@odata.context', '@odate.etag', '@odata.id', '@odata.type',
'HttpHeaders', 'Oem', 'Name', 'Description'
}
class RedfishVendorPassthru(base.VendorInterface): class RedfishVendorPassthru(base.VendorInterface):
@ -49,6 +59,12 @@ class RedfishVendorPassthru(base.VendorInterface):
if method == 'eject_vmedia': if method == 'eject_vmedia':
self._validate_eject_vmedia(task, kwargs) self._validate_eject_vmedia(task, kwargs)
return return
if method == 'create_subscription':
self._validate_create_subscription(task, kwargs)
return
if method == 'delete_subscription':
self._validate_delete_subscription(task, kwargs)
return
super(RedfishVendorPassthru, self).validate(task, method, **kwargs) super(RedfishVendorPassthru, self).validate(task, method, **kwargs)
def _validate_eject_vmedia(self, task, kwargs): def _validate_eject_vmedia(self, task, kwargs):
@ -90,3 +106,179 @@ class RedfishVendorPassthru(base.VendorInterface):
# If boot_device not provided all vmedia devices will be ejected # If boot_device not provided all vmedia devices will be ejected
boot_device = kwargs.get('boot_device') boot_device = kwargs.get('boot_device')
redfish_boot.eject_vmedia(task, boot_device) redfish_boot.eject_vmedia(task, boot_device)
def _validate_create_subscription(self, task, kwargs):
"""Verify that the args input are valid."""
destination = kwargs.get('Destination')
event_types = kwargs.get('EventTypes')
context = kwargs.get('Context')
protocol = kwargs.get('Protocol')
if event_types is not None:
event_service = redfish_utils.get_event_service(task.node)
allowed_values = set(
event_service.get_event_types_for_subscription())
if not (isinstance(event_types, list)
and set(event_types).issubset(allowed_values)):
raise exception.InvalidParameterValue(
_("EventTypes %s is not a valid value, allowed values %s")
% (str(event_types), str(allowed_values)))
# NOTE(iurygregory): check only if they are strings.
# BMCs will fail to create a subscription if the context, protocol or
# destination are invalid.
if not isinstance(context, str):
raise exception.InvalidParameterValue(
_("Context %s is not a valid string") % context)
if not isinstance(protocol, str):
raise exception.InvalidParameterValue(
_("Protocol %s is not a string") % protocol)
try:
parsed = rfc3986.uri_reference(destination)
if not parsed.is_valid(require_scheme=True,
require_authority=True):
# NOTE(iurygregory): raise error because the parsed
# destination does not contain scheme or authority.
raise TypeError
except TypeError:
raise exception.InvalidParameterValue(
_("Destination %s is not a valid URI") % destination)
def _filter_subscription_fields(self, subscription_json):
filter_subscription = {k: v for k, v in subscription_json.items()
if k not in SUBSCRIPTION_FIELDS_REMOVE}
return filter_subscription
@METRICS.timer('RedfishVendorPassthru.create_subscription')
@base.passthru(['POST'], async_call=False,
description=_("Creates a subscription on a node. "
"Required argument: a dictionary of "
"{'destination': 'destination_url'}"))
def create_subscription(self, task, **kwargs):
"""Creates a subscription.
:param task: A TaskManager object.
:param kwargs: The arguments sent with vendor passthru.
:raises: RedfishError, if any problem occurs when trying to create
a subscription.
"""
payload = {
'Destination': kwargs.get('Destination'),
'Protocol': kwargs.get('Protocol', "Redfish"),
'Context': kwargs.get('Context', ""),
'EventTypes': kwargs.get('EventTypes', ["Alert"])
}
try:
event_service = redfish_utils.get_event_service(task.node)
subscription = event_service.subscriptions.create(payload)
return self._filter_subscription_fields(subscription.json)
except sushy.exceptions.SushyError as e:
error_msg = (_('Failed to create subscription on node %(node)s. '
'Subscription payload: %(payload)s. '
'Error: %(error)s') % {'node': task.node.uuid,
'payload': str(payload),
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
def _validate_delete_subscription(self, task, kwargs):
"""Verify that the args input are valid."""
# We can only check if the kwargs contain the id field.
if not kwargs.get('id'):
raise exception.InvalidParameterValue(_("id can't be None"))
@METRICS.timer('RedfishVendorPassthru.delete_subscription')
@base.passthru(['DELETE'], async_call=False,
description=_("Delete a subscription on a node. "
"Required argument: a dictionary of "
"{'id': 'subscription_bmc_id'}"))
def delete_subscription(self, task, **kwargs):
"""Creates a subscription.
:param task: A TaskManager object.
:param kwargs: The arguments sent with vendor passthru.
:raises: RedfishError, if any problem occurs when trying to delete
the subscription.
"""
try:
event_service = redfish_utils.get_event_service(task.node)
redfish_subscriptions = event_service.subscriptions
bmc_id = kwargs.get('id')
# NOTE(iurygregory): some BMCs doesn't report the last /
# in the path for the resource, since we will add the ID
# we need to make sure the separator is present.
separator = "" if redfish_subscriptions.path[-1] == "/" else "/"
resource = redfish_subscriptions.path + separator + bmc_id
subscription = redfish_subscriptions.get_member(resource)
msg = (_('Sucessfuly deleted subscription %(id)s on node '
'%(node)s') % {'id': bmc_id, 'node': task.node.uuid})
subscription.delete()
LOG.debug(msg)
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish delete_subscription failed for '
'subscription %(id)s on node %(node)s. '
'Error: %(error)s') % {'id': bmc_id,
'node': task.node.uuid,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
@METRICS.timer('RedfishVendorPassthru.get_subscriptions')
@base.passthru(['GET'], async_call=False,
description=_("Returns all subscriptions on the node."))
def get_all_subscriptions(self, task, **kwargs):
"""Get all Subscriptions on the node
:param task: A TaskManager object.
:param kwargs: Not used.
:raises: RedfishError, if any problem occurs when retrieving all
subscriptions.
"""
try:
event_service = redfish_utils.get_event_service(task.node)
subscriptions = event_service.subscriptions.json
return subscriptions
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish get_subscriptions failed for '
'node %(node)s. '
'Error: %(error)s') % {'node': task.node.uuid,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
@METRICS.timer('RedfishVendorPassthru.get_subscription')
@base.passthru(['GET'], async_call=False,
description=_("Get a subscription on the node. "
"Required argument: a dictionary of "
"{'id': 'subscription_bmc_id'}"))
def get_subscription(self, task, **kwargs):
"""Get a specific subscription on the node
:param task: A TaskManager object.
:param kwargs: The arguments sent with vendor passthru.
:raises: RedfishError, if any problem occurs when retrieving the
subscription.
"""
try:
event_service = redfish_utils.get_event_service(task.node)
redfish_subscriptions = event_service.subscriptions
bmc_id = kwargs.get('id')
# NOTE(iurygregory): some BMCs doesn't report the last /
# in the path for the resource, since we will add the ID
# we need to make sure the separator is present.
separator = "" if redfish_subscriptions.path[-1] == "/" else "/"
resource = redfish_subscriptions.path + separator + bmc_id
subscription = event_service.subscriptions.get_member(resource)
return self._filter_subscription_fields(subscription.json)
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish get_subscription failed for '
'subscription %(id)s on node %(node)s. '
'Error: %(error)s') % {'id': bmc_id,
'node': task.node.uuid,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)

View File

@ -168,64 +168,6 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
response = redfish_utils.parse_driver_info(self.node) response = redfish_utils.parse_driver_info(self.node)
self.assertEqual(self.parsed_driver_info, response) self.assertEqual(self.parsed_driver_info, response)
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system(self, mock_sushy):
fake_conn = mock_sushy.return_value
fake_system = fake_conn.get_system.return_value
response = redfish_utils.get_system(self.node)
self.assertEqual(fake_system, response)
fake_conn.get_system.assert_called_once_with(
'/redfish/v1/Systems/FAKESYSTEM')
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_resource_not_found(self, mock_sushy):
fake_conn = mock_sushy.return_value
fake_conn.get_system.side_effect = (
sushy.exceptions.ResourceNotFoundError('GET',
'/',
requests.Response()))
self.assertRaises(exception.RedfishError,
redfish_utils.get_system, self.node)
fake_conn.get_system.assert_called_once_with(
'/redfish/v1/Systems/FAKESYSTEM')
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_multiple_systems(self, mock_sushy):
self.node.driver_info.pop('redfish_system_id')
fake_conn = mock_sushy.return_value
redfish_utils.get_system(self.node)
fake_conn.get_system.assert_called_once_with(None)
@mock.patch.object(time, 'sleep', lambda seconds: None)
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_resource_connection_error_retry(self, mock_sushy):
# Redfish specific configurations
self.config(connection_attempts=3, group='redfish')
fake_conn = mock_sushy.return_value
fake_conn.get_system.side_effect = sushy.exceptions.ConnectionError()
self.assertRaises(exception.RedfishConnectionError,
redfish_utils.get_system, self.node)
expected_get_system_calls = [
mock.call(self.parsed_driver_info['system_id']),
mock.call(self.parsed_driver_info['system_id']),
mock.call(self.parsed_driver_info['system_id']),
]
fake_conn.get_system.assert_has_calls(expected_get_system_calls)
self.assertEqual(fake_conn.get_system.call_count,
redfish_utils.CONF.redfish.connection_attempts)
def test_get_task_monitor(self): def test_get_task_monitor(self):
redfish_utils._get_connection = mock.Mock() redfish_utils._get_connection = mock.Mock()
fake_monitor = mock.Mock() fake_monitor = mock.Mock()
@ -245,6 +187,64 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
self.assertRaises(exception.RedfishError, self.assertRaises(exception.RedfishError,
redfish_utils.get_task_monitor, self.node, uri) redfish_utils.get_task_monitor, self.node, uri)
def test_get_update_service(self):
redfish_utils._get_connection = mock.Mock()
mock_update_service = mock.Mock()
redfish_utils._get_connection.return_value = mock_update_service
result = redfish_utils.get_update_service(self.node)
self.assertEqual(mock_update_service, result)
def test_get_update_service_error(self):
redfish_utils._get_connection = mock.Mock()
redfish_utils._get_connection.side_effect =\
sushy.exceptions.MissingAttributeError
self.assertRaises(exception.RedfishError,
redfish_utils.get_update_service, self.node)
def test_get_event_service(self):
redfish_utils._get_connection = mock.Mock()
mock_event_service = mock.Mock()
redfish_utils._get_connection.return_value = mock_event_service
result = redfish_utils.get_event_service(self.node)
self.assertEqual(mock_event_service, result)
def test_get_event_service_error(self):
redfish_utils._get_connection = mock.Mock()
redfish_utils._get_connection.side_effect =\
sushy.exceptions.MissingAttributeError
self.assertRaises(exception.RedfishError,
redfish_utils.get_event_service, self.node)
class RedfishUtilsAuthTestCase(db_base.DbTestCase):
def setUp(self):
super(RedfishUtilsAuthTestCase, self).setUp()
# Default configurations
self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
enabled_boot_interfaces=['redfish-virtual-media'],
enabled_management_interfaces=['redfish'])
# Redfish specific configurations
self.config(connection_attempts=1, group='redfish')
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
self.parsed_driver_info = {
'address': 'https://example.com',
'system_id': '/redfish/v1/Systems/FAKESYSTEM',
'username': 'username',
'password': 'password',
'verify_ca': True,
'auth_type': 'auto',
'node_uuid': self.node.uuid
}
@mock.patch.object(sushy, 'Sushy', autospec=True) @mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.' @mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {}) 'SessionCache._sessions', {})
@ -359,22 +359,87 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
auth=mock_basic_auth.return_value auth=mock_basic_auth.return_value
) )
def test_get_update_service(self):
redfish_utils._get_connection = mock.Mock()
mock_update_service = mock.Mock()
redfish_utils._get_connection.return_value = mock_update_service
result = redfish_utils.get_update_service(self.node) class RedfishUtilsSystemTestCase(db_base.DbTestCase):
self.assertEqual(mock_update_service, result) def setUp(self):
super(RedfishUtilsSystemTestCase, self).setUp()
# Default configurations
self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
enabled_boot_interfaces=['redfish-virtual-media'],
enabled_management_interfaces=['redfish'])
# Redfish specific configurations
self.config(connection_attempts=1, group='redfish')
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
self.parsed_driver_info = {
'address': 'https://example.com',
'system_id': '/redfish/v1/Systems/FAKESYSTEM',
'username': 'username',
'password': 'password',
'verify_ca': True,
'auth_type': 'auto',
'node_uuid': self.node.uuid
}
def test_get_update_service_error(self): @mock.patch.object(sushy, 'Sushy', autospec=True)
redfish_utils._get_connection = mock.Mock() @mock.patch('ironic.drivers.modules.redfish.utils.'
redfish_utils._get_connection.side_effect =\ 'SessionCache._sessions', {})
sushy.exceptions.MissingAttributeError def test_get_system(self, mock_sushy):
fake_conn = mock_sushy.return_value
fake_system = fake_conn.get_system.return_value
response = redfish_utils.get_system(self.node)
self.assertEqual(fake_system, response)
fake_conn.get_system.assert_called_once_with(
'/redfish/v1/Systems/FAKESYSTEM')
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_resource_not_found(self, mock_sushy):
fake_conn = mock_sushy.return_value
fake_conn.get_system.side_effect = (
sushy.exceptions.ResourceNotFoundError('GET',
'/',
requests.Response()))
self.assertRaises(exception.RedfishError, self.assertRaises(exception.RedfishError,
redfish_utils.get_update_service, self.node) redfish_utils.get_system, self.node)
fake_conn.get_system.assert_called_once_with(
'/redfish/v1/Systems/FAKESYSTEM')
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_multiple_systems(self, mock_sushy):
self.node.driver_info.pop('redfish_system_id')
fake_conn = mock_sushy.return_value
redfish_utils.get_system(self.node)
fake_conn.get_system.assert_called_once_with(None)
@mock.patch.object(time, 'sleep', lambda seconds: None)
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache._sessions', {})
def test_get_system_resource_connection_error_retry(self, mock_sushy):
# Redfish specific configurations
self.config(connection_attempts=3, group='redfish')
fake_conn = mock_sushy.return_value
fake_conn.get_system.side_effect = sushy.exceptions.ConnectionError()
self.assertRaises(exception.RedfishConnectionError,
redfish_utils.get_system, self.node)
expected_get_system_calls = [
mock.call(self.parsed_driver_info['system_id']),
mock.call(self.parsed_driver_info['system_id']),
mock.call(self.parsed_driver_info['system_id']),
]
fake_conn.get_system.assert_has_calls(expected_get_system_calls)
self.assertEqual(fake_conn.get_system.call_count,
redfish_utils.CONF.redfish.connection_attempts)
@mock.patch.object(time, 'sleep', lambda seconds: None) @mock.patch.object(time, 'sleep', lambda seconds: None)
@mock.patch.object(sushy, 'Sushy', autospec=True) @mock.patch.object(sushy, 'Sushy', autospec=True)

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from unittest import mock from unittest import mock
from oslo_utils import importutils from oslo_utils import importutils
@ -19,6 +20,7 @@ from oslo_utils import importutils
from ironic.common import exception from ironic.common import exception
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers.modules.redfish import boot as redfish_boot from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.drivers.modules.redfish import vendor as redfish_vendor from ironic.drivers.modules.redfish import vendor as redfish_vendor
from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils from ironic.tests.unit.db import utils as db_utils
@ -81,3 +83,211 @@ class RedfishVendorPassthruTestCase(db_base.DbTestCase):
self.assertRaises( self.assertRaises(
exception.InvalidParameterValue, exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'eject_vmedia', **kwargs) task.driver.vendor.validate, task, 'eject_vmedia', **kwargs)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_validate_invalid_create_subscription(self,
mock_get_event_service):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
kwargs = {'Destination': 10000}
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'create_subscription',
**kwargs)
kwargs = {'Context': 10}
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'create_subscription',
**kwargs)
kwargs = {'Protocol': 10}
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'create_subscription',
**kwargs)
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.get_event_types_for_subscription.return_value = \
['Alert']
kwargs = {'EventTypes': ['Other']}
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'create_subscription',
**kwargs)
def test_validate_invalid_delete_subscription(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
kwargs = {} # Empty missing id key
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'delete_subscription',
**kwargs)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_delete_subscription(self, mock_get_event_service):
kwargs = {'id': '30'}
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.path.return_value = \
"/redfish/v1/EventService/Subscriptions/"
subscription = mock_subscriptions.get_member.return_value
subscription.delete.return_value = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.vendor.delete_subscription(task, **kwargs)
self.assertTrue(subscription.delete.called)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_invalid_delete_subscription(self, mock_get_event_service):
kwargs = {'id': '30'}
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.path.return_value = \
"/redfish/v1/EventService/Subscriptions/"
uri = "/redfish/v1/EventService/Subscriptions/" + kwargs.get('id')
mock_subscriptions.get_member.side_effect = [
sushy.exceptions.ResourceNotFoundError('GET', uri, mock.Mock())
]
subscription = mock_subscriptions.get_member.return_value
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.RedfishError,
task.driver.vendor.delete_subscription,
task, **kwargs)
self.assertFalse(subscription.delete.called)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_get_all_subscriptions_empty(self, mock_get_event_service):
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.json.return_value = {
"@odata.context": "<some context>",
"@odata.id": "/redfish/v1/EventService/Subscriptions",
"@odata.type": "#EventDestinationCollection",
"Description": "List of Event subscriptions",
"Members": [],
"Members@odata.count": 0,
"Name": "Event Subscriptions Collection"
}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
output = task.driver.vendor.get_all_subscriptions(task)
self.assertEqual(len(output.return_value['Members']), 0)
mock_get_event_service.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_get_all_subscriptions(self, mock_get_event_service):
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.json.return_value = {
"@odata.context": "<some context>",
"@odata.id": "/redfish/v1/EventService/Subscriptions",
"@odata.type": "#EventDestinationCollection.",
"Description": "List of Event subscriptions",
"Members": [
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/33/"
}
],
"Members@odata.count": 1,
"Name": "Event Subscriptions Collection"
}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
output = task.driver.vendor.get_all_subscriptions(task)
self.assertEqual(len(output.return_value['Members']), 1)
mock_get_event_service.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_get_subscription_does_not_exist(self, mock_get_event_service):
kwargs = {'id': '30'}
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.path.return_value = \
"/redfish/v1/EventService/Subscriptions/"
uri = "/redfish/v1/EventService/Subscriptions/" + kwargs.get('id')
mock_subscriptions.get_member.side_effect = [
sushy.exceptions.ResourceNotFoundError('GET', uri, mock.Mock())
]
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.RedfishError,
task.driver.vendor.get_subscription,
task, **kwargs)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_create_subscription(self, mock_get_event_service):
subscription_json = {
"@odata.context": "",
"@odata.etag": "",
"@odata.id": "/redfish/v1/EventService/Subscriptions/100",
"@odata.type": "#EventDestination.v1_0_0.EventDestination",
"Id": "100",
"Context": "Ironic",
"Description": "iLO Event Subscription",
"Destination": "https://someurl",
"EventTypes": [
"Alert"
],
"HttpHeaders": [],
"Name": "Event Subscription",
"Oem": {
},
"Protocol": "Redfish"
}
mock_event_service = mock_get_event_service.return_value
subscription = mock.MagicMock()
subscription.json.return_value = subscription_json
mock_event_service.subscriptions.create = subscription
kwargs = {'destination': 'https://someurl'}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.vendor.create_subscription(task, **kwargs)
@mock.patch.object(redfish_utils, 'get_event_service', autospec=True)
def test_get_subscription_exists(self, mock_get_event_service):
kwargs = {'id': '36'}
mock_subscriptions = mock.MagicMock()
mock_evt_serv = mock_get_event_service.return_value
mock_evt_serv.subscriptions = mock_subscriptions
mock_subscriptions.path.return_value = \
"/redfish/v1/EventService/Subscriptions/"
subscription = mock_subscriptions.get_member.return_value
subscription.json.return_value = {
"@odata.context": "",
"@odata.etag": "",
"@odata.id": "/redfish/v1/EventService/Subscriptions/36",
"@odata.type": "#EventDestination.v1_0_0.EventDestination",
"Id": "36",
"Context": "Ironic",
"Description": "iLO Event Subscription",
"Destination": "https://someurl",
"EventTypes": [
"Alert"
],
"HttpHeaders": [],
"Name": "Event Subscription",
"Oem": {
},
"Protocol": "Redfish"
}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.vendor.get_subscription(task, **kwargs)

View File

@ -0,0 +1,5 @@
---
features:
- |
Provides new vendor passthru methods for Redfish to create, delete
and get subscriptions.