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
20 # all output is written to this file
21 BM_NODE_LOG= "/tmp/bm.log"
22 VARS_FILE = "configuration"
24 # the new contents of PATH when the boot manager is running
25 BIN_PATH= ('/usr/local/bin',
32 def read_configuration_file(filename):
33 # read in and store all variables in VARS_FILE into each line
34 # is in the format name=val (any whitespace around the = is
35 # removed. everything after the = to the end of the line is
38 vars_file= file(filename,'r')
40 for line in vars_file:
41 # if its a comment or a whitespace line, ignore
42 if line[:1] == "#" or string.strip(line) == "":
45 parts= string.split(line,"=")
48 raise Exception( "Invalid line in vars file: %s" % line )
50 name= string.strip(parts[0])
51 value= string.strip(parts[1])
52 value= value.replace("'", "") # remove quotes
53 value= value.replace('"', "") # remove quotes
58 raise Exception( "Unable to read configuration vars." )
60 # find out which directory we are running it, and set a variable
61 # for that. future steps may need to get files out of the bootmanager
63 current_dir= os.getcwd()
64 vars['BM_SOURCE_DIR']= current_dir
68 ##############################
71 format="%H:%M:%S(%Z) "
73 def __init__( self, OutputFilePath= None ):
75 self.OutputFile= open( OutputFilePath, "w")
76 self.OutputFilePath= OutputFilePath
78 print( "bootmanager log : Unable to open output file %r, continuing"%OutputFilePath )
83 vars = read_configuration_file(VARS_FILE)
86 self.LogEntry( str(e) )
89 def LogEntry( self, str, inc_newline= 1, display_screen= 1 ):
90 now=time.strftime(log.format, time.localtime())
92 self.OutputFile.write( now+str )
94 sys.stdout.write( now+str )
98 sys.stdout.write( "\n" )
100 self.OutputFile.write( "\n" )
103 self.OutputFile.flush()
105 def write( self, str ):
107 make log behave like a writable file object (for traceback
110 self.LogEntry( str, 0, 1 )
112 # bm log uploading is available back again, as of nodeconfig-5.0-2
113 def Upload( self, extra_file=None ):
115 upload the contents of the log to the server
117 if self.OutputFile is not None:
118 self.OutputFile.flush()
120 self.LogEntry( "Uploading logs to %s" % self.VARS['UPLOAD_LOG_SCRIPT'] )
122 self.OutputFile.close()
123 self.OutputFile= None
125 hostname= self.VARS['INTERFACE_SETTINGS']['hostname'] + "." + \
126 self.VARS['INTERFACE_SETTINGS']['domainname']
127 bs_request = BootServerRequest.BootServerRequest(self.VARS)
129 # this was working until f10
130 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
131 GetVars = None, PostVars = None,
132 DoSSL = True, DoCertCheck = True,
133 FormData = ["log=@" + self.OutputFilePath,
134 "hostname=" + hostname,
139 bs_request.MakeRequest(PartialPath = self.VARS['UPLOAD_LOG_SCRIPT'],
140 GetVars = None, PostVars = None,
141 DoSSL = True, DoCertCheck = True,
142 FormData = [('log',(pycurl.FORM_FILE, self.OutputFilePath)),
143 ("hostname",hostname),
145 if extra_file is not None:
146 # NOTE: for code-reuse, evoke the bash function 'upload_logs';
147 # by adding --login, bash reads .bash_profile before execution.
148 # Also, never fail, since this is an optional feature.
149 utils.sysexec_noerr( """bash --login -c "upload_logs %s" """ % extra_file, self)
152 ##############################
155 # file containing initial variables/constants
157 # the set of valid node run states
158 NodeRunStates = {'reinstall':None,
164 def __init__(self, log, forceState):
165 # override machine's current state from the command line
166 self.forceState = forceState
168 # the main logging point
171 # set to 1 if we can run after initialization
175 # this contains a set of information used and updated by each step
180 # not sure what the current PATH is set to, replace it with what
181 # we know will work with all the boot cds
182 os.environ['PATH']= string.join(BIN_PATH,":")
188 core boot manager logic.
190 the way errors are handled is as such: if any particular step
191 cannot continue or unexpectibly fails, an exception is thrown.
192 in this case, the boot manager cannot continue running.
194 these step functions can also return a 0/1 depending on whether
195 or not it succeeded. In the case of steps like ConfirmInstallWithUser,
196 a 0 is returned and no exception is thrown if the user chose not
197 to confirm the install. The same goes with the CheckHardwareRequirements.
198 If requriements not met, but tests were succesfull, return 0.
200 for steps that run within the installer, they are expected to either
201 complete succesfully and return 1, or throw an exception.
203 For exact return values and expected operations, see the comments
204 at the top of each of the invididual step functions.
207 def _nodeNotInstalled(message='MSG_NODE_NOT_INSTALLED'):
208 # called by the _xxxState() functions below upon failure
209 self.VARS['RUN_LEVEL']= 'failboot'
210 notify = getattr(notify_messages, message)
211 self.VARS['STATE_CHANGE_NOTIFY']= 1
212 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= notify
213 raise BootManagerException, notify
216 # implements the boot logic, which consists of first
217 # double checking that the node was properly installed,
218 # checking whether someone added or changed disks, and
219 # then finally chain boots.
221 # starting the fallback/debug ssh daemon for safety:
222 # if the node install somehow hangs, or if it simply takes ages,
223 # we can still enter and investigate
225 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
229 InstallInit.Run( self.VARS, self.LOG )
230 ret = ValidateNodeInstall.Run( self.VARS, self.LOG )
232 # Thierry - feb. 2013 turning off WriteModprobeConfig for now on lxc
233 # for one thing this won't work at all with f18, as modules.pcimap
234 # has disappeared (Daniel suggested modules.aliases could be used instead)
235 # and second, in any case it's been years now that modprobe.conf was deprecated
236 # so most likely this code has no actual effect
237 if self.VARS['virt'] == 'vs':
238 WriteModprobeConfig.Run( self.VARS, self.LOG )
239 WriteNetworkConfig.Run( self.VARS, self.LOG )
240 CheckForNewDisks.Run( self.VARS, self.LOG )
241 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
242 ChainBootNode.Run( self.VARS, self.LOG )
244 _nodeNotInstalled('MSG_NODE_FILESYSTEM_CORRUPT')
246 _nodeNotInstalled('MSG_NODE_MOUNT_FAILED')
248 _nodeNotInstalled('MSG_NODE_MISSING_KERNEL')
254 # starting the fallback/debug ssh daemon for safety:
255 # if the node install somehow hangs, or if it simply takes ages,
256 # we can still enter and investigate
258 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
262 # implements the reinstall logic, which will check whether
263 # the min. hardware requirements are met, install the
264 # software, and upon correct installation will switch too
265 # 'boot' state and chainboot into the production system
266 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
267 self.VARS['RUN_LEVEL']= 'failboot'
268 raise BootManagerException, "Hardware requirements not met."
271 InstallInit.Run( self.VARS, self.LOG )
272 InstallPartitionDisks.Run( self.VARS, self.LOG )
273 InstallBootstrapFS.Run( self.VARS, self.LOG )
274 InstallWriteConfig.Run( self.VARS, self.LOG )
275 InstallUninitHardware.Run( self.VARS, self.LOG )
276 self.VARS['BOOT_STATE']= 'boot'
277 self.VARS['STATE_CHANGE_NOTIFY']= 1
278 self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
279 notify_messages.MSG_INSTALL_FINISHED
280 AnsibleHook.Run( self.VARS, self.LOG )
281 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
285 # implements the new install logic, which will first check
286 # with the user whether it is ok to install on this
287 # machine, switch to 'reinstall' state and then invoke the reinstall
288 # logic. See reinstallState logic comments for further
290 if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
292 self.VARS['BOOT_STATE']= 'reinstall'
294 AnsibleHook.Run( self.VARS, self.LOG )
297 def _debugRun(state='failboot'):
298 # implements debug logic, which starts the sshd and just waits around
299 self.VARS['RUN_LEVEL']=state
300 StartDebug.Run( self.VARS, self.LOG )
301 # fsck/mount fs if present, and ignore return value if it's not.
302 ValidateNodeInstall.Run( self.VARS, self.LOG )
305 # should never happen; log event
306 self.LOG.write( "\nInvalid BOOT_STATE = %s\n" % self.VARS['BOOT_STATE'])
309 # setup state -> function hash table
310 BootManager.NodeRunStates['reinstall'] = _reinstallRun
311 BootManager.NodeRunStates['boot'] = _bootRun
312 BootManager.NodeRunStates['safeboot'] = lambda : _debugRun('safeboot')
313 BootManager.NodeRunStates['disabled'] = lambda : _debugRun('disabled')
317 InitializeBootManager.Run( self.VARS, self.LOG )
318 ReadNodeConfiguration.Run( self.VARS, self.LOG )
319 AuthenticateWithPLC.Run( self.VARS, self.LOG )
320 UpdateLastBootOnce.Run( self.VARS, self.LOG )
321 StartRunlevelAgent.Run( self.VARS, self.LOG )
322 GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
324 # override machine's current state from the command line
325 if self.forceState is not None:
326 self.VARS['BOOT_STATE']= self.forceState
327 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
329 stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'],_badstateRun)
334 self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
335 except BootManagerException, e:
336 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
337 except BootManagerAuthenticationException, e:
338 self.LOG.write( "\n\nFailed to Authenticate Node: %s\n" % str(e) )
339 # sets /tmp/CANCEL_BOOT flag
340 StartDebug.Run(self.VARS, self.LOG )
341 # Return immediately b/c any other calls to API will fail
344 self.LOG.write( "\n\nImplementation Error\n")
345 traceback.print_exc(file=self.LOG.OutputFile)
346 traceback.print_exc()
351 except BootManagerException, e:
352 self.LOG.write( "\n\nException while running: %s\n" % str(e) )
354 self.LOG.write( "\n\nImplementation Error\n")
355 traceback.print_exc(file=self.LOG.OutputFile)
356 traceback.print_exc()
364 utils.prompt_for_breakpoint_mode()
366 # utils.breakpoint ("Entering BootManager::main")
368 # set to 1 if error occurred
371 # all output goes through this class so we can save it and post
372 # the data back to PlanetLab central
373 LOG= log( BM_NODE_LOG )
375 # NOTE: assume CWD is BM's source directory, but never fail
376 utils.sysexec_noerr("./setup_bash_history_scripts.sh", LOG)
378 LOG.LogEntry( "BootManager started at: %s" % \
379 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
385 if BootManager.NodeRunStates.has_key(fState):
388 LOG.LogEntry("FATAL: cannot force node run state to=%s" % fState)
391 traceback.print_exc(file=LOG.OutputFile)
392 traceback.print_exc()
395 LOG.LogEntry( "BootManager finished at: %s" % \
396 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
401 bm= BootManager(LOG,forceState)
403 LOG.LogEntry( "Unable to initialize BootManager." )
405 LOG.LogEntry( "Running version %s of BootManager." % bm.VARS['VERSION'] )
408 LOG.LogEntry( "\nDone!" );
410 LOG.LogEntry( "\nError occurred!" );
413 traceback.print_exc(file=LOG.OutputFile)
414 traceback.print_exc()
416 LOG.LogEntry( "BootManager finished at: %s" % \
417 time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) )
423 if __name__ == "__main__":
424 error = main(sys.argv)