# Copyright 2021 DMTF. All rights reserved.
#
#    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 unittest import mock

from oslo_utils import units
import sushy

from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import raid as redfish_raid
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils

INFO_DICT = db_utils.get_test_redfish_info()


def _mock_drive(identity, block_size_bytes=None, capacity_bytes=None,
                media_type=None, name=None, protocol=None):
    return mock.MagicMock(
        _path='/redfish/v1/Systems/1/Storage/1/Drives/' + identity,
        identity=identity,
        block_size_bytes=block_size_bytes,
        capacity_bytes=capacity_bytes,
        media_type=media_type,
        name=name,
        protocol=protocol
    )


def _mock_volume(identity, raid_type=None,
                 capacity_bytes=units.Gi, volume_name=None):
    volume = mock.MagicMock(
        _path='/redfish/v1/Systems/1/Storage/1/Volumes/' + identity,
        identity=identity,
        raid_type=raid_type,
        capacity_bytes=capacity_bytes
    )
    if volume_name:
        volume.name = volume_name
    else:
        volume.name = 'Volume ' + identity
    # Mocking Immediate that does not return anything
    volume.delete.return_value = None
    return volume


@mock.patch.object(redfish_utils, 'get_system', autospec=True)
class RedfishRAIDTestCase(db_base.DbTestCase):

    def setUp(self):
        super(RedfishRAIDTestCase, self).setUp()
        self.config(enabled_hardware_types=['redfish'],
                    enabled_power_interfaces=['redfish'],
                    enabled_boot_interfaces=['redfish-virtual-media'],
                    enabled_management_interfaces=['redfish'],
                    enabled_inspect_interfaces=['redfish'],
                    enabled_bios_interfaces=['redfish'],
                    enabled_raid_interfaces=['redfish']
                    )
        self.node = obj_utils.create_test_node(
            self.context, driver='redfish', driver_info=INFO_DICT)
        self.mock_storage = mock.MagicMock(identity='RAID controller 1')
        self.drive_id1 = '35D38F11ACEF7BD3'
        self.drive_id2 = '3F5A8C54207B7233'
        self.drive_id3 = '32ADF365C6C1B7BD'
        self.drive_id4 = '3D58ECBC375FD9F2'
        self.drive_id5 = '5C966D719B0E1770'
        self.drive_id6 = '778B2A13449B8292'
        self.drive_id7 = 'E901FB234162E503'
        mock_drives = []
        for i in [self.drive_id1, self.drive_id2, self.drive_id3,
                  self.drive_id4]:
            mock_drives.append(_mock_drive(
                identity=i, block_size_bytes=512, capacity_bytes=899527000000,
                media_type='HDD', name='Drive',
                protocol=sushy.PROTOCOL_TYPE_SAS))
        for i in [self.drive_id5, self.drive_id6, self.drive_id7]:
            mock_drives.append(_mock_drive(
                identity=i, block_size_bytes=512, capacity_bytes=479559942144,
                media_type='SSD', name='Solid State Drive',
                protocol=sushy.PROTOCOL_TYPE_SATA))
        self.mock_storage.drives = mock_drives
        mock_controller = mock.Mock()
        mock_controller.raid_types = ['RAID1', 'RAID5', 'RAID10']
        self.mock_storage.storage_controllers = [mock_controller]
        mock_volumes = mock.MagicMock()
        self.mock_storage.volumes = mock_volumes
        self.free_space_bytes = {d: d.capacity_bytes for d in
                                 mock_drives}
        self.physical_disks = mock_drives

    def test__max_volume_size_bytes_raid0(self, mock_get_system):
        spans = redfish_raid._calculate_spans('0', 3)
        max_size = redfish_raid._max_volume_size_bytes(
            '0', self.physical_disks[0:3], self.free_space_bytes,
            spans_count=spans)
        self.assertEqual(2698380312576, max_size)

    def test__max_volume_size_bytes_raid1(self, mock_get_system):
        spans = redfish_raid._calculate_spans('1', 2)
        max_size = redfish_raid._max_volume_size_bytes(
            '1', self.physical_disks[0:2], self.free_space_bytes,
            spans_count=spans)
        self.assertEqual(899460104192, max_size)

    def test__max_volume_size_bytes_raid5(self, mock_get_system):
        spans = redfish_raid._calculate_spans('5', 3)
        max_size = redfish_raid._max_volume_size_bytes(
            '5', self.physical_disks[0:3], self.free_space_bytes,
            spans_count=spans)
        self.assertEqual(1798920208384, max_size)

    def test__max_volume_size_bytes_raid6(self, mock_get_system):
        spans = redfish_raid._calculate_spans('6', 4)
        max_size = redfish_raid._max_volume_size_bytes(
            '6', self.physical_disks[0:4], self.free_space_bytes,
            spans_count=spans)
        self.assertEqual(1798920208384, max_size)

    def test__volume_usage_per_disk_bytes_raid5(self, mock_get_system):
        logical_disk = {
            'size_gb': 100,
            'raid_level': '5',
            'controller': 'Smart Array P822 in Slot 3',
            'physical_disks': [
                '35D38F11ACEF7BD3',
                '3F5A8C54207B7233',
                '32ADF365C6C1B7BD'
            ],
            'is_root_volume': True
        }
        logical_disk['size_bytes'] = logical_disk['size_gb'] * units.Gi
        del logical_disk['size_gb']
        spans = redfish_raid._calculate_spans('5', 3)
        usage_bytes = redfish_raid._volume_usage_per_disk_bytes(
            logical_disk, self.physical_disks[0:3], spans_count=spans)
        self.assertEqual(53687091200, usage_bytes)

    def test__volume_usage_per_disk_bytes_raid10(self, mock_get_system):
        logical_disk = {
            'size_gb': 50,
            'raid_level': '1+0',
            'controller': 'RAID.Integrated.1-1',
            'volume_name': 'root_volume',
            'is_root_volume': True,
            'physical_disks': [
                '35D38F11ACEF7BD3',
                '3F5A8C54207B7233',
                '32ADF365C6C1B7BD',
                '3D58ECBC375FD9F2'
            ]
        }
        logical_disk['size_bytes'] = logical_disk['size_gb'] * units.Gi
        del logical_disk['size_gb']
        spans = redfish_raid._calculate_spans('1+0', 4)
        usage_bytes = redfish_raid._volume_usage_per_disk_bytes(
            logical_disk, self.physical_disks[0:4], spans_count=spans)
        self.assertEqual(26843545600, usage_bytes)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_1a(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 'MAX',
                    'raid_level': '5',
                    'is_root_volume': True
                }
            ]
        }
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            self.assertRaisesRegex(
                exception.InvalidParameterValue,
                "'physical_disks' is missing from logical_disk while "
                "'size_gb'='MAX' was requested",
                task.driver.raid.create_configuration, task)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_1b(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True
                }
            ]
        }
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.save()
        mock_sdii = mock.Mock()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.set_driver_internal_info = mock_sdii
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3}
                    ]
                }
            }
            self.mock_storage.volumes.create.assert_called_once_with(
                expected_payload, apply_time=None
            )
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual({}, task.node.raid_config)
        self.assertEqual(2, mock_sdii.call_count)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_1b_apply_time_immediate(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                    'volume_name': 'test-volume'
                }
            ]
        }
        created_volumes = [_mock_volume('1', raid_type=sushy.RAIDType.RAID5,
                                        capacity_bytes=100 * units.Gi,
                                        volume_name='test-volume')]
        volumes = mock.MagicMock()
        # Called after volumes created
        volumes.get_members.return_value = created_volumes
        op_apply_time_support = mock.MagicMock()
        op_apply_time_support.mapped_supported_values = [
            sushy.APPLY_TIME_IMMEDIATE, sushy.APPLY_TIME_ON_RESET]
        volumes.operation_apply_time_support = op_apply_time_support
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        volumes.create.return_value = resource
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            self.assertIsNone(task.driver.raid.create_configuration(task))
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3}
                    ]
                },
                'Name': 'test-volume'
            }
            self.mock_storage.volumes.create.assert_called_once_with(
                expected_payload, apply_time=sushy.APPLY_TIME_IMMEDIATE)
            mock_set_async_step_flags.assert_called_once_with(
                task.node, reboot=False, skip_current_step=True, polling=True)
            self.assertEqual(mock_node_power_action.call_count, 0)
            self.assertEqual(mock_build_agent_options.call_count, 0)
            self.assertEqual(mock_prepare_ramdisk.call_count, 0)
            self.assertIsNone(
                task.node.driver_internal_info.get('raid_configs'))
            self.assertEqual(
                [{'controller': 'RAID controller 1',
                  'id': '1',
                  'name': 'test-volume',
                  'raid_level': '5',
                  'size_gb': 100}],
                task.node.raid_config['logical_disks'])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_1b_apply_time_on_reset(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True
                }
            ]
        }
        volumes = mock.MagicMock()
        op_apply_time_support = mock.MagicMock()
        op_apply_time_support.mapped_supported_values = [
            sushy.APPLY_TIME_ON_RESET]
        volumes.operation_apply_time_support = op_apply_time_support
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        task_mon = mock.MagicMock()
        task_mon.task_monitor_uri = '/TaskService/123'
        volumes.create.return_value = task_mon
        self.node.target_raid_config = target_raid_config
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            result = task.driver.raid.create_configuration(task)
            self.assertEqual(states.DEPLOYWAIT, result)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3}
                    ]
                }
            }
            self.mock_storage.volumes.create.assert_called_once_with(
                expected_payload, apply_time=sushy.APPLY_TIME_ON_RESET)
            mock_set_async_step_flags.assert_called_once_with(
                task.node, reboot=True, skip_current_step=True, polling=True)
            mock_node_power_action.assert_called_once_with(
                task, states.REBOOT)
            mock_build_agent_options.assert_called_once_with(task.node)
            self.assertEqual(mock_prepare_ramdisk.call_count, 1)
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual({}, task.node.raid_config)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_2(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):

        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                    'disk_type': 'ssd'
                },
                {
                    'size_gb': 500,
                    'raid_level': '1',
                    'disk_type': 'hdd'
                }
            ]
        }
        created_volumes = [
            _mock_volume('1', raid_type=sushy.RAIDType.RAID5,
                         capacity_bytes=100 * units.Gi),
            _mock_volume('2', raid_type=sushy.RAIDType.RAID1,
                         capacity_bytes=500 * units.Gi)]
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        self.mock_storage.volumes.create.return_value = resource
        # Called after volumes created
        self.mock_storage.volumes.get_members.return_value = created_volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload1 = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id5},
                        {'@odata.id': pre + self.drive_id6},
                        {'@odata.id': pre + self.drive_id7}
                    ]
                }
            }
            expected_payload2 = {
                'RAIDType': 'RAID1',
                'CapacityBytes': 536870912000,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2}
                    ]
                }
            }
            self.assertEqual(
                self.mock_storage.volumes.create.call_count, 2)
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload1, apply_time=None
            )
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload2, apply_time=None
            )
            self.assertEqual(
                [{'controller': 'RAID controller 1',
                  'id': '1',
                  'name': 'Volume 1',
                  'raid_level': '5',
                  'size_gb': 100},
                 {'controller': 'RAID controller 1',
                  'id': '2',
                  'name': 'Volume 2',
                  'raid_level': '1',
                  'size_gb': 500}],
                task.node.raid_config['logical_disks'])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_2_on_reset(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):

        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                    'disk_type': 'ssd'
                },
                {
                    'size_gb': 500,
                    'raid_level': '1',
                    'disk_type': 'hdd'
                }
            ]
        }
        volumes = mock.MagicMock()
        op_apply_time_support = mock.MagicMock()
        op_apply_time_support.mapped_supported_values = [
            sushy.APPLY_TIME_ON_RESET]
        volumes.operation_apply_time_support = op_apply_time_support
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        task_mon = mock.MagicMock()
        task_mon.task_monitor_uri = '/TaskService/123'
        volumes.create.return_value = task_mon
        self.node.target_raid_config = target_raid_config
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload = {
                'RAIDType': 'RAID1',
                'CapacityBytes': 536870912000,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2}
                    ]
                }
            }
            expected_raid_configs = {
                'operation': 'create',
                'pending': {'RAID controller 1': [
                    {'controller': 'RAID controller 1',
                     'disk_type': 'ssd',
                     'is_root_volume': True,
                     'physical_disks': [self.drive_id5,
                                        self.drive_id6,
                                        self.drive_id7],
                     'raid_level': '5',
                     'size_bytes': 107374182400,
                     'span_depth': 1,
                     'span_length': 3.0}]},
                'task_monitor_uri': ['/TaskService/123']}
            self.assertEqual(
                self.mock_storage.volumes.create.call_count, 1)
            self.mock_storage.volumes.create.assert_called_with(
                expected_payload, apply_time=sushy.APPLY_TIME_ON_RESET)
            self.assertEqual(
                expected_raid_configs,
                task.node.driver_internal_info.get('raid_configs'))
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual({}, task.node.raid_config)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_3(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'controller': 'Smart Array P822 in Slot 3',
                    # 'physical_disks': ['6I:1:5', '6I:1:6', '6I:1:7'],
                    'physical_disks': [
                        '35D38F11ACEF7BD3',
                        '3F5A8C54207B7233',
                        '32ADF365C6C1B7BD'
                    ],
                    'is_root_volume': True
                }
            ]
        }
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.save()
        sdii_mock = mock.Mock()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.set_driver_internal_info = sdii_mock
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3}
                    ]
                }
            }
            self.mock_storage.volumes.create.assert_called_once_with(
                expected_payload, apply_time=None
            )
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual({}, task.node.raid_config)
        sdii_mock.assert_has_calls([
            mock.call('raid_configs', {'operation': 'create',
                                       'pending': {},
                                       'task_monitor_uri': mock.ANY}),
            mock.call('agent_start_attempted', True)])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_4(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        # TODO(bdodd): update self.mock_storage to add more drives to satisfy
        #              both logical disks
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 50,
                    'raid_level': '1+0',
                    'controller': 'RAID.Integrated.1-1',
                    'volume_name': 'root_volume',
                    'is_root_volume': True,
                    # 'physical_disks': [
                    #     'Disk.Bay.0:Encl.Int.0-1:RAID.Integrated.1-1',
                    #     'Disk.Bay.1:Encl.Int.0-1:RAID.Integrated.1-1'
                    # ]
                    'physical_disks': [
                        '35D38F11ACEF7BD3',
                        '3F5A8C54207B7233',
                        '32ADF365C6C1B7BD',
                        '3D58ECBC375FD9F2'
                    ]
                },
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'controller': 'RAID.Integrated.1-1',
                    'volume_name': 'data_volume',
                    # 'physical_disks': [
                    #     'Disk.Bay.2:Encl.Int.0-1:RAID.Integrated.1-1',
                    #     'Disk.Bay.3:Encl.Int.0-1:RAID.Integrated.1-1',
                    #     'Disk.Bay.4:Encl.Int.0-1:RAID.Integrated.1-1'
                    # ]
                    'physical_disks': [
                        '3F5A8C54207B7233',
                        '32ADF365C6C1B7BD',
                        '3D58ECBC375FD9F2'
                    ]
                }
            ]
        }
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        self.mock_storage.volumes.create.return_value = resource
        created_volumes = [
            _mock_volume(
                '1', raid_type=sushy.RAIDType.RAID10,
                capacity_bytes=50 * units.Gi),
            _mock_volume(
                '2', raid_type=sushy.RAIDType.RAID5,
                capacity_bytes=100 * units.Gi)]
        # Called after volumes created
        self.mock_storage.volumes.get_members.return_value = created_volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload1 = {
                'RAIDType': 'RAID10',
                'CapacityBytes': 53687091200,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3},
                        {'@odata.id': pre + self.drive_id4}
                    ]
                },
                'Name': 'root_volume'
            }
            expected_payload2 = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id2},
                        {'@odata.id': pre + self.drive_id3},
                        {'@odata.id': pre + self.drive_id4}
                    ]
                },
                'Name': 'data_volume'
            }
            self.assertEqual(
                self.mock_storage.volumes.create.call_count, 2)
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload1, apply_time=None
            )
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload2, apply_time=None
            )
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual(
                [{'controller': 'RAID controller 1',
                  'id': '1',
                  'name': 'Volume 1',
                  'raid_level': '1+0',
                  'size_gb': 50},
                 {'controller': 'RAID controller 1',
                  'id': '2',
                  'name': 'Volume 2',
                  'raid_level': '5',
                  'size_gb': 100}],
                task.node.raid_config['logical_disks'])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_5a(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '1',
                    'controller': 'software'
                },
                {
                    'size_gb': 'MAX',
                    'raid_level': '0',
                    'controller': 'software'
                }
            ]
        }
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            self.assertRaisesRegex(
                exception.InvalidParameterValue,
                "'physical_disks' is missing from logical_disk while "
                "'size_gb'='MAX' was requested",
                task.driver.raid.create_configuration, task)

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_5b(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '1',
                    'controller': 'software'
                },
                {
                    'size_gb': 500,
                    'raid_level': '0',
                    'controller': 'software'
                }
            ]
        }
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        self.mock_storage.volumes.create.return_value = resource
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload1 = {
                'RAIDType': 'RAID1',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2}
                    ]
                }
            }
            expected_payload2 = {
                'RAIDType': 'RAID0',
                'CapacityBytes': 536870912000,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id3}
                    ]
                }
            }
            self.assertEqual(
                self.mock_storage.volumes.create.call_count, 2)
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload1, apply_time=None
            )
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload2, apply_time=None
            )

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_case_6(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 'MAX',
                    'raid_level': '0',
                    'controller': 'software',
                    'physical_disks': [
                        {'size': '> 100'},
                        {'size': '> 100'}
                    ]
                }
            ]
        }
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.save()
        # TODO(bdodd): update when impl can handle disk size evaluation
        #  (see _calculate_volume_props())
        """
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
        """

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_create_config_interface_type(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):

        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                    'interface_type': 'sata'
                },
                {
                    'size_gb': 500,
                    'raid_level': '1',
                    'interface_type': 'sas'
                }
            ]
        }
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        self.mock_storage.volumes.create.return_value = resource
        created_volumes = [
            _mock_volume(
                '1', raid_type=sushy.RAIDType.RAID5,
                capacity_bytes=100 * units.Gi),
            _mock_volume(
                '2', raid_type=sushy.RAIDType.RAID1,
                capacity_bytes=500 * units.Gi)]
        # Called after volumes created
        self.mock_storage.volumes.get_members.return_value = created_volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        self.node.target_raid_config = target_raid_config
        self.node.save()
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)
            pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
            expected_payload1 = {
                'RAIDType': 'RAID5',
                'CapacityBytes': 107374182400,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id5},
                        {'@odata.id': pre + self.drive_id6},
                        {'@odata.id': pre + self.drive_id7}
                    ]
                }
            }
            expected_payload2 = {
                'RAIDType': 'RAID1',
                'CapacityBytes': 536870912000,
                'Links': {
                    'Drives': [
                        {'@odata.id': pre + self.drive_id1},
                        {'@odata.id': pre + self.drive_id2}
                    ]
                }
            }
            self.assertEqual(
                self.mock_storage.volumes.create.call_count, 2)
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload1, apply_time=None
            )
            self.mock_storage.volumes.create.assert_any_call(
                expected_payload2, apply_time=None
            )
            self.assertEqual(
                [{'controller': 'RAID controller 1',
                  'id': '1',
                  'name': 'Volume 1',
                  'raid_level': '5',
                  'size_gb': 100},
                 {'controller': 'RAID controller 1',
                  'id': '2',
                  'name': 'Volume 2',
                  'raid_level': '1',
                  'size_gb': 500}],
                task.node.raid_config['logical_disks'])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_delete_config_immediate(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        mock_volumes = []
        for i in ["1", "2"]:
            mock_volumes.append(_mock_volume(
                i, raid_type=sushy.RAIDType.RAID1))
        op_apply_time_support = mock.MagicMock()
        op_apply_time_support.mapped_supported_values = [
            sushy.APPLY_TIME_IMMEDIATE, sushy.APPLY_TIME_ON_RESET]
        self.mock_storage.volumes.operation_apply_time_support = (
            op_apply_time_support)
        # 2nd call to mock no volumes after delete
        self.mock_storage.volumes.get_members.side_effect = [mock_volumes, []]
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            last_updated = '2022-05-18 08:49:17.585443'
            task.node.raid_config = {
                'logical_disks': [{
                    'controller': 'RAID controller 1',
                    'id': '1',
                    'name': 'Volume 1',
                    'raid_level': '1',
                    'size_gb': 100}],
                'last_updated': last_updated}
            self.assertIsNone(task.driver.raid.delete_configuration(task))
            self.assertEqual(mock_volumes[0].delete.call_count, 1)
            self.assertEqual(mock_volumes[1].delete.call_count, 1)
            mock_set_async_step_flags.assert_called_once_with(
                task.node, reboot=False, skip_current_step=True, polling=True)
            self.assertEqual(mock_node_power_action.call_count, 0)
            self.assertEqual(mock_build_agent_options.call_count, 0)
            self.assertEqual(mock_prepare_ramdisk.call_count, 0)
            self.assertIsNone(
                task.node.driver_internal_info.get('raid_configs'))
            self.assertEqual([], task.node.raid_config['logical_disks'])
            self.assertNotEqual(
                last_updated, task.node.raid_config['last_updated'])

    @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk',
                       spec_set=True, autospec=True)
    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
    def test_delete_config_on_reset(
            self,
            mock_set_async_step_flags,
            mock_node_power_action,
            mock_build_agent_options,
            mock_prepare_ramdisk,
            mock_get_system):
        mock_volumes = []
        for i in ["1", "2"]:
            mock_volumes.append(_mock_volume(
                i, raid_type=sushy.RAIDType.RAID1))
        op_apply_time_support = mock.MagicMock()
        op_apply_time_support.mapped_supported_values = [
            sushy.APPLY_TIME_ON_RESET]
        self.mock_storage.volumes.operation_apply_time_support = (
            op_apply_time_support)
        self.mock_storage.volumes.get_members.return_value = mock_volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        task_mon = mock.MagicMock()
        task_mon.task_monitor_uri = '/TaskService/123'
        mock_volumes[0].delete.return_value = task_mon
        mock_volumes[1].delete.return_value = task_mon
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            raid_config = {
                'logical_disks': [{
                    'controller': 'RAID controller 1',
                    'id': '1',
                    'name': 'Volume 1',
                    'raid_level': '1',
                    'size_gb': 100}],
                'last_updated': '2022-05-18 08:49:17.585443'}
            task.node.raid_config = raid_config
            task.node.deploy_step = {
                'step': 'delete_configuration',
                'interface': 'raid'}
            result = task.driver.raid.delete_configuration(task)
            self.assertEqual(states.DEPLOYWAIT, result)
            self.assertEqual(mock_volumes[0].delete.call_count, 1)
            self.assertEqual(mock_volumes[1].delete.call_count, 0)
            mock_set_async_step_flags.assert_called_once_with(
                task.node, reboot=True, skip_current_step=True, polling=True)
            mock_node_power_action.assert_called_once_with(
                task, states.REBOOT)
            mock_build_agent_options.assert_called_once_with(task.node)
            self.assertEqual(mock_prepare_ramdisk.call_count, 1)
            self.assertEqual(
                {'operation': 'delete',
                 'pending': True,
                 'task_monitor_uri': ['/TaskService/123']},
                task.node.driver_internal_info.get('raid_configs'))
            # Async operation, raid_config shouldn't be updated yet
            self.assertEqual(raid_config, task.node.raid_config)

    def test_volume_create_error_handler(self, mock_get_system):
        volume_collection = self.mock_storage.volumes
        sushy_error = sushy.exceptions.SushyError()
        volume_collection.create.side_effect = sushy_error
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        mock_error_handler = mock.Mock()
        drive_id = '35D38F11ACEF7BD3'
        physical_disks = [drive_id]
        capacity_bytes = 53739520000
        pre = '/redfish/v1/Systems/1/Storage/1/Drives/'
        expected_payload = {
            'RAIDType': 'RAID1',
            'CapacityBytes': capacity_bytes,
            'Links': {
                'Drives': [{'@odata.id': pre + drive_id}]
            }
        }
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            redfish_raid.create_virtual_disk(
                task, None, physical_disks, '1', capacity_bytes,
                error_handler=mock_error_handler)
            self.assertEqual(mock_error_handler.call_count, 1)
            mock_error_handler.assert_called_once_with(
                task, sushy_error, volume_collection, expected_payload
            )

    def test_validate_raid_config(self, mock_get_system):
        raid_config = {
            'logical_disks': [
                {
                    'size_gb': 500,
                    'raid_level': '1+0',
                    'interface_type': 'sata'
                },
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                }
            ]
        }

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.validate_raid_config(task, raid_config)

    def test_validate_raid_config_scsi(self, mock_get_system):
        raid_config = {
            'logical_disks': [
                {
                    'size_gb': 500,
                    'raid_level': '1+0',
                    'interface_type': 'sata'
                },
                {
                    'size_gb': 100,
                    'raid_level': '5',
                    'is_root_volume': True,
                    'interface_type': 'scsi'
                }
            ]
        }

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            self.assertRaisesRegex(
                exception.InvalidParameterValue,
                "interface type `scsi` not supported by Redfish RAID",
                task.driver.raid.validate_raid_config, task, raid_config)

    def test_get_physical_disks(self, mock_get_system):
        nonraid_controller = mock.Mock()
        nonraid_controller.raid_types = []
        nonraid_storage = mock.MagicMock()
        nonraid_storage.controllers = mock.MagicMock()
        nonraid_storage.controllers.get_members.return_value = [
            nonraid_controller]
        nonraid_storage.drives = [_mock_drive(
            identity='Drive1', block_size_bytes=512,
            capacity_bytes=899527000000,
            media_type='HDD', name='Drive', protocol='SAS')]

        mock_get_system.return_value.storage.get_members.return_value = [
            nonraid_storage, self.mock_storage]

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            disks, disk_to_controller = redfish_raid.get_physical_disks(
                task.node)

            for drive in self.mock_storage.drives:
                self.assertIn(drive, disks)
                self.assertIn(drive, disk_to_controller)

            self.assertNotIn(nonraid_storage.drives[0], disks)
            self.assertNotIn(nonraid_storage.drives[0], disk_to_controller)

    def test__get_storage_controller(self, mock_get_system):
        nonraid_controller = mock.Mock()
        nonraid_controller.raid_types = []
        nonraid_storage = mock.MagicMock()
        nonraid_storage.controllers = None
        nonraid_storage.storage_controllers = [nonraid_controller]
        nonraid_storage.drives = mock.Mock()

        mock_get_system.return_value.storage.get_members.return_value = [
            nonraid_storage, self.mock_storage]

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            storage = redfish_raid._get_storage_controller(
                task.node, mock_get_system.return_value, ['32ADF365C6C1B7BD'])

            self.assertEqual(storage, self.mock_storage)
            nonraid_storage.drives.assert_not_called()

    def test__get_controller(self, mock_get_system):
        nonraid_controller = mock.Mock()
        nonraid_controller.raid_types = []
        nonraid_storage = mock.MagicMock()
        nonraid_storage.get_members.return_value = [nonraid_controller]
        nonraid_storage.controllers = mock.MagicMock()
        nonraid_storage.controllers.get_members.return_value = [
            nonraid_controller]
        nonraid_storage.drives = [_mock_drive(
            identity='Drive1', block_size_bytes=512,
            capacity_bytes=899527000000,
            media_type='HDD', name='Drive', protocol='SAS')]

        mock_get_system.return_value.storage.get_members.return_value = [
            nonraid_storage, self.mock_storage]

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            storage = redfish_raid._get_storage_controller(
                task.node, mock_get_system.return_value, ['32ADF365C6C1B7BD'])

            self.assertEqual(storage, self.mock_storage)

    def test__get_first_controller_controllers(self, mock_get_system):
        controller1 = mock.Mock(name="controller")
        controller2 = mock.Mock(name="storage_controller")

        storage = mock.MagicMock()
        storage.get_members.return_value = [controller1]

        storage.storage_controllers = mock.MagicMock()
        storage.controllers = mock.MagicMock()

        storage.controllers.get_members.return_value = [controller1]
        storage.storage_controllers.return_value = [controller2]

        result = redfish_utils.get_first_controller(storage)
        self.assertEqual(result, controller1)

    def test__get_first_controller_storage_controllers(self, mock_get_system):
        storage = mock.Mock()
        controller_mock = mock.Mock()
        storage.storage_controllers = [controller_mock]
        type(storage).controllers = mock.PropertyMock(
            side_effect=sushy.exceptions.MissingAttributeError)

        result = redfish_utils.get_first_controller(storage)
        self.assertEqual(result, controller_mock)

    def test__get_controller_no_controllers(self, mock_get_system):
        storage = mock.Mock()
        storage.get_members.return_value = None
        storage.controllers.return_value = None
        storage.controllers.get_members.return_value = None
        storage.storage_controllers.return_value = None

        result = redfish_utils.get_first_controller(storage)
        self.assertIsNone(result)

    def test__get_first_controller_empty(self, mock_get_system):
        storage = mock.Mock()
        storage.controllers = mock.Mock()
        storage.controllers.get_members.return_value = []

        result = redfish_utils.get_first_controller(storage)
        self.assertIsNone(result)

    @mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
    @mock.patch.object(redfish_raid.LOG, 'info', autospec=True)
    def test__raid_config_in_progress_success(
            self, mock_info, mock_get_task_monitor, mock_get_system):
        mock_task = mock.Mock()
        mock_task.task_state = sushy.TASK_STATE_COMPLETED
        mock_task.task_status = sushy.HEALTH_OK
        mock_task.messages = []
        mock_task_monitor = mock.Mock()
        mock_task_monitor.is_processing = False
        mock_task_monitor.get_task.return_value = mock_task
        mock_get_task_monitor.return_value = mock_task_monitor

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:

            raid = redfish_raid.RedfishRAID()
            result = raid._raid_config_in_progress(
                task, '/TaskService/123', 'create')
            self.assertEqual(False, result)
            mock_info.assert_called_once()

    @mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
    @mock.patch.object(redfish_raid.LOG, 'info', autospec=True)
    def test__raid_config_in_progress_task_mon_error(
            self, mock_info, mock_get_task_monitor, mock_get_system):
        mock_get_task_monitor.side_effect = exception.RedfishError(
            error='Task not found')

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:

            raid = redfish_raid.RedfishRAID()
            result = raid._raid_config_in_progress(
                task, '/TaskService/123', 'create')
            self.assertEqual(False, result)
            mock_info.assert_called_once()

    @mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
    @mock.patch.object(redfish_raid.LOG, 'debug', autospec=True)
    def test__raid_config_in_progress_still_processing(
            self, mock_debug, mock_get_task_monitor, mock_get_system):
        mock_task_monitor = mock.Mock()
        mock_task_monitor.is_processing = True
        mock_get_task_monitor.return_value = mock_task_monitor

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:

            raid = redfish_raid.RedfishRAID()
            result = raid._raid_config_in_progress(
                task, '/TaskService/123', 'create')
            self.assertEqual(True, result)
            mock_debug.assert_called_once()

    @mock.patch.object(redfish_utils, 'get_task_monitor', autospec=True)
    @mock.patch.object(redfish_raid.LOG, 'error', autospec=True)
    def test__raid_config_in_progress_failed(
            self, mock_error, mock_get_task_monitor, mock_get_system):
        mock_message = mock.Mock()
        mock_message.message = 'RAID configuration failed'
        mock_message.severity = sushy.SEVERITY_CRITICAL
        mock_task = mock.Mock()
        mock_task.task_state = sushy.TASK_STATE_COMPLETED
        mock_task.task_status = sushy.HEALTH_CRITICAL
        mock_task.messages = [mock_message]
        mock_task_monitor = mock.Mock()
        mock_task_monitor.is_processing = False
        mock_task_monitor.get_task.return_value = mock_task
        mock_get_task_monitor.return_value = mock_task_monitor

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:

            raid = redfish_raid.RedfishRAID()
            result = raid._raid_config_in_progress(
                task, '/TaskService/123', 'create')
            self.assertEqual(False, result)
            mock_error.assert_called_once()

    @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
                       autospec=True)
    @mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
                       autospec=True)
    @mock.patch.object(redfish_utils, 'get_task_monitor',
                       autospec=True)
    def test__check_node_raid_config_deploy(
            self, mock_get_task_monitor, mock_resume_deploy,
            mock_resume_clean, mock_get_system):
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.deploy_step = {'priority': 100, 'interface': 'raid',
                                     'step': 'delete_configuration',
                                     'argsinfo': {}}
            info = task.node.driver_internal_info
            info['raid_configs'] = {'operation': 'delete', 'pending': {},
                                    'task_monitor_uri': ['/TaskService/123']}
            task.node.driver_internal_info = info
            task.node.save()

            mock_task_monitor = mock_get_task_monitor.return_value
            mock_task_monitor.is_processing = False
            mock_task_monitor.response.status_code = 200

            raid = redfish_raid.RedfishRAID()
            raid._check_node_raid_config(task)

            mock_resume_deploy.assert_called_with(task)
            mock_resume_clean.assert_not_called()
            self.assertEqual([], task.node.raid_config['logical_disks'])
            self.assertIsNotNone(task.node.raid_config.get('last_updated'))

    @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
                       autospec=True)
    @mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
                       autospec=True)
    @mock.patch.object(redfish_utils, 'get_task_monitor',
                       autospec=True)
    def test__check_node_raid_config_clean(
            self, mock_get_task_monitor, mock_resume_deploy,
            mock_resume_clean, mock_get_system):
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.clean_step = {'interface': 'raid',
                                    'step': 'delete_configuration',
                                    'argsinfo': {}}
            info = task.node.driver_internal_info
            info['raid_configs'] = {'operation': 'delete', 'pending': {},
                                    'task_monitor_uri': ['/TaskService/123']}
            task.node.driver_internal_info = info
            task.node.save()

            mock_task_monitor = mock_get_task_monitor.return_value
            mock_task_monitor.is_processing = False
            mock_task_monitor.response.status_code = 200

            raid = redfish_raid.RedfishRAID()
            raid._check_node_raid_config(task)

            mock_resume_deploy.assert_not_called()
            mock_resume_clean.assert_called_with(task)
            self.assertEqual([], task.node.raid_config['logical_disks'])
            self.assertIsNotNone(task.node.raid_config.get('last_updated'))

    @mock.patch.object(redfish_utils, 'get_task_monitor',
                       autospec=True)
    @mock.patch.object(redfish_raid.RedfishRAID,
                       '_submit_create_configuration', autospec=True)
    @mock.patch.object(redfish_raid.RedfishRAID,
                       '_submit_delete_configuration', autospec=True)
    @mock.patch.object(deploy_utils, 'reboot_to_finish_step', autospec=True)
    def test__check_node_raid_config_pending_create(
            self, mock_reboot, mock_submit_delete, mock_submit_create,
            mock_get_task_monitor, mock_get_system):
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.clean_step = {'interface': 'raid',
                                    'step': 'create_configuration',
                                    'argsinfo': {}}
            info = task.node.driver_internal_info
            raid_configs = {
                'operation': 'create',
                'pending': {'RAID controller 1': [
                    {'controller': 'RAID controller 1',
                     'disk_type': 'ssd',
                     'is_root_volume': True,
                     'physical_disks': [self.drive_id5,
                                        self.drive_id6,
                                        self.drive_id7],
                     'raid_level': '5',
                     'size_bytes': 107374182400,
                     'span_depth': 1,
                     'span_length': 3.0}]},
                'task_monitor_uri': ['/TaskService/123']}
            info['raid_configs'] = raid_configs
            task.node.driver_internal_info = info
            task.node.save()

            mock_task_monitor = mock_get_task_monitor.return_value
            mock_task_monitor.is_processing = False
            mock_task_monitor.response.status_code = 200

            mock_submit_create.return_value = ({}, True)

            raid = redfish_raid.RedfishRAID()
            raid._check_node_raid_config(task)

            mock_submit_create.assert_called_with(
                raid, task, raid_configs['pending'])
            mock_submit_delete.assert_not_called()
            mock_reboot.assert_called_with(task)
            # Not yet updated as in progress
            self.assertEqual({}, task.node.raid_config)

    @mock.patch.object(redfish_utils, 'get_task_monitor',
                       autospec=True)
    @mock.patch.object(redfish_raid.RedfishRAID,
                       '_submit_create_configuration', autospec=True)
    @mock.patch.object(redfish_raid.RedfishRAID,
                       '_submit_delete_configuration', autospec=True)
    @mock.patch.object(deploy_utils, 'reboot_to_finish_step', autospec=True)
    def test__check_node_raid_config_pending_delete(
            self, mock_reboot, mock_submit_delete, mock_submit_create,
            mock_get_task_monitor, mock_get_system):
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.node.clean_step = {'interface': 'raid',
                                    'step': 'delete_configuration',
                                    'argsinfo': {}}
            info = task.node.driver_internal_info
            raid_configs = {
                'operation': 'delete',
                'pending': True,
                'task_monitor_uri': ['/TaskService/123']}
            info['raid_configs'] = raid_configs
            task.node.driver_internal_info = info
            task.node.save()

            mock_task_monitor = mock_get_task_monitor.return_value
            mock_task_monitor.is_processing = False
            mock_task_monitor.response.status_code = 200

            mock_submit_delete.return_value = ({}, False)

            raid = redfish_raid.RedfishRAID()
            raid._check_node_raid_config(task)

            mock_submit_create.assert_not_called()
            mock_submit_delete.assert_called_with(raid, task)
            mock_reboot.assert_not_called()
            # Not yet updated as in progress
            self.assertEqual({}, task.node.raid_config)

    @mock.patch.object(redfish_raid, 'LOG', autospec=True)
    def test_update_raid_config_missing_raid_type(
            self, mock_log, mock_get_system):
        volumes = [
            _mock_volume(
                '1', raid_type=None,
                capacity_bytes=100 * units.Gi),
            _mock_volume(
                '2', raid_type=None,
                capacity_bytes=500 * units.Gi)]
        self.mock_storage.volumes.get_members.return_value = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]

        redfish_raid.update_raid_config(self.node)

        self.assertEqual([], self.node.raid_config['logical_disks'])
        mock_log.warning.assert_called_once()


