mark NodeUpdate itself as a crucial package for auto-update
[nodeupdate.git] / NodeUpdate.py
index 47029c2..ff0403c 100644 (file)
@@ -23,7 +23,6 @@ RPM_PATH = "/bin/rpm"
 
 RPM_GPG_PATH = "/etc/pki/rpm-gpg"
 
-
 # location of file containing http/https proxy info, if needed
 PROXY_FILE = '/etc/planetlab/http_proxy'
 
@@ -35,9 +34,11 @@ REBOOT_FLAG = '/etc/planetlab/update-reboot'
 # location of directory containing boot server ssl certs
 SSL_CERT_DIR='/mnt/cdrom/bootme/cacert/'
 
-# file containing list of extra groups to attempt to update,
-# if necessary.
-EXTRA_GROUPS_FILE= '/etc/planetlab/extra-node-groups'
+# file containing the list of extensions this node has, each
+# correspond to a package group in yum repository.
+# this is expected to be updated from the 'extensions tag' 
+# through the 'extensions.php' nodeconfig script
+EXTENSIONS_FILE='/etc/planetlab/extensions'
 
 # file containing a list of rpms that we should attempt to delete
 # before updating everything else. This list is not removed with 
@@ -45,6 +46,18 @@ EXTRA_GROUPS_FILE= '/etc/planetlab/extra-node-groups'
 # that were not intended to be deleted.
 DELETE_RPM_LIST_FILE= '/etc/planetlab/delete-rpm-list'
 
+# ok, so the logic should be simple, just yum update the world
+# however there are many cases in the real life where this 
+# just does not work, because of a glitch somewhere
+# so, we force the update of crucial pkgs independently, as 
+# the whole group is sometimes too much to swallow 
+# this one is builtin
+CRUCIAL_PACKAGES_BUILTIN=[ 'NodeManager' , 'NodeUpdate' ]
+# and operations can also try to push a list through a conf_file
+# should use the second one for consistency, try the first one as well for legacy
+CRUCIAL_PACKAGES_OPTIONAL_PATH1='/etc/planetlab/NodeUpdate.packages'
+CRUCIAL_PACKAGES_OPTIONAL_PATH2='/etc/planetlab/crucial-rpm-list'
+
 
 # print out a message only if we are displaying output
 def Message(message):
@@ -55,7 +68,7 @@ def Message(message):
         print strftime(TIMEFORMAT),
         print message
 
-# print out a message only if we are displaying output
+# always print errors
 def Error(Str):
     print strftime(TIMEFORMAT),
     print Str
@@ -72,6 +85,7 @@ def UpdateCronFile():
         
         f = open( CRON_FILE, 'w' );
         f.write( "# %s\n" % (TARGET_DESC) );
