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:
parent
7b42258ab9
commit
4bc5142df2
@ -11,7 +11,7 @@ python-dracclient>=5.1.0,<7.0.0
|
||||
python-xclarityclient>=0.1.6
|
||||
|
||||
# The Redfish hardware type uses the Sushy library
|
||||
sushy>=3.8.0
|
||||
sushy>=3.10.0
|
||||
|
||||
# Ansible-deploy interface
|
||||
ansible>=2.7
|
||||
|
@ -263,6 +263,23 @@ def get_update_service(node):
|
||||
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):
|
||||
"""Get a Redfish System that represents a node.
|
||||
|
||||
|
@ -16,6 +16,9 @@ Vendor Interface for Redfish drivers and its supporting methods.
|
||||
"""
|
||||
|
||||
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.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 utils as redfish_utils
|
||||
|
||||
sushy = importutils.try_import('sushy')
|
||||
|
||||
LOG = log.getLogger(__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):
|
||||
@ -49,6 +59,12 @@ class RedfishVendorPassthru(base.VendorInterface):
|
||||
if method == 'eject_vmedia':
|
||||
self._validate_eject_vmedia(task, kwargs)
|
||||
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)
|
||||
|
||||
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
|
||||
boot_device = kwargs.get('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)
|
||||
|
@ -168,64 +168,6 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
|
||||
response = redfish_utils.parse_driver_info(self.node)
|
||||
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):
|
||||
redfish_utils._get_connection = mock.Mock()
|
||||
fake_monitor = mock.Mock()
|
||||
@ -245,6 +187,64 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
|
||||
self.assertRaises(exception.RedfishError,
|
||||
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('ironic.drivers.modules.redfish.utils.'
|
||||
'SessionCache._sessions', {})
|
||||
@ -359,22 +359,87 @@ class RedfishUtilsTestCase(db_base.DbTestCase):
|
||||
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):
|
||||
redfish_utils._get_connection = mock.Mock()
|
||||
redfish_utils._get_connection.side_effect =\
|
||||
sushy.exceptions.MissingAttributeError
|
||||
@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_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(sushy, 'Sushy', autospec=True)
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
@ -19,6 +20,7 @@ from oslo_utils import importutils
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
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.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
@ -81,3 +83,211 @@ class RedfishVendorPassthruTestCase(db_base.DbTestCase):
|
||||
self.assertRaises(
|
||||
exception.InvalidParameterValue,
|
||||
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)
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Provides new vendor passthru methods for Redfish to create, delete
|
||||
and get subscriptions.
|
Loading…
Reference in New Issue
Block a user