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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
22 class BMCException(Exception):
26 def __init__(self, host, user, passwd, priv_level='ADMINISTRATOR', log_path=None):
30 self._priv_level = priv_level
32 self._log_path = log_path
34 self._log_path = 'console.log'
36 self._host_name = None
38 def set_host_name(self, host_name):
39 if not self._host_name:
40 self._host_name = host_name
42 def get_host_name(self):
44 return self._host_name
57 def get_priv_level(self):
58 return self._priv_level
61 logging.info('Reset BMC of %s: %s', self.get_host_name(), self.get_host())
63 self._run_ipmitool_command('bmc reset cold')
65 success = self._wait_for_bmc_reset(180)
67 raise BMCException('BMC reset failed, BMC did not come up')
69 def _set_boot_from_virtual_media(self):
70 raise NotImplementedError
72 def _detach_virtual_media(self):
73 raise NotImplementedError
75 def _get_bmc_nfs_service_status(self):
76 raise NotImplementedError
78 def _wait_for_bmc_responding(self, timeout, expected_to_respond=True):
79 if expected_to_respond:
80 logging.debug('Wait for BMC to start responding')
82 logging.debug('Wait for BMC to stop responding')
84 start_time = int(time.time()*1000)
86 response = (not expected_to_respond)
87 while response != expected_to_respond:
88 rc, _ = self._run_ipmitool_command('bmc info', can_fail=True)
91 if response == expected_to_respond:
94 time_now = int(time.time()*1000)
95 if time_now-start_time > timeout*1000:
96 logging.debug('Wait timed out')
99 logging.debug('Still waiting for BMC')
102 return response == expected_to_respond
104 def _wait_for_bmc_webpage(self, timeout):
105 host = ipaddress.ip_address(unicode(self._host))
106 if host.version == 6:
109 command = 'curl -g --insecure -o /dev/null https://{}/index.html'.format(host)
111 start_time = int(time.time()*1000)
114 p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
115 _, _ = p.communicate()
121 time_now = int(time.time()*1000)
122 if time_now-start_time > timeout*1000:
123 logging.debug('Wait timed out')
126 logging.debug('Still waiting for BMC webpage')
131 def _wait_for_bmc_not_responding(self, timeout):
132 return self._wait_for_bmc_responding(timeout, False)
134 def _wait_for_bmc_reset(self, timeout):
135 logging.debug('Wait for BMC to reset')
138 if not self._wait_for_bmc_not_responding(timeout):
140 msg = 'BMC did not go down as expected'
143 logging.debug('As expected, BMC is not responding')
145 if not self._wait_for_bmc_responding(timeout):
147 msg = 'BMC did not come up as expected'
150 logging.debug('As expected, BMC is responding')
152 if not self._wait_for_bmc_webpage(timeout):
154 msg = 'BMC webpage did not start to respond'
157 logging.debug('As expected, BMC webpage is responding')
161 def setup_boot_options_for_virtual_media(self):
162 logging.debug('Setup boot options')
164 self._disable_boot_flag_timeout()
165 self._set_boot_from_virtual_media()
167 def power(self, power_command):
168 logging.debug('Run host power command (%s) %s', self._host, power_command)
170 return self._run_ipmitool_command('power {}'.format(power_command)).strip()
172 def wait_for_bootup(self):
173 logging.debug('Wait for prompt after booting from hd')
176 self._expect_flag_in_console('localhost login:', timeout=1200)
177 except BMCException as ex:
178 self._send_to_console('\n')
179 self._expect_flag_in_console('localhost login:', timeout=30)
182 logging.debug('Setup SOL for %s', self._host)
184 self._run_ipmitool_command('sol set non-volatile-bit-rate 115.2')
185 self._run_ipmitool_command('sol set volatile-bit-rate 115.2')
187 def boot_from_virtual_media(self):
188 logging.info('Boot from virtual media')
191 self._wait_for_virtual_media_detach_phase()
193 self._detach_virtual_media()
194 self._set_boot_from_hd_no_boot()
196 logging.info('Boot should continue from disk now')
200 self._sol.terminate()
204 def _convert_to_hex(ascii_string, padding=False, length=0):
206 ascii_string = ascii_string.ljust(length, '\0')
207 return ' '.join('0x{}'.format(c.encode('hex')) for c in ascii_string)
210 def _convert_to_ascii(hex_string):
211 return hex_string.replace('0x','').replace(' ','').decode('hex')
213 def _execute_ipmitool_command(self, ipmi_command):
214 command = 'ipmitool -I lanplus -H {} -U {} -P {} -L {} {}'.format(self._host, self._user, self._passwd, self._priv_level, ipmi_command)
216 p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
217 out, _ = p.communicate()
222 def _run_ipmitool_command(self, ipmi_command, can_fail=False, retries=5):
223 logging.debug('Run ipmitool command: %s', ipmi_command)
226 return self._execute_ipmitool_command(ipmi_command)
229 rc, out = self._execute_ipmitool_command(ipmi_command)
234 logging.debug('Retry command')
240 logging.warning('ipmitool failed: %s', out)
241 raise BMCException('ipmitool call failed with rc: {}'.format(rc))
245 def _run_ipmitool_raw_command(self, ipmi_raw_command):
246 logging.debug('Run ipmitool raw command')
248 out = self._run_ipmitool_command('raw {}'.format(ipmi_raw_command))
250 out_bytes = out.replace('\n', '').strip().split(' ')
253 def _disable_boot_flag_timeout(self):
254 logging.debug('Disable boot flag timeout (%s)', self._host)
256 status_code = self._run_ipmitool_raw_command('0x00 0x08 0x03 0x1f')
257 if status_code[0] != '':
258 raise BMCException('Could not disable boot flag timeout (rc={})'.format(status_code[0]))
260 def _open_console(self):
261 logging.debug('Open SOL console (log in %s)', self._log_path)
263 expect_session = pexpect.spawn('ipmitool -I lanplus -H {} -U {} -P {} -L {} sol deactivate'.format(self._host, self._user, self._passwd, self._priv_level))
264 expect_session.expect(pexpect.EOF)
266 logfile = open(self._log_path, 'ab')
268 expect_session = pexpect.spawn('ipmitool -I lanplus -H {} -U {} -P {} -L {} sol activate'.format(self._host, self._user, self._passwd, self._priv_level), timeout=None, logfile=logfile)
270 return expect_session
272 def _send_to_console(self, chars):
273 logging.debug('Sending %s to console', chars.replace('\n', '\\n'))
276 self._sol = self._open_console()
278 self._sol.send(chars)
280 def _expect_flag_in_console(self, flags, timeout=600):
281 logging.debug('Expect a flag in console output within %s seconds ("%s")', timeout, flags)
283 time_begin = time.time()
285 remaining_time = timeout
287 while remaining_time > 0:
290 self._sol = self._open_console()
291 except pexpect.TIMEOUT as e:
293 raise BMCException('Could not open console: {}'.format(str(e)))
296 self._sol.expect(flags, timeout=remaining_time)
297 logging.debug('Flag found in log')
299 except pexpect.TIMEOUT as e:
301 raise BMCException('Expected message in console did not occur in time ({})'.format(flags))
302 except pexpect.EOF as e:
303 logging.warning('Got EOF from console')
304 if 'SOL session closed by BMC' in self._sol.before:
305 logging.debug('Found: "SOL session closed by BMC" in console')
306 elapsed_time = time.time()-time_begin
307 remaining_time = timeout-elapsed_time
308 if remaining_time > 0:
309 logging.info('Retry to expect a flag in console, %s seconds remaining', remaining_time)
312 raise BMCException('Expected message in console did not occur in time ({})'.format(flags))
314 def _wait_for_bios_settings_done(self):
315 logging.debug('Wait until BIOS settings are updated')
317 self._expect_flag_in_console('Booting...', timeout=300)
319 def _set_boot_from_hd_no_boot(self):
320 logging.debug('Set boot from hd (%s), no boot', self._host)
322 self._run_ipmitool_command('chassis bootdev disk options=persistent')
324 def _wait_for_virtual_media_detach_phase(self):
325 logging.debug('Wait until virtual media can be detached')
327 self._expect_flag_in_console(['Copying cloud guest image',
328 'Installing OS to HDD',
329 'Extending partition and filesystem size'],
332 def _wait_for_bmc_nfs_service(self, timeout, expected_status):
333 logging.debug('Wait for BMC NFS service')
335 start_time = int(time.time()*1000)
338 while status != expected_status:
339 status = self._get_bmc_nfs_service_status()
341 if status == expected_status or status == 'nfserror':
342 logging.debug('Breaking from wait loop. status = %s', status)
345 time_now = int(time.time()*1000)
346 if time_now-start_time > timeout*1000:
347 logging.debug('Wait timed out')
351 return status == expected_status
353 def _trigger_boot(self):
354 logging.debug('Trigger boot')
356 power_state = self.power('status')
357 logging.debug('State is: %s', power_state)
358 if power_state == 'Chassis Power is off':