Merge "Support RAID configuration via irmc driver."
This commit is contained in:
commit
0d23c8cac7
|
@ -16,6 +16,7 @@
|
|||
eLCM functionality.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import time
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
@ -23,25 +24,25 @@ import requests
|
|||
|
||||
from scciclient.irmc import scci
|
||||
|
||||
|
||||
"""
|
||||
List of profile names
|
||||
"""
|
||||
PROFILE_BIOS_CONFIG = 'BiosConfig'
|
||||
|
||||
PROFILE_RAID_CONFIG = 'RAIDAdapter'
|
||||
|
||||
"""
|
||||
List of URL paths for profiles
|
||||
"""
|
||||
URL_PATH_PROFILE_MGMT = '/rest/v1/Oem/eLCM/ProfileManagement/'
|
||||
|
||||
|
||||
"""
|
||||
List of request params for profiles
|
||||
"""
|
||||
PARAM_PATH_SYSTEM_CONFIG = 'Server/SystemConfig/'
|
||||
PARAM_PATH_BIOS_CONFIG = PARAM_PATH_SYSTEM_CONFIG + PROFILE_BIOS_CONFIG
|
||||
|
||||
PARAM_PATH_HW_CONFIG = 'Server/HWConfigurationIrmc/Adapters/'
|
||||
PARAM_PATH_RAID_CONFIG = PARAM_PATH_HW_CONFIG + PROFILE_RAID_CONFIG
|
||||
|
||||
"""
|
||||
Timeout values
|
||||
|
@ -49,6 +50,7 @@ Timeout values
|
|||
PROFILE_CREATE_TIMEOUT = 300 # 300 secs
|
||||
PROFILE_SET_TIMEOUT = 300 # 300 secs
|
||||
BIOS_CONFIG_SESSION_TIMEOUT = 30 * 60 # 30 mins
|
||||
RAID_CONFIG_SESSION_TIMEOUT = 30 * 60 # 30 mins
|
||||
|
||||
|
||||
class ELCMInvalidResponse(scci.SCCIError):
|
||||
|
@ -76,6 +78,11 @@ class SecureBootConfigNotFound(scci.SCCIError):
|
|||
super(SecureBootConfigNotFound, self).__init__(message)
|
||||
|
||||
|
||||
class ELCMValueError(scci.SCCIError):
|
||||
def __init__(self, message):
|
||||
super(ELCMValueError, self).__init__(message)
|
||||
|
||||
|
||||
def _parse_elcm_response_body_as_json(response):
|
||||
"""parse eLCM response body as json data
|
||||
|
||||
|
@ -311,11 +318,13 @@ def elcm_profile_set(irmc_info, input_data):
|
|||
_irmc_info = dict(irmc_info)
|
||||
_irmc_info['irmc_client_timeout'] = PROFILE_SET_TIMEOUT
|
||||
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
if input_data['Server'].get('HWConfigurationIrmc'):
|
||||
content_type = 'application/json'
|
||||
resp = elcm_request(_irmc_info,
|
||||
method='POST',
|
||||
path=URL_PATH_PROFILE_MGMT + 'set',
|
||||
headers={'Content-type':
|
||||
'application/x-www-form-urlencoded'},
|
||||
headers={'Content-type': content_type},
|
||||
data=data)
|
||||
|
||||
if resp.status_code == 202:
|
||||
|
@ -507,17 +516,22 @@ def elcm_session_delete(irmc_info, session_id, terminate=False):
|
|||
'error': resp.status_code}))
|
||||
|
||||
|
||||
def _process_session_bios_config(irmc_info, operation, session_id,
|
||||
session_timeout=BIOS_CONFIG_SESSION_TIMEOUT):
|
||||
"""process session for Bios config backup/restore operation
|
||||
def _process_session_data(irmc_info, operation, session_id,
|
||||
session_timeout=BIOS_CONFIG_SESSION_TIMEOUT):
|
||||
"""process session for Bios config backup/restore or RAID config operation
|
||||
|
||||
:param irmc_info: node info
|
||||
:param operation: one of 'BACKUP' and 'RESTORE'
|
||||
:param operation: one of 'BACKUP_BIOS', 'RESTORE_BIOS' or 'CONFIG_RAID'
|
||||
:param session_id: session id
|
||||
:param session_timeout: session timeout
|
||||
:return: a dict with following values:
|
||||
{
|
||||
'bios_config': <data in case of BACKUP operation>,
|
||||
'bios_config': <data in case of BACKUP/RESTORE_BIOS operation>,
|
||||
'warning': <warning message if there is>
|
||||
}
|
||||
or
|
||||
{
|
||||
'raid_config': <data of raid adapter profile>,
|
||||
'warning': <warning message if there is>
|
||||
}
|
||||
"""
|
||||
|
@ -535,14 +549,18 @@ def _process_session_bios_config(irmc_info, operation, session_id,
|
|||
elif status == 'terminated regularly':
|
||||
result = {}
|
||||
|
||||
if operation == 'BACKUP':
|
||||
if operation == 'BACKUP_BIOS':
|
||||
# Bios profile is created, get the data now
|
||||
result['bios_config'] = elcm_profile_get(
|
||||
irmc_info=irmc_info,
|
||||
profile_name=PROFILE_BIOS_CONFIG)
|
||||
elif operation == 'RESTORE':
|
||||
elif operation == 'RESTORE_BIOS':
|
||||
# Bios config applied successfully
|
||||
pass
|
||||
elif operation == 'CONFIG_RAID':
|
||||
# Getting raid config
|
||||
result['raid_config'] = elcm_profile_get(irmc_info,
|
||||
PROFILE_RAID_CONFIG)
|
||||
|
||||
# Cleanup operation by deleting related session and profile.
|
||||
# In case of error, report it as warning instead of error.
|
||||
|
@ -550,8 +568,12 @@ def _process_session_bios_config(irmc_info, operation, session_id,
|
|||
elcm_session_delete(irmc_info=irmc_info,
|
||||
session_id=session_id,
|
||||
terminate=True)
|
||||
if operation == 'CONFIG_RAID':
|
||||
return result
|
||||
|
||||
elcm_profile_delete(irmc_info=irmc_info,
|
||||
profile_name=PROFILE_BIOS_CONFIG)
|
||||
|
||||
except scci.SCCIError as e:
|
||||
result['warning'] = e
|
||||
|
||||
|
@ -562,14 +584,14 @@ def _process_session_bios_config(irmc_info, operation, session_id,
|
|||
session_id=session_id)
|
||||
|
||||
raise scci.SCCIClientError(
|
||||
('Failed to %(operation)s bios config. '
|
||||
('Failed to %(operation)s config. '
|
||||
'Session log is "%(session_log)s".' %
|
||||
{'operation': operation,
|
||||
'session_log': jsonutils.dumps(session_log)}))
|
||||
|
||||
else:
|
||||
raise ELCMSessionTimeout(
|
||||
('Failed to %(operation)s bios config. '
|
||||
('Failed to %(operation)s config. '
|
||||
'Session %(session_id)s log is timeout.' %
|
||||
{'operation': operation,
|
||||
'session_id': session_id}))
|
||||
|
@ -578,7 +600,7 @@ def _process_session_bios_config(irmc_info, operation, session_id,
|
|||
def backup_bios_config(irmc_info):
|
||||
"""backup current bios configuration
|
||||
|
||||
This function sends a BACKUP request to the server. Then when the bios
|
||||
This function sends a BACKUP BIOS request to the server. Then when the bios
|
||||
config data are ready for retrieving, it will return the data to the
|
||||
caller. Note that this operation may take time.
|
||||
|
||||
|
@ -609,9 +631,9 @@ def backup_bios_config(irmc_info):
|
|||
# 3. Profile creation is in progress, we monitor the session
|
||||
session_timeout = irmc_info.get('irmc_bios_session_timeout',
|
||||
BIOS_CONFIG_SESSION_TIMEOUT)
|
||||
return _process_session_bios_config(
|
||||
return _process_session_data(
|
||||
irmc_info=irmc_info,
|
||||
operation='BACKUP',
|
||||
operation='BACKUP_BIOS',
|
||||
session_id=session['Session']['Id'],
|
||||
session_timeout=session_timeout)
|
||||
|
||||
|
@ -619,13 +641,15 @@ def backup_bios_config(irmc_info):
|
|||
def restore_bios_config(irmc_info, bios_config):
|
||||
"""restore bios configuration
|
||||
|
||||
This function sends a RESTORE request to the server. Then when the bios
|
||||
This function sends a RESTORE BIOS request to the server. Then when the
|
||||
bios
|
||||
is ready for restoring, it will apply the provided settings and return.
|
||||
Note that this operation may take time.
|
||||
|
||||
:param irmc_info: node info
|
||||
:param bios_config: bios config
|
||||
"""
|
||||
|
||||
def _process_bios_config():
|
||||
try:
|
||||
if isinstance(bios_config, dict):
|
||||
|
@ -666,10 +690,10 @@ def restore_bios_config(irmc_info, bios_config):
|
|||
# 4. Param values applying is in progress, we monitor the session
|
||||
session_timeout = irmc_info.get('irmc_bios_session_timeout',
|
||||
BIOS_CONFIG_SESSION_TIMEOUT)
|
||||
_process_session_bios_config(irmc_info=irmc_info,
|
||||
operation='RESTORE',
|
||||
session_id=session['Session']['Id'],
|
||||
session_timeout=session_timeout)
|
||||
_process_session_data(irmc_info=irmc_info,
|
||||
operation='RESTORE_BIOS',
|
||||
session_id=session['Session']['Id'],
|
||||
session_timeout=session_timeout)
|
||||
|
||||
|
||||
def get_secure_boot_mode(irmc_info):
|
||||
|
@ -713,3 +737,230 @@ def set_secure_boot_mode(irmc_info, enable):
|
|||
}
|
||||
}
|
||||
restore_bios_config(irmc_info=irmc_info, bios_config=bios_config_data)
|
||||
|
||||
|
||||
def _update_raid_input_data(target_raid_config, raid_input):
|
||||
"""Process raid input data.
|
||||
|
||||
:param target_raid_config: node raid info
|
||||
:param raid_input: raid information for creating via eLCM
|
||||
:raises ELCMValueError: raise msg if wrong input
|
||||
:return: raid_input: raid input data which create raid configuration
|
||||
{
|
||||
"Server":{
|
||||
"HWConfigurationIrmc":{
|
||||
"@Processing":"execute",
|
||||
"Adapters":{
|
||||
"RAIDAdapter":[
|
||||
{
|
||||
"@AdapterId":"RAIDAdapter0",
|
||||
"@ConfigurationType":"Addressing",
|
||||
"LogicalDrives":{
|
||||
"LogicalDrive":[
|
||||
{
|
||||
"@Number":0,
|
||||
"@Action":"Create",
|
||||
"RaidLevel":"1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"@Version":"1.00"
|
||||
},
|
||||
"@Version":"1.01"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
logical_disk_list = target_raid_config['logical_disks']
|
||||
|
||||
raid_input['Server']['HWConfigurationIrmc'].update({'@Processing':
|
||||
'execute'})
|
||||
array_info = raid_input['Server']['HWConfigurationIrmc']['Adapters'][
|
||||
'RAIDAdapter'][0]
|
||||
# Set max value for BGI
|
||||
array_info['BGIRate'] = 100
|
||||
array_info['LogicalDrives'] = {'LogicalDrive': []}
|
||||
array_info['Arrays'] = {'Array': []}
|
||||
|
||||
for i, logical_disk in enumerate(logical_disk_list):
|
||||
physical_disks = logical_disk.get('physical_disks')
|
||||
|
||||
# Auto create logical drive along with random physical disks.
|
||||
# Allow auto create along with raid 10 and raid 50
|
||||
# with specific physical drive.
|
||||
if not physical_disks or logical_disk['raid_level'] \
|
||||
in ('10', '50'):
|
||||
array_info['LogicalDrives']['LogicalDrive'].append(
|
||||
{'@Action': 'Create',
|
||||
'RaidLevel': logical_disk['raid_level'],
|
||||
'InitMode': 'fast'})
|
||||
array_info['LogicalDrives']['LogicalDrive'][i].update({
|
||||
"@Number": i})
|
||||
|
||||
else:
|
||||
# Create array disks with specific physical servers
|
||||
arrays = {
|
||||
"@Number": i,
|
||||
"@ConfigurationType": "Setting",
|
||||
"PhysicalDiskRefs": {
|
||||
"PhysicalDiskRef": []
|
||||
}
|
||||
}
|
||||
|
||||
lo_drive = {
|
||||
"@Number": i,
|
||||
"@Action": "Create",
|
||||
"RaidLevel": "",
|
||||
"ArrayRefs": {
|
||||
"ArrayRef": [
|
||||
]
|
||||
},
|
||||
"InitMode": "fast"
|
||||
}
|
||||
|
||||
array_info['Arrays']['Array'].append(arrays)
|
||||
array_info['LogicalDrives']['LogicalDrive'].append(lo_drive)
|
||||
|
||||
lo_drive.update({'RaidLevel': logical_disk['raid_level']})
|
||||
lo_drive['ArrayRefs']['ArrayRef'].append({"@Number": i})
|
||||
|
||||
for element in logical_disk['physical_disks']:
|
||||
arrays['PhysicalDiskRefs']['PhysicalDiskRef'].append({
|
||||
'@Number': element})
|
||||
if logical_disk['size_gb'] != "MAX":
|
||||
# Ensure correctly order these items in dict
|
||||
size = collections.OrderedDict()
|
||||
size['@Unit'] = 'GB'
|
||||
size['#text'] = logical_disk['size_gb']
|
||||
array_info['LogicalDrives']['LogicalDrive'][i]['Size'] = size
|
||||
|
||||
return raid_input
|
||||
|
||||
|
||||
def get_raid_adapter(irmc_info):
|
||||
"""Collect raid information on the server.
|
||||
|
||||
:param irmc_info: node info
|
||||
:returns: raid_adapter: get latest raid adapter information
|
||||
"""
|
||||
|
||||
# Update raid adapter, due to raid adapter cannot auto update after
|
||||
# created raid configuration.
|
||||
_create_raid_adapter_profile(irmc_info)
|
||||
|
||||
return elcm_profile_get(irmc_info, PROFILE_RAID_CONFIG)
|
||||
|
||||
|
||||
def _get_existing_logical_drives(raid_adapter):
|
||||
"""Collect existing logical drives on the server.
|
||||
|
||||
:param raid_adapter: raid adapter info
|
||||
:returns: existing_logical_drives: get logical drive on server
|
||||
"""
|
||||
existing_logical_drives = []
|
||||
logical_drives = raid_adapter['Server']['HWConfigurationIrmc'][
|
||||
'Adapters']['RAIDAdapter'][0].get('LogicalDrives')
|
||||
if logical_drives is not None:
|
||||
for drive in logical_drives['LogicalDrive']:
|
||||
existing_logical_drives.append(drive['@Number'])
|
||||
|
||||
return existing_logical_drives
|
||||
|
||||
|
||||
def _create_raid_adapter_profile(irmc_info):
|
||||
"""Attempt delete exist adapter then create new raid adapter on the server.
|
||||
|
||||
:param irmc_info: node info
|
||||
:returns: result: a dict with following values:
|
||||
{
|
||||
'raid_config': <data of raid adapter profile>,
|
||||
'warning': <warning message if there is>
|
||||
}
|
||||
"""
|
||||
|
||||
try:
|
||||
# Attempt erase exist adapter on BM Server
|
||||
elcm_profile_delete(irmc_info, PROFILE_RAID_CONFIG)
|
||||
except ELCMProfileNotFound:
|
||||
# Ignore this error as it's not an error in this case
|
||||
pass
|
||||
|
||||
session = elcm_profile_create(irmc_info, PARAM_PATH_RAID_CONFIG)
|
||||
|
||||
# Monitoring currently session until done.
|
||||
session_timeout = irmc_info.get('irmc_raid_session_timeout',
|
||||
RAID_CONFIG_SESSION_TIMEOUT)
|
||||
|
||||
return _process_session_data(irmc_info, 'CONFIG_RAID',
|
||||
session['Session']['Id'],
|
||||
session_timeout)
|
||||
|
||||
|
||||
def create_raid_configuration(irmc_info, target_raid_config):
|
||||
"""Process raid_input then perform raid configuration into server.
|
||||
|
||||
:param irmc_info: node info
|
||||
:param target_raid_config: node raid information
|
||||
"""
|
||||
|
||||
if len(target_raid_config['logical_disks']) < 1:
|
||||
raise ELCMValueError(message="logical_disks must not be empty")
|
||||
|
||||
# Check RAID config in the new RAID adapter. Must be erased before
|
||||
# create new RAID config.
|
||||
raid_adapter = get_raid_adapter(irmc_info)
|
||||
logical_drives = raid_adapter['Server']['HWConfigurationIrmc'][
|
||||
'Adapters']['RAIDAdapter'][0]['LogicalDrives']
|
||||
session_timeout = irmc_info.get('irmc_raid_session_timeout',
|
||||
RAID_CONFIG_SESSION_TIMEOUT)
|
||||
if logical_drives is not None:
|
||||
# Delete exist logical drives in server.
|
||||
# NOTE(trungnv): Wait session complete and raise error if
|
||||
# delete raid config during BGI(BackGround Initialize) in-progress
|
||||
# in previous mechanism.
|
||||
delete_raid_configuration(irmc_info)
|
||||
# Updating raid adapter profile after deleted profile.
|
||||
raid_adapter = get_raid_adapter(irmc_info)
|
||||
|
||||
# Create raid configuration based on target_raid_config of node
|
||||
raid_input = _update_raid_input_data(target_raid_config, raid_adapter)
|
||||
session = elcm_profile_set(irmc_info, raid_input)
|
||||
# Monitoring raid creation session until done.
|
||||
_process_session_data(irmc_info, 'CONFIG_RAID',
|
||||
session['Session']['Id'],
|
||||
session_timeout)
|
||||
|
||||
|
||||
def delete_raid_configuration(irmc_info):
|
||||
"""Delete whole raid configuration or one of logical drive on the server.
|
||||
|
||||
:param irmc_info: node info
|
||||
"""
|
||||
# Attempt to get raid configuration on BM Server
|
||||
raid_adapter = get_raid_adapter(irmc_info)
|
||||
existing_logical_drives = _get_existing_logical_drives(raid_adapter)
|
||||
# Ironic requires delete_configuration first. Will pass if blank raid
|
||||
# configuration in server.
|
||||
if not existing_logical_drives:
|
||||
return
|
||||
raid_adapter['Server']['HWConfigurationIrmc'].update({
|
||||
'@Processing': 'execute'})
|
||||
logical_drive = raid_adapter['Server']['HWConfigurationIrmc'][
|
||||
'Adapters']['RAIDAdapter'][0]['LogicalDrives']['LogicalDrive']
|
||||
|
||||
for drive in logical_drive:
|
||||
drive['@Action'] = 'Delete'
|
||||
|
||||
# Attempt to delete logical drive in the raid config
|
||||
session = elcm_profile_set(irmc_info, raid_adapter)
|
||||
# Monitoring raid config delete session until done.
|
||||
session_timeout = irmc_info.get('irmc_raid_session_timeout',
|
||||
RAID_CONFIG_SESSION_TIMEOUT)
|
||||
_process_session_data(irmc_info, 'CONFIG_RAID', session['Session']['Id'],
|
||||
session_timeout)
|
||||
|
||||
# Attempt to delete raid adapter
|
||||
elcm_profile_delete(irmc_info, PROFILE_RAID_CONFIG)
|
||||
|
|
|
@ -541,3 +541,34 @@ def get_capabilities_properties(d_info,
|
|||
return v
|
||||
except (snmp.SNMPFailure, ipmi.IPMIFailure) as err:
|
||||
raise SCCIClientError('Capabilities inspection failed: %s' % err)
|
||||
|
||||
|
||||
def get_raid_bgi_status(report):
|
||||
"""Gather bgi(background initialize) information of raid configuration
|
||||
|
||||
This function returns a bgi status which contains activity status
|
||||
and its values from the report.
|
||||
|
||||
:param report: SCCI report information
|
||||
:returns: dict of bgi status of logical_drives, such as BGI(10%) or
|
||||
Idle. e.g: {'0': 'Idle', '1': 'BGI(10%)'}
|
||||
:raises: SCCIInvalidInputError: fail report input.
|
||||
"""
|
||||
bgi_status = {}
|
||||
raid_path = "./Software/ServerView/ServerViewRaid"
|
||||
|
||||
if not report.find(raid_path):
|
||||
raise SCCIInvalidInputError(
|
||||
"ServerView RAID not available in Bare metal Server")
|
||||
if not report.find(raid_path + "/amEMSV/System/Adapter/LogicalDrive"):
|
||||
raise SCCIInvalidInputError(
|
||||
"RAID configuration not configure in Bare metal Server yet")
|
||||
|
||||
logical_drives = report.findall(raid_path +
|
||||
"/amEMSV/System/Adapter/LogicalDrive")
|
||||
for logical_drive_name in logical_drives:
|
||||
status = logical_drive_name.find("./Activity").text
|
||||
name = logical_drive_name.find("./LogDriveNumber").text
|
||||
bgi_status.update({name: status})
|
||||
|
||||
return bgi_status
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -827,3 +827,35 @@ class SCCITestCase(testtools.TestCase):
|
|||
gpu_ids,
|
||||
**kwargs)
|
||||
self.assertEqual('Capabilities inspection failed: IPMI error', str(e))
|
||||
|
||||
def test_fail_get_raid_bgi_status(self):
|
||||
report_fake = self.report_ok_xml
|
||||
report_fake.find("./Software/ServerView/ServerViewRaid").clear()
|
||||
self.assertRaises(scci.SCCIInvalidInputError,
|
||||
scci.get_raid_bgi_status, report=report_fake)
|
||||
|
||||
def test_fail_get_raid_bgi_status_1(self):
|
||||
report_fake = self.report_ok_xml
|
||||
report_fake.find("./Software/ServerView/ServerViewRaid/amEMSV"
|
||||
"/System/Adapter").clear()
|
||||
self.assertRaises(scci.SCCIInvalidInputError,
|
||||
scci.get_raid_bgi_status, report=report_fake)
|
||||
|
||||
def test_get_raid_bgi_status_ok(self):
|
||||
|
||||
# Fake activity status of BGI in xml report
|
||||
url = "./Software/ServerView/ServerViewRaid/amEMSV/System/Adapter"
|
||||
report_fake = self.report_ok_xml
|
||||
report_input = report_fake.find(url)
|
||||
element_1 = ET.Element("LogicalDrive", name="LogicalDrive")
|
||||
element_2 = ET.Element("LogDriveNumber", name="LogDriveNumber")
|
||||
element_3 = ET.Element("Activity", name="Activity")
|
||||
report_input.append(element_1)
|
||||
report_input.find("./LogicalDrive").append(element_2)
|
||||
report_fake.find(url + "/LogicalDrive/LogDriveNumber").text = '0'
|
||||
report_input.find("./LogicalDrive").append(element_3)
|
||||
report_fake.find(url + "/LogicalDrive/Activity").text = 'Idle'
|
||||
|
||||
bgi_status_expect = {'0': 'Idle'}
|
||||
result = scci.get_raid_bgi_status(report_fake)
|
||||
self.assertEqual(result, bgi_status_expect)
|
||||
|
|
Loading…
Reference in New Issue