def __init__(self, id, properties, parent): """Constructor for a LogicalDrive object.""" # Strip off 'Logical Drive' before storing it in id self.id = id[15:] self.parent = parent self.properties = properties # 'string_to_bytes' takes care of converting any returned # (like 500MB, 25GB) unit of storage space to bytes (Integer value). # It requires space to be stripped. try: size = self.properties['Size'].replace(' ', '') # TODO(rameshg87): Reduce the disk size by 1 to make sure Ironic # has enough space to write a config drive. Remove this when # Ironic doesn't need it. self.size_gb = int( strutils.string_to_bytes(size, return_int=True) / (1024 * 1024 * 1024)) - 1 except KeyError: msg = ("Can't get 'Size' parameter from ssacli output for logical " "disk '%(logical_disk)s' of RAID array '%(array)s' in " "controller '%(controller)s'." % { 'logical_disk': self.id, 'array': self.parent.id, 'controller': self.parent.parent.id }) raise exception.HPSSAOperationError(reason=msg) except ValueError: msg = ("ssacli returned unknown size '%(size)s' for logical " "disk '%(logical_disk)s' of RAID array '%(array)s' in " "controller '%(controller)s'." % { 'size': size, 'logical_disk': self.id, 'array': self.parent.id, 'controller': self.parent.parent.id }) raise exception.HPSSAOperationError(reason=msg) self.raid_level = self.properties.get('Fault Tolerance') # For RAID levels (like 5+0 and 6+0), HPSSA names them differently. # Check if we have mapping stored, otherwise use the same. raid_level_mapping = constants.RAID_LEVEL_HPSSA_TO_INPUT_MAPPING self.raid_level = raid_level_mapping.get(self.raid_level, self.raid_level) self.volume_name = self.properties.get('Logical Drive Label') # Trim down the WWN to 16 digits (8 bytes) so that it matches # lsblk output in Linux. wwn = self.properties.get('Unique Identifier') if wwn: wwn = '0x' + wwn[:16].lower() self.wwn = wwn
def __init__(self, id, properties, parent): """Constructor for a PhysicalDrive object.""" self.parent = parent self.properties = properties # Strip off physicaldrive before storing it in id self.id = id[14:] # 'string_to_bytes' takes care of converting any returned # (like 500MB, 25GB) unit of storage space to bytes (Integer value). # It requires space to be stripped. try: size = self.properties['Size'].replace(' ', '') self.size_gb = int( strutils.string_to_bytes(size, return_int=True) / (1024 * 1024 * 1024)) except KeyError: msg = ( "Can't get 'Size' parameter from ssacli output for physical " "disk '%(physical_disk)s' of controller '%(controller)s'." % { 'physical_disk': self.id, 'controller': self.parent.parent.id }) raise exception.HPSSAOperationError(reason=msg) except ValueError: msg = ("ssacli returned unknown size '%(size)s' for physical " "disk '%(physical_disk)s' of controller " "'%(controller)s'." % { 'size': size, 'physical_disk': self.id, 'controller': self.parent.id }) raise exception.HPSSAOperationError(reason=msg) try: ssa_interface = self.properties['Interface Type'] except KeyError: msg = ("Can't get 'Interface Type' parameter from ssacli output " "for physical disk '%(physical_disk)s' of controller " "'%(controller)s'." % { 'physical_disk': self.id, 'controller': self.parent.parent.id }) raise exception.HPSSAOperationError(reason=msg) self.interface_type = constants.get_interface_type(ssa_interface) self.disk_type = constants.get_disk_type(ssa_interface) self.model = self.properties.get('Model') self.firmware = self.properties.get('Firmware Revision') self.erase_status = self.properties.get('Status')
def _select_controllers_by(server, select_condition, msg): """Filters out the hpssa controllers based on the condition. This method updates the server with only the controller which satisfies the condition. The controllers which doesn't satisfies the selection condition will be removed from the list. :param server: The object containing all the supported hpssa controllers details. :param select_condition: A lambda function to select the controllers based on requirement. :param msg: A String which describes the controller selection. :raises exception.HPSSAOperationError, if all the controller are in HBA mode. """ all_controllers = server.controllers supported_controllers = [c for c in all_controllers if select_condition(c)] if not supported_controllers: reason = ("None of the available SSA controllers %(controllers)s " "have %(msg)s" % { 'controllers': ', '.join([c.id for c in all_controllers]), 'msg': msg }) raise exception.HPSSAOperationError(reason=reason) server.controllers = supported_controllers
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, **kwargs) except (OSError, processutils.ProcessExecutionError) as e: if not dont_transform_to_hpssa_exception: raise exception.HPSSAOperationError(reason=e) else: raise return stdout, stderr
def _hpssacli(*args): """Wrapper function for executing hpssacli command.""" try: stdout, stderr = processutils.execute("hpssacli", *args) except (OSError, processutils.ProcessExecutionError) as e: raise exception.HPSSAOperationError(reason=e) return stdout, stderr
def can_accomodate(self, logical_disk): """Check if this RAID array can accomodate the logical disk. This method uses hpssacli/ssacli 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=?") if logical_disk['size_gb'] != "MAX": desired_disk_size = logical_disk['size_gb'] else: desired_disk_size = constants.MINIMUM_DISK_SIZE try: stdout, stderr = self.parent.execute_cmd( *args, dont_transform_to_hpssa_exception=True) except processutils.ProcessExecutionError as ex: # hpssacli/ssacli 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 desired_disk_size <= max_size_gb
def _ssacli(*args, **kwargs): """Wrapper function for executing hpssacli/ssacli command. This function executes ssacli command if it exists, else it falls back to hpssacli. :param args: args to be provided to hpssacli/ssacli 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 only when hpssa controller is available. This is useful when the return code from hpssacli/ssacli 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_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: if os.path.exists("/usr/sbin/ssacli"): stdout, stderr = processutils.execute("ssacli", *args, **kwargs) else: stdout, stderr = processutils.execute("hpssacli", *args, **kwargs) except (OSError, processutils.ProcessExecutionError) as e: if 'No controllers detected' in str(e): msg = ("SSA controller not found. Enable ssa controller" " to continue with the desired operation") raise exception.HPSSAOperationError(reason=msg) elif not dont_transform_to_hpssa_exception: raise exception.HPSSAOperationError(reason=e) else: raise return stdout, stderr
def test_erase_devices_not_supported(self, erase_mock, generic_erase_mock): node = {} port = {} value = ("Sanitize erase not supported in the " "available controllers") e = exception.HPSSAOperationError(reason=value) erase_mock.side_effect = e exc = self.assertRaises(exception.HPSSAOperationError, self.hardware_manager.erase_devices, node, port) self.assertIn(value, str(exc))
def test_erase_devices_exception(self, execute_mock, get_all_details_mock): get_all_details_mock.return_value = raid_constants.SSA_ERASE_DRIVE server = objects.Server() d = [x for x in server.controllers[0].unassigned_physical_drives] controller = server.controllers[0] value = 'Some Exception' execute_mock.side_effect = [ exception.HPSSAOperationError(reason=value), None ] ex = self.assertRaises(exception.HPSSAOperationError, controller.erase_devices, d) self.assertIn(value, str(ex))
def test_erase_devices_not_supported(self, erase_mock, generic_erase_mock): node = {} port = {} value = ("Sanitize erase not supported in the " "available controllers") e = exception.HPSSAOperationError(value) erase_mock.side_effect = e generic_erase_mock.return_value = {'foo': 'bar'} expt_return = {'Sanitize Erase': erase_mock.side_effect} expt_return.update(generic_erase_mock.return_value) self.hardware_manager.erase_devices(node, port) generic_erase_mock.assert_called_once_with(node, port)
def create_configuration(raid_config): """Create a RAID configuration on this server. This method creates the given RAID configuration on the server based on the input passed. :param raid_config: The dictionary containing the requested RAID configuration. This data structure should be as follows: raid_config = {'logical_disks': [{'raid_level': 1, 'size_gb': 100}, <info-for-logical-disk-2> ]} :returns: the current raid configuration. This is same as raid_config with some extra properties like root_device_hint, volume_name, controller, physical_disks, etc filled for each logical disk after its creation. :raises exception.InvalidInputError, if input is invalid. :raises exception.HPSSAOperationError, if all the controllers are in HBA mode. """ server = objects.Server() select_controllers = lambda x: not x.properties.get( 'HBA Mode Enabled', False) _select_controllers_by(server, select_controllers, 'RAID enabled') validate(raid_config) # Make sure we create the large disks first. This is avoid the # situation that we avoid giving large disks to smaller requests. # For example, consider this: # - two logical disks - LD1(50), LD(100) # - have 4 physical disks - PD1(50), PD2(50), PD3(100), PD4(100) # # In this case, for RAID1 configuration, if we were to consider # LD1 first and allocate PD3 and PD4 for it, then allocation would # fail. So follow a particular order for allocation. # # Also make sure we create the MAX logical_disks the last to make sure # we allot only the remaining space available. logical_disks_sorted = ( sorted( (x for x in raid_config['logical_disks'] if x['size_gb'] != "MAX"), reverse=True, key=lambda x: x['size_gb']) + [x for x in raid_config['logical_disks'] if x['size_gb'] == "MAX"]) if any(logical_disk['share_physical_disks'] for logical_disk in logical_disks_sorted if 'share_physical_disks' in logical_disk): logical_disks_sorted = _sort_shared_logical_disks(logical_disks_sorted) # We figure out the new disk created by recording the wwns # before and after the create, and then figuring out the # newly found wwn from it. wwns_before_create = set([x.wwn for x in server.get_logical_drives()]) for logical_disk in logical_disks_sorted: if 'physical_disks' not in logical_disk: disk_allocator.allocate_disks(logical_disk, server, raid_config) controller_id = logical_disk['controller'] controller = server.get_controller_by_id(controller_id) if not controller: msg = ( "Unable to find controller named '%(controller)s'." " The available controllers are '%(ctrl_list)s'." % { 'controller': controller_id, 'ctrl_list': ', '.join([c.id for c in server.controllers]) }) raise exception.InvalidInputError(reason=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) controller.create_logical_drive(logical_disk) # Now find the new logical drive created. server.refresh() wwns_after_create = set([x.wwn for x in server.get_logical_drives()]) new_wwn = wwns_after_create - wwns_before_create if not new_wwn: reason = ("Newly created logical disk with raid_level " "'%(raid_level)s' and size %(size_gb)s GB not " "found." % { 'raid_level': logical_disk['raid_level'], 'size_gb': logical_disk['size_gb'] }) raise exception.HPSSAOperationError(reason=reason) new_logical_disk = server.get_logical_drive_by_wwn(new_wwn.pop()) new_log_drive_properties = new_logical_disk.get_logical_drive_dict() logical_disk.update(new_log_drive_properties) wwns_before_create = wwns_after_create.copy() _update_physical_disk_details(raid_config, server) return raid_config