Seed code for ironic_virtmedia_driver
[ta/ironic-virtmedia-driver.git] / src / ironic_virtmedia_driver / vendors / nokia / rm18.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 import time
17
18 from ironic.conductor import utils as manager_utils
19 from ironic.common import boot_devices
20 from ironic.common import exception
21 from ironic.drivers.modules import ipmitool
22
23 from .nokia_hw import NokiaIronicVirtMediaHW
24
25 class RM18(NokiaIronicVirtMediaHW):
26     def __init__(self, log):
27         super(RM18, self).__init__(log)
28
29     def get_disk_attachment_status(self, task):
30         # Check NFS Service Status
31         try:
32             out, err = ipmitool.send_raw(task, '0x32 0xd8 0x06 0x01 0x01 0x00')
33             _image_name = str(bytearray.fromhex(out.replace('\n', '').strip()))
34             return 'mounted'
35         except Exception:
36             return 'nfserror'
37
38
39     def _get_virtual_media_device_count(self, task, devicetype):
40         try:
41             _num_inst = 0
42             # Get num of enabled devices
43             if devicetype == 'CD':
44                 _devparam = '0x04'
45                 self.log.debug('Get virtual CD count')
46             elif devicetype == 'FD':
47                 _devparam = '0x05'
48                 self.log.debug('Get virtual FD count')
49             elif devicetype == 'HD':
50                 _devparam = '0x06'
51                 self.log.debug('Get virtual HD count')
52             else:
53                 self.log.warning('Unknown device type "%s"' % devicetype)
54                 return _num_inst
55
56             cmd = '0x32 0xca %s' % _devparam
57             out, err = ipmitool.send_raw(task, cmd)
58             _num_inst = int(out.strip())
59             self.log.debug('Number of enabled %s devices is %d' % (devicetype, _num_inst))
60             return _num_inst
61         except Exception as err:
62             # Drive might not be mounted to start with
63             self.log.debug('Exception when getting number of enabled %s devices. error: %s' % (devicetype, str(err)))
64
65
66     def _set_virtual_media_device_count(self, task, devicetype, devicecount):
67         # Chapter 46.2 page 181
68         if not 0 <= devicecount <= 4:
69             self.log.warning('Number of devices must be in range 0 to 4')
70             return False
71
72         if devicetype == 'CD':
73             _devparam = '0x04'
74             self.log.debug('Setting virtual CD count to %d' % devicecount)
75         elif devicetype == 'HD':
76             _devparam = '0x06'
77             self.log.debug('Setting virtual HD count to %d' % devicecount)
78         else:
79             self.log.warning('_set_virtual_media_device_count: Unknown device type "%s"' % devicetype)
80             return False
81
82         try:
83             cmd = '0x32 0xcb %s 0x%s' % (_devparam, str(devicecount))
84             ipmitool.send_raw(task, cmd)
85
86             _conf_device_num = self._get_virtual_media_device_count(task, devicetype)
87             _tries = 4
88             while _conf_device_num != devicecount and _tries > 0:
89                 self.log.debug('Virtual %s count is %d expecting %d' % (devicetype, _conf_device_num, devicecount))
90                 time.sleep(5)
91                 _conf_device_num = self._get_virtual_media_device_count(task, devicetype)
92                 _tries = _tries -1
93
94         except Exception as err:
95             self.log.warning('Exception when setting virtual media device count, error: %s' % str(err))
96             return False
97         return True
98
99     def _check_virtual_media_started(self, task):
100         service_status = None
101         # check virtmedia service status
102         try:
103             cmd = '0x32 0xca 0x08'
104             out, err = ipmitool.send_raw(task, cmd)
105             service_status = out.strip()
106             self.log.warning('Virtual media service status: %s' % str(service_status))
107         except Exception as err:
108             self.log.warning('Exception when checking virtual media service: %s' % str(err))
109         if service_status == '01':
110             return True
111         return False
112
113     def _start_virtual_media(self, task):
114         # Enable "Remote Media Support" in GUI (p145)
115         try:
116             cmd = '0x32 0xcb 0x08 0x01'
117             self.log.debug('Start virtual media service')
118             ipmitool.send_raw(task, cmd)
119         except Exception as err:
120             self.log.warning('Exception when starting virtual media service: %s' % str(err))
121
122     def _restart_virtual_media_service(self, task):
123         try:
124             cmd = '0x32 0xcb 0x0a 0x01'
125             self.log.debug('Restart virtual media service')
126             ipmitool.send_raw(task, cmd)
127         except Exception as err:
128             self.log.warning('Exception when restarting virtual media service: %s' % str(err))
129
130     def _restart_ris(self, task):
131         try:
132             self.log.debug('Restart RIS')
133             cmd = '0x32 0x9f 0x08 0x0b'
134             ipmitool.send_raw(task, cmd)
135         except Exception as err:
136             self.log.warning('Exception when restarting RIS: %s' % str(err))
137             return False
138         return True
139
140     def _restart_ris_cd(self, task):
141         try:
142             self.log.debug('Restart RIS CD media')
143             cmd = '0x32 0x9f 0x01 0x0b 0x01'
144             ipmitool.send_raw(task, cmd)
145         except Exception as err:
146             self.log.warning('Exception when restarting RIS CD media: %s' % str(err))
147             return False
148         return True
149
150     def _enable_virtual_media(self, task):
151         # Speed up things if it service is already running
152         if self._check_virtual_media_started(task):
153             self.log.debug('Virtual media service already running.')
154             # Service is already started
155             return True
156
157         _max_tries = 6
158         _try = 1
159         self._start_virtual_media(task)
160         # Just enabling the service doe not seem to start it (in all HW)
161         # Resetting it after enabling helps
162         self._restart_virtual_media_service(task)
163         while (not self._check_virtual_media_started(task)):
164             if _try > _max_tries:
165                 self.log.warning('Ensure virtual media service start failed, attempts exceeded.')
166                 return False
167             time.sleep(5)
168             _try = _try + 1
169         return True
170
171     def _set_nfs_server_ip(self, driver_info, task):
172         try:
173             cmd = '0x32 0x9f 0x01 0x02 0x00 %s' % (self.hex_convert(driver_info['provisioning_server'], True, 63))
174             self.log.debug('Virtual media server "%s"' % driver_info['provisioning_server'])
175             ipmitool.send_raw(task, cmd)
176         except Exception as err:
177             self.log.warning('Exception when setting virtual media server: %s' % str(err))
178             raise err
179
180     def _set_share_type(self, task):
181         try:
182             cmd = '0x32 0x9f 0x01 0x05 0x00 0x6e 0x66 0x73 0x00 0x00 0x00'
183             self.log.debug('Virtual media share type to NFS.')
184             ipmitool.send_raw(task, cmd)
185         except Exception as err:
186             self.log.warning('Exception when setting virtual media service type NFS: %s' % str(err))
187             raise err
188
189     def _set_nfs_root_path(self, driver_info, task):
190         try:
191             self.log.debug('Virtual media path to "%s"' % self.remote_share)
192             # set progress bit (hmm. seems to return error if it is already set.. So should check..)
193             # Welp there is no way checking this. As workaround clearing it first ( does not seem to
194             # return error even if alreay cleared).
195             # clear progress bit
196             cmd = '0x32 0x9f 0x01 0x01 0x00 0x00'
197             ipmitool.send_raw(task, cmd)
198
199             # set progress bit
200             cmd = '0x32 0x9f 0x01 0x01 0x00 0x01'
201             ipmitool.send_raw(task, cmd)
202             time.sleep(2)
203             cmd = '0x32 0x9f 0x01 0x01 0x01 %s' % (self.hex_convert(self.remote_share, True, 64))
204             ipmitool.send_raw(task, cmd)
205             time.sleep(2)
206             # clear progress bit
207             cmd = '0x32 0x9f 0x01 0x01 0x00 0x00'
208             ipmitool.send_raw(task, cmd)
209         except Exception as err:
210             self.log.warning('Exception when setting virtual media path: %s' % str(err))
211             return False
212
213     def _set_setup_nfs(self, driver_info, task):
214         try:
215             # Set share type NFS
216             self._set_share_type(task)
217             # NFS server IP
218             self._set_nfs_server_ip(driver_info, task)
219             # Set NFS Mount Root path
220             self._set_nfs_root_path(driver_info, task)
221             return True
222
223         except Exception:
224             return False
225
226     def _toggle_virtual_device(self, enabled, task):
227         # Enable "Mount CD/DVD" in GUI (p144) should cause vmedia restart withing 2 seconds.
228         # Seems "Mount CD/DVD" need to be enabled (or toggled) after config. refresh/vmedia restart
229         # is not enough(?)
230         try:
231             # cmd = '0x32 0xcb 0x00 0x0%s' %(str(int(enabled)))
232             if enabled:
233                 _stat = '01'
234             else:
235                 _stat = '00'
236
237             cmd = '0x32 0xcb 0x00 0x%s' % _stat
238             self.log.debug('Set mount CD/DVD enable status %s' % str(enabled))
239             ipmitool.send_raw(task, cmd)
240
241             _max_tries = 6
242             _try = 1
243             _status = '00'
244             self.log.debug('Ensure CD/DVD enable status is %s' % str(enabled))
245             while (_status != _stat):
246                 if _try > _max_tries:
247                     self.log.warning('Ensure virtual media status failed, attempts exceeded.')
248                     return False
249                 time.sleep(2)
250                 cmd = '0x32 0xca 0x00'
251                 out, err = ipmitool.send_raw(task, cmd)
252                 _status = out.strip()
253                 self.log.debug('CD/DVD enable status is "%s"' % str(_status))
254                 _try = _try + 1
255
256         except Exception as err:
257             self.log.warning('Exception when CD/DVD virtual media new firmware? ignoring... Error: %s' % str(err))
258         return True
259
260     def _mount_virtual_device(self, task):
261         return self._toggle_virtual_device(True, task)
262
263     def _demount_virtual_device(self, task):
264         return self._toggle_virtual_device(False, task)
265
266     def _get_mounted_image_count(self, task):
267         count = 0
268         try:
269             cmd = '0x32 0xd8 0x00 0x01'
270             out, err = ipmitool.send_raw(task, cmd)
271             out = out.strip()
272             data = out[3:5]
273             count = int(data, 16)
274             self.log.debug('Available image count: %d' % count)
275         except Exception as err:
276             self.log.debug('Exception when trying to get the image count: %s' % str(err))
277         return count
278
279     def _set_image_name(self, image_filename, task):
280         try:
281             #cmd = '0x32 0xd7 0x01 0x01 0x01 0x01 %s' % (self.hex_convert(image_filename))
282             cmd = '0x32 0xd7 0x01 0x01 0x01 0x01 %s' % (self.hex_convert(image_filename, True, 64))
283             self.log.debug('Setting virtual media image: %s' % image_filename)
284             ipmitool.send_raw(task, cmd)
285         except Exception as err:
286             self.log.debug('Exception when setting virtual media image: %s' % str(err))
287             return False
288         return True
289
290     def _stop_remote_redirection(self, task):
291         try:
292             # Get num of enabled devices
293             _num_inst = self._get_virtual_media_device_count(task, 'CD')
294             for driveindex in range(0, _num_inst):
295                 cmd = '0x32 0xd7 0x00 0x01 0x01 0x00 %s' % hex(driveindex)
296                 self.log.debug('Stop redirection CD/DVD drive index %d' % driveindex)
297                 out, err = ipmitool.send_raw(task, cmd)
298                 self.log.debug('ipmitool out = %s' % (out))
299         except Exception as err:
300             # Drive might not be mounted to start with
301             self.log.debug('_stop_remote_redirection: Ignoring exception when stopping redirection CD/DVD drive index %d error: %s' % (driveindex, str(err)))
302             pass
303
304     def _clear_ris_configuration(self, task):
305         # Clear RIS configuration
306         try:
307             cmd = '0x32 0x9f 0x01 0x0d'
308             self.log.debug('Clear RIS configuration.')
309             ipmitool.send_raw(task, cmd)
310         except Exception as err:
311             self.log.warning('Exception when clearing RIS NFS configuration: %s' % str(err))
312             return False
313         return True
314
315     def _wait_for_mount_count(self, task):
316         # Poll until we got some images from server
317         _max_tries = 12
318         _try = 1
319         while self._get_mounted_image_count(task) == 0:
320             self.log.debug('Check available images count try %d/%d' % (_try, _max_tries))
321             if _try > _max_tries:
322                 self.log.warning('Available images count 0, attempts exceeded.')
323                 return False
324             time.sleep(10)
325             _try = _try + 1
326         return True
327
328     def attach_virtual_cd(self, image_filename, driver_info, task):
329
330         #Enable virtual media
331         if not self._enable_virtual_media(task):
332             self.log.error("Failed to enable virtual media")
333             return False
334
335         #Enable CD/DVD device
336         if not self._toggle_virtual_device(True, task):
337             self.log.error("Failed to enable virtual device")
338             return False
339
340         #Clear RIS configuration
341         if not self._clear_ris_configuration(task):
342             self.log.error("Failed to clear RIS configuration")
343             return False
344
345         #Setup nfs
346         if not self._set_setup_nfs(driver_info, task):
347             self.log.error("Failed to setup nfs")
348             return False
349
350         # Restart Remote Image CD
351         if not self._restart_ris_cd(task):
352             self.log.error("Failed to restart RIS CD")
353             return False
354
355         #Wait for device to be mounted
356         if not self._wait_for_mount_count(task):
357             self.log.error("Failed when waiting for the device to appear")
358             return False
359
360         # Set Image Name
361         if not self._set_image_name(image_filename, task):
362             self.log.error("Failed to set image name")
363             return False
364
365         return self.check_and_wait_for_cd_mounting(image_filename, task, driver_info)
366
367     def detach_virtual_cd(self, driver_info, task):
368         """Detaches virtual cdrom on the node.
369
370         :param task: an ironic task object
371         """
372         #Enable virtual media
373         if not self._enable_virtual_media(task):
374             self.log.error("detach_virtual_cd: Failed to enable virtual media")
375             return False
376
377         # Restart Remote Image Service
378         if not self._restart_ris(task):
379             self.log.error("Failed to restart RIS")
380             return False
381
382         # Stop redirection
383         self._stop_remote_redirection(task)
384
385         #Clear RIS configuration
386         if not self._clear_ris_configuration(task):
387             self.log.error("detach_virtual_cd: Failed to clear RIS configuration")
388             return False
389
390         #Demount virtual device
391         if not self._demount_virtual_device(task):
392             self.log.error('detach_virtual_cd: Exception when disabling CD/DVD virtual media')
393             return False
394
395         # Reduce the number of virtual devices (both HD and CD default to 4 devices each)
396         if not self._set_virtual_media_device_count(task, 'HD', 0):
397             return False
398         if not self._set_virtual_media_device_count(task, 'CD', 1):
399             return False
400
401         return True
402
403     def set_boot_device(self, task):
404         manager_utils.node_set_boot_device(task, boot_devices.FLOPPY, persistent=True)
405 #        try:
406 #            #Set boot device to virtual remote CD/DVD persistenly
407 #            #0xC0 persisten
408 #            #0x20 remote cdrom
409 #            #P 422 of ipmi spec
410 #            cmd = '0x00 0x08 0x05 0xC0 0x20 0x00 0x00 0x00'
411 #            out, err = ipmitool.send_raw(task, cmd)
412 #            #BMC boot flag valid bit clearing 1f -> all bit set
413 #            #P 420 of ipmi spec
414 #            cmd = '0x00 0x08 0x03 0x1f'
415 #            out, err = ipmitool.send_raw(task, cmd)
416 #            self.log.info('Set the boot device to remote cd')
417 #        except Exception as err:
418 #            self.log.warning('Error when setting boot device to remote cd')
419 #