check in all bootmanager sources
[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
60
61
62 # all output is written to this file
63 LOG_FILE= "/tmp/bm.log"
64 CURL_PATH= "curl"
65 UPLOAD_LOG_URL = "http://boot.planet-lab.org/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         # for upload
90         os.system( "ifconfig eth0 > /tmp/ifconfig" )
91
92     
93     def LogEntry( self, str, inc_newline= 1, display_screen= 1 ):
94         if self.OutputFile:
95             self.OutputFile.write( str )
96         if display_screen:
97             sys.stdout.write( str )
98             
99         if inc_newline:
100             if display_screen:
101                 sys.stdout.write( "\n" )
102             if self.OutputFile:
103                 self.OutputFile.write( "\n" )
104
105         if self.OutputFile:
106             self.OutputFile.flush()
107
108             
109
110     def write( self, str ):
111         """
112         make log behave like a writable file object (for traceback
113         prints)
114         """
115         self.LogEntry( str, 0, 1 )
116
117
118     
119     def Upload( self ):
120         """
121         upload the contents of the log to the server
122         """
123     
124         self.LogEntry( "Uploading logs to %s" % UPLOAD_LOG_URL )
125         
126         self.OutputFile.close()
127         self.OutputFile= None
128                 
129         curl_cmd= "%s -s --connect-timeout 60 --max-time 600 " \
130                   "--form log=@%s --form ifconfig=\</tmp/ifconfig %s" % \
131                   (CURL_PATH, self.OutputFilePath, UPLOAD_LOG_URL)
132         os.system( curl_cmd )
133         
134     
135
136         
137
138
139 class BootManager:
140
141     # file containing initial variables/constants
142     VARS_FILE = "configuration"
143
144     
145     def __init__(self, log):
146         # this contains a set of information used and updated
147         # by each step
148         self.VARS= {}
149
150         # the main logging point
151         self.LOG= log
152
153         # set to 1 if we can run after initialization
154         self.CAN_RUN = 0
155              
156         if not self.ReadBMConf():
157             self.LOG.LogEntry( "Unable to read configuration vars." )
158             return
159
160         # find out which directory we are running it, and set a variable
161         # for that. future steps may need to get files out of the bootmanager
162         # directory
163         current_dir= os.getcwd()
164         self.VARS['BM_SOURCE_DIR']= current_dir
165
166         # not sure what the current PATH is set to, replace it with what
167         # we know will work with all the boot cds
168         os.environ['PATH']= string.join(BIN_PATH,":")
169                    
170         self.CAN_RUN= 1
171         
172
173
174
175     def ReadBMConf(self):
176         """
177         read in and store all variables in VARS_FILE into
178         self.VARS
179         
180         each line is in the format name=val (any whitespace around
181         the = is removed. everything after the = to the end of
182         the line is the value
183         """
184         
185         vars_file= file(self.VARS_FILE,'r')
186         for line in vars_file:
187             # if its a comment or a whitespace line, ignore
188             if line[:1] == "#" or string.strip(line) == "":
189                 continue
190
191             parts= string.split(line,"=")
192             if len(parts) != 2:
193                 self.LOG.LogEntry( "Invalid line in vars file: %s" % line )
194                 return 0
195
196             name= string.strip(parts[0])
197             value= string.strip(parts[1])
198
199             self.VARS[name]= value
200
201         return 1
202     
203
204     def Run(self):
205         """
206         core boot manager logic.
207
208         the way errors are handled is as such: if any particular step
209         cannot continue or unexpectibly fails, an exception is thrown.
210         in this case, the boot manager cannot continue running.
211
212         these step functions can also return a 0/1 depending on whether
213         or not it succeeded. In the case of steps like ConfirmInstallWithUser,
214         a 0 is returned and no exception is thrown if the user chose not
215         to confirm the install. The same goes with the CheckHardwareRequirements.
216         If requriements not met, but tests were succesfull, return 0.
217
218         for steps that run within the installer, they are expected to either
219         complete succesfully and return 1, or throw an execption.
220
221         For exact return values and expected operations, see the comments
222         at the top of each of the invididual step functions.
223         """
224         
225         try:
226             InitializeBootManager.Run( self.VARS, self.LOG )
227             ReadNodeConfiguration.Run( self.VARS, self.LOG )
228             AuthenticateWithPLC.Run( self.VARS, self.LOG )
229             GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
230             
231             if self.VARS['BOOT_STATE'] == 'new' or \
232                    self.VARS['BOOT_STATE'] == 'inst':
233                 if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
234                     return 0
235                 
236                 self.VARS['BOOT_STATE']= 'rins'
237                 UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
238             
239                 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
240                     self.VARS['BOOT_STATE']= 'dbg'
241                     UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
242                     raise BootManagerException, "Hardware requirements not met."
243
244                 self.RunInstaller()
245
246                 if ValidateNodeInstall.Run( self.VARS, self.LOG ):
247                     SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
248                     ChainBootNode.Run( self.VARS, self.LOG )
249                 else:
250                     self.VARS['BOOT_STATE']= 'dbg'
251                     self.VARS['STATE_CHANGE_NOTIFY']= 1
252                     self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
253                               notify_messages.MSG_NODE_NOT_INSTALLED
254                     UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
255                     
256
257             elif self.VARS['BOOT_STATE'] == 'rins':
258                 if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
259                     self.VARS['BOOT_STATE']= 'dbg'
260                     UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
261                     raise BootManagerException, "Hardware requirements not met."
262                 
263                 self.RunInstaller()
264
265                 if ValidateNodeInstall.Run( self.VARS, self.LOG ):
266                     SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
267                     ChainBootNode.Run( self.VARS, self.LOG )
268                 else:
269                     self.VARS['BOOT_STATE']= 'dbg'
270                     self.VARS['STATE_CHANGE_NOTIFY']= 1
271                     self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
272                               notify_messages.MSG_NODE_NOT_INSTALLED
273                     UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
274
275             elif self.VARS['BOOT_STATE'] == 'boot':
276                 if ValidateNodeInstall.Run( self.VARS, self.LOG ):
277                     UpdateNodeConfiguration.Run( self.VARS, self.LOG )
278                     CheckForNewDisks.Run( self.VARS, self.LOG )
279                     SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
280                     ChainBootNode.Run( self.VARS, self.LOG )
281                 else:
282                     self.VARS['BOOT_STATE']= 'dbg'
283                     self.VARS['STATE_CHANGE_NOTIFY']= 1
284                     self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
285                               notify_messages.MSG_NODE_NOT_INSTALLED
286                     UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
287                     
288             elif self.VARS['BOOT_STATE'] == 'dbg':
289                 StartDebug.Run( self.VARS, self.LOG )
290
291         except KeyError, e:
292             self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
293         except BootManagerException, e:
294             self.LOG.write( "\n\nException while running: %s\n" % str(e) )
295         
296         return 1
297             
298
299             
300     def RunInstaller(self):
301         """
302         since the installer can be invoked at more than one place
303         in the boot manager logic, seperate the steps necessary
304         to do it here
305         """
306         
307         InstallInit.Run( self.VARS, self.LOG )                    
308         InstallPartitionDisks.Run( self.VARS, self.LOG )            
309         InstallBootstrapRPM.Run( self.VARS, self.LOG )            
310         InstallBase.Run( self.VARS, self.LOG )            
311         InstallWriteConfig.Run( self.VARS, self.LOG )
312         InstallBuildVServer.Run( self.VARS, self.LOG )
313         InstallNodeInit.Run( self.VARS, self.LOG )
314         InstallUninitHardware.Run( self.VARS, self.LOG )
315         
316         self.VARS['BOOT_STATE']= 'boot'
317         self.VARS['STATE_CHANGE_NOTIFY']= 1
318         self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
319                                        notify_messages.MSG_INSTALL_FINISHED
320         UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
321
322         SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
323
324     
325     
326 if __name__ == "__main__":
327
328     # set to 0 if no error occurred
329     error= 1
330     
331     # all output goes through this class so we can save it and post
332     # the data back to PlanetLab central
333     LOG= log( LOG_FILE )
334
335     LOG.LogEntry( "BootManager started at: %s" % \
336                   strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
337
338     try:
339         bm= BootManager(LOG)
340         if bm.CAN_RUN == 0:
341             LOG.LogEntry( "Unable to initialize BootManager." )
342         else:
343             LOG.LogEntry( "Running version %s of BootManager." %
344                           bm.VARS['VERSION'] )
345             success= bm.Run()
346             if success:
347                 LOG.LogEntry( "\nDone!" );
348             else:
349                 LOG.LogEntry( "\nError occurred!" );
350
351     except:
352         traceback.print_exc(file=LOG.OutputFile)
353         traceback.print_exc()
354
355     LOG.LogEntry( "BootManager finished at: %s" % \
356                   strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
357
358     LOG.Upload()
359     
360     sys.exit(error)