From c00954e51458e1d24015dfd09ffb0051963cf3bf Mon Sep 17 00:00:00 2001 From: Alexandru Antone Date: Wed, 15 Jan 2020 11:28:07 +0200 Subject: [PATCH] Add support for Ampere Falcon HW This should also work for Ampere Hawk. Signed-off-by: Alexandru Antone Change-Id: I5b6821c42128c227aaa9619e49ac60ee3f9fb357 --- remote-installer.spec | 2 +- .../installer/bmc_management/bmctools.py | 13 +- .../installer/bmc_management/falcon.py | 400 +++++++++++++++++++++ 3 files changed, 407 insertions(+), 8 deletions(-) create mode 100644 src/remoteinstaller/installer/bmc_management/falcon.py diff --git a/remote-installer.spec b/remote-installer.spec index 2dba6c7..e23b04c 100644 --- a/remote-installer.spec +++ b/remote-installer.spec @@ -14,7 +14,7 @@ Name: remote-installer Version: %{_version} -Release: 4%{?dist} +Release: 5%{?dist} Summary: Contains components for the remote-installer Group: %{_platform_group} License: %{_platform_licence} diff --git a/src/remoteinstaller/installer/bmc_management/bmctools.py b/src/remoteinstaller/installer/bmc_management/bmctools.py index cbe9565..b5be98d 100644 --- a/src/remoteinstaller/installer/bmc_management/bmctools.py +++ b/src/remoteinstaller/installer/bmc_management/bmctools.py @@ -1,5 +1,6 @@ # Copyright 2019 Nokia - +# Copyright 2020 ENEA +# # 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 @@ -201,15 +202,13 @@ class BMC(object): @staticmethod def _convert_to_hex(ascii_string, padding=False, length=0): - hex_value = ''.join('0x{} '.format(c.encode('hex')) for c in ascii_string).strip() - if padding and (len(ascii_string) < length): - hex_value += ''.join(' 0x00' for _ in range(len(ascii_string), length)) - - return hex_value + if padding: + ascii_string = ascii_string.ljust(length, '\0') + return ' '.join('0x{}'.format(c.encode('hex')) for c in ascii_string) @staticmethod def _convert_to_ascii(hex_string): - return ''.join('{}'.format(c.decode('hex')) for c in hex_string) + return hex_string.replace('0x','').replace(' ','').decode('hex') def _execute_ipmitool_command(self, ipmi_command): command = 'ipmitool -I lanplus -H {} -U {} -P {} -L {} {}'.format(self._host, self._user, self._passwd, self._priv_level, ipmi_command) diff --git a/src/remoteinstaller/installer/bmc_management/falcon.py b/src/remoteinstaller/installer/bmc_management/falcon.py new file mode 100644 index 0000000..34b3e42 --- /dev/null +++ b/src/remoteinstaller/installer/bmc_management/falcon.py @@ -0,0 +1,400 @@ +# Copyright 2019 Nokia +# Copyright 2020 ENEA +# +# 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. + +import logging +import time +from .bmctools import BMC + +RAW_CHECK_NFS_SERVICE_STATUS = '0x32 0xd8 0x06 0x01 0x01 0x00' + +RAW_GET_VMEDIA_DEVICE_COUNT = '0x32 0xca %s' # (type) +RAW_SET_VMEDIA_DEVICE_COUNT = '0x32 0xcb %s %s' # (type, count) +( VMEDIA_DEVICE_TYPE_CD, + VMEDIA_DEVICE_TYPE_FD, + VMEDIA_DEVICE_TYPE_HD ) = ('0x04', '0x05', '0x06') + +RAW_GET_VMEDIA_MOUNT_STATUS = '0x32 0xca 0x00' +RAW_SET_VMEDIA_MOUNT_STATUS = '0x32 0xcb 0x00 %s' + +RAW_GET_VMEDIA_STATUS = '0x32 0xca 0x08' +RAW_SET_VMEDIA_STATUS = '0x32 0xcb 0x08 %s' +RAW_RESTART_VMEDIA = '0x32 0xcb 0x0a 0x01' + +# Remote Image Service commands +RAW_RESTART_RIS_CD = '0x32 0x9f 0x01 0x0b 0x01' +RAW_SET_RIS_NFS = '0x32 0x9f 0x01 0x05 0x00 0x6e 0x66 0x73 0x00 0x00 0x00' +RAW_SET_RIS_NFS_IP = '0x32 0x9f 0x01 0x02 0x00 %s' +RAW_SET_RIS_NFS_PATH = '0x32 0x9f 0x01 0x01 0x01 %s' +RAW_SET_RIS_PROGRESS = '0x32 0x9f 0x01 0x01 0x00 %s' +RAW_CLEAR_RIS_CONFIG = '0x32 0x9f 0x01 0x0d' +RAW_RESTART_RIS = '0x32 0x9f 0x08 0x0b' + +RAW_GET_MOUNTED_IMG_COUNT = '0x32 0xd8 0x00 0x01' +RAW_SET_IMG_NAME = '0x32 0xd7 0x01 0x01 0x01 0x01 %s' +RAW_STOP_REDIRECT = '0x32 0xd7 0x01 0x01 0x01 0x00 %s' + +class BMCException(Exception): + pass + +class FALCON(BMC): + def __init__(self, host, user, passwd, priv_level='ADMINISTRATOR', log_path=None): + super(FALCON, self).__init__(host, user, passwd, priv_level, log_path) + + def _clear_ris_configuration(self): + # Clear Remote Image Service configuration + try: + logging.debug('Clear RIS configuration.') + self._run_ipmitool_raw_command(RAW_CLEAR_RIS_CONFIG) + except Exception as err: + logging.warning('Exception when clearing RIS NFS configuration: %s', str(err)) + return False + return True + + def _check_virtual_media_started(self): + # Check virtmedia service status + try: + out = self._run_ipmitool_raw_command(RAW_GET_VMEDIA_STATUS) + service_status = out[0] + logging.debug('Virtual media service status: %s', service_status) + except Exception as err: + logging.warning('Exception when checking virtual media service: %s', str(err)) + + return service_status == '01' + + def _start_virtual_media(self): + # Enable "Remote Media Support" in GUI (p145) + try: + logging.debug('Start virtual media service') + self._run_ipmitool_raw_command(RAW_SET_VMEDIA_STATUS % '0x01') + except Exception as err: + logging.warning('Exception when starting virtual media service: %s', str(err)) + + def _set_setup_nfs(self, nfs_host, mount_path): + + # Set share type NFS + try: + logging.debug('Virtual media share type to NFS.') + self._run_ipmitool_raw_command(RAW_SET_RIS_NFS) + except Exception as err: + logging.warning('Exception when setting virtual media service type NFS: %s', str(err)) + return False + + # NFS server IP + try: + cmd = RAW_SET_RIS_NFS_IP % (self._convert_to_hex(nfs_host, True, 63)) + logging.debug('Virtual media server "%s"', nfs_host) + self._run_ipmitool_raw_command(cmd) + except Exception as err: + logging.warning('Exception when setting virtual media server: %s', str(err)) + return False + + # Set NFS Mount Root path + try: + logging.debug('Virtual media path to "%s"', mount_path) + + self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x00') + time.sleep(2) + self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x01') + time.sleep(2) + self._run_ipmitool_raw_command(RAW_SET_RIS_NFS_PATH % (self._convert_to_hex(mount_path, True, 64))) + time.sleep(2) + self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x00') + + except Exception as err: + logging.warning('Exception when setting virtual media path: %s', str(err)) + return False + return True + + def _enable_virtual_media(self): + # Speed up things if it service is already running + if self._check_virtual_media_started(): + logging.debug('Virtual media service already running.') + return True + + # Just enabling the service does not seem to start it (in all HW) + # Resetting it after enabling helps + self._start_virtual_media() + self._restart_virtual_media_service() + + tries = 60 + while tries > 0: + if self._check_virtual_media_started(): + return True + time.sleep(5) + tries -= 1 + + logging.warning('Ensure virtual media service start failed: attempts exceeded.') + return False + + def _get_virtual_media_device_count(self, devicetype): + try: + _num_inst = 0 + # Get num of enabled devices + if devicetype == 'CD': + _devparam = VMEDIA_DEVICE_TYPE_CD + logging.debug('Get virtual CD count') + elif devicetype == 'FD': + _devparam = VMEDIA_DEVICE_TYPE_FD + logging.debug('Get virtual FD count') + elif devicetype == 'HD': + _devparam = VMEDIA_DEVICE_TYPE_HD + logging.debug('Get virtual HD count') + else: + logging.warning('Unknown device type "%s"', devicetype) + return _num_inst + + cmd = RAW_GET_VMEDIA_DEVICE_COUNT % _devparam + out = self._run_ipmitool_raw_command(cmd) + _num_inst = int(out[0], 16) + logging.debug('Number of enabled %s devices is %d', devicetype, _num_inst) + return _num_inst + except Exception as err: + raise BMCException('Exception when getting number of enabled %s devices. error: %s' % (devicetype, str(err))) + + def _set_virtual_media_device_count(self, devicetype, devicecount): + if not 0 <= devicecount <= 4: + logging.warning('Number of devices must be in range 0 to 4') + return False + + if devicetype == 'CD': + _devparam = VMEDIA_DEVICE_TYPE_CD + logging.debug('Setting virtual CD count to %d', devicecount) + elif devicetype == 'HD': + _devparam = VMEDIA_DEVICE_TYPE_HD + logging.debug('Setting virtual HD count to %d', devicecount) + else: + logging.warning('Unknown device type "%s"', devicetype) + return False + + try: + cmd = RAW_SET_VMEDIA_DEVICE_COUNT % (_devparam, hex(devicecount)) + self._run_ipmitool_raw_command(cmd) + + _conf_device_num = self._get_virtual_media_device_count(devicetype) + _tries = 40 + while _conf_device_num != devicecount and _tries > 0: + logging.debug('Virtual %s count is %d expecting %d', devicetype, _conf_device_num, devicecount) + time.sleep(5) + _conf_device_num = self._get_virtual_media_device_count(devicetype) + _tries = _tries -1 + + except Exception as err: + raise BMCException('Exception when setting virtual media device count : %s' % str(err)) + return True + + def _restart_virtual_media_service(self): + try: + cmd = RAW_RESTART_VMEDIA + logging.debug('Restart virtual media service') + self._run_ipmitool_raw_command(cmd) + except Exception as err: + raise BMCException('Exception when restarting virtual media service: %s' % str(err)) + + def _restart_ris(self): + try: + cmd = RAW_RESTART_RIS + logging.debug('Restart RIS') + self._run_ipmitool_raw_command(cmd) + except Exception as err: + raise BMCException('Exception when restarting RIS: %s' % str(err)) + + return True + + def _restart_ris_cd(self): + try: + cmd = RAW_RESTART_RIS_CD + logging.debug('Restart RIS CD media') + self._run_ipmitool_raw_command(cmd) + except Exception as err: + raise BMCException('Exception when restarting RIS CD media: %s' % str(err)) + + return True + + def _check_vmedia_mount_state(self, enabled): + expected_state = 'enabled' if enabled else 'disabled' + logging.debug('Check if CD/DVD device is %s', expected_state) + + tries = 10 + while tries > 0: + try: + out = self._run_ipmitool_raw_command(RAW_GET_VMEDIA_MOUNT_STATUS) + status = out[0] + logging.debug('Virtual media mount status: %s', status) + except Exception as err: + status = None + logging.warning('Exception when checking VMedia mount status: %s', str(err)) + + matched_state = (status == '01') if enabled else (status == '00') + if matched_state: + # Virtual media mount found in expected state + return True + + tries -= 1 + time.sleep(6) + + logging.warning('Failed: CD/DVD mount is not %s (attempts exceeded).' + 'Ignoring and trying to continue.', + expected_state) + return False + + def _toggle_virtual_device(self, enabled): + state_raw = '0x01' if enabled else '0x00' + state_str = 'enable' if enabled else 'disable' + + logging.debug('Try to %s VMedia mount.', state_str) + try: + self._run_ipmitool_raw_command(RAW_SET_VMEDIA_MOUNT_STATUS % state_raw) + time.sleep(1) + return self._check_vmedia_mount_state(enabled) + except Exception as err: + logging.warning('Exception when tying to %s VMedia mount: %s. Ignoring... ', + state_str, str(err)) + return True + + def _mount_virtual_device(self): + return self._toggle_virtual_device(True) + + def _demount_virtual_device(self): + return self._toggle_virtual_device(False) + + def _get_mounted_image_count(self): + count = 0 + try: + out = self._run_ipmitool_raw_command(RAW_GET_MOUNTED_IMG_COUNT) + count = int(out[0], 16) + logging.warning('Available image count: %d', count) + except Exception as err: + logging.warning('Exception when trying to get the image count: %s', str(err)) + return count + + def _wait_for_mount_count(self): + # Poll until we got some images from server + tries = 12 + while tries > 0: + if self._get_mounted_image_count() > 0: + return True + tries -= 1 + logging.debug('Check available images count tries left: %d', tries) + time.sleep(10) + + logging.warning('Available images count 0, attempts exceeded.') + return False + + def _set_image_name(self, image_filename): + try: + logging.debug('Setting virtual media image: %s', image_filename) + self._run_ipmitool_raw_command(RAW_SET_IMG_NAME % self._convert_to_hex(image_filename, True, 64)) + except Exception as err: + logging.debug('Exception when setting virtual media image: %s', str(err)) + return False + return True + + def _get_bmc_nfs_service_status(self): + try: + out = self._run_ipmitool_raw_command(RAW_CHECK_NFS_SERVICE_STATUS) + _image_name = str(bytearray.fromhex(''.join(out))) + logging.debug('Found mounted image: %s', _image_name) + return 'mounted' + except Exception: + return 'nfserror' + + def _stop_remote_redirection(self): + _num_inst = self._get_virtual_media_device_count('CD') + for driveindex in range(0, _num_inst): + cmd = RAW_STOP_REDIRECT % hex(driveindex) + logging.debug('Stop redirection CD/DVD drive index %d', driveindex) + try: + out = self._run_ipmitool_raw_command(cmd) + logging.debug('ipmitool out = "%s"', out) + except Exception as err: + # Drive might not be mounted to start with + logging.debug('Ignoring exception when stopping redirection CD/DVD drive index %d error: %s', + driveindex, str(err)) + + def _set_boot_from_virtual_media(self): + logging.debug('Set boot from cd (%s), and boot after that', self._host) + try: + self._run_ipmitool_command('chassis bootdev floppy options=persistent') + except Exception as err: + raise BMCException('Set Boot to CD failed: %s' % str(err)) + + def _detach_virtual_media(self): + logging.debug('Detach virtual media') + + #Enable virtual media + if not self._enable_virtual_media(): + raise BMCException("detach_virtual_cd: Failed to enable virtual media") + + # Restart Remote Image Service + if not self._restart_ris(): + raise BMCException("Failed to restart RIS") + + # Stop redirection + self._stop_remote_redirection() + + #Clear RIS configuration + if not self._clear_ris_configuration(): + raise BMCException("detach_virtual_cd: Failed to clear RIS configuration") + + #Demount virtual device + if not self._demount_virtual_device(): + raise BMCException('detach_virtual_cd: Exception when disabling CD/DVD virtual media') + + # Reduce the number of virtual devices (both HD and CD default to 4 devices each) + if not self._set_virtual_media_device_count('HD', 0): + BMCException('Failed to set virtual media device count for HD') + if not self._set_virtual_media_device_count('CD', 1): + BMCException('Failed to set virtual media device count for CD') + + def attach_virtual_cd(self, nfs_host, nfs_mount, boot_iso_filename): + # Detach first + self._detach_virtual_media() + + logging.debug('Attach virtual media') + + #Enable virtual media + if not self._enable_virtual_media(): + raise BMCException("Failed to enable virtual media") + + #Enable CD/DVD device + if not self._toggle_virtual_device(True): + raise BMCException("Failed to enable virtual device") + + #Clear RIS configuration + if not self._clear_ris_configuration(): + raise BMCException("Failed to clear RIS configuration") + + #Setup nfs + if not self._set_setup_nfs(nfs_host, nfs_mount): + raise BMCException("Failed to setup nfs") + + # Restart Remote Image CD + if not self._restart_ris_cd(): + raise BMCException("Failed to restart RIS CD") + + #Wait for device to be mounted + if not self._wait_for_mount_count(): + raise BMCException("Failed when waiting for the device to appear") + + # Set Image Name + time.sleep(5) + if not self._set_image_name(boot_iso_filename): + raise BMCException("Failed to set image name") + + success = self._wait_for_bmc_nfs_service(90, 'mounted') + if success: + return True + else: + raise BMCException('NFS service setup failed') -- 2.16.6