dnf-aware
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Tue, 1 Dec 2015 11:06:04 +0000 (12:06 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Tue, 1 Dec 2015 11:06:04 +0000 (12:06 +0100)
uses dnf group upgrade for upgrading the planetlab group

NodeUpdate.py

index b33696d..09183fb 100755 (executable)
@@ -1,7 +1,10 @@
 #!/usr/bin/python2
 
+from __future__ import print_function
+
 import sys
 import os
+import os.path
 import string
 from random import Random
 from types import StringTypes
@@ -19,6 +22,8 @@ TARGET_SHELL = '/bin/bash'
 CRON_FILE = '/etc/cron.d/NodeUpdate.cron'
 
 YUM_PATH = "/usr/bin/yum"
+DNF_PATH = "/usr/bin/dnf"
+HAS_DNF = os.path.exists(DNF_PATH)
 
 RPM_PATH = "/bin/rpm"
 
@@ -71,21 +76,79 @@ CRUCIAL_PACKAGES_OPTIONAL_PATHS = [
 ]
 
 # print out a message only if we are displaying output
-def Message(message):
+def Message(*messages):
     if displayOutput:
-        if isinstance(message,StringTypes) \
-           and len(message) >= 2 \
-           and message[0] == "\n":
-            print "\n",
-            message = message[1:]
-        print strftime(TIMEFORMAT),
-        print message
+        print(5*'*', strftime(TIMEFORMAT), *messages)
 
 # always print errors
-def Error(Str):
-    print strftime(TIMEFORMAT),
-    print Str
+def Error(*messages):
+    print(10*'!', strftime(TIMEFORMAT),*messages)
+
+class YumDnf:
+    def __init__(self):
+        command = DNF_PATH if HAS_DNF else YUM_PATH
+        self.command = command
+
+        options = ""
+        # --verbose option
+        Message("Checking if {} supports --verbose".format(command))
+        if os.system("{} --help | grep -q verbose".format(command)) == 0:
+            Message("It does, using --verbose option")
+            options += " --verbose"
+        else:
+            Message("Unsupported, not using --verbose option")
+        # --sslcertdir option
+        Message("Checking if {} supports SSL certificate checks"
+                .format(command))
+        if os.system("{} --help | grep -q sslcertdir".format(command)) == 0:
+            Message("It does, using --sslcertdir option")
+            sslcertdir = "--sslcertdir=" + SSL_CERT_DIR
+        else:
+            Message("Unsupported, not using --sslcertdir option")
+            sslcertdir = ""
+        self.options = options
 
+    ########## one individual package
+    def handle_package(self, package):
+        if not self.is_packaged_installed(package):
+            return self.do_package(package, "install")
+        else:
+            return self.do_package(package, "update")
+
+    def is_packaged_installed(self, package):
+        cmd = "rpm -q {} > /dev/null".format(package)
+        return os.system(cmd) == 0
+
+    def do_package(self, package, subcommand):
+        cmd = \
+            "{} {} -y {} {}".format(self.command, self.options,
+                                    subcommand, package)
+        Message("Invoking {}".format(cmd))
+        return os.system(cmd) == 0
+
+    ########## update one group
+    def update_group(self, group):
+        # it is important to invoke dnf group *upgrade* and not *update*
+        # because the semantics of groups has changed within dnf
+        if HAS_DNF:
+            cmd = \
+                "{} {} -y group upgrade {}".format(self.command, self.options, group)
+        else:
+            cmd = \
+                "{} {} -y groupinstall {}".format(self.command, self.options, group)
+        Message("Invoking {}".format(cmd))
+        return os.system(cmd) == 0
+
+    ########## update the whole system
+    def update_system(self):
+        cmd = "{} {} -y update".format(self.command, self.options)
+        Message("Invoking {}".format(cmd))
+        return os.system(cmd) == 0
+
+    def clean_all(self):
+        cmd = "{} clean all".format(self.command)
+        Message("Invoking {}".format(cmd))
+        return os.system(cmd) == 0
 
 # create an entry in /etc/cron.d so we run periodically.
 # we will be run once a day at a 0-59 minute random offset
@@ -97,13 +160,13 @@ def UpdateCronFile():
         randomHour= Random().randrange(0, 11, 1);
         
         f = open(CRON_FILE, 'w');
-        f.write("# %s\n" % (TARGET_DESC));
+        f.write("# {}\n".format(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" %
-                 (randomMinute, randomHour,
-                  randomHour + 12, TARGET_USER, TARGET_SCRIPT));
+        f.write("MAILTO={}\n".format(TARGET_USER));
+        f.write("SHELL={}\n".format(TARGET_SHELL));
+        f.write("{} {},{} * * * {} {}\n\n"
+                .format (randomMinute, randomHour,
+                         randomHour + 12, TARGET_USER, TARGET_SCRIPT));
         f.close()
     
         print("Created new cron.d entry.")
@@ -133,42 +196,32 @@ class NodeUpdate:
         Message("Checking existence of proxy config file...")
         if os.access(PROXY_FILE, os.R_OK) and os.path.isfile(PROXY_FILE):
             self.HTTP_PROXY= string.strip(file(PROXY_FILE,'r').readline())
-            Message("Using proxy %s." % self.HTTP_PROXY)
+            Message("Using proxy {}".format(self.HTTP_PROXY))
             return 1
         else:
             Message("Not using any proxy.")
             return 0
 
     def InstallKeys(self):
-        Message("\nRemoving any existing GPG signing keys from the RPM database")
-        os.system("%s --allmatches -e gpg-pubkey" % RPM_PATH)
-        Message("\nInstalling all GPG signing keys in %s" % RPM_GPG_PATH)
-        os.system("%s --import %s/*" % (RPM_PATH, RPM_GPG_PATH))
+        Message("Removing any existing GPG signing keys from the RPM database")
+        os.system("{} --allmatches -e gpg-pubkey".format(RPM_PATH))
+        Message("Installing all GPG signing keys in {}".format(RPM_GPG_PATH))
+        os.system("{} --import {}/*".format(RPM_PATH, RPM_GPG_PATH))
 
     def ClearRebootFlag(self):
-        os.system("/bin/rm -rf %s" % REBOOT_FLAG)
+        os.system("/bin/rm -rf {}".format(REBOOT_FLAG))
 
     def CheckForUpdates(self):
-        Message("\nRemoving any existing reboot flags")
+        Message("Removing any existing reboot flags")
         self.ClearRebootFlag()
         if self.doReboot == 0:
-            Message("\nIgnoring any reboot flags set by RPMs");
-        Message("\nChecking if yum supports SSL certificate checks")
-        if os.system("%s --help | grep -q sslcertdir" % YUM_PATH) == 0:
-            Message("It does, using --sslcertdir option")
-            sslcertdir = "--sslcertdir=" + SSL_CERT_DIR
-        else:
-            Message("Unsupported, not using --sslcertdir option")
-            sslcertdir = ""
+            Message("Ignoring any reboot flags set by RPMs");
                     
-        yum_options=""
-        Message("\nChecking if yum supports --verbose")
-        if os.system("%s --help | grep -q verbose" % YUM_PATH) == 0:
-            Message("It does, using --verbose option")
-            yum_options += " --verbose"
-        else:
-            Message("Unsupported, not using --verbose option")
-        
+        yum_dnf = YumDnf()
+
+        # this of course is quite suboptimal, but proved to be safer
+        yum_dnf.clean_all()
+
         # a configurable list of packages to try and update independently
         # cautious..
         try:
@@ -180,26 +233,20 @@ class NodeUpdate:
                     crucial_packages += file(path).read().split()
                 except:
                     pass
+            Message("List of crucial packages: {}".format(crucial_packages))
             for package in crucial_packages:
-                # if package is not yet installed, like e.g. slice images, 
-                # need to yum install, not yum update
-                if os.system("rpm -q %s > /dev/null"%package) == 0:
-                    Message("\nUpdating crucial package %s" % package)
-                    os.system("%s %s -y update %s" %(YUM_PATH, yum_options, package))
-                else:
-                    Message("\Installing crucial package %s" % package)
-                    os.system("%s %s -y install %s" %(YUM_PATH, yum_options, package))
+                yum_dnf.handle_package(package)
         except:
+            
             pass
 
-        Message("\nUpdating PlanetLab group")
-        os.system("%s %s %s -y groupinstall \"PlanetLab\"" %
-                   (YUM_PATH, yum_options, sslcertdir))
+        Message("Updating PlanetLab group")
+        yum_dnf.update_group("PlanetLab")
 
-        Message("\nUpdating rest of system")
-        os.system("%s %s %s -y update" % (YUM_PATH, yum_options, sslcertdir))
+        Message("Updating rest of system")
+        yum_dnf.update_system()
 
-        Message("\nChecking for extra groups (extensions) to update")
+        Message("Checking 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()
@@ -209,34 +256,29 @@ class NodeUpdate:
             else:
                 extensions_contents.strip()
                 for extension in extensions_contents.split():
-                    group = "extension%s" % extension
-                    Message("\nUpdating %s group" % group)
-                    os.system("%s %s %s -y groupinstall \"%s\"" %
-                               (YUM_PATH, yum_options, sslcertdir, group))
+                    group = "extension{}".format(extension)
+                    yum_dnf.update_group(group)
         else:
             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")
+            Message("At least one update requested the system be rebooted")
             self.ClearRebootFlag()
             os.system("/sbin/shutdown -r now")
 
     def RebuildRPMdb(self):
-        Message("\nRebuilding RPM Database.")
-        try: os.system("rm /var/lib/rpm/__db.*")
-        except Exception, err: print "RebuildRPMdb: %s" % err
-        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)")
+        Message("Rebuilding RPM Database.")
         try:
-            os.system("yum clean all")
-        except:
-            pass
+            os.system("rm /var/lib/rpm/__db.*")
+        except Exception as err:
+            print("RebuildRPMdb: exception {}".format(err))
+        try:
+            os.system("{} --rebuilddb".format(RPM_PATH))
+        except Exception as err:
+            print("RebuildRPMdb: exception {}".format(err))
 
     def RemoveRPMS(self):
-        Message("\nLooking for RPMs to be deleted.")
+        Message("Looking 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().strip()
@@ -247,22 +289,22 @@ class NodeUpdate:
 
             rpm_list= string.split(rpm_list_contents)
             
-            Message("Deleting RPMs from %s: %s" %(DELETE_RPM_LIST_FILE," ".join(rpm_list)))
+            Message("Deleting RPMs from {}: {}".format(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
+                is_installed = os.system ("{} -q {}".format(RPM_PATH, rpm)) == 0
                 if not is_installed:
-                    Message ("Ignoring rpm %s marked to delete, already uninstalled"%rpm)
+                    Message ("Ignoring rpm {} marked to delete, already uninstalled".format(rpm))
                     continue
-                uninstalled = os.system("%s -ev %s" % (RPM_PATH, rpm))==0
+                uninstalled = os.system("{} -ev {}".format(RPM_PATH, rpm)) == 0
                 if uninstalled:
-                    Message ("Successfully removed RPM %s"%rpm)
+                    Message ("Successfully removed RPM {}".format(rpm))
                     continue
                 else:
-                    Error("Unable to delete RPM %s, continuing. rc=%d" % (rpm,uninstalled))
+                    Error("Unable to delete RPM {}, continuing. rc={}".format(rpm, uninstalled))
             
         else:
             Message("No RPMs list file found.")
@@ -276,20 +318,20 @@ if __name__ == "__main__":
     # so the cron only outputs errors and doesn't
     # generate mail when it works correctly
 
-    displayOutput= 0
+    displayOutput = 0
 
     # if we hit an rpm that requests a reboot, do it if this is
     # set to 1. can be turned off by adding noreboot to command line
     # option
     
-    doReboot= 1
+    doReboot = 1
 
     if "start" in sys.argv or "display" in sys.argv:
-        displayOutput= 1
+        displayOutput = 1
         Message ("\nTurning on messages")
 
     if "noreboot" in sys.argv:
-        doReboot= 0
+        doReboot = 0
 
     if "updatecron" in sys.argv:
         # simply update the /etc/cron.d file for us, and exit
@@ -309,26 +351,25 @@ if __name__ == "__main__":
     if os.access(NODEUPDATE_PID_FILE, os.R_OK):
         pid= string.strip(file(NODEUPDATE_PID_FILE).readline())
         if pid <> "":
-            if os.system("/bin/kill -0 %s > /dev/null 2>&1" % pid) == 0:
+            if os.system("/bin/kill -0 {} > /dev/null 2>&1".format(pid)) == 0:
                 Message("It appears we are already running, exiting.")
                 sys.exit(1)
                     
     # write out our process id
-    pidfile= file(NODEUPDATE_PID_FILE, 'w')
-    pidfile.write("%d\n" % os.getpid())
+    pidfile = file(NODEUPDATE_PID_FILE, 'w')
+    pidfile.write("{}\n".format(os.getpid()))
     pidfile.close()
 
     
-    nodeupdate= NodeUpdate(doReboot)
+    nodeupdate = NodeUpdate(doReboot)
     if not nodeupdate:
         Error("Unable to initialize.")
     else:
         nodeupdate.RebuildRPMdb()
         nodeupdate.RemoveRPMS()
         nodeupdate.InstallKeys()
-        nodeupdate.YumCleanAll()
         nodeupdate.CheckForUpdates()
-        Message("\nUpdate complete.")
+        Message("Update complete.")
 
     # remove the PID file
     os.unlink(NODEUPDATE_PID_FILE)