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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 class BMCException(Exception):
25 def __init__(self, host, user, passwd, log_path=None):
30 self._log_path = log_path
32 self._log_path = 'console.log'
34 self._host_name = None
36 def set_host_name(self, host_name):
37 if not self._host_name:
38 self._host_name = host_name
40 def get_host_name(self):
42 return self._host_name
56 logging.info('Reset BMC of %s: %s', self.get_host_name(), self.get_host())
58 self._run_ipmitool_command('bmc reset cold')
60 success = self._wait_for_bmc_reset(180)
62 raise BMCException('BMC reset failed, BMC did not come up')
64 def _set_boot_from_virtual_media(self):
65 raise NotImplementedError
67 def _detach_virtual_media(self):
68 raise NotImplementedError
70 def _get_bmc_nfs_service_status(self):
71 raise NotImplementedError
73 def _wait_for_bmc_responding(self, timeout, expected_to_respond=True):
74 if expected_to_respond:
75 logging.debug('Wait for BMC to start responding')
77 logging.debug('Wait for BMC to stop responding')
79 start_time = int(time.time()*1000)
81 response = (not expected_to_respond)
82 while response != expected_to_respond:
83 rc, _ = self._run_ipmitool_command('bmc info', can_fail=True)
86 if response == expected_to_respond:
89 time_now = int(time.time()*1000)
90 if time_now-start_time > timeout*1000:
91 logging.debug('Wait timed out')
94 logging.debug('Still waiting for BMC')
97 return response == expected_to_respond
99 def _wait_for_bmc_webpage(self, timeout):
100 host = ipaddress.ip_address(unicode(self._host))
101 if host.version == 6:
104 command = 'curl -g --insecure -o /dev/null https://{}/index.html'.format(host)
106 start_time = int(time.time()*1000)
109 p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
110 _, _ = p.communicate()
116 time_now = int(time.time()*1000)
117 if time_now-start_time > timeout*1000:
118 logging.debug('Wait timed out')
121 logging.debug('Still waiting for BMC webpage')
126 def _wait_for_bmc_not_responding(self, timeout):
127 return self._wait_for_bmc_responding(timeout, False)
129 def _wait_for_bmc_reset(self, timeout):
130 logging.debug('Wait for BMC to reset')
133 if not self._wait_for_bmc_not_responding(timeout):
135 msg = 'BMC did not go down as expected'
138 logging.debug('As expected, BMC is not responding')
140 if not self._wait_for_bmc_responding(timeout):
142 msg = 'BMC did not come up as expected'
145 logging.debug('As expected, BMC is responding')
147 if not self._wait_for_bmc_webpage(timeout):
149 msg = 'BMC webpage did not start to respond'
152 logging.debug('As expected, BMC webpage is responding')
156 def setup_boot_options_for_virtual_media(self):
157 logging.debug('Setup boot options')
159 self._disable_boot_flag_timeout()
160 self._set_boot_from_virtual_media()
162 def power(self, power_command):
163 logging.debug('Run host power command (%s) %s', self._host, power_command)
165 return self._run_ipmitool_command('power {}'.format(power_command)).strip()
167 def wait_for_bootup(self):
168 logging.debug('Wait for prompt after booting from hd')
171 self._expect_flag_in_console('localhost login:', timeout=1200)
172 except BMCException as ex:
173 self._send_to_console('\n')
174 self._expect_flag_in_console('localhost login:', timeout=30)
177 logging.debug('Setup SOL for %s', self._host)
179 self._run_ipmitool_command('sol set non-volatile-bit-rate 115.2')
180 self._run_ipmitool_command('sol set volatile-bit-rate 115.2')
182 def boot_from_virtual_media(self):
183 logging.info('Boot from virtual media')
186 self._wait_for_virtual_media_detach_phase()
188 self._detach_virtual_media()
189 self._set_boot_from_hd_no_boot()
191 logging.info('Boot should continue from disk now')
195 self._sol.terminate()
199 def _convert_to_hex(ascii_string, padding=False, length=0):
200 hex_value = ''.join('0x{} '.format(c.encode('hex')) for c in ascii_string).strip()
201 if padding and (len(ascii_string) < length):
202 hex_value += ''.join(' 0x00' for _ in range(len(ascii_string), length))
207 def _convert_to_ascii(hex_string):
208 return ''.join('{}'.format(c.decode('hex')) for c in hex_string)
210 def _execute_ipmitool_command(self, ipmi_command):
211 command = 'ipmitool -I lanplus -H {} -U {} -P {} {}'.format(self._host, self._user, self._passwd, ipmi_command)
213 p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
214 out, _ = p.communicate()
219 def _run_ipmitool_command(self, ipmi_command, can_fail=False, retries=5):
220 logging.debug('Run ipmitool command: %s', ipmi_command)
223 return self._execute_ipmitool_command(ipmi_command)
226 rc, out = self._execute_ipmitool_command(ipmi_command)
231 logging.debug('Retry command')
237 logging.warning('ipmitool failed: %s', out)
238 raise BMCException('ipmitool call failed with rc: {}'.format(rc))
242 def _run_ipmitool_raw_command(self, ipmi_raw_command):
243 logging.debug('Run ipmitool raw command')
245 out = self._run_ipmitool_command('raw {}'.format(ipmi_raw_command))
247 out_bytes = out.replace('\n', '').strip().split(' ')
250 def _disable_boot_flag_timeout(self):
251 logging.debug('Disable boot flag timeout (%s)', self._host)
253 status_code = self._run_ipmitool_raw_command('0x00 0x08 0x03 0x1f')
254 if status_code[0] != '':
255 raise BMCException('Could not disable boot flag timeout (rc={})'.format(status_code[0]))
257 def _open_console(self):
258 logging.debug('Open SOL console (log in %s)', self._log_path)
260 expect_session = pexpect.spawn('ipmitool -I lanplus -H {} -U {} -P {} sol deactivate'.format(self._host, self._user, self._passwd))
261 expect_session.expect(pexpect.EOF)
263 logfile = open(self._log_path, 'ab')
265 expect_session = pexpect.spawn('ipmitool -I lanplus -H {} -U {} -P {} sol activate'.format(self._host, self._user, self._passwd), timeout=None, logfile=logfile)
267 return expect_session
269 def _send_to_console(self, chars):
270 logging.debug('Sending %s to console', chars.replace('\n', '\\n'))
273 self._sol = self._open_console()
275 self._sol.send(chars)
277 def _expect_flag_in_console(self, flags, timeout=600):
278 logging.debug('Expect a flag in console output within %s seconds ("%s")', timeout, flags)
280 time_begin = time.time()
282 remaining_time = timeout
284 while remaining_time > 0:
287 self._sol = self._open_console()
288 except pexpect.TIMEOUT as e:
290 raise BMCException('Could not open console: {}'.format(str(e)))
293 self._sol.expect(flags, timeout=remaining_time)
294 logging.debug('Flag found in log')
296 except pexpect.TIMEOUT as e:
298 raise BMCException('Expected message in console did not occur in time ({})'.format(flags))
299 except pexpect.EOF as e:
300 logging.warning('Got EOF from console')
301 if 'SOL session closed by BMC' in self._sol.before:
302 logging.debug('Found: "SOL session closed by BMC" in console')
303 elapsed_time = time.time()-time_begin
304 remaining_time = timeout-elapsed_time
305 if remaining_time > 0:
306 logging.info('Retry to expect a flag in console, %s seconds remaining', remaining_time)
309 raise BMCException('Expected message in console did not occur in time ({})'.format(flags))
311 def _wait_for_bios_settings_done(self):
312 logging.debug('Wait until BIOS settings are updated')
314 self._expect_flag_in_console('Booting...', timeout=300)
316 def _set_boot_from_hd_no_boot(self):
317 logging.debug('Set boot from hd (%s), no boot', self._host)
319 self._run_ipmitool_command('chassis bootdev disk options=persistent')
321 def _wait_for_virtual_media_detach_phase(self):
322 logging.debug('Wait until virtual media can be detached')
324 self._expect_flag_in_console(['Copying cloud guest image',
325 'Installing OS to HDD',
326 'Extending partition and filesystem size'],
329 def _wait_for_bmc_nfs_service(self, timeout, expected_status):
330 logging.debug('Wait for BMC NFS service')
332 start_time = int(time.time()*1000)
335 while status != expected_status:
336 status = self._get_bmc_nfs_service_status()
338 if status == expected_status or status == 'nfserror':
339 logging.debug('Breaking from wait loop. status = %s', status)
342 time_now = int(time.time()*1000)
343 if time_now-start_time > timeout*1000:
344 logging.debug('Wait timed out')
348 return status == expected_status
350 def _trigger_boot(self):
351 logging.debug('Trigger boot')
353 power_state = self.power('status')
354 logging.debug('State is: %s', power_state)
355 if power_state == 'Chassis Power is off':