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