3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 from ironic.common import boot_devices
17 from ironic.common.i18n import _
18 from ironic.conductor import utils as manager_utils
19 from ironic.drivers.modules import ipmitool
20 from ironic_virtmedia_driver.vendors.ironic_virtmedia_hw import IronicVirtMediaHW
21 from ironic_virtmedia_driver import virtmedia_exception
23 from redfish import redfish_client, AuthMethod
24 from redfish.rest.v1 import ServerDownOrUnreachableError
26 class DELL(IronicVirtMediaHW):
27 def __init__(self, log):
28 super(DELL, self).__init__(log)
29 self.remote_share = '/bootimages/'
30 self.idrac_location = '/redfish/v1/Managers/iDRAC.Embedded.1/'
32 def _init_connection(self, driver_info):
33 """Get connection info and init rest_object"""
34 host = 'https://' + driver_info['address']
35 user = driver_info['username']
36 password = driver_info['password']
38 self.log.debug("Init connection: user: %s, passwd: %s, host: %s", user, password, host)
40 redfishclient = redfish_client(base_url=host, \
41 username=user, password=password)
42 redfishclient.login(auth=AuthMethod.SESSION)
43 except ServerDownOrUnreachableError as error:
44 operation = _("iDRAC not responding")
45 raise virtmedia_exception.VirtmediaOperationError(
46 operation=operation, error=error)
47 except Exception as error:
48 operation = _("Failed to login to iDRAC")
49 raise virtmedia_exception.VirtmediaOperationError(
50 operation=operation, error=error)
54 def _check_success(response):
55 if response.status >= 200 and response.status < 300:
60 raise virtmedia_exception.VirtmediaOperationError("Response status is %d, %s"% (response.status, response.dict["error"]["@Message.ExtendedInfo"][0]["MessageId"].split(".")))
62 raise virtmedia_exception.VirtmediaOperationError("Response status is not 200, %s"% response)
64 def _check_supported_idrac_version(self, connection):
65 response = connection.get('%s/VirtualMedia/CD'%self.idrac_location)
66 self._check_success(response)
68 for i in data.get('Actions', []):
69 if i == "#VirtualMedia.InsertMedia" or i == "#VirtualMedia.EjectMedia":
71 raise virtmedia_exception.VirtmediaOperationError("Unsupported version of iDRAC, please update before continuing")
73 def _get_virtual_media_devices(self, connection):
74 idr = connection.get("%s" % self.idrac_location)
75 self._check_success(idr)
77 virtual_media = connection.get(idr.dict["VirtualMedia"]["@odata.id"])
78 self._check_success(virtual_media)
80 self.log.error("Cannot find a single virtual media device")
81 raise virtmedia_exception.VirtmediaOperationError("Cannot find any virtual media device on the server")
82 return virtual_media.dict["Members"]
84 def _umount_virtual_device(self, connection, media_uri):
85 self.log.debug("Unmount")
86 unmount_location = media_uri + "/Actions/VirtualMedia.EjectMedia"
87 resp = connection.post(unmount_location, body={})
88 self._check_success(resp)
90 def _mount_virtual_device(self, connection, media_uri, image_location):
91 self.log.debug("Mount")
92 mount_location = media_uri + "/Actions/VirtualMedia.InsertMedia"
93 payload = {'Image': image_location, 'Inserted':True, 'WriteProtected':True}
94 resp = connection.post(mount_location, body=payload)
95 self._check_success(resp)
97 def _unmount_all(self, connection):
98 medias = self._get_virtual_media_devices(connection)
100 uri = media.get("@odata.id", None)
101 if not uri or connection.get(uri).dict["ConnectedVia"] == "NotConnected":
103 self._umount_virtual_device(connection, uri)
105 def _find_first_media(self, connection, typeinfo):
106 medias = self._get_virtual_media_devices(connection)
108 response = connection.get(media["@odata.id"])
109 if typeinfo in response.dict["MediaTypes"]:
110 return media["@odata.id"]
113 def _mount_virtual_cd(self, connection, image_location):
114 self._unmount_all(connection)
115 self.log.debug("Mount")
116 media_uri = self._find_first_media(connection, "DVD")
117 self._mount_virtual_device(connection, media_uri, image_location)
119 def attach_virtual_cd(self, image_filename, driver_info, task):
122 self.log.debug("attach_virtual_cd")
123 connection = self._init_connection(driver_info)
124 self._check_supported_idrac_version(connection)
125 image_location = 'http://' + str(driver_info['provisioning_server']) + ':' + str(driver_info['provisioning_server_http_port']) + self.remote_share + image_filename
126 self._mount_virtual_cd(connection, image_location)
135 def detach_virtual_cd(self, driver_info, task):
138 self.log.debug("detach_virtual_cd")
139 connection = self._init_connection(driver_info)
140 self._check_supported_idrac_version(connection)
141 self._unmount_all(connection)
149 def set_boot_device(self, task):
151 #BMC boot flag valid bit clearing 1f -> all bit set
153 # https://www.intel.com/content/www/us/en/servers/ipmi/ipmi-second-gen-interface-spec-v2-rev1-1.html
154 cmd = '0x00 0x08 0x03 0x1f'
155 ipmitool.send_raw(task, cmd)
156 self.log.info('Disable timeout for booting')
157 except Exception as err:
158 self.log.warning('Failed to disable booting options: %s', str(err))
159 #For time being lets do the boot order with ipmitool since, well dell doesn't provide open support
162 # 0x00 0x08 0x05 0x80 0x20: chassis|set|bootdev|for next boot only|remote CD
163 # other options for device (per ipmitool's "ipmi_chassis.c"):
171 # 24: Remote primary media
174 ipmitool.send_raw(task, '0x00 0x08 0x05 0x80 0x20 0x00 0x00 0x00')
175 self.log.info('Set next boot to remote media')
176 except Exception as err:
177 self.log.warning('Failed to set next boot to remote media: %s', str(err))