Muehlenhoff has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/368190 )
Change subject: Adapt debdeploy server components to Cumin ...................................................................... Adapt debdeploy server components to Cumin Work in progress to convert away from Salt, add separate binary packages for further testing in labs. Change-Id: I2a440d4725d7582f2e23747500d9b8906dbbbc0d --- M clients/debdeploy-deploy M clients/debdeploy-restarts M debian/changelog M debian/control M debian/debdeploy-client.install A debian/debdeploy-server.dirs A debian/debdeploy-server.install M master/debdeploy_updatespec.py A server/debdeploy.py A server/debdeploy_conf.py A server/debdeploy_updatespec.py 11 files changed, 349 insertions(+), 6 deletions(-) Approvals: Muehlenhoff: Looks good to me, approved jenkins-bot: Verified diff --git a/clients/debdeploy-deploy b/clients/debdeploy-deploy index 1f1f8c7..64e85e3 100755 --- a/clients/debdeploy-deploy +++ b/clients/debdeploy-deploy @@ -43,7 +43,7 @@ def setup_logger(verbose=False, console_output=False): - log_file = '' + log_file = '/var/log/debdeploy/debdeploy.log' log_path = os.path.dirname(log_file) if not os.path.exists(log_path): diff --git a/clients/debdeploy-restarts b/clients/debdeploy-restarts index 50706e2..f36e982 100755 --- a/clients/debdeploy-restarts +++ b/clients/debdeploy-restarts @@ -27,7 +27,7 @@ help='Enable additional console output') p.add_argument('--json', action='store_true', default=False, help='Return results as JSON') - p.add_argument('--soname', action='store', nargs='+', required=True) + p.add_argument('--libname', action='store', nargs='+', required=True) args = p.parse_args(sys.argv[1:]) @@ -115,8 +115,8 @@ for i in deleted_files: procname, pid, fname = (i) - for soname in args.soname: - if fname.find(soname) != -1: + for libname in args.libname: + if fname.find(libname) != -1: if not restarts_needed.get(procname, None): restarts_needed[procname] = {} diff --git a/debian/changelog b/debian/changelog index 0bfdc32..ec2e473 100644 --- a/debian/changelog +++ b/debian/changelog @@ -25,6 +25,7 @@ * Create a new package debdeploy-client for the Cumin-based client which is co-installable with the Salt minion (still needed for a transition period) + * Create a new package debdeploy-server for the Cumin-based server -- Moritz Muehlenhoff <mmuhlenh...@wikimedia.org> Fri, 07 Jul 2017 13:49:03 +0200 diff --git a/debian/control b/debian/control index 900be08..650abf5 100644 --- a/debian/control +++ b/debian/control @@ -28,3 +28,9 @@ Depends: lsof, python-debian, ${misc:Depends} Description: Debdeploy central package management (client) This is the client package of Debdeploy. + +Package: debdeploy-server +Architecture: all +Depends: lsof, python-debian, ${misc:Depends} +Description: Debdeploy central package management (server) + This is the server package of Debdeploy. diff --git a/debian/debdeploy-client.install b/debian/debdeploy-client.install index 60b0db4..f558e9b 100644 --- a/debian/debdeploy-client.install +++ b/debian/debdeploy-client.install @@ -1,2 +1,3 @@ -client/debdeploy-deploy /usr/bin/ +clients/debdeploy-deploy /usr/bin/ +clients/debdeploy-restarts /usr/bin/ diff --git a/debian/debdeploy-server.dirs b/debian/debdeploy-server.dirs new file mode 100644 index 0000000..6802d02 --- /dev/null +++ b/debian/debdeploy-server.dirs @@ -0,0 +1,4 @@ +/usr/lib/python2.7/dist-packages/ +/usr/sbin/ +/usr/bin/ + diff --git a/debian/debdeploy-server.install b/debian/debdeploy-server.install new file mode 100644 index 0000000..004608c --- /dev/null +++ b/debian/debdeploy-server.install @@ -0,0 +1,3 @@ +server/debdeploy_updatespec.py /usr/lib/python2.7/dist-packages/ +server/debdeploy_conf.py /usr/lib/python2.7/dist-packages/ +server/debdeploy.py /usr/sbin diff --git a/master/debdeploy_updatespec.py b/master/debdeploy_updatespec.py index c683883..68da08e 100644 --- a/master/debdeploy_updatespec.py +++ b/master/debdeploy_updatespec.py @@ -63,7 +63,7 @@ if supported_distros.count(i) >= 1: self.fixes[i] = updatefile["fixes"].get(i) else: - print "Invalid YAML file,", i, "is not a supported distribution. You need to activate it in /deb/debdeploy.conf" + print "Invalid YAML file,", i, "is not a supported distribution. You need to activate it in /etc/debdeploy.conf" sys.exit(1) # Local variables: diff --git a/server/debdeploy.py b/server/debdeploy.py new file mode 100755 index 0000000..259f096 --- /dev/null +++ b/server/debdeploy.py @@ -0,0 +1,206 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +# TODO: +# reinstate rollback handling +# revamp and readd restart handling + +import argparse +import code +import json +import logging +import os +import pkgutil +import signal +import sys +import datetime + +from ClusterShell.NodeSet import NodeSet + +import cumin + +from cumin import backends, query, transport, transports + + +if os.geteuid() != 0: + print "debdeploy needs to be run as root" + sys.exit(1) + +from debdeploy_conf import * + +cumin_config = cumin.Config() +conf = DebDeployConfig("/etc/debdeploy.conf") + +if conf.debug: + logging.basicConfig(filename='/var/log/debdeploy/debdeploy.log', format='%(levelname)s: %(asctime)s : %(funcName)s : %(message)s', level=logging.DEBUG) +else: + logging.basicConfig(filename='/var/log/debdeploy/debdeploy.log', format='%(levelname)s: %(asctime)s : %(funcName)s : %(message)s', level=logging.INFO) + +import pydoc +from debdeploy_updatespec import * + +class logpager: + threshold = 20 # if pager buffer contains more than <threshold> lines, use the pager + def __init__(self): + self.buf = "" + def add(self, *args): + for i in args: + self.buf += str(i) + self.buf += "\n" + def add_nb(self, *args): + for i in args: + self.buf += str(i) + def show(self): + if self.buf.count("\n") > self.threshold: + pydoc.pager(self.buf) + else: + print self.buf + + + +def deploy_update(source, update_type, update_file, servergroup, supported_distros, fixes): + ''' + Initiate a deployment. + + source : Name of the source package (string) + update_type : Various types of packages have different outcome, see doc/readme.txt (string) + update_file : Filename of update specification (string) + servergroup : The name of the server group (string) + ''' + + update_desc = {} + update_desc["tool"] = "Non-daemon update, no service restart needed" + update_desc["daemon-direct"] = "Daemon update without user impact" + update_desc["daemon-disrupt"] = "Daemon update with service availability impact" + update_desc["library"] = "Library update, several services might need to be restarted" + + print "Rolling out", source, ":", + print update_desc[update_type] + + worker = transport.Transport.new(cumin_config, logging) + hosts = query.Query(cumin_config).execute('A:all') + + cmd = '/usr/bin/debdeploy-deploy --source ' + source + ' --updatespec ' + + + for distro in fixes: + if fixes[distro]: + cmd += supported_distros[distro][0][0] + "_" + supported_distros[distro][0][1] + "_" + fixes[distro] + " " + + worker.target = transports.Target(hosts, batch_size=100, batch_sleep=None, logger=logging) + worker.commands = [ cmd ] + + worker.timeout = None + worker.handler = 'sync' + worker.success_threshold = 0.1 + worker.batch_size = 100 + worker.batch_sleep = None + + # with open('/dev/null', 'w') as discard_output: + # oldstdout = sys.stdout + # sys.stdout = discard_output + # exit_code = worker.execute() + # sys.stdout = oldstdout + + exit_code = worker.execute() + + out = {} + for nodeset, output in worker.get_results(): + print output + +# print worker.handler.counters + + +def detect_restarts(libnames, servergroup): + ''' + Query for necessary restarts after a library or interpreter upgrade + + libnames : A list of library base names, e.g. libssl for /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (list of strings) + servergroup : The name of the server group for which process restarts should be queried (string) + ''' + + worker = transport.Transport.new(cumin_config, logging) + hosts = query.Query(cumin_config).execute('A:all') + + cmd = '/usr/bin/debdeploy-restarts --libname ' + for lib in libnames: + cmd += lib + " " + + worker.target = transports.Target(hosts, batch_size=100, batch_sleep=None, logger=logging) + worker.commands = [ cmd ] + + worker.timeout = None + worker.handler = 'sync' + worker.success_threshold = 0.1 + worker.batch_size = 100 + worker.batch_sleep = None + + # with open('/dev/null', 'w') as discard_output: + # oldstdout = sys.stdout + # sys.stdout = discard_output + # exit_code = worker.execute() + # sys.stdout = oldstdout + + exit_code = worker.execute() + + out = {} + for nodeset, output in worker.get_results(): + print output + + +def main(): + p = argparse.ArgumentParser(usage="debdeploy-master [options] command <cmd-option>\n \ + The following commands are supported: \n\n \ + deploy : Install a software update, requires --update and --servers \n \ + query_restart : Query for necessary restarts after a library or interpreter upgrade \n \ + rollback : Rollback a software deployment") + + p.add_argument("-u", "--update", action="store", type=str, dest="updatefile", help="A YAML file containing the update specification (which source package to update and the respective fixed versions") + p.add_argument("-s", "--servers", action="store", type=str, dest="serverlist", help="The group of servers on which the update should be applied") + p.add_argument("--verbose", action="store_true", dest="verbose", help="Enable verbose output, e.g. show full apt output in status-deploy and status-rollback") + + p.add_argument("command") + p.add_argument("command_option", nargs="?", default="unset") + + opt = p.parse_args() + + if opt.command in ("deploy", "rollback", "restart", "query_restart"): + if not opt.serverlist: + p.error("You need to provide a server list (-s)") + + if opt.command in ("deploy", "rollback", "query_restart"): + if not opt.updatefile: + p.error("You need to provide an update file (-u)") + + if opt.command in ("restart"): + if not opt.program: + p.error("You need to provide a program to restart (-p)") + + if opt.command == "deploy": + update = DebDeployUpdateSpec(opt.updatefile, conf.supported_distros) + deploy_update(update.source, update.update_type, opt.updatefile, opt.serverlist, conf.supported_distros, update.fixes) + + elif opt.command == "query_restart": + update = DebDeployUpdateSpec(opt.updatefile, conf.supported_distros) + detect_restarts(update.libraries, opt.serverlist) + + elif opt.command == "status-rollback": + display_status(rollback_mode=True) + + elif opt.command == "rollback": + rollback(opt.serverlist, opt.updatefile) + + +if __name__ == '__main__': + print main() + + +# Local variables: +# mode: python +# End: + + + + + + diff --git a/server/debdeploy_conf.py b/server/debdeploy_conf.py new file mode 100644 index 0000000..c721bf3 --- /dev/null +++ b/server/debdeploy_conf.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +import ConfigParser, sys + +class DebDeployConfig(object): + ''' + Class to read/provide the system-wide configuration of the debdeploy master component. + It contains the following variables: + + supported_distros: List of strings of supported distros (per Debian/Ubuntu codename) + ''' + supported_distros = {} + debug = False + + def __init__(self, configfile): + config = ConfigParser.ConfigParser() + if len(config.read(configfile)) == 0: + print "/etc/debdeploy.conf doesn't exist, you need to create it." + print "See /usr/share/doc/debdeploy-master/examples/debdeploy.conf" + sys.exit(1) + + if not config.has_section("distros"): + print "Could not read list of supported distributions, make sure", configfile, "contains a section [distros]" + sys.exit(1) + + for distro in config.options("distros"): + self.supported_distros[distro] = [] + self.supported_distros[distro].append([x.strip() for x in config.get("distros", distro).split(",")]) + + if len(self.supported_distros) < 1: + print "You need to specify at least one supported distribution in /etc/debdeploy.conf" + sys.exit(1) + + if config.has_section("logging") and config.has_option("logging", "debug"): + if config.getboolean("logging", "debug"): + self.debug = True + +conf = DebDeployConfig("/etc/debdeploy.conf") + +# Local variables: +# mode: python +# End: + + + + + diff --git a/server/debdeploy_updatespec.py b/server/debdeploy_updatespec.py new file mode 100644 index 0000000..29e2d8a --- /dev/null +++ b/server/debdeploy_updatespec.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import salt.client +import yaml +import sys + +class DebDeployUpdateSpec(object): + ''' + Each update is described in a YAML file, see docs/readme.txt for the data + format. + ''' + + source = "" + comment = "" + update_type = "" + fixes = {} + libraries = [] + legit_type = ['tool', 'daemon-direct', 'daemon-disrupt', 'daemon-cluster', 'reboot', 'reboot-cluster', 'library'] + + def __init__(self, updatespec, supported_distros): + ''' + Parse an update spec file. + + updatespec : Filename of the update spec file (string) + supported_distros : These are the distro codenames for which a fixed version can be provided (list of strings) + ''' + + try: + with open(updatespec, "r") as stream: + updatefile = yaml.load(stream) + + except IOError: + print "Error: Could not open", updatespec + sys.exit(1) + + except yaml.scanner.ScannerError, e: + print "Invalid YAML file:" + print e + sys.exit(1) + + if not updatefile.has_key("source"): + print "Invalid YAML file, you need to specify the source package using the 'source' stanza, see the annotated example file for details" + sys.exit(1) + else: + self.source = updatefile["source"] + + if not updatefile.has_key("update_type"): + print "Invalid YAML file, you need to specify the type of update using the 'update_type' stanza, see the annotated example file for details" + sys.exit(1) + else: + if updatefile["update_type"] not in self.legit_type: + print "Invalid YAML file, invalid 'update_type'" + sys.exit(1) + self.update_type = updatefile["update_type"] + + if updatefile.has_key("comment"): + self.comment = updatefile["comment"] + + if updatefile.has_key("libraries"): + self.libraries = updatefile["libraries"] + + if not updatefile.has_key("fixes"): + print "Invalid YAML file, you need to specify at least one fixed version using the 'fixes' stanza, see the annotated example file for details" + sys.exit(1) + else: + for i in updatefile["fixes"]: + if len(supported_distros.keys()) >= 1: + self.fixes[i] = updatefile["fixes"].get(i) + else: + print "Invalid YAML file,", i, "is not a supported distribution. You need to activate it in /deb/debdeploy.conf" + sys.exit(1) + +# Local variables: +# mode: python +# End: -- To view, visit https://gerrit.wikimedia.org/r/368190 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I2a440d4725d7582f2e23747500d9b8906dbbbc0d Gerrit-PatchSet: 6 Gerrit-Project: operations/debs/debdeploy Gerrit-Branch: master Gerrit-Owner: Muehlenhoff <mmuhlenh...@wikimedia.org> Gerrit-Reviewer: Muehlenhoff <mmuhlenh...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits