Seed code for ironic_virtmedia_driver
[ta/ironic-virtmedia-driver.git] / src / ironic_virtmedia_driver / vendors / dell / dell.py
diff --git a/src/ironic_virtmedia_driver/vendors/dell/dell.py b/src/ironic_virtmedia_driver/vendors/dell/dell.py
new file mode 100644 (file)
index 0000000..e4fb255
--- /dev/null
@@ -0,0 +1,161 @@
+# Copyright 2019 Nokia
+#
+# 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.
+#
+
+from ironic.common import boot_devices
+from ironic.common.i18n import _
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import ipmitool
+from ironic_virtmedia_driver.vendors.ironic_virtmedia_hw import IronicVirtMediaHW
+from ironic_virtmedia_driver import virtmedia_exception
+
+from redfish import redfish_client, AuthMethod
+from redfish.rest.v1 import ServerDownOrUnreachableError
+
+class DELL(IronicVirtMediaHW):
+    def __init__(self, log):
+        super(DELL, self).__init__(log)
+        self.remote_share = '/bootimages/'
+        self.idrac_location = '/redfish/v1/Managers/iDRAC.Embedded.1/'
+
+    def _init_connection(self, driver_info):
+        """Get connection info and init rest_object"""
+        host = 'https://' + driver_info['address']
+        user = driver_info['username']
+        password = driver_info['password']
+        redfishclient = None
+        self.log.debug("Init connection: user: %s, passwd: %s, host: %s", user, password, host)
+        try:
+            redfishclient = redfish_client(base_url=host, \
+                  username=user, password=password)
+            redfishclient.login(auth=AuthMethod.SESSION)
+        except ServerDownOrUnreachableError as error:
+            operation = _("iDRAC not responding")
+            raise virtmedia_exception.VirtmediaOperationError(
+                operation=operation, error=error)
+        except Exception as error:
+            operation = _("Failed to login to iDRAC")
+            raise virtmedia_exception.VirtmediaOperationError(
+                operation=operation, error=error)
+        return redfishclient
+
+    @staticmethod
+    def _check_success(response):
+        if response.status >= 200 and response.status < 300:
+            return True
+        else:
+            try:
+                _ = response.dict
+                raise virtmedia_exception.VirtmediaOperationError("Response status is %d, %s"% (response.status, response.dict["error"]["@Message.ExtendedInfo"][0]["MessageId"].split(".")))
+            except Exception:
+                raise virtmedia_exception.VirtmediaOperationError("Response status is not 200, %s"% response)
+
+    def _check_supported_idrac_version(self, connection):
+        response = connection.get('%s/VirtualMedia/CD'%self.idrac_location)
+        self._check_success(response)
+        data = response.dict
+        for i in data.get('Actions', []):
+            if i == "#VirtualMedia.InsertMedia" or i == "#VirtualMedia.EjectMedia":
+                return True
+        raise virtmedia_exception.VirtmediaOperationError("Unsupported version of iDRAC, please update before continuing")
+
+    def _get_virtual_media_devices(self, connection):
+        idr = connection.get("%s" % self.idrac_location)
+        self._check_success(idr)
+        try:
+            virtual_media = connection.get(idr.dict["VirtualMedia"]["@odata.id"])
+            self._check_success(virtual_media)
+        except KeyError:
+            self.log.error("Cannot find a single virtual media device")
+            raise virtmedia_exception.VirtmediaOperationError("Cannot find any virtual media device on the server")
+        return virtual_media.dict["Members"]
+
+    def _umount_virtual_device(self, connection, media_uri):
+        self.log.debug("Unmount")
+        unmount_location = media_uri + "/Actions/VirtualMedia.EjectMedia"
+        resp = connection.post(unmount_location, body={})
+        self._check_success(resp)
+
+    def _mount_virtual_device(self, connection, media_uri, image_location):
+        self.log.debug("Mount")
+        mount_location = media_uri + "/Actions/VirtualMedia.InsertMedia"
+        payload = {'Image': image_location, 'Inserted':True, 'WriteProtected':True}
+        resp = connection.post(mount_location, body=payload)
+        self._check_success(resp)
+
+    def _unmount_all(self, connection):
+        medias = self._get_virtual_media_devices(connection)
+        for media in medias:
+            uri = media.get("@odata.id", None)
+            if not uri or connection.get(uri).dict["ConnectedVia"] == "NotConnected":
+                continue
+            self._umount_virtual_device(connection, uri)
+
+    def _find_first_media(self, connection, typeinfo):
+        medias = self._get_virtual_media_devices(connection)
+        for media in medias:
+            response = connection.get(media["@odata.id"])
+            if typeinfo in response.dict["MediaTypes"]:
+                return media["@odata.id"]
+        return None
+
+    def _mount_virtual_cd(self, connection, image_location):
+        self._unmount_all(connection)
+        self.log.debug("Mount")
+        media_uri = self._find_first_media(connection, "DVD")
+        self._mount_virtual_device(connection, media_uri, image_location)
+
+    def attach_virtual_cd(self, image_filename, driver_info, task):
+        connection = None
+        try:
+            self.log.debug("attach_virtual_cd")
+            connection = self._init_connection(driver_info)
+            self._check_supported_idrac_version(connection)
+            image_location = 'http://' + driver_info['provisioning_server'] + ':' + driver_info['provisioning_server_http_port'] + self.remote_share + image_filename
+            self._mount_virtual_cd(connection, image_location)
+
+            connection.logout()
+            return True
+        except Exception:
+            if connection:
+                connection.logout()
+            raise
+
+    def detach_virtual_cd(self, driver_info, task):
+        connection = None
+        try:
+            self.log.debug("detach_virtual_cd")
+            connection = self._init_connection(driver_info)
+            self._check_supported_idrac_version(connection)
+            self._unmount_all(connection)
+            connection.logout()
+            return True
+        except Exception:
+            if connection:
+                connection.logout()
+            raise
+
+    def set_boot_device(self, task):
+        try:
+            #BMC boot flag valid bit clearing 1f -> all bit set
+            #P 420 of ipmi spec
+            # https://www.intel.com/content/www/us/en/servers/ipmi/ipmi-second-gen-interface-spec-v2-rev1-1.html
+            cmd = '0x00 0x08 0x03 0x1f'
+            ipmitool.send_raw(task, cmd)
+            self.log.info('Disable timeout for booting')
+        except Exception as err:
+            self.log.warning('Failed to disable booting options: %s', str(err))
+        #For time being lets do the boot order with ipmitool since, well dell doesn't provide open support
+        #for this.
+        manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=False)