From: Alexandru Antone Date: Wed, 27 Nov 2019 13:55:37 +0000 (+0200) Subject: Add support for Ampere Falcon server X-Git-Url: https://gerrit.akraino.org/r/gitweb?p=ta%2Fironic-virtmedia-driver.git;a=commitdiff_plain;h=ec0a80d466350d8270441deed766a72ca582f05e Add support for Ampere Falcon server Imported and addapted from Cachengo: https://github.com/cachengo/rec-ironic-virtmedia-driver/commit/93914879ccf6cb554707e8bcb27fcb9f42d23924 Also refactored existing Nokia file to inherit from generic OpenBMC hw type, same as ampere/falcon: vendors/nokia/nokia_hw.py -> vendors/openbmc_hw.py Co-authored-by: Jimmy Lafontaine Rivera Signed-off-by: Alexandru Antone Change-Id: I9f4ae8d9602f96f934725b94f35f23272db56170 --- diff --git a/ironic-virtmedia-driver.spec b/ironic-virtmedia-driver.spec index cd0f243..998e1b1 100644 --- a/ironic-virtmedia-driver.spec +++ b/ironic-virtmedia-driver.spec @@ -15,7 +15,7 @@ Name: ironic-virtmedia-driver Version: %{_version} -Release: 1%{?dist} +Release: 2%{?dist} Summary: Contains ironic drivers for virtualmedia based deployment License: %{_platform_licence} Source0: %{name}-%{version}.tar.gz diff --git a/src/ironic_virtmedia_driver/vendors/ampere/__init__.py b/src/ironic_virtmedia_driver/vendors/ampere/__init__.py new file mode 100644 index 0000000..619ec94 --- /dev/null +++ b/src/ironic_virtmedia_driver/vendors/ampere/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2019 Nokia +# Copyright 2019 Cachengo +# Copyright 2019 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. +# + diff --git a/src/ironic_virtmedia_driver/vendors/ampere/falcon.py b/src/ironic_virtmedia_driver/vendors/ampere/falcon.py new file mode 100644 index 0000000..9f05326 --- /dev/null +++ b/src/ironic_virtmedia_driver/vendors/ampere/falcon.py @@ -0,0 +1,431 @@ +# Copyright 2019 Cachengo +# Copyright 2019 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 time + +from ironic.conductor import utils as manager_utils +from ironic.common import boot_devices +from ironic.common import exception +from ironic.drivers.modules import ipmitool + +from ..openbmc_hw import OpenBMCIronicVirtMediaHW + +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' +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 FALCON(OpenBMCIronicVirtMediaHW): + def __init__(self, log): + super(FALCON, self).__init__(log) + + def get_disk_attachment_status(self, task): + # Check NFS Service Status + try: + cmd = RAW_CHECK_NFS_SERVICE_STATUS + out, err = ipmitool.send_raw(task, cmd) + _image_name = str(bytearray.fromhex(out.replace('\n', '').strip())) + return 'mounted' + except Exception: + return 'nfserror' + + def _get_virtual_media_device_count(self, task, devicetype): + try: + _num_inst = 0 + # Get num of enabled devices + if devicetype == 'CD': + _devparam = VMEDIA_DEVICE_TYPE_CD + self.log.debug('Get virtual CD count') + elif devicetype == 'FD': + _devparam = VMEDIA_DEVICE_TYPE_FD + self.log.debug('Get virtual FD count') + elif devicetype == 'HD': + _devparam = VMEDIA_DEVICE_TYPE_HD + self.log.debug('Get virtual HD count') + else: + self.log.warning('Unknown device type "%s"' % devicetype) + return _num_inst + + cmd = RAW_GET_VMEDIA_DEVICE_COUNT % _devparam + out, err = ipmitool.send_raw(task, cmd) + _num_inst = int(out.strip()) + self.log.debug('Number of enabled %s devices is %d' % (devicetype, _num_inst)) + return _num_inst + except Exception as err: + # Drive might not be mounted to start with + self.log.debug('Exception when getting number of enabled %s devices. error: %s' % (devicetype, str(err))) + + + def _set_virtual_media_device_count(self, task, devicetype, devicecount): + if not 0 <= devicecount <= 4: + self.log.warning('Number of devices must be in range 0 to 4') + return False + + if devicetype == 'CD': + _devparam = VMEDIA_DEVICE_TYPE_CD + self.log.debug('Setting virtual CD count to %d' % devicecount) + elif devicetype == 'HD': + _devparam = VMEDIA_DEVICE_TYPE_HD + self.log.debug('Setting virtual HD count to %d' % devicecount) + else: + self.log.warning('_set_virtual_media_device_count: Unknown device type "%s"' % devicetype) + return False + + try: + cmd = RAW_SET_VMEDIA_DEVICE_COUNT % (_devparam, hex(devicecount)) + ipmitool.send_raw(task, cmd) + + _conf_device_num = self._get_virtual_media_device_count(task, devicetype) + _tries = 40 + while _conf_device_num != devicecount and _tries > 0: + self.log.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(task, devicetype) + _tries = _tries -1 + + except Exception as err: + self.log.warning('Exception when setting virtual media device count, error: %s' % str(err)) + return False + return True + + def _check_virtual_media_started(self, task): + service_status = None + # check virtmedia service status + try: + cmd = RAW_GET_VMEDIA_STATUS + out, err = ipmitool.send_raw(task, cmd) + service_status = out.strip() + self.log.warning('Virtual media service status: %s' % service_status) + except Exception as err: + self.log.warning('Exception when checking virtual media service: %s' % str(err)) + + return service_status == '01' + + def _start_virtual_media(self, task): + # Enable "Remote Media Support" + try: + cmd = RAW_SET_VMEDIA_STATUS % '0x01' + self.log.debug('Start virtual media service') + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when starting virtual media service: %s' % str(err)) + + def _restart_virtual_media_service(self, task): + try: + cmd = RAW_RESTART_VMEDIA + self.log.debug('Restart virtual media service') + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when restarting virtual media service: %s' % str(err)) + + def _restart_ris(self, task): + # Restart Remote Image Service + try: + self.log.debug('Restart Remote Image Service') + cmd = RAW_RESTART_RIS + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when restarting RIS: %s' % str(err)) + return False + return True + + def _restart_ris_cd(self, task): + # Restart Remote Image Service CD media + try: + self.log.debug('Restart Remote Image Service CD media') + cmd = RAW_RESTART_RIS_CD + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when restarting RIS CD media: %s' % str(err)) + return False + return True + + def _enable_virtual_media(self, task): + # Speed up things if it service is already running + if self._check_virtual_media_started(task): + self.log.debug('Virtual media service already running.') + return True + + # Just enabling the service doe not seem to start it (in all HW) + # Resetting it after enabling helps + self._start_virtual_media(task) + self._restart_virtual_media_service(task) + + tries = 60 + while tries > 0: + if self._check_virtual_media_started(task): + return True + time.sleep(5) + tries -= 1 + + self.log.warning('Ensure virtual media service start failed: attempts exceeded.') + return False + + def _set_nfs_server_ip(self, driver_info, task): + try: + cmd = RAW_SET_RIS_NFS_IP % (self.hex_convert(driver_info['provisioning_server'])) + self.log.debug('Virtual media server "%s"' % driver_info['provisioning_server']) + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when setting virtual media server: %s' % str(err)) + raise err + + def _set_share_type(self, task): + try: + cmd = RAW_SET_RIS_NFS + self.log.debug('Virtual media share type to NFS.') + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when setting virtual media service type NFS: %s' % str(err)) + raise err + + def _set_nfs_root_path(self, driver_info, task): + try: + self.log.debug('Virtual media path to "%s"' % self.remote_share) + # set progress bit (hmm. seems to return error if it is already set.. So should check..) + # Welp there is no way checking this. As workaround clearing it first ( does not seem to + # return error even if alreay cleared). + # clear progress bit + cmd = RAW_SET_RIS_PROGRESS % '0x00' + ipmitool.send_raw(task, cmd) + + # set progress bit + cmd = RAW_SET_RIS_PROGRESS % '0x01' + ipmitool.send_raw(task, cmd) + time.sleep(2) + + cmd = RAW_SET_RIS_NFS_PATH % (self.hex_convert(self.remote_share)) + ipmitool.send_raw(task, cmd) + time.sleep(2) + + # clear progress bit + cmd = RAW_SET_RIS_PROGRESS % '0x00' + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when setting virtual media path: %s' % str(err)) + return False + + def _set_setup_nfs(self, driver_info, task): + try: + # Set share type NFS + self._set_share_type(task) + # NFS server IP + self._set_nfs_server_ip(driver_info, task) + # Set NFS Mount Root path + self._set_nfs_root_path(driver_info, task) + return True + + except Exception: + return False + + def _toggle_virtual_device(self, enabled, task): + # Enable "Mount CD/DVD" in GUI should cause vmedia restart withing 2 seconds. + # Seems "Mount CD/DVD" need to be enabled (or toggled) after config. + # refresh/vmedia restart is not enough(?) + try: + status = '01' if enabled else '00' + + cmd = RAW_SET_VMEDIA_MOUNT_STATUS % ('0x' + status) + self.log.debug('Set mount CD/DVD enable status %s' % status) + ipmitool.send_raw(task, cmd) + + self.log.debug('Ensure CD/DVD enable status is %s' % status) + tries = 60 + while tries > 0: + out, err = ipmitool.send_raw(task, RAW_GET_VMEDIA_MOUNT_STATUS) + res = out.strip() + + self.log.debug('CD/DVD enable status is %s' % res) + if res == status: + return True + + tries -= 1 + time.sleep(2) + + except Exception as err: + self.log.warning('Exception when CD/DVD virtual media new firmware? ignoring... Error: %s' % str(err)) + + self.log.warning('Ensure virtual media status failed, attempts exceeded.') + return False + + def _mount_virtual_device(self, task): + return self._toggle_virtual_device(True, task) + + def _demount_virtual_device(self, task): + return self._toggle_virtual_device(False, task) + + def _get_mounted_image_count(self, task): + count = 0 + try: + cmd = RAW_GET_MOUNTED_IMG_COUNT + out, err = ipmitool.send_raw(task, cmd) + out = out.strip() + data = out[3:5] + count = int(data, 16) + self.log.debug('Available image count: %d' % count) + except Exception as err: + self.log.debug('Exception when trying to get the image count: %s' % str(err)) + return count + + def _set_image_name(self, image_filename, task): + try: + cmd = RAW_SET_IMG_NAME % (self.hex_convert(image_filename)) + self.log.debug('Setting virtual media image: %s' % image_filename) + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.debug('Exception when setting virtual media image: %s' % str(err)) + return False + return True + + def _stop_remote_redirection(self, task): + try: + # Get num of enabled devices + _num_inst = self._get_virtual_media_device_count(task, 'CD') + for driveindex in range(0, _num_inst): + cmd = RAW_STOP_REDIRECT % hex(driveindex) + self.log.debug('Stop redirection CD/DVD drive index %d' % driveindex) + out, err = ipmitool.send_raw(task, cmd) + self.log.debug('ipmitool out = %s' % (out)) + except Exception as err: + # Drive might not be mounted to start with + self.log.debug('_stop_remote_redirection: Ignoring exception when stopping redirection CD/DVD drive index %d error: %s' % (driveindex, str(err))) + pass + + def _clear_ris_configuration(self, task): + # Clear Remote Image Service configuration + try: + cmd = RAW_CLEAR_RIS_CONFIG + self.log.debug('Clear Remote Image Service configuration.') + ipmitool.send_raw(task, cmd) + except Exception as err: + self.log.warning('Exception when clearing RIS NFS configuration: %s' % str(err)) + return False + return True + + def _wait_for_mount_count(self, task): + # Poll until we got some images from server + tries = 60 + while tries > 0: + if self._get_mounted_image_count(task) > 0: + return True + tries -= 1 + self.log.debug('Check available images count. Tries left: %d' % tries) + time.sleep(10) + + self.log.warning('Available images count is 0. Attempts exceeded.') + return False + + def attach_virtual_cd(self, image_filename, driver_info, task): + + # Enable virtual media + if not self._enable_virtual_media(task): + self.log.error("Failed to enable virtual media") + return False + + # Enable CD/DVD device + if not self._toggle_virtual_device(True, task): + self.log.error("Failed to enable virtual device") + return False + + # Clear Remote Image Service configuration + if not self._clear_ris_configuration(task): + self.log.error("Failed to clear Remote Image Service configuration") + return False + + # Setup NFS + if not self._set_setup_nfs(driver_info, task): + self.log.error("Failed to setup nfs") + return False + + # Restart Remote Image CD + if not self._restart_ris_cd(task): + self.log.error("Failed to restart Remote Image Service CD") + return False + + # Wait for device to be mounted + if not self._wait_for_mount_count(task): + self.log.error("Failed when waiting for the device to appear") + return False + + # Set Image Name + if not self._set_image_name(image_filename, task): + self.log.error("Failed to set image name") + return False + + return self.check_and_wait_for_cd_mounting(image_filename, task, driver_info) + + def detach_virtual_cd(self, driver_info, task): + """Detaches virtual cdrom on the node. + + :param task: an ironic task object + """ + # Enable virtual media + if not self._enable_virtual_media(task): + self.log.error("detach_virtual_cd: Failed to enable virtual media") + return False + + # Restart Remote Image Service + if not self._restart_ris(task): + self.log.error("Failed to restart Remote Image Service") + return False + + # Stop redirection + self._stop_remote_redirection(task) + + # Clear Remote Image Service configuration + if not self._clear_ris_configuration(task): + self.log.error("detach_virtual_cd: Failed to clear RIS configuration") + return False + + # Unmount virtual device + if not self._demount_virtual_device(task): + self.log.error('detach_virtual_cd: Exception when disabling CD/DVD virtual media') + return False + + # Reduce the number of virtual devices (both HD and CD default to 4 devices each) + if not self._set_virtual_media_device_count(task, 'HD', 0): + return False + if not self._set_virtual_media_device_count(task, 'CD', 1): + return False + + return True + + def set_boot_device(self, task): + manager_utils.node_set_boot_device(task, boot_devices.FLOPPY, persistent=True) diff --git a/src/ironic_virtmedia_driver/vendors/nokia/nokia_hw.py b/src/ironic_virtmedia_driver/vendors/nokia/nokia_hw.py index 9596804..3805cd7 100644 --- a/src/ironic_virtmedia_driver/vendors/nokia/nokia_hw.py +++ b/src/ironic_virtmedia_driver/vendors/nokia/nokia_hw.py @@ -13,114 +13,8 @@ # limitations under the License. # -import time +from ..openbmc_hw import OpenBMCIronicVirtMediaHW -from ironic.drivers.modules import ipmitool -from ironic.common.i18n import _translators -from oslo_concurrency import processutils -from ironic.common import exception - -from ..ironic_virtmedia_hw import IronicVirtMediaHW - -class NokiaIronicVirtMediaHW(IronicVirtMediaHW): +class NokiaIronicVirtMediaHW(OpenBMCIronicVirtMediaHW): def __init__(self, log): super(NokiaIronicVirtMediaHW, self).__init__(log) - self.remote_share = '/remote_image_share_root/' - - def attach_virtual_cd(self, image_filename, driver_info, task): - """ see ironic_virtmedia_hw.py""" - raise NotImplementedError - - def detach_virtual_cd(self, driver_info, task): - """ see ironic_virtmedia_hw.py""" - raise NotImplementedError - - def set_boot_device(self, task): - """ see ironic_virtmedia_hw.py""" - raise NotImplementedError - - def get_disk_attachment_status(self, task): - """ Get the disk attachment status. - :param task: a TaskManager instance. - :returns: : 'mounting' if operation is ongoing - 'nfserror' if failed - 'mounted' if the disk is successfully mounted - """ - raise NotImplementedError - - @staticmethod - def hex_convert(string_value, padding=False, length=0): - hex_value = '0x' - hex_value += ' 0x'.join(x.encode('hex') for x in string_value) - if padding and (len(string_value): 'mounting' if operation is ongoing + 'nfserror' if failed + 'mounted' if the disk is successfully mounted + """ + raise NotImplementedError + + @staticmethod + def hex_convert(string_value, padding=False, length=0): + if padding: + string_value = string_value.ljust(length, '\0') + return ' '.join('0x%s' % x.encode('hex') for x in string_value) + + def _issue_bmc_reset(self, driver_info, task): + """ Issues a bmc reset and waits till the BMC is ready for servicing + """ + cmd = 'bmc reset cold' + node_uuid = task.node.uuid + self.log.debug("Issuing bmc cold reset to node %s" %(task.node.name)) + try: + out, err = ipmitool._exec_ipmitool(driver_info, cmd) + self.log.debug('bmc reset returned stdout: %(stdout)s, stderr:' + ' %(stderr)s', {'stdout': out, 'stderr': err}) + except processutils.ProcessExecutionError as err: + self.log.exception(_translators.log_error('IPMI "bmc reset" failed for node %(node_id)s ' + 'with error: %(error)s.'), + {'node_id': node_uuid, 'error': err}) + raise exception.IPMIFailure(cmd=cmd) + + sleep_count = 10 + cmd = 'bmc info' + while sleep_count: + try: + out, err = ipmitool._exec_ipmitool(driver_info, cmd) + self.log.debug('bmc reset returned stdout: %(stdout)s, stderr:' + ' %(stderr)s', {'stdout': out, 'stderr': err}) + break + except processutils.ProcessExecutionError as err: + self.log.debug(_translators.log_error('IPMI "bmc info" failed for node %(node_id)s ' + 'with error: %(error)s. Sleeping and retrying later.' + 'sleep_count: %(sleep_count)s'), + {'node_id': node_uuid, 'error': err, 'sleep_count': sleep_count}) + time.sleep(10) + sleep_count -= 1 + + if not sleep_count: + self.log.exception('After bmc reset, connection to bmc is lost!') + raise exception.IPMIFailure(cmd='bmc reset') + + + def _wait_for_cd_mounting(self, driver_info, task): + sleep_count = 10 + while self.get_disk_attachment_status(task) == 'mounting' and sleep_count: + self.log.debug("Waiting for the CD to be Mounted") + sleep_count -= 1 + time.sleep(1) + + if sleep_count: + return True + + self.log.warning("NFS mount timed out!. Trying BMC reset!") + self._issue_bmc_reset(driver_info, task) + + def check_and_wait_for_cd_mounting(self, image_filename, task, driver_info): + mount_status = self.get_disk_attachment_status(task) + if mount_status == 'mounting': + if self._wait_for_cd_mounting(driver_info, task): + self.log.debug("Attached CD: %s" %(image_filename)) + return True + else: + return False + elif mount_status == 'nfserror': + self.log.exception("NFS mount failed!. Issue could be with NFS server status or connectivity to target.") + raise exception.InstanceDeployFailure(reason='NFS mount failed!') + elif mount_status == 'mounted': + self.log.debug("Attached CD: %s" %(image_filename)) + return True + else: + self.log.exception("NFS mount failed!. Unknown error!") + raise exception.InstanceDeployFailure(reason='NFS mount failed!')