+        ### xxx is root aliased to the support mailing list ?
         f.write( "MAILTO=%s\n" % (TARGET_USER) );
         f.write( "SHELL=%s\n" % (TARGET_SHELL) );
         f.write( "%s %s,%s * * * %s %s\n\n" %
@@ -138,42 +152,58 @@ class NodeUpdate:
 
         Message( "\nChecking if yum supports SSL certificate checks" )
         if os.system( "%s --help | grep -q sslcertdir" % YUM_PATH ) == 0:
-            Message( "Yes, using --sslcertdir option" )
+            Message( "It does, using --sslcertdir option" )
             sslcertdir = "--sslcertdir=" + SSL_CERT_DIR
         else:
-            Message( "No, not using --sslcertdir option" )
+            Message( "Unsupported, not using --sslcertdir option" )
             sslcertdir = ""
                     
         yum_options=""
         Message( "\nChecking if yum supports --verbose" )
         if os.system( "%s --help | grep -q verbose" % YUM_PATH ) == 0:
-            Message( "Yes, using --verbose option" )
+            Message( "It does, using --verbose option" )
             yum_options += " --verbose"
         else:
-            Message( "No, not using --verbose option" )
-                    
+            Message( "Unsupported, not using --verbose option" )
+        
+        # a configurable list of packages to try and update independently
+        # cautious..
+        try:
+            crucial_packages = []
+            for package in CRUCIAL_PACKAGES_BUILTIN: crucial_packages.append(package)
+            try: crucial_packages += file(CRUCIAL_PACKAGES_OPTIONAL_PATH1).read().split()
+            except: pass
+            try: crucial_packages += file(CRUCIAL_PACKAGES_OPTIONAL_PATH2).read().split()
+            except: pass
+            for package in crucial_packages:
+                Message( "\nUpdating crucial package %s" % package)
+                os.system( "%s %s -y update %s" %(YUM_PATH, yum_options, package))
+        except:
+            pass
+
         Message( "\nUpdating PlanetLab group" )
-        os.system( "%s %s %s -y groupupdate \"PlanetLab\"" %
+        os.system( "%s %s %s -y groupinstall \"PlanetLab\"" %
                    (YUM_PATH, yum_options, sslcertdir) )
 
         Message( "\nUpdating rest of system" )
-        os.system( "%s %s %s -y update" %
-                   (YUM_PATH, yum_options, sslcertdir) )
-
-        Message( "\nChecking for extra groups to update" )
-        if os.access(EXTRA_GROUPS_FILE, os.R_OK) and \
-           os.path.isfile(EXTRA_GROUPS_FILE):
-            extra_groups_contents= file(EXTRA_GROUPS_FILE).read()
-            extra_groups_contents= string.strip(extra_groups_contents)
-            if extra_groups_contents == "":
+        os.system( "%s %s %s -y update" % (YUM_PATH, yum_options, sslcertdir) )
+
+        Message( "\nChecking for extra groups (extensions) to update" )
+        if os.access(EXTENSIONS_FILE, os.R_OK) and \
+           os.path.isfile(EXTENSIONS_FILE):
+            extensions_contents= file(EXTENSIONS_FILE).read()
+            extensions_contents= string.strip(extensions_contents)
+            if extensions_contents == "":
                 Message( "No extra groups found in file." )
             else:
-                for group in string.split(extra_groups_contents,"\n"):
+                extensions_contents.strip()
+                for extension in extensions_contents.split():
+                    group = "extension%s" % extension
                     Message( "\nUpdating %s group" % group )
-                    os.system( "%s %s %s -y groupupdate \"%s\"" %
+                    os.system( "%s %s %s -y groupinstall \"%s\"" %
                                (YUM_PATH, yum_options, sslcertdir, group) )
         else:
-            Message( "No extra groups file found" )
+            Message( "No extensions file found" )
             
         if os.access(REBOOT_FLAG, os.R_OK) and os.path.isfile(REBOOT_FLAG) and self.doReboot:
             Message( "\nAt least one update requested the system be rebooted" )
@@ -187,29 +217,42 @@ class NodeUpdate:
         try: os.system( "%s --rebuilddb" % RPM_PATH )
         except Exception, err: print "RebuildRPMdb: %s" % err
 
+    def YumCleanAll ( self ):
+        Message ("\nCleaning all yum cache (yum clean all)")
+        try:
+            os.system( "yum clean all")
+        except:
+            pass
+
     def RemoveRPMS( self ):
 
         Message( "\nLooking for RPMs to be deleted." )
         if os.access(DELETE_RPM_LIST_FILE, os.R_OK) and \
            os.path.isfile(DELETE_RPM_LIST_FILE):
-            rpm_list_contents= file(DELETE_RPM_LIST_FILE).read()
-            rpm_list_contents= string.strip(rpm_list_contents)
+            rpm_list_contents= file(DELETE_RPM_LIST_FILE).read().strip()
 
             if rpm_list_contents == "":
                 Message( "No RPMs listed in file to delete." )
                 return
 
-            rpm_list= string.join(string.split(rpm_list_contents))
+            rpm_list= string.split(rpm_list_contents)
             
-            Message( "Deleting these RPMs:" )
-            Message( rpm_list_contents )
-            
-            rc= os.system( "%s -ev %s" % (RPM_PATH, rpm_list) )
-
-            if rc != 0:
-                Error( "Unable to delete RPMs, continuing. rc=%d" % rc )
-            else:
-                Message( "RPMs deleted successfully." )
+            Message( "Deleting RPMs from %s: %s" %(DELETE_RPM_LIST_FILE," ".join(rpm_list)))
+
+            # invoke them separately as otherwise one faulty (e.g. already uninstalled)
+            # would prevent the other ones from uninstalling
+            for rpm in rpm_list:
+                # is it installed
+                is_installed = os.system ("%s -q %s"%(RPM_PATH,rpm))==0
+                if not is_installed:
+                    Message ("Ignoring rpm %s marked to delete, already uninstalled"%rpm)
+                    continue
+                uninstalled = os.system( "%s -ev %s" % (RPM_PATH, rpm) )==0
+                if uninstalled:
+                    Message ("Successfully removed RPM %s"%rpm)
+                    continue
+                else:
+                    Error( "Unable to delete RPM %s, continuing. rc=%d" % (rpm,rc ))
             
         else:
             Message( "No RPMs list file found." )
@@ -218,9 +261,10 @@ class NodeUpdate:
 
 if __name__ == "__main__":
 
-    # if we are invoked with 'start', display the output. this
-    # is usefull for running something under cron and as a service
-    # (at startup), so the cron only outputs errors and doesn't
+    # if we are invoked with 'start', display the output. 
+    # this is useful for running something silently 
+    # under cron and as a service (at startup), 
+    # so the cron only outputs errors and doesn't
     # generate mail when it works correctly
 
     displayOutput= 0
@@ -231,8 +275,9 @@ if __name__ == "__main__":
     
     doReboot= 1
 
-    if "start" in sys.argv:
+    if "start" in sys.argv or "display" in sys.argv:
         displayOutput= 1
+        Message ("\nTurning on messages")
 
     if "noreboot" in sys.argv:
         doReboot= 0
@@ -272,6 +317,7 @@ if __name__ == "__main__":
         nodeupdate.RebuildRPMdb()
         nodeupdate.RemoveRPMS()
         nodeupdate.InstallKeys()
+        nodeupdate.YumCleanAll()
         nodeupdate.CheckForUpdates()
         Message( "\nUpdate complete." )