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: %s" % 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 %r, continuing"%OutputFilePath )
84 vars = read_configuration_file(VARS_FILE)
87 self.LogEntry( str(e) )
90 def LogEntry( self, str, inc_newline= 1, display_screen= 1 ):
91 now=time.strftime(log.format, time.localtime())
93 self.OutputFile.write( now+str )
95 sys.stdout.write( now+str )
99 sys.stdout.write( "\n" )
101 self.OutputFile.write( "\n" )
104 self.OutputFile.flush()
106 def write( self, str ):
108 make log behave like a writable file object (for traceback
111 self.LogEntry( str, 0, 1 )
113 def print_stack (self):
115 dump current stack in log
117 self.write ( traceback.format_exc() )
119 # bm log uploading is available back again, as of nodeconfig-5.0-2
120 def Upload( self, extra_file=None ):
122 upload the contents of the log to the server
124 if self.OutputFile is not None:
125 self.OutputFile.flush()
127 self.LogEntry( "Uploading logs to %s" % self.VARS['UPLOAD_LOG_SCRIPT'] )
129 self.OutputFile.close()
130 self.OutputFile= None
132 hostname= self.VARS['INTERFACE_SETTINGS']['hostname'] + "." + \
133 self.VARS['INTERFACE_SETTINGS']['domainname']
134 bs_request = BootServerRequest.BootServerRequest(self.VARS)
136 # this was working until f10
137 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
138 GetVars = None, PostVars = None,
139 DoSSL = True, DoCertCheck = True,
140 FormData = ["log=@" + self.OutputFilePath,
141 "hostname=" + hostname,
146 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
147 GetVars = None, PostVars = None,
148 DoSSL = True, DoCertCheck = True,
149 FormData = [('log',(pycurl.FORM_FILE, self.OutputFilePath)),
150 ("hostname",hostname),
152 if extra_file is not None:
153 # NOTE: for code-reuse, evoke the bash function 'upload_logs';
154 # by adding --login, bash reads .bash_profile before execution.
155 # Also, never fail, since this is an optional feature.
156 utils.sysexec_noerr( """bash --login -c "upload_logs %s" """ % extra_file, self)
159 ##############################
162 # file containing initial variables/constants
164 # the set of valid node run states
165 NodeRunStates = {'reinstall':None,
171 def __init__(self, log, forceState):
172 # override machine's current state from the command line
173 self.forceState = forceState
175 # the main logging point
178 # set to 1 if we can run after initialization
182 # this contains a set of information used and updated by each step
187 # not sure what the current PATH is set to, replace it with what
188 # we know will work with all the boot cds
189 os.environ['PATH']= string.join(BIN_PATH,":")
195 core boot manager logic.
197 the way errors are handled is as such: if any particular step
198 cannot continue or unexpectibly fails, an exception is thrown.
199 in this case, the boot manager cannot continue running.
201 these step functions can also return a 0/1 depending on whether
202 or not it succeeded. In the case of steps like ConfirmInstallWithUser,
203 a 0 is returned and no exception is thrown if the user chose not
204 to confirm the install. The same goes with the CheckHardwareRequirements.
205 If requriements not met, but tests were succesfull, return 0.
207 for steps that run within the installer, they are expected to either
208 complete succesfully and return 1, or throw an exception.
210 For exact return values and expected operations, see the comments
211 at the top of each of the invididual step functions.
214 def _nodeNotInstalled(message='MSG_NODE_NOT_INSTALLED'):
215 # called by the _xxxState() functions below upon failure
216 self.VARS['RUN_LEVEL']= 'failboot'
217 notify = getattr(notify_messages, message)
218 self.VARS['STATE_CHANGE_NOTIFY']= 1
219 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= notify
220 raise BootManagerException, notify
223 # implements the boot logic, which consists of first
224 # double checking that the node was properly installed,
225 # checking whether someone added or changed disks, and
226 # then finally chain boots.
228 # starting the fallback/debug ssh daemon for safety:
229 # if the node install somehow hangs, or if it simply takes ages,
230 # we can still enter and investigate
232 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
236 InstallInit.Run( self.VARS, self.LOG )
237 ret = ValidateNodeInstall.Run( self.VARS, self.LOG )
239 # Thierry - feb. 2013 turning off WriteModprobeConfig for now on lxc
240 # for one thing this won't work at all with f18, as modules.pcimap
241 # has disappeared (Daniel suggested modules.aliases could be used instead)
242 # and second, in any case it's been years now that modprobe.conf was deprecated
243 # so most likely this code has no actual effect
244 if self.VARS['virt'] == 'vs':
245 WriteModprobeConfig.Run( self.VARS, self.LOG )
246 WriteNetworkConfig.Run( self.VARS, self.LOG )
247 CheckForNewDisks.Run( self.VARS, self.LOG )
248 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
249 ChainBootNode.Run( self.VARS, self.LOG )
251 _nodeNotInstalled('MSG_NODE_FILESYSTEM_CORRUPT')
253 _nodeNotInstalled('MSG_NODE_MOUNT_FAILED')
255 _nodeNotInstalled('MSG_NODE_MISSING_KERNEL')
261 # starting the fallback/debug ssh daemon for safety:
262 # if the node install somehow hangs, or if it simply takes ages,
263 # we can still enter and investigate
265 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
269 # implements the reinstall logic, which will check whether
270 # the min. hardware requirements are met, install the
271 # software, and upon correct installation will switch too
272 # 'boot' state and chainboot into the production system
273 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
274 self.VARS['RUN_LEVEL']= 'failboot'
275 raise BootManagerException, "Hardware requirements not met."
278 InstallInit.Run( self.VARS, self.LOG )
279 InstallPartitionDisks.Run( self.VARS, self.LOG )
280 InstallBootstrapFS.Run( self.VARS, self.LOG )
281 InstallWriteConfig.Run( self.VARS, self.LOG )
282 InstallUninitHardware.Run( self.VARS, self.LOG )
283 self.VARS['BOOT_STATE']= 'boot'
284 self.VARS['STATE_CHANGE_NOTIFY']= 1
285 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
286 notify_messages.MSG_INSTALL_FINISHED
287 AnsibleHook.Run( self.VARS, self.LOG )
288 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
292 # implements the new install logic, which will first check
293 # with the user whether it is ok to install on this
294 # machine, switch to 'reinstall' state and then invoke the reinstall
295 # logic. See reinstallState logic comments for further
297 if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
299 self.VARS['BOOT_STATE']= 'reinstall'
301 AnsibleHook.Run( self.VARS, self.LOG )
304 def _debugRun(state='failboot'):
305 # implements debug logic, which starts the sshd and just waits around
306 self.VARS['RUN_LEVEL']=state
307 StartDebug.Run( self.VARS, self.LOG )
308 # fsck/mount fs if present, and ignore return value if it's not.
309 ValidateNodeInstall.Run( self.VARS, self.LOG )
312 # should never happen; log event
313 self.LOG.write( "\nInvalid BOOT_STATE = %s\n" % self.VARS['BOOT_STATE'])
316 # setup state -> function hash table
317 BootManager.NodeRunStates['reinstall'] = _reinstallRun
318 BootManager.NodeRunStates['boot'] = _bootRun
319 BootManager.NodeRunStates['safeboot'] = lambda : _debugRun('safeboot')
320 BootManager.NodeRunStates['disabled'] = lambda : _debugRun('disabled')
324 InitializeBootManager.Run( self.VARS, self.LOG )
325 ReadNodeConfiguration.Run( self.VARS, self.LOG )
326 AuthenticateWithPLC.Run( self.VARS, self.LOG )
327 UpdateLastBootOnce.Run( self.VARS, self.LOG )
328 StartRunlevelAgent.Run( self.VARS, self.LOG )
329 GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
331 # override machine's current state from the command line
332 if self.forceState is not None:
333 self.VARS['BOOT_STATE']= self.forceState
334 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
336 stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'],_badstateRun)
341 self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
342 self.LOG.print_stack ()
343 except BootManagerException, e:
344 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
345 self.LOG.print_stack ()
346 except BootManagerAuthenticationException, e:
347 self.LOG.write( "\n\nFailed to Authenticate Node: %s\n" % str(e) )
348 self.LOG.print_stack ()
349 # sets /tmp/CANCEL_BOOT flag
350 StartDebug.Run(self.VARS, self.LOG )
351 # Return immediately b/c any other calls to API will fail
354 self.LOG.write( "\n\nImplementation Error\n")
355 self.LOG.print_stack ()
360 except BootManagerException, e:
361 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
363 self.LOG.write( "\n\nImplementation Error\n")
364 traceback.print_exc(file=self.LOG.OutputFile)
365 traceback.print_exc()
373 utils.prompt_for_breakpoint_mode()
375 # utils.breakpoint ("Entering BootManager::main")
377 # set to 1 if error occurred
380 # all output goes through this class so we can save it and post
381 # the data back to PlanetLab central
382 LOG= log( BM_NODE_LOG )
384 # NOTE: assume CWD is BM's source directory, but never fail
385 utils.sysexec_noerr("./setup_bash_history_scripts.sh", LOG)
387 LOG.LogEntry( "BootManager started at: %s" % \
388 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
394 if BootManager.NodeRunStates.has_key(fState):
397 LOG.LogEntry("FATAL: cannot force node run state to=%s" % fState)
400 traceback.print_exc(file=LOG.OutputFile)
401 traceback.print_exc()
404 LOG.LogEntry( "BootManager finished at: %s" % \
405 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
410 bm= BootManager(LOG,forceState)
412 LOG.LogEntry( "Unable to initialize BootManager." )
414 LOG.LogEntry( "Running version %s of BootManager." % bm.VARS['VERSION'] )
417 LOG.LogEntry( "\nDone!" );
419 LOG.LogEntry( "\nError occurred!" );
422 traceback.print_exc(file=LOG.OutputFile)
423 traceback.print_exc()
425 LOG.LogEntry( "BootManager finished at: %s" % \
426 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
432 if __name__ == "__main__":
433 error = main(sys.argv)