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