use BootServerRequest to upload logs to the right server
[bootmanager.git] / source / BootManager.py
1 #!/usr/bin/python2 -u
2
3 # ------------------------------------------------------------------------
4 # THIS file used to be named alpina.py, from the node installer. Since then
5 # the installer has been expanded to include all the functions of the boot
6 # manager as well, hence the new name for this file.
7 # ------------------------------------------------------------------------
8
9 # Copyright (c) 2003 Intel Corporation
10 # All rights reserved.
11
12 # Redistribution and use in source and binary forms, with or without
13 # modification, are permitted provided that the following conditions are
14 # met:
15
16 #     * Redistributions of source code must retain the above copyright
17 #       notice, this list of conditions and the following disclaimer.
18
19 #     * Redistributions in binary form must reproduce the above
20 #       copyright notice, this list of conditions and the following
21 #       disclaimer in the documentation and/or other materials provided
22 #       with the distribution.
23
24 #     * Neither the name of the Intel Corporation nor the names of its
25 #       contributors may be used to endorse or promote products derived
26 #       from this software without specific prior written permission.
27
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
32 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
41 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
42 # export regulations applicable in licensee's jurisdiction. Under
43 # CURRENT (May 2000) U.S. export regulations this software is eligible
44 # for export from the U.S. and can be downloaded by or otherwise
45 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
46 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
47 # Afghanistan and any other country to which the U.S. has embargoed
48 # goods and services.
49
50
51 import string
52 import sys, os, traceback
53 from time import gmtime, strftime
54 from gzip import GzipFile
55
56 from steps import *
57 from Exceptions import *
58 import notify_messages
59 import BootServerRequest
60
61
62
63 # all output is written to this file
64 LOG_FILE= "/tmp/bm.log"
65 UPLOAD_LOG_PATH = "/alpina-logs/upload.php"
66
67 # the new contents of PATH when the boot manager is running
68 BIN_PATH= ('/usr/local/bin',
69            '/usr/local/sbin',
70            '/bin',
71            '/sbin',
72            '/usr/bin',
73            '/usr/sbin',
74            '/usr/local/planetlab/bin')
75            
76
77
78 class log:
79
80     def __init__( self, OutputFilePath= None ):
81         if OutputFilePath:
82             try:
83                 self.OutputFilePath= OutputFilePath
84                 self.OutputFile= GzipFile( OutputFilePath, "w", 9 )
85             except:
86                 print( "Unable to open output file for log, continuing" )
87                 self.OutputFile= None
88
89     
90     def LogEntry( self, str, inc_newline= 1, display_screen= 1 ):
91         if self.OutputFile:
92             self.OutputFile.write( str )
93         if display_screen:
94             sys.stdout.write( str )
95             
96         if inc_newline:
97             if display_screen:
98                 sys.stdout.write( "\n" )
99             if self.OutputFile:
100                 self.OutputFile.write( "\n" )
101
102         if self.OutputFile:
103             self.OutputFile.flush()
104
105             
106
107     def write( self, str ):
108         """
109         make log behave like a writable file object (for traceback
110         prints)
111         """
112         self.LogEntry( str, 0, 1 )
113
114
115     
116     def Upload( self ):
117         """
118         upload the contents of the log to the server
119         """
120
121         if self.OutputFile is not None:
122             self.LogEntry( "Uploading logs to %s" % UPLOAD_LOG_PATH )
123             
124             self.OutputFile.close()
125             self.OutputFile= None
126
127             bs_request = BootServerRequest.BootServerRequest()
128             bs_request.MakeRequest(PartialPath = UPLOAD_LOG_PATH,
129                                    GetVars = None, PostVars = None,
130                                    FormData = ["log=@" + self.OutputFilePath],
131                                    DoSSL = True, DoCertCheck = True)
132         
133     
134
135         
136
137
138 class BootManager:
139
140     # file containing initial variables/constants
141     VARS_FILE = "configuration"
142
143     
144     def __init__(self, log):
145         # this contains a set of information used and updated
146         # by each step
147         self.VARS= {}
148
149         # the main logging point
150         self.LOG= log
151
152         # set to 1 if we can run after initialization
153         self.CAN_RUN = 0
154              
155         if not self.ReadBMConf():
156             self.LOG.LogEntry( "Unable to read configuration vars." )
157             return
158
159         # find out which directory we are running it, and set a variable
160         # for that. future steps may need to get files out of the bootmanager
161         # directory
162         current_dir= os.getcwd()
163         self.VARS['BM_SOURCE_DIR']= current_dir
164
165         # not sure what the current PATH is set to, replace it with what
166         # we know will work with all the boot cds
167         os.environ['PATH']= string.join(BIN_PATH,":")
168                    
169         self.CAN_RUN= 1
170
171     def ReadBMConf(self):
172         """
173         read in and store all variables in VARS_FILE into
174         self.VARS
175         
176         each line is in the format name=val (any whitespace around
177         the = is removed. everything after the = to the end of
178         the line is the value
179         """
180         
181         vars_file= file(self.VARS_FILE,'r')
182         for line in vars_file:
183             # if its a comment or a whitespace line, ignore
184             if line[:1] == "#" or string.strip(line) == "":
185                 continue
186
187             parts= string.split(line,"=")
188             if len(parts) != 2:
189                 self.LOG.LogEntry( "Invalid line in vars file: %s" % line )
190                 return 0
191
192             name= string.strip(parts[0])
193             value= string.strip(parts[1])
194
195             self.VARS[name]= value
196
197         return 1
198     
199     def Run(self):
200         """
201         core boot manager logic.
202
203         the way errors are handled is as such: if any particular step
204         cannot continue or unexpectibly fails, an exception is thrown.
205         in this case, the boot manager cannot continue running.
206
207         these step functions can also return a 0/1 depending on whether
208         or not it succeeded. In the case of steps like ConfirmInstallWithUser,
209         a 0 is returned and no exception is thrown if the user chose not
210         to confirm the install. The same goes with the CheckHardwareRequirements.
211         If requriements not met, but tests were succesfull, return 0.
212
213         for steps that run within the installer, they are expected to either
214         complete succesfully and return 1, or throw an execption.
215
216         For exact return values and expected operations, see the comments
217         at the top of each of the invididual step functions.
218         """
219
220         def _nodeNotInstalled():
221             # called by the _xxxState() functions below upon failure
222             self.VARS['BOOT_STATE']= 'dbg'
223             self.VARS['STATE_CHANGE_NOTIFY']= 1
224             self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
225                       notify_messages.MSG_NODE_NOT_INSTALLED
226             UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
227
228         def _rinsRun():
229             # implements the reinstall logic, which will check whether
230             # the min. hardware requirements are met, install the
231             # software, and upon correct installation will switch too
232             # 'boot' state and chainboot into the production system
233             if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
234                 self.VARS['BOOT_STATE']= 'dbg'
235                 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
236                 raise BootManagerException, "Hardware requirements not met."
237
238             self.RunInstaller()
239
240             if ValidateNodeInstall.Run( self.VARS, self.LOG ):
241                 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
242                 ChainBootNode.Run( self.VARS, self.LOG )
243             else:
244                 _nodeNotInstalled()
245
246         def _newRun():
247             # implements the new install logic, which will first check
248             # with the user whether it is ok to install on this
249             # machine, switch to 'rins' state and then invoke the rins
250             # logic.  See rinsState logic comments for further
251             # details.
252             if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
253                 return 0
254             self.VARS['BOOT_STATE']= 'rins'
255             UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
256             _rinsRun()
257
258         def _bootRun():
259             # implements the boot logic, which consists of first
260             # double checking that the node was properly installed,
261             # checking whether someone added or changed disks, and
262             # then finally chain boots.
263
264             if ValidateNodeInstall.Run( self.VARS, self.LOG ):
265                 UpdateNodeConfiguration.Run( self.VARS, self.LOG )
266                 CheckForNewDisks.Run( self.VARS, self.LOG )
267                 SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
268                 ChainBootNode.Run( self.VARS, self.LOG )
269             else:
270                 _nodeNotInstalled()
271
272
273         def _debugRun():
274             # implements debug logic, which just starts the sshd
275             # and just waits around
276             StartDebug.Run( self.VARS, self.LOG )
277
278         def _badRun():
279             # should never happen; log event
280             self.LOG.write( "\nInvalid BOOT_STATE = %s\n" % self.VARS['BOOT_STATE'])
281             self.VARS['BOOT_STATE']= 'dbg'
282             UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
283             StartDebug.Run( self.VARS, self.LOG )            
284
285         # setup state -> function hash table
286         states = {'new':_newRun,
287                   'inst':_newRun,
288                   'rins':_rinsRun,
289                   'boot':_bootRun,
290                   'dbg':_debugRun}
291         try:
292             InitializeBootManager.Run( self.VARS, self.LOG )
293             ReadNodeConfiguration.Run( self.VARS, self.LOG )
294             AuthenticateWithPLC.Run( self.VARS, self.LOG )
295             GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
296
297             stateRun = states.get(self.VARS['BOOT_STATE'],_badRun)
298             stateRun()
299
300         except KeyError, e:
301             self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
302         except BootManagerException, e:
303             self.LOG.write( "\n\nException while running: %s\n" % str(e) )
304         
305         return 1
306             
307
308             
309     def RunInstaller(self):
310         """
311         since the installer can be invoked at more than one place
312         in the boot manager logic, seperate the steps necessary
313         to do it here
314         """
315         
316         InstallInit.Run( self.VARS, self.LOG )                    
317         InstallPartitionDisks.Run( self.VARS, self.LOG )            
318         InstallBootstrapRPM.Run( self.VARS, self.LOG )            
319         InstallWriteConfig.Run( self.VARS, self.LOG )
320         InstallBuildVServer.Run( self.VARS, self.LOG )
321         InstallNodeInit.Run( self.VARS, self.LOG )
322         InstallUninitHardware.Run( self.VARS, self.LOG )
323         
324         self.VARS['BOOT_STATE']= 'boot'
325         self.VARS['STATE_CHANGE_NOTIFY']= 1
326         self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
327                                        notify_messages.MSG_INSTALL_FINISHED
328         UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
329
330         SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
331
332     
333     
334 if __name__ == "__main__":
335
336     # set to 0 if no error occurred
337     error= 1
338     
339     # all output goes through this class so we can save it and post
340     # the data back to PlanetLab central
341     LOG= log( LOG_FILE )
342
343     LOG.LogEntry( "BootManager started at: %s" % \
344                   strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
345
346     try:
347         bm= BootManager(LOG)
348         if bm.CAN_RUN == 0:
349             LOG.LogEntry( "Unable to initialize BootManager." )
350         else:
351             LOG.LogEntry( "Running version %s of BootManager." %
352                           bm.VARS['VERSION'] )
353             success= bm.Run()
354             if success:
355                 LOG.LogEntry( "\nDone!" );
356             else:
357                 LOG.LogEntry( "\nError occurred!" );
358
359     except:
360         traceback.print_exc(file=LOG.OutputFile)
361         traceback.print_exc()
362
363     LOG.LogEntry( "BootManager finished at: %s" % \
364                   strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
365
366     LOG.Upload()
367     
368     sys.exit(error)