@mock.patch.object(redfish_utils, 'get_system', autospec=True)
class RedfishRAIDMAXSizeTestCase(db_base.DbTestCase):
    """Test cases for MAX size_gb handling in Redfish RAID."""

    def setUp(self):
        super(RedfishRAIDMAXSizeTestCase, self).setUp()
        self.config(enabled_hardware_types=['redfish'],
                    enabled_power_interfaces=['redfish'],
                    enabled_boot_interfaces=['redfish-virtual-media'],
                    enabled_management_interfaces=['redfish'],
                    enabled_inspect_interfaces=['redfish'],
                    enabled_bios_interfaces=['redfish'],
                    enabled_raid_interfaces=['redfish'])
        self.node = obj_utils.create_test_node(
            self.context, driver='redfish', driver_info=INFO_DICT)
        self.mock_storage = mock.MagicMock(identity='RAID controller 1')
        self.drive_id1 = '35D38F11ACEF7BD3'
        self.drive_id2 = '3F5A8C54207B7233'
        mock_drives = []
        for i in [self.drive_id1, self.drive_id2]:
            mock_drives.append(_mock_drive(
                identity=i, block_size_bytes=512,
                capacity_bytes=899527000000,
                media_type='HDD', name='Drive',
                protocol=sushy.PROTOCOL_TYPE_SAS))
        self.mock_storage.drives = mock_drives

    def test_construct_volume_payload_omits_capacity_bytes_for_max(
            self, mock_get_system):
        """Test that CapacityBytes is omitted when size_bytes is 'MAX'."""
        node = self.node
        storage = self.mock_storage

        payload = redfish_raid._construct_volume_payload(
            node, storage, 'controller1', [self.drive_id1, self.drive_id2],
            '1', 'MAX')

        self.assertNotIn('CapacityBytes', payload)
        self.assertEqual(payload['RAIDType'], 'RAID1')
        self.assertIn('Links', payload)

    def test_construct_volume_payload_includes_capacity_bytes_for_exact_size(
            self, mock_get_system):
        """Test that CapacityBytes is included when size_bytes is exact."""
        node = self.node
        storage = self.mock_storage
        size_bytes = 100 * units.Gi

        payload = redfish_raid._construct_volume_payload(
            node, storage, 'controller1', [self.drive_id1, self.drive_id2],
            '1', size_bytes)

        self.assertIn('CapacityBytes', payload)
        self.assertEqual(payload['CapacityBytes'], size_bytes)
        self.assertEqual(payload['RAIDType'], 'RAID1')

    def test_calculate_volume_props_preserves_max(
            self, mock_get_system):
        """Test that _calculate_volume_props preserves 'MAX' as size_bytes."""
        logical_disk = {
            'size_bytes': 'MAX',
            'raid_level': '1',
            'physical_disks': [self.drive_id1, self.drive_id2]
        }
        physical_disks = self.mock_storage.drives
        free_space_bytes = {
            disk: disk.capacity_bytes for disk in physical_disks}
        disk_to_storage = {
            disk: self.mock_storage for disk in physical_disks}

        redfish_raid._calculate_volume_props(
            logical_disk, physical_disks, free_space_bytes, disk_to_storage)

        # MAX should be preserved
        self.assertEqual(logical_disk['size_bytes'], 'MAX')
        # Controller should be set
        self.assertIn('controller', logical_disk)

    def test_create_configuration_with_max_size_omits_capacity_bytes(
            self, mock_get_system):
        """Test that create_configuration omits CapacityBytes for MAX size."""
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 'MAX',
                    'raid_level': '1',
                    'is_root_volume': True,
                    'physical_disks': [self.drive_id1, self.drive_id2]
                }
            ]
        }
        created_volumes = [
            _mock_volume('1', raid_type=sushy.RAIDType.RAID1,
                         capacity_bytes=899527000000)
        ]
        volumes = mock.MagicMock()
        volumes.get_members.return_value = created_volumes
        volumes.operation_apply_time_support = None
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        volumes.create.return_value = resource
        self.node.target_raid_config = target_raid_config
        self.node.save()

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)

            # Verify CapacityBytes was NOT in the payload
            call_args = volumes.create.call_args
            payload = call_args[0][0]
            self.assertNotIn('CapacityBytes', payload)
            self.assertEqual(payload['RAIDType'], 'RAID1')

            # Verify volume was created
            self.assertEqual(volumes.create.call_count, 1)

    def test_create_configuration_with_exact_size_includes_capacity_bytes(
            self, mock_get_system):
        """Test inclusion of CapacityBytes for exact size."""
        target_raid_config = {
            'logical_disks': [
                {
                    'size_gb': 100,
                    'raid_level': '1',
                    'is_root_volume': True,
                    'physical_disks': [self.drive_id1, self.drive_id2]
                }
            ]
        }
        created_volumes = [
            _mock_volume('1', raid_type=sushy.RAIDType.RAID1,
                         capacity_bytes=100 * units.Gi)
        ]
        volumes = mock.MagicMock()
        volumes.get_members.return_value = created_volumes
        volumes.operation_apply_time_support = None
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]
        resource = mock.MagicMock(spec=['resource_name'])
        resource.resource_name = 'volume'
        volumes.create.return_value = resource
        self.node.target_raid_config = target_raid_config
        self.node.save()

        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=True) as task:
            task.driver.raid.create_configuration(task)

            # Verify CapacityBytes WAS in the payload
            call_args = volumes.create.call_args
            payload = call_args[0][0]
            self.assertIn('CapacityBytes', payload)
            self.assertEqual(payload['CapacityBytes'], 100 * units.Gi)
            self.assertEqual(payload['RAIDType'], 'RAID1')

    def test_update_raid_config_stores_actual_size_for_max_volume(
            self, mock_get_system):
        """Test that update_raid_config queries and stores actual size."""
        # Create a volume with actual size (simulating what controller created)
        actual_size_bytes = 899527000000  # Actual size from controller
        created_volumes = [
            _mock_volume('1', raid_type=sushy.RAIDType.RAID1,
                         capacity_bytes=actual_size_bytes)
        ]
        volumes = mock.MagicMock()
        volumes.get_members.return_value = created_volumes
        self.mock_storage.volumes = volumes
        mock_get_system.return_value.storage.get_members.return_value = [
            self.mock_storage]

        redfish_raid.update_raid_config(self.node)

        # Verify the actual size was stored (not 'MAX')
        self.assertIn('logical_disks', self.node.raid_config)
        logical_disk = self.node.raid_config['logical_disks'][0]
        expected_size_gb = int(actual_size_bytes / units.Gi)
        self.assertEqual(logical_disk['size_gb'], expected_size_gb)
        self.assertNotEqual(logical_disk['size_gb'], 'MAX')
