Seed code for ironic_virtmedia_driver
[ta/ironic-virtmedia-driver.git] / src / ironic_virtmedia_driver / vendors / dell / dell.py
1 # Copyright 2019 Nokia
2 #
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
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14 #
15
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
22
23 from redfish import redfish_client, AuthMethod
24 from redfish.rest.v1 import ServerDownOrUnreachableError
25
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/'
31
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']
37         redfishclient = None
38         self.log.debug("Init connection: user: %s, passwd: %s, host: %s", user, password, host)
39         try:
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)
51         return redfishclient
52
53     @staticmethod
54     def _check_success(response):
55         if response.status >= 200 and response.status < 300:
56             return True
57         else:
58             try:
59                 _ = response.dict
60                 raise virtmedia_exception.VirtmediaOperationError("Response status is %d, %s"% (response.status, response.dict["error"]["@Message.ExtendedInfo"][0]["MessageId"].split(".")))
61             except Exception:
62                 raise virtmedia_exception.VirtmediaOperationError("Response status is not 200, %s"% response)
63
64     def _check_supported_idrac_version(self, connection):
65         response = connection.get('%s/VirtualMedia/CD'%self.idrac_location)
66         self._check_success(response)
67         data = response.dict
68         for i in data.get('Actions', []):
69             if i == "#VirtualMedia.InsertMedia" or i == "#VirtualMedia.EjectMedia":
70                 return True
71         raise virtmedia_exception.VirtmediaOperationError("Unsupported version of iDRAC, please update before continuing")
72
73     def _get_virtual_media_devices(self, connection):
74         idr = connection.get("%s" % self.idrac_location)
75         self._check_success(idr)
76         try:
77             virtual_media = connection.get(idr.dict["VirtualMedia"]["@odata.id"])
78             self._check_success(virtual_media)
79         except KeyError:
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"]
83
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)
89
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)
96
97     def _unmount_all(self, connection):
98         medias = self._get_virtual_media_devices(connection)
99         for media in medias:
100             uri = media.get("@odata.id", None)
101             if not uri or connection.get(uri).dict["ConnectedVia"] == "NotConnected":
102                 continue
103             self._umount_virtual_device(connection, uri)
104
105     def _find_first_media(self, connection, typeinfo):
106         medias = self._get_virtual_media_devices(connection)
107         for media in medias:
108             response = connection.get(media["@odata.id"])
109             if typeinfo in response.dict["MediaTypes"]:
110                 return media["@odata.id"]
111         return None
112
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)
118
119     def attach_virtual_cd(self, image_filename, driver_info, task):
120         connection = None
121         try:
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://' + driver_info['provisioning_server'] + ':' + driver_info['provisioning_server_http_port'] + self.remote_share + image_filename
126             self._mount_virtual_cd(connection, image_location)
127
128             connection.logout()
129             return True
130         except Exception:
131             if connection:
132                 connection.logout()
133             raise
134
135     def detach_virtual_cd(self, driver_info, task):
136         connection = None
137         try:
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)
142             connection.logout()
143             return True
144         except Exception:
145             if connection:
146                 connection.logout()
147             raise
148
149     def set_boot_device(self, task):
150         try:
151             #BMC boot flag valid bit clearing 1f -> all bit set
152             #P 420 of ipmi spec
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
160         #for this.
161         manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=False)