Add support for Ampere Falcon HW
[ta/remote-installer.git] / src / remoteinstaller / installer / bmc_management / falcon.py
1 # Copyright 2019 Nokia
2 # Copyright 2020 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 import logging
17 import time
18 from .bmctools import BMC
19
20 RAW_CHECK_NFS_SERVICE_STATUS = '0x32 0xd8 0x06 0x01 0x01 0x00'
21
22 RAW_GET_VMEDIA_DEVICE_COUNT = '0x32 0xca %s'     # (type)
23 RAW_SET_VMEDIA_DEVICE_COUNT = '0x32 0xcb %s %s'  # (type, count)
24 ( VMEDIA_DEVICE_TYPE_CD,
25   VMEDIA_DEVICE_TYPE_FD,
26   VMEDIA_DEVICE_TYPE_HD ) = ('0x04', '0x05', '0x06')
27
28 RAW_GET_VMEDIA_MOUNT_STATUS = '0x32 0xca 0x00'
29 RAW_SET_VMEDIA_MOUNT_STATUS = '0x32 0xcb 0x00 %s'
30
31 RAW_GET_VMEDIA_STATUS = '0x32 0xca 0x08'
32 RAW_SET_VMEDIA_STATUS = '0x32 0xcb 0x08 %s'
33 RAW_RESTART_VMEDIA =    '0x32 0xcb 0x0a 0x01'
34
35 # Remote Image Service commands
36 RAW_RESTART_RIS_CD =   '0x32 0x9f 0x01 0x0b 0x01'
37 RAW_SET_RIS_NFS =      '0x32 0x9f 0x01 0x05 0x00 0x6e 0x66 0x73 0x00 0x00 0x00'
38 RAW_SET_RIS_NFS_IP =   '0x32 0x9f 0x01 0x02 0x00 %s'
39 RAW_SET_RIS_NFS_PATH = '0x32 0x9f 0x01 0x01 0x01 %s'
40 RAW_SET_RIS_PROGRESS = '0x32 0x9f 0x01 0x01 0x00 %s'
41 RAW_CLEAR_RIS_CONFIG = '0x32 0x9f 0x01 0x0d'
42 RAW_RESTART_RIS =      '0x32 0x9f 0x08 0x0b'
43
44 RAW_GET_MOUNTED_IMG_COUNT = '0x32 0xd8 0x00 0x01'
45 RAW_SET_IMG_NAME =  '0x32 0xd7 0x01 0x01 0x01 0x01 %s'
46 RAW_STOP_REDIRECT = '0x32 0xd7 0x01 0x01 0x01 0x00 %s'
47
48 class BMCException(Exception):
49     pass
50
51 class FALCON(BMC):
52     def __init__(self, host, user, passwd, priv_level='ADMINISTRATOR', log_path=None):
53         super(FALCON, self).__init__(host, user, passwd, priv_level, log_path)
54
55     def _clear_ris_configuration(self):
56         # Clear Remote Image Service configuration
57         try:
58             logging.debug('Clear RIS configuration.')
59             self._run_ipmitool_raw_command(RAW_CLEAR_RIS_CONFIG)
60         except Exception as err:
61             logging.warning('Exception when clearing RIS NFS configuration: %s', str(err))
62             return False
63         return True
64
65     def _check_virtual_media_started(self):
66         # Check virtmedia service status
67         try:
68             out = self._run_ipmitool_raw_command(RAW_GET_VMEDIA_STATUS)
69             service_status = out[0]
70             logging.debug('Virtual media service status: %s', service_status)
71         except Exception as err:
72             logging.warning('Exception when checking virtual media service: %s', str(err))
73
74         return service_status == '01'
75
76     def _start_virtual_media(self):
77         # Enable "Remote Media Support" in GUI (p145)
78         try:
79             logging.debug('Start virtual media service')
80             self._run_ipmitool_raw_command(RAW_SET_VMEDIA_STATUS % '0x01')
81         except Exception as err:
82             logging.warning('Exception when starting virtual media service: %s', str(err))
83
84     def _set_setup_nfs(self, nfs_host, mount_path):
85
86         # Set share type NFS
87         try:
88             logging.debug('Virtual media share type to NFS.')
89             self._run_ipmitool_raw_command(RAW_SET_RIS_NFS)
90         except Exception as err:
91             logging.warning('Exception when setting virtual media service type NFS: %s', str(err))
92             return False
93
94         # NFS server IP
95         try:
96             cmd = RAW_SET_RIS_NFS_IP % (self._convert_to_hex(nfs_host, True, 63))
97             logging.debug('Virtual media server "%s"', nfs_host)
98             self._run_ipmitool_raw_command(cmd)
99         except Exception as err:
100             logging.warning('Exception when setting virtual media server: %s', str(err))
101             return False
102
103         # Set NFS Mount Root path
104         try:
105             logging.debug('Virtual media path to "%s"', mount_path)
106
107             self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x00')
108             time.sleep(2)
109             self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x01')
110             time.sleep(2)
111             self._run_ipmitool_raw_command(RAW_SET_RIS_NFS_PATH % (self._convert_to_hex(mount_path, True, 64)))
112             time.sleep(2)
113             self._run_ipmitool_raw_command(RAW_SET_RIS_PROGRESS % '0x00')
114
115         except Exception as err:
116             logging.warning('Exception when setting virtual media path: %s', str(err))
117             return False
118         return True
119
120     def _enable_virtual_media(self):
121         # Speed up things if it service is already running
122         if self._check_virtual_media_started():
123             logging.debug('Virtual media service already running.')
124             return True
125
126         # Just enabling the service does not seem to start it (in all HW)
127         # Resetting it after enabling helps
128         self._start_virtual_media()
129         self._restart_virtual_media_service()
130
131         tries = 60
132         while tries > 0:
133             if self._check_virtual_media_started():
134                 return True
135             time.sleep(5)
136             tries -= 1
137
138         logging.warning('Ensure virtual media service start failed: attempts exceeded.')
139         return False
140
141     def _get_virtual_media_device_count(self, devicetype):
142         try:
143             _num_inst = 0
144             # Get num of enabled devices
145             if devicetype == 'CD':
146                 _devparam = VMEDIA_DEVICE_TYPE_CD
147                 logging.debug('Get virtual CD count')
148             elif devicetype == 'FD':
149                 _devparam = VMEDIA_DEVICE_TYPE_FD
150                 logging.debug('Get virtual FD count')
151             elif devicetype == 'HD':
152                 _devparam = VMEDIA_DEVICE_TYPE_HD
153                 logging.debug('Get virtual HD count')
154             else:
155                 logging.warning('Unknown device type "%s"', devicetype)
156                 return _num_inst
157
158             cmd = RAW_GET_VMEDIA_DEVICE_COUNT % _devparam
159             out = self._run_ipmitool_raw_command(cmd)
160             _num_inst = int(out[0], 16)
161             logging.debug('Number of enabled %s devices is %d', devicetype, _num_inst)
162             return _num_inst
163         except Exception as err:
164             raise BMCException('Exception when getting number of enabled %s devices. error: %s' % (devicetype, str(err)))
165
166     def _set_virtual_media_device_count(self, devicetype, devicecount):
167         if not 0 <= devicecount <= 4:
168             logging.warning('Number of devices must be in range 0 to 4')
169             return False
170
171         if devicetype == 'CD':
172             _devparam = VMEDIA_DEVICE_TYPE_CD
173             logging.debug('Setting virtual CD count to %d', devicecount)
174         elif devicetype == 'HD':
175             _devparam = VMEDIA_DEVICE_TYPE_HD
176             logging.debug('Setting virtual HD count to %d', devicecount)
177         else:
178             logging.warning('Unknown device type "%s"', devicetype)
179             return False
180
181         try:
182             cmd = RAW_SET_VMEDIA_DEVICE_COUNT % (_devparam, hex(devicecount))
183             self._run_ipmitool_raw_command(cmd)
184
185             _conf_device_num = self._get_virtual_media_device_count(devicetype)
186             _tries = 40
187             while _conf_device_num != devicecount and _tries > 0:
188                 logging.debug('Virtual %s count is %d expecting %d', devicetype, _conf_device_num, devicecount)
189                 time.sleep(5)
190                 _conf_device_num = self._get_virtual_media_device_count(devicetype)
191                 _tries = _tries -1
192
193         except Exception as err:
194             raise BMCException('Exception when setting virtual media device count : %s' % str(err))
195         return True
196
197     def _restart_virtual_media_service(self):
198         try:
199             cmd = RAW_RESTART_VMEDIA
200             logging.debug('Restart virtual media service')
201             self._run_ipmitool_raw_command(cmd)
202         except Exception as err:
203             raise BMCException('Exception when restarting virtual media service: %s' % str(err))
204
205     def _restart_ris(self):
206         try:
207             cmd = RAW_RESTART_RIS
208             logging.debug('Restart RIS')
209             self._run_ipmitool_raw_command(cmd)
210         except Exception as err:
211             raise BMCException('Exception when restarting RIS: %s' % str(err))
212
213         return True
214
215     def _restart_ris_cd(self):
216         try:
217             cmd = RAW_RESTART_RIS_CD
218             logging.debug('Restart RIS CD media')
219             self._run_ipmitool_raw_command(cmd)
220         except Exception as err:
221             raise BMCException('Exception when restarting RIS CD media: %s' % str(err))
222
223         return True
224
225     def _check_vmedia_mount_state(self, enabled):
226         expected_state = 'enabled' if enabled else 'disabled'
227         logging.debug('Check if CD/DVD device is %s', expected_state)
228
229         tries = 10
230         while tries > 0:
231             try:
232                 out = self._run_ipmitool_raw_command(RAW_GET_VMEDIA_MOUNT_STATUS)
233                 status = out[0]
234                 logging.debug('Virtual media mount status: %s', status)
235             except Exception as err:
236                 status = None
237                 logging.warning('Exception when checking VMedia mount status: %s', str(err))
238
239             matched_state = (status == '01') if enabled else (status == '00')
240             if matched_state:
241                 # Virtual media mount found in expected state
242                 return True
243
244             tries -= 1
245             time.sleep(6)
246
247         logging.warning('Failed: CD/DVD mount is not %s (attempts exceeded).'
248                         'Ignoring and trying to continue.',
249                         expected_state)
250         return False
251
252     def _toggle_virtual_device(self, enabled):
253         state_raw = '0x01' if enabled else '0x00'
254         state_str = 'enable' if enabled else 'disable'
255
256         logging.debug('Try to %s VMedia mount.', state_str)
257         try:
258             self._run_ipmitool_raw_command(RAW_SET_VMEDIA_MOUNT_STATUS % state_raw)
259             time.sleep(1)
260             return self._check_vmedia_mount_state(enabled)
261         except Exception as err:
262             logging.warning('Exception when tying to %s VMedia mount: %s. Ignoring... ',
263                             state_str, str(err))
264         return True
265
266     def _mount_virtual_device(self):
267         return self._toggle_virtual_device(True)
268
269     def _demount_virtual_device(self):
270         return self._toggle_virtual_device(False)
271
272     def _get_mounted_image_count(self):
273         count = 0
274         try:
275             out = self._run_ipmitool_raw_command(RAW_GET_MOUNTED_IMG_COUNT)
276             count = int(out[0], 16)
277             logging.warning('Available image count: %d', count)
278         except Exception as err:
279             logging.warning('Exception when trying to get the image count: %s', str(err))
280         return count
281
282     def _wait_for_mount_count(self):
283         # Poll until we got some images from server
284         tries = 12
285         while tries > 0:
286             if self._get_mounted_image_count() > 0:
287                 return True
288             tries -= 1
289             logging.debug('Check available images count tries left: %d', tries)
290             time.sleep(10)
291
292         logging.warning('Available images count 0, attempts exceeded.')
293         return False
294
295     def _set_image_name(self, image_filename):
296         try:
297             logging.debug('Setting virtual media image: %s', image_filename)
298             self._run_ipmitool_raw_command(RAW_SET_IMG_NAME % self._convert_to_hex(image_filename, True, 64))
299         except Exception as err:
300             logging.debug('Exception when setting virtual media image: %s', str(err))
301             return False
302         return True
303
304     def _get_bmc_nfs_service_status(self):
305         try:
306             out = self._run_ipmitool_raw_command(RAW_CHECK_NFS_SERVICE_STATUS)
307             _image_name = str(bytearray.fromhex(''.join(out)))
308             logging.debug('Found mounted image: %s', _image_name)
309             return 'mounted'
310         except Exception:
311             return 'nfserror'
312
313     def _stop_remote_redirection(self):
314         _num_inst = self._get_virtual_media_device_count('CD')
315         for driveindex in range(0, _num_inst):
316             cmd = RAW_STOP_REDIRECT % hex(driveindex)
317             logging.debug('Stop redirection CD/DVD drive index %d', driveindex)
318             try:
319                 out = self._run_ipmitool_raw_command(cmd)
320                 logging.debug('ipmitool out = "%s"', out)
321             except Exception as err:
322                 # Drive might not be mounted to start with
323                 logging.debug('Ignoring exception when stopping redirection CD/DVD drive index %d error: %s',
324                               driveindex, str(err))
325
326     def _set_boot_from_virtual_media(self):
327         logging.debug('Set boot from cd (%s), and boot after that', self._host)
328         try:
329             self._run_ipmitool_command('chassis bootdev floppy options=persistent')
330         except Exception as err:
331             raise BMCException('Set Boot to CD failed: %s' % str(err))
332
333     def _detach_virtual_media(self):
334         logging.debug('Detach virtual media')
335
336         #Enable virtual media
337         if not self._enable_virtual_media():
338             raise BMCException("detach_virtual_cd: Failed to enable virtual media")
339
340         # Restart Remote Image Service
341         if not self._restart_ris():
342             raise BMCException("Failed to restart RIS")
343
344         # Stop redirection
345         self._stop_remote_redirection()
346
347         #Clear RIS configuration
348         if not self._clear_ris_configuration():
349             raise BMCException("detach_virtual_cd: Failed to clear RIS configuration")
350
351         #Demount virtual device
352         if not self._demount_virtual_device():
353             raise BMCException('detach_virtual_cd: Exception when disabling CD/DVD virtual media')
354
355         # Reduce the number of virtual devices (both HD and CD default to 4 devices each)
356         if not self._set_virtual_media_device_count('HD', 0):
357             BMCException('Failed to set virtual media device count for HD')
358         if not self._set_virtual_media_device_count('CD', 1):
359             BMCException('Failed to set virtual media device count for CD')
360
361     def attach_virtual_cd(self, nfs_host, nfs_mount, boot_iso_filename):
362         # Detach first
363         self._detach_virtual_media()
364
365         logging.debug('Attach virtual media')
366
367         #Enable virtual media
368         if not self._enable_virtual_media():
369             raise BMCException("Failed to enable virtual media")
370
371         #Enable CD/DVD device
372         if not self._toggle_virtual_device(True):
373             raise BMCException("Failed to enable virtual device")
374
375         #Clear RIS configuration
376         if not self._clear_ris_configuration():
377             raise BMCException("Failed to clear RIS configuration")
378
379         #Setup nfs
380         if not self._set_setup_nfs(nfs_host, nfs_mount):
381             raise BMCException("Failed to setup nfs")
382
383         # Restart Remote Image CD
384         if not self._restart_ris_cd():
385             raise BMCException("Failed to restart RIS CD")
386
387         #Wait for device to be mounted
388         if not self._wait_for_mount_count():
389             raise BMCException("Failed when waiting for the device to appear")
390
391         # Set Image Name
392         time.sleep(5)
393         if not self._set_image_name(boot_iso_filename):
394             raise BMCException("Failed to set image name")
395
396         success = self._wait_for_bmc_nfs_service(90, 'mounted')
397         if success:
398             return True
399         else:
400             raise BMCException('NFS service setup failed')