a first stab at upgrading nodes
[bootmanager.git] / source / BootManager.py
index fd5c06f..55686ea 100755 (executable)
@@ -6,6 +6,8 @@
 # Copyright (c) 2004-2006 The Trustees of Princeton University
 # All rights reserved.
 
+from __future__ import print_function
+
 import sys, os
 import traceback
 import string
@@ -23,12 +25,12 @@ BM_NODE_LOG = "/tmp/bm.log"
 VARS_FILE = "configuration"
 
 # the new contents of PATH when the boot manager is running
-BIN_PATH= ('/usr/local/bin',
-           '/usr/local/sbin',
-           '/usr/bin',
-           '/usr/sbin',
-           '/bin',
-           '/sbin')
+BIN_PATH = ('/usr/local/bin',
+            '/usr/local/sbin',
+            '/usr/bin',
+            '/usr/sbin',
+            '/bin',
+            '/sbin')
 
 def read_configuration_file(filename):
     # read in and store all variables in VARS_FILE into each line
@@ -43,7 +45,7 @@ def read_configuration_file(filename):
         if line[:1] == "#" or string.strip(line) == "":
             continue
 
-        parts = string.split(line,"=")
+        parts = string.split(line, "=")
         if len(parts) != 2:
             validConfFile = False
             raise Exception("Invalid line in vars file: {}".format(line))
@@ -87,14 +89,14 @@ class log:
         except Exception, e:
             self.LogEntry(str(e))
             return
-    
+
     def LogEntry(self, str, inc_newline = 1, display_screen = 1):
         now = time.strftime(log.format, time.localtime())
         if self.OutputFile:
             self.OutputFile.write(now + str)
         if display_screen:
             sys.stdout.write(now + str)
-            
+
         if inc_newline:
             if display_screen:
                 sys.stdout.write("\n")
@@ -106,11 +108,11 @@ class log:
 
     def write(self, str):
         """
-        make log behave like a writable file object (for traceback
-        prints)
+        make log behave like a writable file object
+        (for traceback prints)
         """
         self.LogEntry(str, 0, 1)
