#****************************************************************************** # ESO/DFS # # "@(#) $Id: ngasInstallSys.py,v 1.17 2006/06/30 08:14:38 jknudstr Exp $" # # Who When What # -------- ---------- ------------------------------------------------------- # jknudstr 27/11/2003 Created # """ Contains functions to install and configure a NGAS system. This is build on top of an existing Linux installation, which is targeted for NGAS. Note, this script must be executed as user root. """ # This script is supposed to be executable on a basic installation without # the availability of any libraries provided by the NGAS Project. import os, sys, commands, getpass, shutil, time, smtplib, glob, traceback import urllib # Constants. # Command line options. PAR_INST_ROOT = "--INSTALLATION-ROOT" PAR_NGAS_DIST = "--NGAS-DIST" PAR_SRC_URL = "--SRC-URL" PAR_POST_INST_PI = "--POST-INSTALLATION-PI" PAR_INTERACTIVE = "--INTERACTIVE" PAR_NOTIF_EMAIL = "--NOTIF-EMAIL" PAR_REBOOT = "--REBOOT" PAR_HELP = "--HELP" PAR_PI_ROOT = "--PI-ROOT" PAR_SKIP_NGAS_INST = "--SKIP-NGAS-INST" INST_REP_FORMAT = 80 * "=" + "\n" +\ "NGAS INSTALLATION UTILITY\n" +\ 80 * "-" + "\n" +\ "Date: %s\n" +\ "User: %s\n" +\ "Host: %s\n" +\ "Status: %%s\n" +\ 80 * "=" + "\n" _instRep = INST_REP_FORMAT % (time.strftime("%Y-%m-%dT%H:%M:%S %Z", time.localtime()), getpass.getuser(), os.uname()[1].split(".")[0]) _errorCount = 0 def sendEmail(subject, to, msg, contentType = None, attachmentName = None): """ Send an e-mail to the recipient with the given subject. subject: Subject of mail message (string). to: Recipient, e.g. user@test.com (string). msg: Message to send (string). contentType: Mime-type of message (string). attachmentName: Name of attachment in mail (string). Returns: Void. """ info("Entering sendEmail() ...") smtpHost = "smtphost.hq.eso.org" emailList = to.split(",") fromField = getpass.getuser() + "@" + os.uname()[1].split(".")[0] for emailAdr in emailList: try: hdr = "Subject: " + subject + "\n" if (contentType): hdr += "Content-type: " + contentType + "\n" if (attachmentName): hdr += "Content-disposition: attachment; filename=" +\ attachmentName + "\n" tmpMsg = hdr + "\n" + msg server = smtplib.SMTP(smtpHost) server.sendmail("From: " + fromField, "Bcc: " + emailAdr, tmpMsg) except Exception, e: print "Error sending email to recipient: " + str(emailAdr) +\ ". Error: " + str(e) info("Leaving sendEmail()") def input(msg, default = None, accValues = []): """ Request an information from the user. """ if (accValues): msg += " %s" % str(accValues) if (default): msg += " [%s]" % str(default) while (1): msg = "INPUT> %s: " % msg choice = raw_input(msg).strip() if ((choice == "") and default != None): return default if (choice): try: accValues.index(choice) global _instRep _instRep += msg + str(choice) + "\n" return choice except: print "INPUT> Invalid input: %s" % str(choice) def error(msg): """ Raise an exception based on the given error message. msg: Message to report (string). Return: Void. """ global _errorCount _errorCount += 1 raise Exception, msg def info(msg): """ Handle the logging. msg: Message to log (string). Returns: Void. """ msg = "INFO> %s" % str(msg) print msg global _instRep _instRep += msg + "\n" def execCmd(cmd, expRetVal = None): """ Execute a shell command and return the exit value + stdout/stderr. expRetVal: Expected return value of shell command (integer). Returns: Returns status: (, ) (tuple). """ info("Executing shell command: %s" % str(cmd)) stat, out = commands.getstatusoutput(cmd) if (expRetVal != None): if (stat != expRetVal): error("Error encountered: %s" % str(out)) return stat, out def chDir(path): """ Enter the given path. path: Path to enter (string). Returns: Void. """ info("Entering path: %s" % path) os.chdir(path) def rmFile(filename): """ Remove the given file or path. filename: Name of file/path to remove (string). Returns: Void. """ execCmd("rm -rf %s" % filename) def uncompressFile(filename): """ Uncompress the given file. filename: Name of compressed file (string). Returns: Void. """ execCmd("gunzip %s" % filename) def unTarFile(filename): """ Untar the given file. filename: Name of tarball (string). Returns: Void. """ execCmd("tar xvf %s" % filename) def getPackage(url, destFile): """ Download the package referenced. If the package is a local file or file on CDROM, a copy is made with the name indicated by the destFile. If a package is contained in a local file, the path should be of the form: file:// - e.g.: file:///cdrom/dist/MyPackage.tar.gz url: Must be a valid URL (string). destFile: Name of target file to where the file pointed to by the URL will be stored (string). Returns: Void. """ info("Retrieving package from URL: %s into target file: %s ..." %\ (url, destFile)) filename, headers = urllib.urlretrieve(url, destFile) if (filename != destFile): execCmd("cp %s %s" % (filename, destFile)) def installNgas(instPars): """ Installation procedure to install the NGAS Package. instPars: Dictionary with parameters to use for the installation (dictionary). Returns: Void. """ info("Installing NGAS Package ...") info("Selected NGAS Package Name: %s" % instPars[PAR_NGAS_DIST]) url = "%s/%s.tar.gz" % (instPars[PAR_SRC_URL], instPars[PAR_NGAS_DIST]) targFile = "/tmp/%s.tar" % instPars[PAR_NGAS_DIST] tmpTargFile = targFile + ".gz" rmFile("%s*" % targFile) getPackage(url, tmpTargFile) uncompressFile(tmpTargFile) # Remove all Installation Plug-Ins since the new installation is merged # on top of the old one. chDir(instPars[PAR_INST_ROOT]) rmFile("%s/ngasInstall/plug-ins/*.py" % instPars[PAR_INST_ROOT]) unTarFile(targFile) # Compile the NG/AMS Package. ngamsDir = os.path.normpath(instPars[PAR_INST_ROOT] + "/ngams") chDir(ngamsDir) execCmd("./bootstrap") execCmd("./configure --prefix=%s" % instPars[PAR_INST_ROOT]) chDir("ngamsCClient") execCmd("make clean all install") info("Installed NGAS Package") def execPlugIn(piName): """ Execute the given Installation Plug-In. piName: Name of the Installation Plug-In (string). Returns: Void. """ if (piName.find(".py") != -1): importName = os.path.basename(piName)[0:-3] else: importName = piName info("Executing Installation Plug-In: %s" % importName) try: exec "import " + importName res = eval(importName + "." + importName + "(instPars)") info("Executed Installation Plug-In: " + importName) except Exception, e: error("Error occurred invoking Installation " +\ "Plug-In: " + importName + ". Error: " + str(e)) def _runInstallPlugIns(instPars): """ Run the NGAS Installation Plug-Ins found in ngasInstall/plug-ins. instPars: Dictionary with parameters to use for the installation (dictionary). Returns: Void. """ if (instPars[PAR_PI_ROOT]): piRoot = instPars[PAR_PI_ROOT] else: piRoot = instPars[PAR_INST_ROOT] + "/ngasInstall/plug-ins" piDir = os.path.normpath(piRoot) ngasInstallDir = os.path.normpath(piDir + "/../../") pathTup = [ngasInstallDir] sys.path.extend(pathTup) import ngasInstall instPlugInList = glob.glob("%s/Install*.py" % piDir) if (not instPlugInList): info("No Installation Plug-Ins found") return for instPlugIn in instPlugInList: # Execute the Installation Plug-In if specified in the list, # otherwise, if --interactive is chosen, prompt the user whether to # execute it or not. instPlugInImportName = os.path.basename(instPlugIn)[0:-3] instPlugInImportNameUp = instPlugInImportName.upper() if (instPars.has_key("--%s" % instPlugInImportNameUp)): execute = "y" else: if (instPars[PAR_INTERACTIVE]): while (1): execute = input("Do you wish to execute Installation " +\ "Plug-In: " + instPlugInImportName +\ " (y/n) [y]?") if ((execute != "") and (execute != "y") and (execute != "n")): error("Enter a valid choice!") continue break else: execute = "n" if (execute == "n"): info("Not executing Installation Plug-In %s" % instPlugInImportName) elif ((execute == "") or (execute == "y")): execPlugIn(instPlugInImportName) def runInstPlugIns(instPars): """ Run possible installation plug-ins. instPars: Dictionary with parameters to use for the installation (dictionary). Returns: Void. """ info("Entering runInstallPlugIns() ...") try: _runInstallPlugIns(instPars) info("Leaving runInstallPlugIns()") except Exception, e: err = "Error encountered while running Installation Plug-Ins. "+\ "Error: %s" % str(e) raise Exception, err def submitReport(instPars): """ Send out an Installation Report to the email recipients listed. instPars: Dictionary with parameters to use for the installation (dictionary). Returns: Void. """ info("Entering submitReport() ...") global _instRep, _errorCount _instRep += 80 * "=" + "\n" if (_errorCount): status = "Error(s) encountered - check report!" else: status = "OK" rep = _instRep % status sendEmail("NGAS INSTALLATION REPORT", instPars[PAR_NOTIF_EMAIL], rep, "text/plain") info("Leaving submitReport()") def correctUsage(): """ Print correct usage of tool on stdout. Returns: Void. """ print "\nCorrect usage is:\n\n" +\ "% python [/]ngasInstallSys[.py]\n" +\ "\t--installation-root=\n" +\ "\t--src-url=\n" +\ "\t[--ngas-dist=]\n" +\ "\t[--post-installation-pi=]\n" +\ "\t[--reboot]\n" +\ "\t[--notif-email=]\n" +\ "\t[--interactive]\n" +\ "\t[--=[]]\n" +\ "\t[--pi-root=\n" +\ "\t[--skip-ngas-inst]\n" +\ "\n\n" def procRunning(incPats, exclPats = []): """ Check if the NG/AMS Server is running and return 1 in case yes. incPats: Patterns to consider while checking for the task, e.g. 'ngamsServer' (string). exclPats: Pattern to exclude. Process entries containing one or more of the given patterns are excluded (vector/string). Returns: 1 if a process matching the conditions is running on this node, otherwise 0 (integer/0|1). """ try: cmd = "ps -efww" for incPat in incPats: cmd += " | grep " + incPat for exclPat in exclPats: cmd += " | grep -v " + exclPat cmd += " | grep -v root" stat, out = execCmd(cmd) if (out.strip()): return 1 except: return 0 return 0 def getOptPar(orgPar, exception = 1): """ Get a parameter for a long option type command option. The part after the equal sign is returned. If there is no value to the parameter, "" is returned. orgPar: Parameter as given on the shell (string). exception: If 1 an exception is raised if the is no value for the command line option (integer/0|1). Returns: The value or "" (string). """ idx = orgPar.find("=") if (idx == -1): if (exception): raise Exception, "Illegal option: %s" % orgPar else: val = "" else: val = orgPar[(idx + 1):] return val def unpackInstPiPars(piPars): """ Unpack the parameters for an Installation Plug-In. It is in the format: =,=,... piPars: Parameters for the plug-in (string). Returns: Dictionary with the parameter names as keys and the associated values as corresponding value (dictionary). """ info("Unpacking parameters: %s" % piPars) parDic = {} pars = piPars.split(",") for par in pars: par = par.strip() if (par != ""): name, val = par.split("=") parDic[name.strip()] = val.strip() return parDic if __name__ == '__main__': """ Main program invoking the installation functions. """ # Default values for input parameters. instPars = {} instPars[PAR_INST_ROOT] = None instPars[PAR_NGAS_DIST] = "NGAS-CURRENT" instPars[PAR_SRC_URL] = None instPars[PAR_POST_INST_PI] = None instPars[PAR_REBOOT] = 0 instPars[PAR_NOTIF_EMAIL] = None instPars[PAR_INTERACTIVE] = 0 instPars[PAR_HELP] = None instPars[PAR_PI_ROOT] = None instPars[PAR_SKIP_NGAS_INST] = 0 try: # This script should run as root. if (os.getuid() != 0): error("ngasInstallSys must run as user 'root'!") if (procRunning(["ngamsServer", "/opsw/util/python/bin/python"])): error("NG/AMS Server is running - please shut down first!") info("Running installation tool with command line options: %s" %\ str(sys.argv[1:]).replace("'", "").replace(",", "")[1:-1]) # Parse input parameters. idx = 1 while idx < len(sys.argv): parEls = sys.argv[idx].split("=") parUp = parEls[0].upper() if (parUp == PAR_INST_ROOT): instPars[PAR_INST_ROOT] = getOptPar(sys.argv[idx]) elif (parUp == PAR_NGAS_DIST): instPars[PAR_NGAS_DIST] = getOptPar(sys.argv[idx]) elif (parUp ==PAR_POST_INST_PI): instPars[PAR_POST_INST_PI] = getOptPar(sys.argv[idx]) elif (parUp == PAR_INTERACTIVE): instPars[PAR_INTERACTIVE] = 1 elif (parUp == PAR_NOTIF_EMAIL): instPars[PAR_NOTIF_EMAIL] = getOptPar(sys.argv[idx]) elif (parUp == PAR_SRC_URL): instPars[PAR_SRC_URL] = getOptPar(sys.argv[idx]) elif (parUp == PAR_REBOOT): instPars[PAR_REBOOT] = 1 elif (parUp == PAR_HELP): correctUsage() sys.exit(0) pass elif (parUp == PAR_PI_ROOT): instPars[PAR_PI_ROOT] = getOptPar(sys.argv[idx]) elif (parUp == PAR_SKIP_NGAS_INST): instPars[PAR_SKIP_NGAS_INST] = 1 else: # Assume an Installation Plug-In. # --= instPars[parUp] = getOptPar(sys.argv[idx], 0) idx += 1 # Check command line options. if (not instPars[PAR_INST_ROOT]): raise Exception, "Please provide a valid installation root " +\ "(parameter --installation-root)" if (not instPars[PAR_SRC_URL]): raise Exception, "Please provide a source URL hosting the " +\ "packages (parameter --src-url)!" # Let the installation begin. if (not instPars[PAR_SKIP_NGAS_INST]): installNgas(instPars) runInstPlugIns(instPars) if (instPars[PAR_POST_INST_PI]): execPlugIn(instPars[PAR_POST_INST_PI]) info("Executed system installation/configuration") if (instPars[PAR_NOTIF_EMAIL]): submitReport(instPars) # Reboot the machine and send out a report if requested. if (instPars[PAR_REBOOT]): execCmd("/sbin/reboot") else: info("A reboot of the node was not requested") except Exception, e: if (str(e) != "0"): info("Error occurred: %s" % str(e)) if (instPars[PAR_NOTIF_EMAIL]): submitReport(instPars) sys.exit(1) # EOF