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