3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
16 from Exceptions import *
17 import notify_messages
18 import BootServerRequest
21 # all output is written to this file
22 BM_NODE_LOG = "/tmp/bm.log"
23 VARS_FILE = "configuration"
25 # the new contents of PATH when the boot manager is running
26 BIN_PATH = ('/usr/local/bin',
33 def read_configuration_file(filename):
34 # read in and store all variables in VARS_FILE into each line
35 # is in the format name=val (any whitespace around the = is
36 # removed. everything after the = to the end of the line is
39 vars_file = file(filename,'r')
41 for line in vars_file:
42 # if its a comment or a whitespace line, ignore
43 if line[:1] == "#" or string.strip(line) == "":
46 parts = string.split(line, "=")
49 raise Exception("Invalid line in vars file: {}".format(line))
51 name = string.strip(parts[0])
52 value = string.strip(parts[1])
53 value = value.replace("'", "") # remove quotes
54 value = value.replace('"', "") # remove quotes
59 raise Exception("Unable to read configuration vars.")
61 # find out which directory we are running it, and set a variable
62 # for that. future steps may need to get files out of the bootmanager
64 current_dir = os.getcwd()
65 vars['BM_SOURCE_DIR'] = current_dir
69 ##############################
72 format = "%H:%M:%S(%Z) "
74 def __init__(self, OutputFilePath=None):
76 self.OutputFile = open(OutputFilePath, "w")
77 self.OutputFilePath = OutputFilePath
79 print("bootmanager log : Unable to open output file {}, continuing"\
80 .format(OutputFilePath))
81 self.OutputFile = None
85 vars = read_configuration_file(VARS_FILE)
91 def LogEntry(self, str, inc_newline = 1, display_screen = 1):
92 now = time.strftime(log.format, time.localtime())
94 self.OutputFile.write(now + str)
96 sys.stdout.write(now + str)
100 sys.stdout.write("\n")
102 self.OutputFile.write("\n")
105 self.OutputFile.flush()
107 def write(self, str):
109 make log behave like a writable file object (for traceback
112 self.LogEntry(str, 0, 1)
114 def print_stack(self):
116 dump current stack in log
118 self.write(traceback.format_exc())
120 # bm log uploading is available back again, as of nodeconfig-5.0-2
121 def Upload(self, extra_file=None):
123 upload the contents of the log to the server
125 if self.OutputFile is not None:
126 self.OutputFile.flush()
128 self.LogEntry("Uploading logs to {}".format(self.VARS['UPLOAD_LOG_SCRIPT']))
130 self.OutputFile.close()
131 self.OutputFile = None
133 hostname = self.VARS['INTERFACE_SETTINGS']['hostname'] + "." + \
134 self.VARS['INTERFACE_SETTINGS']['domainname']
135 bs_request = BootServerRequest.BootServerRequest(self.VARS)
137 # this was working until f10
138 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
139 GetVars = None, PostVars = None,
140 DoSSL = True, DoCertCheck = True,
141 FormData = ["log=@" + self.OutputFilePath,
142 "hostname=" + hostname,
147 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
148 GetVars = None, PostVars = None,
149 DoSSL = True, DoCertCheck = True,
150 FormData = [('log',(pycurl.FORM_FILE, self.OutputFilePath)),
151 ("hostname",hostname),
153 if extra_file is not None:
154 # NOTE: for code-reuse, evoke the bash function 'upload_logs';
155 # by adding --login, bash reads .bash_profile before execution.
156 # Also, never fail, since this is an optional feature.
157 utils.sysexec_noerr("""bash --login -c "upload_logs {}" """.format(extra_file), self)
160 ##############################
163 # file containing initial variables/constants
165 # the set of valid node run states
166 NodeRunStates = {'reinstall' : None,
173 def __init__(self, log, forceState):
174 # override machine's current state from the command line
175 self.forceState = forceState
177 # the main logging point
180 # set to 1 if we can run after initialization
184 # this contains a set of information used and updated by each step
189 # not sure what the current PATH is set to, replace it with what
190 # we know will work with all the boot cds
191 os.environ['PATH'] = string.join(BIN_PATH,":")
197 core boot manager logic.
199 the way errors are handled is as such: if any particular step
200 cannot continue or unexpectibly fails, an exception is thrown.
201 in this case, the boot manager cannot continue running.
203 these step functions can also return a 0/1 depending on whether
204 or not it succeeded. In the case of steps like ConfirmInstallWithUser,
205 a 0 is returned and no exception is thrown if the user chose not
206 to confirm the install. The same goes with the CheckHardwareRequirements.
207 If requriements not met, but tests were succesfull, return 0.
209 for steps that run within the installer, they are expected to either
210 complete succesfully and return 1, or throw an exception.
212 For exact return values and expected operations, see the comments
213 at the top of each of the invididual step functions.
216 def _nodeNotInstalled(message='MSG_NODE_NOT_INSTALLED'):
217 # called by the _xxxState() functions below upon failure
218 self.VARS['RUN_LEVEL'] = 'failboot'
219 notify = getattr(notify_messages, message)
220 self.VARS['STATE_CHANGE_NOTIFY'] = 1
221 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE'] = notify
222 raise BootManagerException, notify
225 # implements the boot logic, which consists of first
226 # double checking that the node was properly installed,
227 # checking whether someone added or changed disks, and
228 # then finally chain boots.
230 # starting the fallback/debug ssh daemon for safety:
231 # if the node install somehow hangs, or if it simply takes ages,
232 # we can still enter and investigate
234 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
238 InstallInit.Run(self.VARS, self.LOG)
239 ret = ValidateNodeInstall.Run(self.VARS, self.LOG)
241 # Thierry - feb. 2013 turning off WriteModprobeConfig for now on lxc
242 # for one thing this won't work at all with f18, as modules.pcimap
243 # has disappeared (Daniel suggested modules.aliases could be used instead)
244 # and second, in any case it's been years now that modprobe.conf was deprecated
245 # so most likely this code has no actual effect
246 if self.VARS['virt'] == 'vs':
247 WriteModprobeConfig.Run(self.VARS, self.LOG)
248 WriteNetworkConfig.Run(self.VARS, self.LOG)
249 CheckForNewDisks.Run(self.VARS, self.LOG)
250 SendHardwareConfigToPLC.Run(self.VARS, self.LOG)
251 ChainBootNode.Run(self.VARS, self.LOG)
253 _nodeNotInstalled('MSG_NODE_FILESYSTEM_CORRUPT')
255 _nodeNotInstalled('MSG_NODE_MOUNT_FAILED')
257 _nodeNotInstalled('MSG_NODE_MISSING_KERNEL')
261 def _reinstallRun(upgrade=False):
263 # starting the fallback/debug ssh daemon for safety:
264 # if the node install somehow hangs, or if it simply takes ages,
265 # we can still enter and investigate
267 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
271 # implements the reinstall logic, which will check whether
272 # the min. hardware requirements are met, install the
273 # software, and upon correct installation will switch too
274 # 'boot' state and chainboot into the production system
275 if not CheckHardwareRequirements.Run(self.VARS, self.LOG):
276 self.VARS['RUN_LEVEL'] = 'failboot'
277 raise BootManagerException, "Hardware requirements not met."
280 InstallInit.Run(self.VARS, self.LOG)
282 InstallPartitionDisks.Run(self.VARS, self.LOG)
283 InstallBootstrapFS.Run(self.VARS, self.LOG)
284 InstallWriteConfig.Run(self.VARS, self.LOG)
285 InstallUninitHardware.Run(self.VARS, self.LOG)
286 self.VARS['BOOT_STATE'] = 'boot'
287 self.VARS['STATE_CHANGE_NOTIFY'] = 1
288 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE'] = \
289 notify_messages.MSG_INSTALL_FINISHED
290 AnsibleHook.Run(self.VARS, self.LOG)
291 UpdateBootStateWithPLC.Run(self.VARS, self.LOG)
295 # implements the new install logic, which will first check
296 # with the user whether it is ok to install on this
297 # machine, switch to 'reinstall' state and then invoke the reinstall
298 # logic. See reinstallState logic comments for further
300 if not ConfirmInstallWithUser.Run(self.VARS, self.LOG):
302 self.VARS['BOOT_STATE'] = 'reinstall'
304 AnsibleHook.Run(self.VARS, self.LOG)
307 def _debugRun(state='failboot'):
308 # implements debug logic, which starts the sshd and just waits around
309 self.VARS['RUN_LEVEL'] = state
310 StartDebug.Run(self.VARS, self.LOG)
311 # fsck/mount fs if present, and ignore return value if it's not.
312 ValidateNodeInstall.Run(self.VARS, self.LOG)
315 # should never happen; log event
316 self.LOG.write("\nInvalid BOOT_STATE = {}\n".format(self.VARS['BOOT_STATE']))
319 # setup state -> function hash table
320 BootManager.NodeRunStates['reinstall'] = lambda : _reinstallRun(upgrade=False)
321 BootManager.NodeRunStates['upgrade'] = lambda : _reinstallRun(upgrade=True)
322 BootManager.NodeRunStates['boot'] = _bootRun
323 BootManager.NodeRunStates['safeboot'] = lambda : _debugRun('safeboot')
324 BootManager.NodeRunStates['disabled'] = lambda : _debugRun('disabled')
328 InitializeBootManager.Run(self.VARS, self.LOG)
329 ReadNodeConfiguration.Run(self.VARS, self.LOG)
330 AuthenticateWithPLC.Run(self.VARS, self.LOG)
331 UpdateLastBootOnce.Run(self.VARS, self.LOG)
332 StartRunlevelAgent.Run(self.VARS, self.LOG)
333 GetAndUpdateNodeDetails.Run(self.VARS, self.LOG)
335 # override machine's current state from the command line
336 if self.forceState is not None:
337 self.VARS['BOOT_STATE'] = self.forceState
338 UpdateBootStateWithPLC.Run(self.VARS, self.LOG)
340 stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'], _badstateRun)
344 except KeyError as e:
345 self.LOG.write("\n\nKeyError while running: {}\n".format(e))
346 self.LOG.print_stack ()
347 except BootManagerException as e:
348 self.LOG.write("\n\nException while running: {}\n".format(e))
349 self.LOG.print_stack ()
350 except BootManagerAuthenticationException as e:
351 self.LOG.write("\n\nFailed to Authenticate Node: {}\n".format(e))
352 self.LOG.print_stack ()
353 # sets /tmp/CANCEL_BOOT flag
354 StartDebug.Run(self.VARS, self.LOG)
355 # Return immediately b/c any other calls to API will fail
358 self.LOG.write("\n\nImplementation Error\n")
359 self.LOG.print_stack ()
364 except BootManagerException, e:
365 self.LOG.write("\n\nException while running: {}\n".format(e))
367 self.LOG.write("\n\nImplementation Error\n")
368 traceback.print_exc(file=self.LOG.OutputFile)
369 traceback.print_exc()
377 utils.prompt_for_breakpoint_mode()
379 # utils.breakpoint ("Entering BootManager::main")
381 # set to 1 if error occurred
384 # all output goes through this class so we can save it and post
385 # the data back to PlanetLab central
386 LOG = log(BM_NODE_LOG)
388 # NOTE: assume CWD is BM's source directory, but never fail
389 utils.sysexec_noerr("./setup_bash_history_scripts.sh", LOG)
391 LOG.LogEntry("BootManager started at: {}"\
392 .format(time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())))
398 if BootManager.NodeRunStates.has_key(fState):
401 LOG.LogEntry("FATAL: cannot force node run state to={}".format(fState))
404 traceback.print_exc(file=LOG.OutputFile)
405 traceback.print_exc()
408 LOG.LogEntry("BootManager finished at: {}"\
409 .format(time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())))
414 bm = BootManager(LOG, forceState)
416 LOG.LogEntry("Unable to initialize BootManager.")
418 LOG.LogEntry("Running version {} of BootManager.".format(bm.VARS['VERSION']))
421 LOG.LogEntry("\nDone!");
423 LOG.LogEntry("\nError occurred!");
426 traceback.print_exc(file=LOG.OutputFile)
427 traceback.print_exc()
429 LOG.LogEntry("BootManager finished at: {}"\
430 .format(time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())))
436 if __name__ ==e "__main__":
437 error = main(sys.argv)