Add support for Ampere Falcon server 57/2057/9
authorAlexandru Antone <Alexandru.Antone@enea.com>
Wed, 27 Nov 2019 13:55:37 +0000 (15:55 +0200)
committerAlexandru Antone <Alexandru.Antone@enea.com>
Mon, 16 Dec 2019 14:53:44 +0000 (16:53 +0200)
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 <lafonj@gmail.com>
Signed-off-by: Alexandru Antone <Alexandru.Antone@enea.com>
Change-Id: I9f4ae8d9602f96f934725b94f35f23272db56170

ironic-virtmedia-driver.spec
src/ironic_virtmedia_driver/vendors/ampere/__init__.py [new file with mode: 0644]
src/ironic_virtmedia_driver/vendors/ampere/falcon.py [new file with mode: 0644]
src/ironic_virtmedia_driver/vendors/nokia/nokia_hw.py
src/ironic_virtmedia_driver/vendors/openbmc_hw.py [new file with mode: 0644]

index cd0f243..998e1b1 100644 (file)
@@ -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 (file)
index 0000000..619ec94
--- /dev/null
@@ -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 (file)
index 0000000..9f05326
--- /dev/null
@@ -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)
index 9596804..3805cd7 100644 (file)
 # 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: <str>: '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)<length):
-            hex_value += ' 0x'
-            hex_value += ' 0x'.join('00' for _ in range(len(string_value), length)) 
-        return hex_value
-
-    def _issue_bmc_reset(self, driver_info, task):
-        """ Issues a bmc reset and waits till the bcm 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!')
diff --git a/src/ironic_virtmedia_driver/vendors/openbmc_hw.py b/src/ironic_virtmedia_driver/vendors/openbmc_hw.py
new file mode 100644 (file)
index 0000000..e150d4f
--- /dev/null
@@ -0,0 +1,113 @@
+# 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.
+#
+
+import time
+
+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 OpenBMCIronicVirtMediaHW(IronicVirtMediaHW):
+    def __init__(self, log):
+        super(OpenBMCIronicVirtMediaHW, self).__init__(log)
+        self.remote_share = '/remote_image_share_root/'
+
+    def get_disk_attachment_status(self, task):
+        """ Get the disk attachment status.
+        :param task: a TaskManager instance.
+        :returns: <str>: '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!')