3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
10 import sys, os, traceback
15 from Exceptions import *
16 import notify_messages
17 import BootServerRequest
19 # all output is written to this file
20 BM_NODE_LOG= "/tmp/bm.log"
21 UPLOAD_LOG_SCRIPT = "/boot/upload-bmlog.php"
23 # the new contents of PATH when the boot manager is running
24 BIN_PATH= ('/usr/local/bin',
31 ##############################
34 format="%H:%M:%S(%Z) "
36 def __init__( self, OutputFilePath= None ):
38 self.OutputFile= open( OutputFilePath, "w")
39 self.OutputFilePath= OutputFilePath
41 print( "bootmanager log : Unable to open output file %r, continuing"%OutputFilePath )
44 def LogEntry( self, str, inc_newline= 1, display_screen= 1 ):
45 now=time.strftime(log.format, time.localtime())
47 self.OutputFile.write( now+str )
49 sys.stdout.write( now+str )
53 sys.stdout.write( "\n" )
55 self.OutputFile.write( "\n" )
58 self.OutputFile.flush()
60 def write( self, str ):
62 make log behave like a writable file object (for traceback
65 self.LogEntry( str, 0, 1 )
67 # bm log uploading is available back again, as of nodeconfig-5.0-2
70 upload the contents of the log to the server
72 if self.OutputFile is not None:
73 self.OutputFile.flush()
75 self.LogEntry( "Uploading logs to %s" % UPLOAD_LOG_SCRIPT )
77 self.OutputFile.close()
80 bs_request = BootServerRequest.BootServerRequest()
81 bs_request.MakeRequest(PartialPath = UPLOAD_LOG_SCRIPT,
82 GetVars = None, PostVars = None,
83 FormData = ["log=@" + self.OutputFilePath],
84 DoSSL = True, DoCertCheck = True)
86 ##############################
89 # file containing initial variables/constants
90 VARS_FILE = "configuration"
92 # the set of valid node run states
93 NodeRunStates = {'reinstall':None,
99 def __init__(self, log, forceState):
100 # override machine's current state from the command line
101 self.forceState = forceState
103 # the main logging point
106 # set to 1 if we can run after initialization
109 # read in and store all variables in VARS_FILE into each line
110 # is in the format name=val (any whitespace around the = is
111 # removed. everything after the = to the end of the line is
114 vars_file= file(self.VARS_FILE,'r')
116 for line in vars_file:
117 # if its a comment or a whitespace line, ignore
118 if line[:1] == "#" or string.strip(line) == "":
121 parts= string.split(line,"=")
123 self.LOG.LogEntry( "Invalid line in vars file: %s" % line )
124 validConfFile = False
127 name= string.strip(parts[0])
128 value= string.strip(parts[1])
132 if not validConfFile:
133 self.LOG.LogEntry( "Unable to read configuration vars." )
136 # find out which directory we are running it, and set a variable
137 # for that. future steps may need to get files out of the bootmanager
139 current_dir= os.getcwd()
140 vars['BM_SOURCE_DIR']= current_dir
142 # not sure what the current PATH is set to, replace it with what
143 # we know will work with all the boot cds
144 os.environ['PATH']= string.join(BIN_PATH,":")
146 # this contains a set of information used and updated by each step
153 core boot manager logic.
155 the way errors are handled is as such: if any particular step
156 cannot continue or unexpectibly fails, an exception is thrown.
157 in this case, the boot manager cannot continue running.
159 these step functions can also return a 0/1 depending on whether
160 or not it succeeded. In the case of steps like ConfirmInstallWithUser,
161 a 0 is returned and no exception is thrown if the user chose not
162 to confirm the install. The same goes with the CheckHardwareRequirements.
163 If requriements not met, but tests were succesfull, return 0.
165 for steps that run within the installer, they are expected to either
166 complete succesfully and return 1, or throw an execption.
168 For exact return values and expected operations, see the comments
169 at the top of each of the invididual step functions.
172 def _nodeNotInstalled(message='MSG_NODE_NOT_INSTALLED'):
173 # called by the _xxxState() functions below upon failure
174 self.VARS['RUN_LEVEL']= 'failboot'
175 notify = getattr(notify_messages, message)
176 self.VARS['STATE_CHANGE_NOTIFY']= 1
177 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= notify
178 raise BootManagerException, notify
181 # implements the boot logic, which consists of first
182 # double checking that the node was properly installed,
183 # checking whether someone added or changed disks, and
184 # then finally chain boots.
186 # starting the fallback/debug ssh daemon for safety:
187 # if the node install somehow hangs, or if it simply takes ages,
188 # we can still enter and investigate
190 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
194 InstallInit.Run( self.VARS, self.LOG )
195 ret = ValidateNodeInstall.Run( self.VARS, self.LOG )
197 WriteModprobeConfig.Run( self.VARS, self.LOG )
198 MakeInitrd.Run( self.VARS, self.LOG )
199 WriteNetworkConfig.Run( self.VARS, self.LOG )
200 CheckForNewDisks.Run( self.VARS, self.LOG )
201 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
202 ChainBootNode.Run( self.VARS, self.LOG )
204 _nodeNotInstalled('MSG_NODE_FILESYSTEM_CORRUPT')
206 _nodeNotInstalled('MSG_NODE_MOUNT_FAILED')
208 _nodeNotInstalled('MSG_NODE_MISSING_KERNEL')
214 # starting the fallback/debug ssh daemon for safety:
215 # if the node install somehow hangs, or if it simply takes ages,
216 # we can still enter and investigate
218 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
222 # implements the reinstall logic, which will check whether
223 # the min. hardware requirements are met, install the
224 # software, and upon correct installation will switch too
225 # 'boot' state and chainboot into the production system
226 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
227 self.VARS['RUN_LEVEL']= 'failboot'
228 raise BootManagerException, "Hardware requirements not met."
231 InstallInit.Run( self.VARS, self.LOG )
232 InstallPartitionDisks.Run( self.VARS, self.LOG )
233 InstallBootstrapFS.Run( self.VARS, self.LOG )
234 InstallWriteConfig.Run( self.VARS, self.LOG )
235 InstallUninitHardware.Run( self.VARS, self.LOG )
236 self.VARS['BOOT_STATE']= 'boot'
237 self.VARS['STATE_CHANGE_NOTIFY']= 1
238 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
239 notify_messages.MSG_INSTALL_FINISHED
240 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
244 # implements the new install logic, which will first check
245 # with the user whether it is ok to install on this
246 # machine, switch to 'reinstall' state and then invoke the reinstall
247 # logic. See reinstallState logic comments for further
249 if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
251 self.VARS['BOOT_STATE']= 'reinstall'
252 UpdateRunLevelWithPLC.Run( self.VARS, self.LOG )
255 def _debugRun(state='failboot'):
256 # implements debug logic, which starts the sshd and just waits around
257 self.VARS['RUN_LEVEL']=state
258 UpdateRunLevelWithPLC.Run( self.VARS, self.LOG )
259 StartDebug.Run( self.VARS, self.LOG )
260 # fsck/mount fs if present, and ignore return value if it's not.
261 ValidateNodeInstall.Run( self.VARS, self.LOG )
264 # should never happen; log event
265 self.LOG.write( "\nInvalid BOOT_STATE = %s\n" % self.VARS['BOOT_STATE'])
268 # setup state -> function hash table
269 BootManager.NodeRunStates['reinstall'] = _reinstallRun
270 BootManager.NodeRunStates['boot'] = _bootRun
271 BootManager.NodeRunStates['safeboot'] = lambda : _debugRun('safeboot')
272 BootManager.NodeRunStates['disabled'] = lambda : _debugRun('disabled')
276 InitializeBootManager.Run( self.VARS, self.LOG )
277 ReadNodeConfiguration.Run( self.VARS, self.LOG )
278 AuthenticateWithPLC.Run( self.VARS, self.LOG )
279 StartRunlevelAgent.Run( self.VARS, self.LOG )
280 GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
282 # override machine's current state from the command line
283 if self.forceState is not None:
284 self.VARS['BOOT_STATE']= self.forceState
285 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
286 UpdateRunLevelWithPLC.Run( self.VARS, self.LOG )
288 stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'],_badstateRun)
293 self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
294 except BootManagerException, e:
295 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
297 self.LOG.write( "\n\nImplementation Error\n")
298 traceback.print_exc(file=self.LOG.OutputFile)
299 traceback.print_exc()
304 except BootManagerException, e:
305 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
307 self.LOG.write( "\n\nImplementation Error\n")
308 traceback.print_exc(file=self.LOG.OutputFile)
309 traceback.print_exc()
317 utils.prompt_for_breakpoint_mode()
319 utils.breakpoint ("Entering BootManager::main")
321 # set to 1 if error occurred
324 # all output goes through this class so we can save it and post
325 # the data back to PlanetLab central
326 LOG= log( BM_NODE_LOG )
328 LOG.LogEntry( "BootManager started at: %s" % \
329 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
335 if BootManager.NodeRunStates.has_key(fState):
338 LOG.LogEntry("FATAL: cannot force node run state to=%s" % fState)
341 traceback.print_exc(file=LOG.OutputFile)
342 traceback.print_exc()
345 LOG.LogEntry( "BootManager finished at: %s" % \
346 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
351 bm= BootManager(LOG,forceState)
353 LOG.LogEntry( "Unable to initialize BootManager." )
355 LOG.LogEntry( "Running version %s of BootManager." % bm.VARS['VERSION'] )
358 LOG.LogEntry( "\nDone!" );
360 LOG.LogEntry( "\nError occurred!" );
363 traceback.print_exc(file=LOG.OutputFile)
364 traceback.print_exc()
366 LOG.LogEntry( "BootManager finished at: %s" % \
367 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
373 if __name__ == "__main__":
374 error = main(sys.argv)