diff --git a/proliantutils/hpssa/disk_allocator.py b/proliantutils/hpssa/disk_allocator.py index d984b5a3..e023adef 100644 --- a/proliantutils/hpssa/disk_allocator.py +++ b/proliantutils/hpssa/disk_allocator.py @@ -32,19 +32,19 @@ def _get_criteria_matching_disks(logical_disk, physical_drives): criteria_to_consider = [x for x in FILTER_CRITERIA if x in logical_disk] - for physical_drive in physical_drives: + for physical_drive_object in physical_drives: for criteria in criteria_to_consider: logical_drive_value = logical_disk.get(criteria) - physical_drive_value = getattr(physical_drive, criteria) + physical_drive_value = getattr(physical_drive_object, criteria) if logical_drive_value != physical_drive_value: break else: - matching_physical_drives.append(physical_drive) + matching_physical_drives.append(physical_drive_object) return matching_physical_drives -def allocate_disks(logical_disk, server): +def allocate_disks(logical_disk, server, raid_config): """Allocate physical disks to a logical disk. This method allocated physical disks to a logical @@ -54,6 +54,7 @@ def allocate_disks(logical_disk, server): :param logical_disk: a dictionary of a logical disk from the RAID configuration input to the module. :param server: An objects.Server object + :param raid_config: The target RAID configuration requested. :raises: PhysicalDisksNotFoundError, if cannot find physical disks for the request. """ @@ -63,6 +64,7 @@ def allocate_disks(logical_disk, server): 'number_of_physical_disks', constants.RAID_LEVEL_MIN_DISKS[raid_level]) share_physical_disks = logical_disk.get('share_physical_disks', False) + # Try to create a new independent array for this request. for controller in server.controllers: physical_drives = controller.unassigned_physical_drives physical_drives = _get_criteria_matching_disks(logical_disk, @@ -76,13 +78,39 @@ def allocate_disks(logical_disk, server): logical_disk['controller'] = controller.id physical_disks = selected_drive_ids[:number_of_physical_disks] logical_disk['physical_disks'] = physical_disks - break + return - if not share_physical_disks: - # TODO(rameshg87): When this logical drives can share disks - # with other arrays, figure out free space in other arrays - # and then consider which array to use. - pass - else: - raise exception.PhysicalDisksNotFoundError(size_gb=size_gb, - raid_level=raid_level) + # We didn't find physical disks to create an independent array. + # Check if we can get some shared arrays. + if share_physical_disks: + sharable_disk_wwns = [] + for sharable_logical_disk in raid_config['logical_disks']: + if (sharable_logical_disk.get('share_physical_disks', False) and + 'root_device_hint' in sharable_logical_disk): + wwn = sharable_logical_disk['root_device_hint']['wwn'] + sharable_disk_wwns.append(wwn) + + for controller in server.controllers: + sharable_arrays = [x for x in controller.raid_arrays if + x.logical_drives[0].wwn in sharable_disk_wwns] + + for array in sharable_arrays: + + # Check if criterias for the logical disk match the ones with + # physical disks in the raid array. + criteria_matched_disks = _get_criteria_matching_disks( + logical_disk, array.physical_drives) + + # Check if all disks in the array don't match the criteria + if len(criteria_matched_disks) != len(array.physical_drives): + continue + + # Check if raid array can accomodate the logical disk. + if array.can_accomodate(logical_disk): + logical_disk['controller'] = controller.id + logical_disk['array'] = array.id + return + + # We check both options and couldn't get any physical disks. + raise exception.PhysicalDisksNotFoundError(size_gb=size_gb, + raid_level=raid_level) diff --git a/proliantutils/hpssa/manager.py b/proliantutils/hpssa/manager.py index 7ee2b646..b261a325 100644 --- a/proliantutils/hpssa/manager.py +++ b/proliantutils/hpssa/manager.py @@ -116,7 +116,8 @@ def create_configuration(raid_config): for logical_disk in logical_disks_sorted: if 'physical_disks' not in logical_disk: - disk_allocator.allocate_disks(logical_disk, server) + disk_allocator.allocate_disks(logical_disk, server, + raid_config) controller_id = logical_disk['controller'] @@ -125,18 +126,17 @@ def create_configuration(raid_config): msg = ("Unable to find controller named '%s'" % controller_id) raise exception.InvalidInputError(reason=msg) - for physical_disk in logical_disk['physical_disks']: - disk_obj = controller.get_physical_drive_by_id(physical_disk) - if not disk_obj: - msg = ("Unable to find physical disk '%(physical_disk)s' " - "on '%(controller)s'" % - {'physical_disk': physical_disk, - 'controller': controller_id}) - raise exception.InvalidInputError(msg) + if 'physical_disks' in logical_disk: + for physical_disk in logical_disk['physical_disks']: + disk_obj = controller.get_physical_drive_by_id(physical_disk) + if not disk_obj: + msg = ("Unable to find physical disk '%(physical_disk)s' " + "on '%(controller)s'" % + {'physical_disk': physical_disk, + 'controller': controller_id}) + raise exception.InvalidInputError(msg) - physical_drive_ids = logical_disk['physical_disks'] - - controller.create_logical_drive(logical_disk, physical_drive_ids) + controller.create_logical_drive(logical_disk) # Now find the new logical drive created. server.refresh() diff --git a/proliantutils/hpssa/objects.py b/proliantutils/hpssa/objects.py index 9973e412..2dd911ac 100644 --- a/proliantutils/hpssa/objects.py +++ b/proliantutils/hpssa/objects.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re import time from oslo.concurrency import processutils @@ -115,13 +116,36 @@ def _convert_to_dict(stdout): return info_dict -def _hpssacli(*args): - """Wrapper function for executing hpssacli command.""" +def _hpssacli(*args, **kwargs): + """Wrapper function for executing hpssacli command. + + :param args: args to be provided to hpssacli command + :param kwargs: kwargs to be sent to processutils except the + following: + - dont_transform_to_hpssa_exception - Set to True if this + method shouldn't transform other exceptions to hpssa + exceptions. This is useful when the return code from hpssacli + is useful for analysis. + :returns: a tuple containing the stdout and stderr after running + the process. + :raises: HPSSAOperationError, if some error was encountered and + dont_dont_transform_to_hpssa_exception was set to False. + :raises: OSError or processutils.ProcessExecutionError if execution + failed and dont_dont_transform_to_hpssa_exception was set to True. + """ + + dont_transform_to_hpssa_exception = kwargs.get( + 'dont_transform_to_hpssa_exception', False) + kwargs.pop('dont_transform_to_hpssa_exception', None) + try: stdout, stderr = processutils.execute("hpssacli", - *args) + *args, **kwargs) except (OSError, processutils.ProcessExecutionError) as e: - raise exception.HPSSAOperationError(reason=e) + if not dont_transform_to_hpssa_exception: + raise exception.HPSSAOperationError(reason=e) + else: + raise return stdout, stderr @@ -318,22 +342,23 @@ class Controller(object): return phy_drive return None - def execute_cmd(self, *args): + def execute_cmd(self, *args, **kwargs): """Execute a given hpssacli command on the controller. This method executes a given command on the controller. :params args: a tuple consisting of sub-commands to be appended after specifying the controller in hpssacli command. + :param kwargs: kwargs to be passed to execute() in processutils :raises: HPSSAOperationError, if hpssacli operation failed. """ slot = self.properties['Slot'] base_cmd = ("controller", "slot=%s" % slot) cmd = base_cmd + args - return _hpssacli(*cmd) + return _hpssacli(*cmd, **kwargs) - def create_logical_drive(self, logical_drive_info, physical_drive_ids): + def create_logical_drive(self, logical_drive_info): """Create a logical drive on the controller. This method creates a logical drive on the controller when the @@ -341,10 +366,18 @@ class Controller(object): :param logical_drive_info: a dictionary containing the details of the logical drive as specified in raid config. - :param physical_drive_ids: a list of physical drive ids to be used. :raises: HPSSAOperationError, if hpssacli operation failed. """ - phy_drive_ids = ','.join(physical_drive_ids) + cmd_args = [] + if 'array' in logical_drive_info: + cmd_args.extend(['array', logical_drive_info['array']]) + + cmd_args.extend(['create', "type=logicaldrive"]) + + if 'physical_disks' in logical_drive_info: + phy_drive_ids = ','.join(logical_drive_info['physical_disks']) + cmd_args.append("drives=%s" % phy_drive_ids) + size_mb = logical_drive_info['size_gb'] * 1024 raid_level = logical_drive_info['raid_level'] @@ -352,10 +385,9 @@ class Controller(object): # Check if we have mapping stored, otherwise use the same. raid_level = constants.RAID_LEVEL_INPUT_TO_HPSSA_MAPPING.get( raid_level, raid_level) - self.execute_cmd("create", "type=logicaldrive", - "drives=%s" % phy_drive_ids, - "raid=%s" % raid_level, - "size=%s" % size_mb) + cmd_args.extend(["raid=%s" % raid_level, "size=%s" % size_mb]) + + self.execute_cmd(*cmd_args) def delete_all_logical_drives(self): """Deletes all logical drives on trh controller. @@ -395,6 +427,48 @@ class RaidArray(object): properties[physical_drive], self)) + def can_accomodate(self, logical_disk): + """Check if this RAID array can accomodate the logical disk. + + This method uses hpssacli command's option to check if the + logical disk with desired size and RAID level can be created + on this RAID array. + + :param logical_disk: Dictionary of logical disk to be created. + :returns: True, if logical disk can be created on the RAID array + False, otherwise. + """ + raid_level = constants.RAID_LEVEL_INPUT_TO_HPSSA_MAPPING.get( + logical_disk['raid_level'], logical_disk['raid_level']) + args = ("array", self.id, "create", "type=logicaldrive", + "raid=%s" % raid_level, "size=?") + + try: + stdout, stderr = self.parent.execute_cmd( + *args, dont_transform_to_hpssa_exception=True) + except processutils.ProcessExecutionError as ex: + # hpssacli returns error code 1 when RAID level of the + # logical disk is not supported on the array. + # If that's the case, just return saying the logical disk + # cannot be accomodated in the array. + # If exist_code is not 1, then it's some other error that we + # don't expect to appear and hence raise it back. + if ex.exit_code == 1: + return False + else: + raise exception.HPSSAOperationError(reason=ex) + except Exception as ex: + raise exception.HPSSAOperationError(reason=ex) + + # TODO(rameshg87): This always returns in MB, but confirm with + # HPSSA folks. + match = re.search('Max: (\d+)', stdout) + if not match: + return False + + max_size_gb = int(match.group(1)) / 1024 + return logical_disk['size_gb'] <= max_size_gb + class LogicalDrive(object): """Class for LogicalDrive object.""" diff --git a/proliantutils/tests/hpssa/raid_constants.py b/proliantutils/tests/hpssa/raid_constants.py index 11d00bb8..f1b3db32 100644 --- a/proliantutils/tests/hpssa/raid_constants.py +++ b/proliantutils/tests/hpssa/raid_constants.py @@ -1411,3 +1411,383 @@ Smart Array P822 in Slot 2 Carrier Application Version: 11 Carrier Bootloader Version: 6 ''' + +ARRAY_ACCOMODATE_LOGICAL_DISK = ''' + +Available options are: + Max: 1042188 (Units in MB) + Min: 16 (Units in MB) + +''' + +ARRAY_ACCOMODATE_LOGICAL_DISK_INVALID = ''' + +Error: "raid=1" is not a valid option for array A + +Available options are: + 0 + 1adm + 5 (default value) + +''' + +HPSSA_NO_DRIVES_2_PHYSICAL_DISKS = ''' +Smart Array P822 in Slot 2 + Bus Interface: PCI + Slot: 2 + Serial Number: PDVTF0BRH5T0MO + Cache Serial Number: PBKUD0BRH5T3I6 + RAID 6 (ADG) Status: Enabled + Controller Status: OK + Hardware Revision: B + Firmware Version: 4.68 + Wait for Cache Room: Disabled + Surface Analysis Inconsistency Notification: Disabled + Post Prompt Timeout: 15 secs + Cache Board Present: True + Cache Status: OK + Drive Write Cache: Disabled + Total Cache Size: 2.0 GB + Total Cache Memory Available: 1.8 GB + No-Battery Write Cache: Disabled + Cache Backup Power Source: Capacitors + Battery/Capacitor Count: 1 + Battery/Capacitor Status: OK + SATA NCQ Supported: True + Spare Activation Mode: Activate on physical drive failure (default) + Controller Temperature (C): 88 + Cache Module Temperature (C): 37 + Capacitor Temperature (C): 21 + Number of Ports: 6 (2 Internal / 4 External ) + Driver Name: hpsa + Driver Version: 3.4.4 + Driver Supports HP SSD Smart Path: True + + + + unassigned + + physicaldrive 5I:1:1 + Port: 5I + Box: 1 + Bay: 1 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 500 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD6 + Serial Number: 6SL7G55D0000N4173JLT + Model: HP EF0600FARNA + Current Temperature (C): 35 + Maximum Temperature (C): 43 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + + physicaldrive 5I:1:2 + Port: 5I + Box: 1 + Bay: 2 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 600 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD6 + Serial Number: 6SL7H2DM0000B41800Y0 + Model: HP EF0600FARNA + Current Temperature (C): 35 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + + SEP (Vendor ID PMCSIERA, Model SRCv24x6G) 380 + Device Number: 380 + Firmware Version: RevB + WWID: 5001438028842E1F + Vendor ID: PMCSIERA + Model: SRCv24x6G +''' + +ONE_DRIVE_RAID_1 = ''' + +Smart Array P822 in Slot 2 + Bus Interface: PCI + Slot: 2 + Serial Number: PDVTF0BRH5T0MO + Cache Serial Number: PBKUD0BRH5T3I6 + RAID 6 (ADG) Status: Enabled + Controller Status: OK + Hardware Revision: B + Firmware Version: 4.68 + Rebuild Priority: Medium + Expand Priority: Medium + Surface Scan Delay: 3 secs + Surface Scan Mode: Idle + Queue Depth: Automatic + Monitor and Performance Delay: 60 min + Elevator Sort: Enabled + Degraded Performance Optimization: Disabled + Inconsistency Repair Policy: Disabled + Wait for Cache Room: Disabled + Surface Analysis Inconsistency Notification: Disabled + Post Prompt Timeout: 15 secs + Cache Board Present: True + Cache Status: OK + Cache Ratio: 10% Read / 90% Write + Drive Write Cache: Disabled + Total Cache Size: 2.0 GB + Total Cache Memory Available: 1.8 GB + No-Battery Write Cache: Disabled + Cache Backup Power Source: Capacitors + Battery/Capacitor Count: 1 + Battery/Capacitor Status: OK + SATA NCQ Supported: True + Spare Activation Mode: Activate on physical drive failure (default) + Controller Temperature (C): 88 + Cache Module Temperature (C): 38 + Capacitor Temperature (C): 23 + Number of Ports: 6 (2 Internal / 4 External ) + Driver Name: hpsa + Driver Version: 3.4.4 + Driver Supports HP SSD Smart Path: True + + Array: A + Interface Type: SAS + Unused Space: 1042189 MB + Status: OK + MultiDomain Status: OK + Array Type: Data + HP SSD Smart Path: disable + + + + Logical Drive: 1 + Size: 50.0 GB + Fault Tolerance: 1 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 12850 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Unique Identifier: 600508B1001C02BDBCB659B8A264186A + Disk Name: /dev/sda + Mount Points: None + Logical Drive Label: 02896A0EPDVTF0BRH5T0MOEBAA + Mirror Group 0: + physicaldrive 5I:1:1 (port 5I:box 1:bay 1, SAS, 600 GB, OK) + Mirror Group 1: + physicaldrive 5I:1:2 (port 5I:box 1:bay 2, SAS, 600 GB, OK) + Drive Type: Data + LD Acceleration Method: Controller Cache + + physicaldrive 5I:1:1 + Port: 5I + Box: 1 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 600 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD5 + Serial Number: 6SL7G55D0000N4173JLT + Model: HP EF0600FARNA + Current Temperature (C): 37 + Maximum Temperature (C): 43 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + + physicaldrive 5I:1:2 + Port: 5I + Box: 1 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 600 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD6 + Serial Number: 6SL7H2DM0000B41800Y0 + Model: HP EF0600FARNA + Current Temperature (C): 37 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + +''' + +DRIVE_2_RAID_1_OKAY_TO_SHARE = ''' + +Available options are: + Max: 521094 (Units in MB) + Min: 16 (Units in MB) + + + +''' + +TWO_DRIVES_50GB_RAID1 = ''' + +Smart Array P822 in Slot 2 + Bus Interface: PCI + Slot: 2 + Serial Number: PDVTF0BRH5T0MO + Cache Serial Number: PBKUD0BRH5T3I6 + RAID 6 (ADG) Status: Enabled + Controller Status: OK + Hardware Revision: B + Firmware Version: 4.68 + Rebuild Priority: Medium + Expand Priority: Medium + Surface Scan Delay: 3 secs + Surface Scan Mode: Idle + Queue Depth: Automatic + Monitor and Performance Delay: 60 min + Elevator Sort: Enabled + Degraded Performance Optimization: Disabled + Inconsistency Repair Policy: Disabled + Wait for Cache Room: Disabled + Surface Analysis Inconsistency Notification: Disabled + Post Prompt Timeout: 15 secs + Cache Board Present: True + Cache Status: OK + Cache Ratio: 10% Read / 90% Write + Drive Write Cache: Disabled + Total Cache Size: 2.0 GB + Total Cache Memory Available: 1.8 GB + No-Battery Write Cache: Disabled + Cache Backup Power Source: Capacitors + Battery/Capacitor Count: 1 + Battery/Capacitor Status: OK + SATA NCQ Supported: True + Spare Activation Mode: Activate on physical drive failure (default) + Controller Temperature (C): 88 + Cache Module Temperature (C): 38 + Capacitor Temperature (C): 23 + Number of Ports: 6 (2 Internal / 4 External ) + Driver Name: hpsa + Driver Version: 3.4.4 + Driver Supports HP SSD Smart Path: True + + Array: A + Interface Type: SAS + Unused Space: 939791 MB + Status: OK + MultiDomain Status: OK + Array Type: Data + HP SSD Smart Path: disable + + + + Logical Drive: 1 + Size: 50.0 GB + Fault Tolerance: 1 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 12850 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Unique Identifier: 600508B1001C02BDBCB659B8A264186A + Disk Name: /dev/sda + Mount Points: None + Logical Drive Label: 02896A0EPDVTF0BRH5T0MOEBAA + Mirror Group 0: + physicaldrive 5I:1:1 (port 5I:box 1:bay 1, SAS, 600 GB, OK) + Mirror Group 1: + physicaldrive 5I:1:2 (port 5I:box 1:bay 2, SAS, 600 GB, OK) + Drive Type: Data + LD Acceleration Method: Controller Cache + Logical Drive: 2 + Size: 50.0 GB + Fault Tolerance: 1 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 12850 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Unique Identifier: 600508B1001C1614116817E8A9DA1D2F + Disk Name: /dev/sdb + Mount Points: None + Logical Drive Label: 06896EEAPDVTF0BRH5T0MO55C7 + Mirror Group 0: + physicaldrive 5I:1:1 (port 5I:box 1:bay 1, SAS, 600 GB, OK) + Mirror Group 1: + physicaldrive 5I:1:2 (port 5I:box 1:bay 2, SAS, 600 GB, OK) + Drive Type: Data + LD Acceleration Method: Controller Cache + + physicaldrive 5I:1:1 + Port: 5I + Box: 1 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 600 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD6 + Serial Number: 6SL7G55D0000N4173JLT + Model: HP EF0600FARNA + Current Temperature (C): 37 + Maximum Temperature (C): 43 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + + physicaldrive 5I:1:2 + Port: 5I + Box: 1 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 600 GB + Native Block Size: 512 + Rotational Speed: 15000 + Firmware Revision: HPD6 + Serial Number: 6SL7H2DM0000B41800Y0 + Model: HP EF0600FARNA + Current Temperature (C): 37 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 6.0Gbps, Unknown + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + + SEP (Vendor ID PMCSIERA, Model SRCv24x6G) 380 + Device Number: 380 + Firmware Version: RevB + WWID: 5001438028842E1F + Vendor ID: PMCSIERA + Model: SRCv24x6G +''' diff --git a/proliantutils/tests/hpssa/test_disk_allocator.py b/proliantutils/tests/hpssa/test_disk_allocator.py index c122af8d..31a05ad0 100644 --- a/proliantutils/tests/hpssa/test_disk_allocator.py +++ b/proliantutils/tests/hpssa/test_disk_allocator.py @@ -107,7 +107,8 @@ class DiskAllocatorTestCase(testtools.TestCase): disk1.size_gb = 300 disk2.size_gb = 300 - disk_allocator.allocate_disks(logical_disk, server) + raid_config = {'logical_disks': [logical_disk]} + disk_allocator.allocate_disks(logical_disk, server, raid_config) self.assertEqual('Smart Array P822 in Slot 2', logical_disk['controller']) self.assertEqual(sorted(['5I:1:3', '6I:1:7']), @@ -122,9 +123,10 @@ class DiskAllocatorTestCase(testtools.TestCase): 'raid_level': '1', 'disk_type': 'hdd', 'interface_type': 'sas'} + raid_config = {'logical_disks': [logical_disk]} exc = self.assertRaises(exception.PhysicalDisksNotFoundError, disk_allocator.allocate_disks, - logical_disk, server) + logical_disk, server, raid_config) self.assertIn("of size 700 GB and raid level 1", str(exc)) def test_allocate_disks_disk_not_enough_disks(self, @@ -139,7 +141,92 @@ class DiskAllocatorTestCase(testtools.TestCase): 'raid_level': '5', 'disk_type': 'hdd', 'interface_type': 'sas'} + raid_config = {'logical_disks': [logical_disk]} exc = self.assertRaises(exception.PhysicalDisksNotFoundError, disk_allocator.allocate_disks, - logical_disk, server) + logical_disk, server, raid_config) self.assertIn("of size 600 GB and raid level 5", str(exc)) + + @mock.patch.object(objects.Controller, 'execute_cmd') + def test_allocate_disks_share_physical_disks(self, execute_mock, + get_all_details_mock): + + get_all_details_mock.return_value = raid_constants.ONE_DRIVE_RAID_1 + execute_mock.return_value = ( + raid_constants.DRIVE_2_RAID_1_OKAY_TO_SHARE, None) + + rdh = {'wwn': '0x600508b1001c02bd'} + controller = 'Smart Array P822 in Slot 2' + physical_disks = ['5I:1:1', '5I:1:2'] + + raid_config = {'logical_disks': [{'size_gb': 50, + 'raid_level': '1', + 'share_physical_disks': True, + 'root_device_hint': rdh, + 'controller': controller, + 'physical_disks': physical_disks}, + {'size_gb': 50, + 'raid_level': '1', + 'share_physical_disks': True}]} + + logical_disk = raid_config['logical_disks'][1] + server = objects.Server() + disk_allocator.allocate_disks(logical_disk, server, raid_config) + self.assertEqual(controller, logical_disk['controller']) + self.assertEqual('A', logical_disk['array']) + self.assertNotIn('physical_disks', logical_disk) + + @mock.patch.object(objects.Controller, 'execute_cmd') + def test_allocate_disks_share_physical_disks_no_space( + self, execute_mock, get_all_details_mock): + + get_all_details_mock.return_value = raid_constants.ONE_DRIVE_RAID_1 + execute_mock.return_value = ( + raid_constants.DRIVE_2_RAID_1_OKAY_TO_SHARE, None) + + rdh = {'wwn': '0x600508b1001c02bd'} + controller = 'Smart Array P822 in Slot 2' + physical_disks = ['5I:1:1', '5I:1:2'] + + raid_config = {'logical_disks': [{'size_gb': 50, + 'raid_level': '1', + 'share_physical_disks': True, + 'root_device_hint': rdh, + 'controller': controller, + 'physical_disks': physical_disks}, + {'size_gb': 600, + 'raid_level': '1', + 'share_physical_disks': True}]} + + logical_disk = raid_config['logical_disks'][1] + server = objects.Server() + self.assertRaises(exception.PhysicalDisksNotFoundError, + disk_allocator.allocate_disks, + logical_disk, server, raid_config) + + def test_allocate_disks_share_physical_disks_criteria_mismatch( + self, get_all_details_mock): + + # Both the drives don't have firmware HPD6 + get_all_details_mock.return_value = raid_constants.ONE_DRIVE_RAID_1 + + rdh = {'wwn': '0x600508b1001c02bd'} + controller = 'Smart Array P822 in Slot 2' + physical_disks = ['5I:1:1', '5I:1:2'] + + raid_config = {'logical_disks': [{'size_gb': 50, + 'raid_level': '1', + 'share_physical_disks': True, + 'root_device_hint': rdh, + 'controller': controller, + 'physical_disks': physical_disks}, + {'size_gb': 50, + 'raid_level': '1', + 'firmware': 'HPD6', + 'share_physical_disks': True}]} + + logical_disk = raid_config['logical_disks'][1] + server = objects.Server() + self.assertRaises(exception.PhysicalDisksNotFoundError, + disk_allocator.allocate_disks, + logical_disk, server, raid_config) diff --git a/proliantutils/tests/hpssa/test_manager.py b/proliantutils/tests/hpssa/test_manager.py index 3177e3d0..9a02a43e 100644 --- a/proliantutils/tests/hpssa/test_manager.py +++ b/proliantutils/tests/hpssa/test_manager.py @@ -163,6 +163,44 @@ class ManagerTestCases(testtools.TestCase): raid_info) self.assertIn("of size 50 GB and raid level 1", str(exc)) + @mock.patch.object(objects.Controller, 'execute_cmd') + def test_create_configuration_share_physical_disks( + self, controller_exec_cmd_mock, get_all_details_mock): + no_drives = raid_constants.HPSSA_NO_DRIVES_2_PHYSICAL_DISKS + one_drive = raid_constants.ONE_DRIVE_RAID_1 + two_drives = raid_constants.TWO_DRIVES_50GB_RAID1 + get_all_details_mock.side_effect = [no_drives, one_drive, two_drives] + controller_exec_cmd_mock.side_effect = [ + (None, None), + (raid_constants.DRIVE_2_RAID_1_OKAY_TO_SHARE, None), + (None, None)] + raid_info = {'logical_disks': [{'size_gb': 50, + 'share_physical_disks': True, + 'raid_level': '1', + 'disk_type': 'hdd'}, + {'size_gb': 50, + 'share_physical_disks': True, + 'raid_level': '1', + 'disk_type': 'hdd'}]} + raid_info = manager.create_configuration(raid_info) + ld1 = raid_info['logical_disks'][0] + ld2 = raid_info['logical_disks'][1] + self.assertEqual('Smart Array P822 in Slot 2', ld1['controller']) + self.assertEqual('Smart Array P822 in Slot 2', ld2['controller']) + self.assertEqual(sorted(['5I:1:1', '5I:1:2']), + sorted(ld1['physical_disks'])) + self.assertEqual(sorted(['5I:1:1', '5I:1:2']), + sorted(ld2['physical_disks'])) + controller_exec_cmd_mock.assert_any_call( + 'create', 'type=logicaldrive', 'drives=5I:1:1,5I:1:2', + 'raid=1', 'size=51200') + controller_exec_cmd_mock.assert_any_call( + 'array', 'A', 'create', 'type=logicaldrive', 'raid=1', 'size=?', + dont_transform_to_hpssa_exception=True) + controller_exec_cmd_mock.assert_any_call( + 'array', 'A', 'create', 'type=logicaldrive', 'raid=1', + 'size=51200') + @mock.patch.object(objects.Controller, 'execute_cmd') def test_delete_configuration(self, controller_exec_cmd_mock, get_all_details_mock): diff --git a/proliantutils/tests/hpssa/test_objects.py b/proliantutils/tests/hpssa/test_objects.py index a39375dd..f498b346 100644 --- a/proliantutils/tests/hpssa/test_objects.py +++ b/proliantutils/tests/hpssa/test_objects.py @@ -208,8 +208,8 @@ class ControllerTest(testtools.TestCase): 'foo', 'bar') @mock.patch.object(objects.Controller, 'execute_cmd') - def test_create_logical_drive(self, execute_mock, - get_all_details_mock): + def test_create_logical_drive_with_physical_disks(self, execute_mock, + get_all_details_mock): get_all_details_mock.return_value = raid_constants.HPSSA_NO_DRIVES @@ -225,16 +225,36 @@ class ControllerTest(testtools.TestCase): '5I:1:2', '5I:1:3']} - controller.create_logical_drive(logical_drive_info, - ['5I:1:1', - '5I:1:2', - '5I:1:3']) + controller.create_logical_drive(logical_drive_info) execute_mock.assert_called_once_with("create", "type=logicaldrive", "drives=5I:1:1,5I:1:2,5I:1:3", "raid=1", "size=51200") + @mock.patch.object(objects.Controller, 'execute_cmd') + def test_create_logical_drive_with_raid_array(self, execute_mock, + get_all_details_mock): + + get_all_details_mock.return_value = raid_constants.HPSSA_NO_DRIVES + + server = objects.Server() + controller = server.controllers[0] + + logical_drive_info = {'size_gb': 50, + 'raid_level': '1', + 'volume_name': 'boot_volume', + 'is_boot_volume': 'true', + 'controller': 'Smart Array P822 in Slot 2', + 'array': 'A'} + + controller.create_logical_drive(logical_drive_info) + execute_mock.assert_called_once_with("array", "A", + "create", + "type=logicaldrive", + "raid=1", + "size=51200") + @mock.patch.object(objects.Controller, 'execute_cmd') def test_create_logical_drive_raid_level_mapping(self, execute_mock, get_all_details_mock): @@ -256,9 +276,7 @@ class ControllerTest(testtools.TestCase): '5I:1:5', '6I:1:6']} - controller.create_logical_drive(logical_drive_info, - ['5I:1:1', '5I:1:2', '5I:1:3', - '5I:1:4', '5I:1:5', '6I:1:6']) + controller.create_logical_drive(logical_drive_info) execute_mock.assert_called_once_with( "create", "type=logicaldrive", "drives=5I:1:1,5I:1:2,5I:1:3,5I:1:4,5I:1:5,6I:1:6", @@ -329,6 +347,95 @@ class LogicalDriveTest(testtools.TestCase): self.assertIn(msg, str(ex)) +@mock.patch.object(objects.Server, '_get_all_details') +class ArrayTest(testtools.TestCase): + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_okay(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + get_all_details_mock.return_value = current_config + execute_mock.return_value = ( + raid_constants.ARRAY_ACCOMODATE_LOGICAL_DISK, None) + logical_disk = {'size_gb': 500, 'raid_level': '5'} + server = objects.Server() + ret_val = server.controllers[0].raid_arrays[0].can_accomodate( + logical_disk) + self.assertTrue(ret_val) + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_not_enough_space(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + get_all_details_mock.return_value = current_config + execute_mock.return_value = ( + raid_constants.ARRAY_ACCOMODATE_LOGICAL_DISK, None) + logical_disk = {'size_gb': 1500, 'raid_level': '5'} + server = objects.Server() + ret_val = server.controllers[0].raid_arrays[0].can_accomodate( + logical_disk) + self.assertFalse(ret_val) + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_invalid_raid_level(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + get_all_details_mock.return_value = current_config + exc = processutils.ProcessExecutionError( + stdout=raid_constants.ARRAY_ACCOMODATE_LOGICAL_DISK_INVALID, + stderr=None, + exit_code=1) + execute_mock.side_effect = exc + logical_disk = {'size_gb': 1500, 'raid_level': '1'} + server = objects.Server() + ret_val = server.controllers[0].raid_arrays[0].can_accomodate( + logical_disk) + self.assertFalse(ret_val) + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_some_other_error(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + get_all_details_mock.return_value = current_config + exc = processutils.ProcessExecutionError( + stdout=raid_constants.ARRAY_ACCOMODATE_LOGICAL_DISK_INVALID, + stderr=None, + exit_code=2) + execute_mock.side_effect = exc + logical_disk = {'size_gb': 1500, 'raid_level': '1'} + server = objects.Server() + self.assertRaises( + exception.HPSSAOperationError, + server.controllers[0].raid_arrays[0].can_accomodate, + logical_disk) + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_oserror(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + get_all_details_mock.return_value = current_config + execute_mock.side_effect = OSError + logical_disk = {'size_gb': 1500, 'raid_level': '1'} + server = objects.Server() + self.assertRaises( + exception.HPSSAOperationError, + server.controllers[0].raid_arrays[0].can_accomodate, + logical_disk) + + @mock.patch.object(processutils, 'execute') + def test_can_accomodate_map_raid_level(self, execute_mock, + get_all_details_mock): + current_config = raid_constants.HPSSA_TWO_DRIVES_100GB_RAID5_50GB_RAID1 + execute_mock.return_value = ("", None) + get_all_details_mock.return_value = current_config + logical_disk = {'size_gb': 1500, 'raid_level': '5+0'} + server = objects.Server() + server.controllers[0].raid_arrays[0].can_accomodate(logical_disk) + execute_mock.assert_called_once_with( + "hpssacli", "controller", "slot=2", "array", mock.ANY, "create", + "type=logicaldrive", "raid=50", "size=?") + + @mock.patch.object(objects.Server, '_get_all_details') class PhysicalDriveTest(testtools.TestCase): @@ -383,3 +490,30 @@ class PhysicalDriveTest(testtools.TestCase): self.assertEqual('HP EF0600FARNA', ret['model']) self.assertEqual('HPD6', ret['firmware']) self.assertEqual('ready', ret['status']) + + +class PrivateMethodsTestCase(testtools.TestCase): + + @mock.patch.object(processutils, 'execute') + def test__hpssacli(self, execute_mock): + execute_mock.return_value = ("stdout", "stderr") + stdout, stderr = objects._hpssacli("foo", "bar", + check_exit_code=[0, 1, 2, 3]) + execute_mock.assert_called_once_with( + "hpssacli", "foo", "bar", check_exit_code=[0, 1, 2, 3]) + self.assertEqual("stdout", stdout) + self.assertEqual("stderr", stderr) + + @mock.patch.object(processutils, 'execute') + def test__hpssacli_raises_error(self, execute_mock): + execute_mock.side_effect = OSError + self.assertRaises(exception.HPSSAOperationError, + objects._hpssacli, "foo", "bar") + + @mock.patch.object(processutils, 'execute') + def test__hpssacli_raises_error_no_transform(self, execute_mock): + execute_mock.side_effect = OSError + self.assertRaises(OSError, + objects._hpssacli, "foo", "bar", + dont_transform_to_hpssa_exception=True) + execute_mock.assert_called_once_with("hpssacli", "foo", "bar")