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 = {'install':None,
101 def __init__(self, log, forceState):
102 # override machine's current state from the command line
103 self.forceState = forceState
105 # the main logging point
108 # set to 1 if we can run after initialization
111 # read in and store all variables in VARS_FILE into each line
112 # is in the format name=val (any whitespace around the = is
113 # removed. everything after the = to the end of the line is
116 vars_file= file(self.VARS_FILE,'r')
118 for line in vars_file:
119 # if its a comment or a whitespace line, ignore
120 if line[:1] == "#" or string.strip(line) == "":
123 parts= string.split(line,"=")
125 self.LOG.LogEntry( "Invalid line in vars file: %s" % line )
126 validConfFile = False
129 name= string.strip(parts[0])
130 value= string.strip(parts[1])
134 if not validConfFile:
135 self.LOG.LogEntry( "Unable to read configuration vars." )
138 # find out which directory we are running it, and set a variable
139 # for that. future steps may need to get files out of the bootmanager
141 current_dir= os.getcwd()
142 vars['BM_SOURCE_DIR']= current_dir
144 # not sure what the current PATH is set to, replace it with what
145 # we know will work with all the boot cds
146 os.environ['PATH']= string.join(BIN_PATH,":")
148 # this contains a set of information used and updated by each step
155 core boot manager logic.
157 the way errors are handled is as such: if any particular step
158 cannot continue or unexpectibly fails, an exception is thrown.
159 in this case, the boot manager cannot continue running.
161 these step functions can also return a 0/1 depending on whether
162 or not it succeeded. In the case of steps like ConfirmInstallWithUser,
163 a 0 is returned and no exception is thrown if the user chose not
164 to confirm the install. The same goes with the CheckHardwareRequirements.
165 If requriements not met, but tests were succesfull, return 0.
167 for steps that run within the installer, they are expected to either
168 complete succesfully and return 1, or throw an execption.
170 For exact return values and expected operations, see the comments
171 at the top of each of the invididual step functions.
174 def _nodeNotInstalled():
175 # called by the _xxxState() functions below upon failure
176 self.VARS['BOOT_STATE']= 'failboot'
177 self.VARS['STATE_CHANGE_NOTIFY']= 1
178 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
179 notify_messages.MSG_NODE_NOT_INSTALLED
180 raise BootManagerException, \
181 notify_messages.MSG_NODE_NOT_INSTALLED
184 # implements the boot logic, which consists of first
185 # double checking that the node was properly installed,
186 # checking whether someone added or changed disks, and
187 # then finally chain boots.
189 # starting the fallback/debug ssh daemon for safety:
190 # if the node install somehow hangs, or if it simply takes ages,
191 # we can still enter and investigate
193 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
197 InstallInit.Run( self.VARS, self.LOG )
198 if ValidateNodeInstall.Run( self.VARS, self.LOG ):
199 WriteModprobeConfig.Run( self.VARS, self.LOG )
200 MakeInitrd.Run( self.VARS, self.LOG )
201 WriteNetworkConfig.Run( self.VARS, self.LOG )
202 CheckForNewDisks.Run( self.VARS, self.LOG )
203 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
204 ChainBootNode.Run( self.VARS, self.LOG )
210 # starting the fallback/debug ssh daemon for safety:
211 # if the node install somehow hangs, or if it simply takes ages,
212 # we can still enter and investigate
214 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
218 # implements the reinstall logic, which will check whether
219 # the min. hardware requirements are met, install the
220 # software, and upon correct installation will switch too
221 # 'boot' state and chainboot into the production system
222 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
223 self.VARS['BOOT_STATE']= 'failboot'
224 raise BootManagerException, "Hardware requirements not met."
227 InstallInit.Run( self.VARS, self.LOG )
228 InstallPartitionDisks.Run( self.VARS, self.LOG )
229 InstallBootstrapFS.Run( self.VARS, self.LOG )
230 InstallWriteConfig.Run( self.VARS, self.LOG )
231 InstallUninitHardware.Run( self.VARS, self.LOG )
232 self.VARS['BOOT_STATE']= 'boot'
233 self.VARS['STATE_CHANGE_NOTIFY']= 1
234 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
235 notify_messages.MSG_INSTALL_FINISHED
236 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
240 # implements the new install logic, which will first check
241 # with the user whether it is ok to install on this
242 # machine, switch to 'reinstall' state and then invoke the reinstall
243 # logic. See reinstallState logic comments for further
245 if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
247 self.VARS['BOOT_STATE']= 'reinstall'
248 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
251 def _debugRun(state='failboot'):
252 # implements debug logic, which starts the sshd and just waits around
253 self.VARS['BOOT_STATE']=state
254 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
255 StartDebug.Run( self.VARS, self.LOG )
256 # fsck/mount fs if present, and ignore return value if it's not.
257 ValidateNodeInstall.Run( self.VARS, self.LOG )
260 # should never happen; log event
261 self.LOG.write( "\nInvalid BOOT_STATE = %s\n" % self.VARS['BOOT_STATE'])
264 # setup state -> function hash table
265 BootManager.NodeRunStates['install'] = _installRun
266 BootManager.NodeRunStates['reinstall'] = _reinstallRun
267 BootManager.NodeRunStates['boot'] = _bootRun
268 BootManager.NodeRunStates['failboot'] = _bootRun # should always try to boot.
269 BootManager.NodeRunStates['safeboot'] = lambda : _debugRun('safeboot')
270 BootManager.NodeRunStates['disabled'] = lambda : _debugRun('disabled')
274 InitializeBootManager.Run( self.VARS, self.LOG )
275 ReadNodeConfiguration.Run( self.VARS, self.LOG )
276 AuthenticateWithPLC.Run( self.VARS, self.LOG )
277 StartRunlevelAgent.Run( self.VARS, self.LOG )
278 GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
280 # override machine's current state from the command line
281 if self.forceState is not None:
282 self.VARS['BOOT_STATE']= self.forceState
283 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
285 stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'],_badstateRun)
290 self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
291 except BootManagerException, e:
292 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
294 self.LOG.write( "\n\nImplementation Error\n")
295 traceback.print_exc(file=self.LOG.OutputFile)
296 traceback.print_exc()
301 except BootManagerException, e:
302 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
304 self.LOG.write( "\n\nImplementation Error\n")
305 traceback.print_exc(file=self.LOG.OutputFile)
306 traceback.print_exc()
314 utils.prompt_for_breakpoint_mode()
316 utils.breakpoint ("Entering BootManager::main")
318 # set to 1 if error occurred
321 # all output goes through this class so we can save it and post
322 # the data back to PlanetLab central
323 LOG= log( BM_NODE_LOG )
325 LOG.LogEntry( "BootManager started at: %s" % \
326 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
332 if BootManager.NodeRunStates.has_key(fState):
335 LOG.LogEntry("FATAL: cannot force node run state to=%s" % fState)
338 traceback.print_exc(file=LOG.OutputFile)
339 traceback.print_exc()
342 LOG.LogEntry( "BootManager finished at: %s" % \
343 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
348 bm= BootManager(LOG,forceState)
350 LOG.LogEntry( "Unable to initialize BootManager." )
352 LOG.LogEntry( "Running version %s of BootManager." % bm.VARS['VERSION'] )
355 LOG.LogEntry( "\nDone!" );
357 LOG.LogEntry( "\nError occurred!" );
360 traceback.print_exc(file=LOG.OutputFile)
361 traceback.print_exc()
363 LOG.LogEntry( "BootManager finished at: %s" % \
364 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
370 if __name__ == "__main__":
371 error = main(sys.argv)