-    
+
     def print_stack(self):
         """
         dump current stack in log
@@ -126,9 +128,9 @@ class log:
             self.OutputFile.flush()
 
             self.LogEntry("Uploading logs to {}".format(self.VARS['UPLOAD_LOG_SCRIPT']))
-            
+
             self.OutputFile.close()
-            self.OutputFile= None
+            self.OutputFile = None
 
             hostname = self.VARS['INTERFACE_SETTINGS']['hostname'] + "." + \
                        self.VARS['INTERFACE_SETTINGS']['domainname']
@@ -139,7 +141,7 @@ class log:
                                        GetVars = None, PostVars = None,
                                        DoSSL = True, DoCertCheck = True,
                                        FormData = ["log=@" + self.OutputFilePath,
-                                                   "hostname=" + hostname, 
+                                                   "hostname=" + hostname,
                                                    "type=bm.log"])
             except:
                 # new pycurl
@@ -151,7 +153,7 @@ class log:
                                                    ("hostname",hostname),
                                                    ("type","bm.log")])
         if extra_file is not None:
-            # NOTE: for code-reuse, evoke the bash function 'upload_logs'; 
+            # NOTE: for code-reuse, evoke the bash function 'upload_logs';
             # by adding --login, bash reads .bash_profile before execution.
             # Also, never fail, since this is an optional feature.
             utils.sysexec_noerr("""bash --login -c "upload_logs {}" """.format(extra_file), self)
@@ -163,12 +165,13 @@ class BootManager:
     # file containing initial variables/constants
 
     # the set of valid node run states
-    NodeRunStates = {'reinstall':None,
-                     'boot':None,
-                     'safeboot':None,
-                     'disabled':None,
+    NodeRunStates = {'reinstall' : None,
+                     'upgrade' : None,
+                     'boot' : None,
+                     'safeboot' : None,
+                     'disabled' : None,
                      }
-    
+
     def __init__(self, log, forceState):
         # override machine's current state from the command line
         self.forceState = forceState
@@ -184,7 +187,7 @@ class BootManager:
             self.VARS = log.VARS
         else:
             return
-             
+
         # not sure what the current PATH is set to, replace it with what
         # we know will work with all the boot cds
         os.environ['PATH'] = string.join(BIN_PATH,":")
@@ -227,14 +230,14 @@ class BootManager:
             # then finally chain boots.
 
             # starting the fallback/debug ssh daemon for safety:
-            # if the node install somehow hangs, or if it simply takes ages, 
+            # if the node install somehow hangs, or if it simply takes ages,
             # we can still enter and investigate
             try:
                 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
             except:
                 pass
 
-            InstallInit.Run(self.VARS, self.LOG)                    
+            InstallInit.Run(self.VARS, self.LOG)
             ret = ValidateNodeInstall.Run(self.VARS, self.LOG)
             if ret == 1:
 # Thierry - feb. 2013 turning off WriteModprobeConfig for now on lxc
@@ -257,10 +260,10 @@ class BootManager:
             else:
                 _nodeNotInstalled()
 
-        def _reinstallRun():
+        def _reinstallRun(upgrade=False):
 
             # starting the fallback/debug ssh daemon for safety:
-            # if the node install somehow hangs, or if it simply takes ages, 
+            # if the node install somehow hangs, or if it simply takes ages,
             # we can still enter and investigate
             try:
                 StartDebug.Run(self.VARS, self.LOG, last_resort = False)
@@ -273,12 +276,15 @@ class BootManager:
             # 'boot' state and chainboot into the production system
             if not CheckHardwareRequirements.Run(self.VARS, self.LOG):
                 self.VARS['RUN_LEVEL'] = 'failboot'
-                raise BootManagerException, "Hardware requirements not met."
+                raise BootManagerException("Hardware requirements not met.")
 
             # runinstaller
-            InstallPartitionDisks.Run( self.VARS, self.LOG )            
-            InstallInit.Run(self.VARS, self.LOG)                    
-            InstallBootstrapFS.Run(self.VARS, self.LOG)            
+            InstallInit.Run(self.VARS, self.LOG)
+            # do not erase disks in upgrade mode
+            if not upgrade:
+                InstallPartitionDisks.Run(self.VARS, self.LOG)
+            # pass upgrade boolean to this step so we can do extra cleanup
+            InstallBootstrapFS.Run(self.VARS, upgrade, self.LOG)
             InstallWriteConfig.Run(self.VARS, self.LOG)
             InstallUninitHardware.Run(self.VARS, self.LOG)
             self.VARS['BOOT_STATE'] = 'boot'
@@ -288,7 +294,7 @@ class BootManager:
             AnsibleHook.Run(self.VARS, self.LOG)
             UpdateBootStateWithPLC.Run(self.VARS, self.LOG)
             _bootRun()
-            
+
         def _installRun():
             # implements the new install logic, which will first check
             # with the user whether it is ok to install on this
@@ -315,7 +321,8 @@ class BootManager:
             _debugRun()
 
         # setup state -> function hash table
-        BootManager.NodeRunStates['reinstall']  = _reinstallRun
+        BootManager.NodeRunStates['reinstall']  = lambda : _reinstallRun(upgrade=False)
+        BootManager.NodeRunStates['upgrade']    = lambda : _reinstallRun(upgrade=True)
         BootManager.NodeRunStates['boot']       = _bootRun
         BootManager.NodeRunStates['safeboot']   = lambda : _debugRun('safeboot')
         BootManager.NodeRunStates['disabled']   = lambda : _debugRun('disabled')
@@ -334,26 +341,26 @@ class BootManager:
                 self.VARS['BOOT_STATE'] = self.forceState
                 UpdateBootStateWithPLC.Run(self.VARS, self.LOG)
 
-            stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'],_badstateRun)
+            stateRun = BootManager.NodeRunStates.get(self.VARS['BOOT_STATE'], _badstateRun)
             stateRun()
             success = 1
 
         except KeyError as e:
             self.LOG.write("\n\nKeyError while running: {}\n".format(e))
-            self.LOG.print_stack ()
+            self.LOG.print_stack()
         except BootManagerException as e:
             self.LOG.write("\n\nException while running: {}\n".format(e))
-            self.LOG.print_stack ()
+            self.LOG.print_stack()
         except BootManagerAuthenticationException as e:
             self.LOG.write("\n\nFailed to Authenticate Node: {}\n".format(e))
-            self.LOG.print_stack ()
+            self.LOG.print_stack()
             # sets /tmp/CANCEL_BOOT flag
             StartDebug.Run(self.VARS, self.LOG)
             # Return immediately b/c any other calls to API will fail
             return success
         except:
             self.LOG.write("\n\nImplementation Error\n")
-            self.LOG.print_stack ()
+            self.LOG.print_stack()
 
         if not success:
             try:
@@ -366,18 +373,17 @@ class BootManager:
                 traceback.print_exc()
 
         return success
-            
-            
+
 def main(argv):
 
     import utils
     utils.prompt_for_breakpoint_mode()
 
-#    utils.breakpoint ("Entering BootManager::main")
-    
+#    utils.breakpoint("Entering BootManager::main")
+
     # set to 1 if error occurred
     error = 0
-    
+
     # all output goes through this class so we can save it and post
     # the data back to PlanetLab central
     LOG = log(BM_NODE_LOG)
@@ -400,7 +406,7 @@ def main(argv):
     except:
         traceback.print_exc(file=LOG.OutputFile)
         traceback.print_exc()
-        
+
     if error:
         LOG.LogEntry("BootManager finished at: {}"\
                      .format(time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())))
@@ -429,7 +435,6 @@ def main(argv):
 
     return error
 
-    
 if __name__ == "__main__":
     error = main(sys.argv)
     sys.exit(error)