#!/usr/bin/env python # coding: utf-8 # Copyright (C) 1994-2018 Altair Engineering, Inc. # For more information, contact Altair at www.altair.com. # # This file is part of the PBS Professional ("PBS Pro") software. # # Open Source License Information: # # PBS Pro is free software. You can redistribute it and/or modify it under the # terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # See the GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # Commercial License Information: # # For a copy of the commercial license terms and conditions, # go to: (http://www.pbspro.com/UserArea/agreement.html) # or contact the Altair Legal Department. # # Altair’s dual-license business model allows companies, individuals, and # organizations to create proprietary derivative works of PBS Pro and # distribute them - whether embedded or bundled with other software - # under a commercial license agreement. # # Use of Altair’s trademarks, including but not limited to "PBS™", # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's # trademark licensing policies. import getopt import logging import errno import ptl from ptl.lib.pbs_testlib import * from ptl.utils.pbs_testsuite import PBS_GROUPS # trap SIGINT and SIGPIPE def trap_exceptions(etype, value, tb): sys.excepthook = sys.__excepthook__ if issubclass(etype, KeyboardInterrupt): pass elif issubclass(etype, IOError) and value.errno == errno.EPIPE: pass else: sys.__excepthook__(etype, value, tb) sys.excepthook = trap_exceptions def usage(): msg = [] msg += ['Usage: ' + os.path.basename(sys.argv[0]) + ' [OPTION]\n\n'] msg += ['-t : comma-separated hosts to operate on. Defaults ' 'to localhost\n'] msg += ['-l : one of DEBUG, INFO, ERROR, FATAL, WARNING\n'] msg += ['\n'] msg += ['--log-conf=: logging config file\n'] msg += ['--as-diag=: Mimic pbs_diag snapshot.\n'] msg += ['--revert-config: revert services to their default ' + 'configuration\n'] msg += ['\t --scheduler: operate on scheduler\n'] msg += ['\t --server: operate on server\n'] msg += ['\t --mom: operate on MoM\n'] msg += ['\t --del-hooks=: If True delete hooks.'] msg += [' Defaults to True\n'] msg += ['\t --del-queues=: Delete non-default queues.'] msg += [' Defaults to True\n'] msg += ['--save-config=: save configuration to file\n'] msg += ['\t revert-config and save-config can operate on the following\n'] msg += ['\t --scheduler: operate on scheduler\n'] msg += ['\t --server: operate on server\n'] msg += ['\t --mom: operate on MoM\n'] msg += ['--load-config=: load configuration from saved file.\n'] msg += ['\n'] msg += ['--vnodify: define vnodes using the following suboptions:\n'] msg += ['\t-a : comma separated list of attributes to set ' + 'on vnodes.\n'] msg += ['\t format: =. Defaults to 8 cpus ' + '8gb of mem.\n'] msg += ['\t-A: set additive mode, leave vnode definitions in ' + 'place. \n'] msg += ['\t Default is to clear all existing vnode definition ' + 'files.\n'] msg += ['\t-d : if y, delete all server nodes. Defaults to y.\n'] msg += ['\t-f : use output of pbsnodes -av from file as ' + 'definition\n'] msg += ['\t-P : number of vnodes per host\n'] msg += ['\t-o : output vnode definition to filename\n'] msg += ['\t-M : MoM to operate on, format @.\n' '\t Defaults to localhost.\n'] msg += ['\t-N : number of vnodes to create. No default.\n'] msg += ['\t-n : name of the natural vnode to create. ' + 'Defaults to MoM FQDN\n'] msg += ['\t-p : prefix of name of node to create. ' + 'Output format: \n'] msg += ['\t prefix followed by [: restart MoM or not, defaults to y\n'] msg += ['\t-s: if set, share vnodes on the host. ' + 'Default is "standalone" hosts\n'] msg += ['\t-u: if set, allocate the natural vnode\n'] msg += ['\n'] msg += ['--multi-mom: Define and create multiple MoMs on a host\n'] msg += ['\t--create=: number of MoMs to create. No default.\n'] msg += ['\t--restart=<[seq]>: restart MoMs in sequence\n'] msg += ['\t--stop=<[seq]>: stop MoMs in sequence\n'] msg += ['\t--serverhost=: hostname of server, defaults to ' 'localhost\n'] msg += ['\t--home-prefix=: prefix to PBS_HOME directory, defaults\n' '\t to /var/spool/PBS_m\n'] msg += ['\t--conf-prefix=: prefix to pbs.conf file. Defaults to\n' '\t /etc/pbs.conf.m\n'] msg += ['\t--init-port=: initial port to allocate. Defaults to\n' '\t 15011\n'] msg += ['\t--step-port=: step for port sequence. Defaults to 2\n'] msg += ['\n'] msg += ['--switch-version=: switch to a given installed ' + 'version of PBS\n'] msg += ['\tcurrently only works for "vanilla" installs, i.e, not ' 'developer installs\n'] msg += ['\tbased on /etc/pbs.conf and "default" PBS_EXEC\n'] msg += ['\n'] msg += ['--check-ug: verifies whether test users and groups are '] msg += [' defined as expected.\n Note that -t option ' 'will be ignored.\n'] msg += ['--make-ug: create users and groups to match what is expected\n'] msg += [' Note that -t option will be ignored\n'] msg += ['--del-ug: delete users and groups which is expected for PTL\n'] msg += [' Note that -t option will be ignored\n'] msg += ['--version: print version number and exit\n'] print "".join(msg) def process_config(hosts, process_obj, conf_file=None, type='default', delqueues=False, delhooks=False): for host in hosts: if MGR_OBJ_SCHED in process_obj: if type == 'default': Scheduler(host).revert_to_defaults() elif type == 'load': Scheduler(host).load_configuration(conf_file) elif type == 'save': Scheduler(host).save_configuration(conf_file) if MGR_OBJ_SERVER in process_obj: if type == 'default': Server(host).revert_to_defaults(delhooks=delhooks, delqueues=delqueues) elif type == 'load': Server(host).load_configuration(conf_file) elif type == 'save': Server(host).save_configuration(conf_file) if MGR_OBJ_NODE in process_obj: if type == 'default': MoM(host).revert_to_defaults() elif type == 'load': MoM(host).load_configuration(conf_file) elif type == 'save': MoM(host).save_configuration(conf_file) def process_attributes(attrs): nattrs = {} for a in attrs.split(','): if '=' not in a: logging.error('attributes must be of the form' + ' =') sys.exit(1) k, v = a.split('=') nattrs[k] = v return nattrs def common_users_groups_ops(): du = DshUtils() g_create = [] u_create = [] gm_expected = {} gm_actual = du.group_memberships(map(lambda g: str(g), PBS_GROUPS)) for g in PBS_GROUPS: gm_expected[g] = g.users for k, v in gm_expected.items(): if str(k) not in gm_actual: g_create.append(k) for _u in v: if _u not in u_create: u_create.append(_u) else: for _u in v: if ((str(_u) not in gm_actual[str(k)]) and (_u not in u_create)): u_create.append(_u) return (gm_expected, gm_actual, g_create, u_create) def check_users_groups(): gm_expected, gm_actual, g_create, u_create = common_users_groups_ops() if ((len(g_create) > 0) or (len(u_create) > 0)): out = ['Expected (format is : [, ...) '] for k, v in gm_expected.items(): out += [str(k) + ': ' + ', '.join(map(lambda u:str(u), v))] out += ['\n', 'Actual: '] for k, v in gm_actual.items(): out += [k + ': ' + ', '.join(v)] print '\n'.join(out) return False else: return True def make_users_groups(): du = DshUtils() _, _, g_create, u_create = common_users_groups_ops() for g in g_create: du.groupadd(g, g.gid, logerr=False) for u in u_create: du.useradd(name=u, uid=u.uid, gid=u.groups[0], groups=u.groups, logerr=False) return True def delete_users_groups(): du = DshUtils() _, gm_actual, _, _ = common_users_groups_ops() for v in gm_actual.values(): for u in v: du.userdel(u, logerr=False) for k in gm_actual.keys(): du.groupdel(k, logerr=False) return True if __name__ == '__main__': if len(sys.argv) < 2: usage() sys.exit(0) # vnodify options vnodify = False vnodeprefix = 'vnode' num_vnodes = None additive = False sharedhost = False filename = None attrs = "resources_available.ncpus=8,resources_available.mem=8gb" hostname = None conf_file = None restart = True delall = True natvnode = None usenatvnode = False vdefname = None # end of vnodify options hosts = None revert = False op = None loadconf = None saveconf = None logconf = None vnodes_per_host = 1 delqueues = True delhooks = True lvl = logging.INFO switchversion = None check_ug = False make_ug = False del_ug = False multimom = False num_moms = None restart_moms = None stop_moms = None clienthost = None serverhost = None init_port = 15011 step_port = 2 import_jobs = False home_prefix = 'PBS_m' conf_prefix = 'pbs.conf_m' asdiag = None process_obj = [] vnodify_args = "a:d:f:N:n:o:P:p:M:l:r:v:Asu" generic_args = "l:t:h" largs = ["scheduler", "server", "mom", "revert-config", "load-config=", "save-config=", "vnodify", "import-jobs", "del-ug", "switch-version=", "log-conf=", "check-ug", "del-hooks=", "del-queues=", "version", "make-ug", "multi-mom", "clienthost=", "home-prefix=", "conf-prefix=", "serverhost=", "init-port=", "step-port=", "as-diag=", "create=", "restart=", "stop="] try: opts, args = getopt.getopt(sys.argv[1:], vnodify_args + generic_args, largs) except: usage() sys.exit(1) for o, val in opts: if o == '-l': lvl = CliUtils().get_logging_level(val) elif o == '-t': hosts = val elif o == '-a': attrs = val elif o == '-A': additive = True elif o == '-d': if val.startswith('y'): delall = True else: delall = False elif o == '-f': filename = CliUtils.expand_abs_path(val) elif o == '-P': vnodes_per_host = int(val) elif o == '-p': vnodeprefix = val elif o == '-M': if '@' in val: (hostname, conf_file) = val.split('@') else: hostname = val elif o == '-N': num_vnodes = int(val) elif o == '-o': vdefname = val elif o == '-s': sharedhost = True elif o == '-r': if val.startswith('y'): restart = True elif o == '-n': natvnode = val elif o == '-u': usenatvnode = True elif o == '--check-ug': check_ug = True elif o == '--make-ug': make_ug = True elif o == '--del-ug': del_ug = True elif o == '--del-hooks': delhooks = eval(val) elif o == '--del-queues': delqueues = eval(val) elif o == '--as-diag': asdiag = CliUtils.expand_abs_path(val) elif o == '--import-jobs': import_jobs = True elif o == '--log-conf': logconf = val elif o == '--multi-mom': multimom = True elif o == '--create': num_moms = int(val) elif o == '--home-prefix': home_prefix = val elif o == '--conf-prefix': conf_prefix = val elif o == '--scheduler': process_obj.append(MGR_OBJ_SCHED) elif o == '--server': process_obj.append(MGR_OBJ_SERVER) elif o == '--mom': process_obj.append(MGR_OBJ_NODE) elif o == '--restart': restart_moms = eval(val, {}, {}) elif o == '--stop': stop_moms = eval(val, {}, {}) elif o == '--vnodify': vnodify = True elif o == '--revert-config': revert = True elif o == '--load-config': loadconf = CliUtils.expand_abs_path(val) elif o == '--save-config': saveconf = CliUtils.expand_abs_path(val) elif o == '--serverhost': serverhost = val elif o == '--init-port': init_port = int(val) elif o == '--step-port': step_port = int(val) elif o == '--switch-version': switchversion = val elif o == '--version': print ptl.__version__ sys.exit(0) else: sys.stderr.write("Unrecognized option " + o + "\n") usage() sys.exit(1) PtlConfig() if logconf: logging.config.fileConfig(logconf) else: logging.basicConfig(level=lvl) if hosts is None: hosts = [socket.gethostname()] else: hosts = hosts.split(',') if check_ug: rv = check_users_groups() if rv: sys.exit(0) sys.exit(1) if del_ug: rv = delete_users_groups() if rv: sys.exit(0) sys.exit(1) if make_ug: rv = make_users_groups() if rv: sys.exit(0) sys.exit(1) if revert: process_config(hosts, process_obj, type='default', delqueues=delqueues, delhooks=delhooks) elif loadconf: # when loading configuration apply the saved configuration based # on what was saved irregardless of what object types were passed in allobjs = [MGR_OBJ_SCHED, MGR_OBJ_SERVER, MGR_OBJ_NODE] process_config(hosts, allobjs, loadconf, type='load', delqueues=delqueues, delhooks=delhooks) elif saveconf: if os.path.isfile(saveconf): answer = raw_input('file ' + saveconf + ' exists, overwrite? ' '[y]/n: ') if answer == 'n': sys.exit(1) if not process_obj: process_obj = [MGR_OBJ_SERVER, MGR_OBJ_SCHED, MGR_OBJ_NODE] process_config(hosts, process_obj, saveconf, type='save', delqueues=delqueues, delhooks=delhooks) elif vnodify: if filename: vdef = BatchUtils().file_to_vnodedef(filename) if vdef: MoM(hostname, pbsconf_file=conf_file).insert_vnode_def(vdef) elif num_vnodes is None: logging.error('A number of vnodes to create is required\n') sys.exit(1) else: nattrs = process_attributes(attrs) for hostname in hosts: s = Server(hostname) m = MoM(hostname, pbsconf_file=conf_file) s.create_vnodes(vnodeprefix, nattrs, num_vnodes, m, additive, sharedhost, restart, delall, natvnode, usenatvnode, fname=vdefname, vnodes_per_host=vnodes_per_host) elif switchversion: pi = PBSInitServices() for host in hosts: pi.switch_version(host, switchversion) elif multimom: if num_moms is not None: if os.getuid() != 0: logging.error('Must be run as root') sys.exit(1) du = DshUtils() conf = du.parse_pbs_config(serverhost) serverhost = DshUtils().get_pbs_server_name(conf) s = Server(serverhost) nattrs = process_attributes(attrs) s.create_moms(num=num_moms, attrib=nattrs, conf_prefix=conf_prefix, home_prefix=home_prefix, momhosts=hosts, init_port=init_port, step_port=step_port) if (restart_moms or stop_moms): mom_op = [] if restart_moms: mom_op = restart_moms if stop_moms: mom_op += stop_moms for i in mom_op: c = os.path.join('/etc', conf_prefix + str(i)) pi = PBSInitServices(serverhost, conf=c) if restart_moms: ret = pi.restart() if stop_moms: ret = pi.stop() if ret['rc'] != 0: logging.error(ret['err']) del pi elif asdiag is not None: if os.getuid() != 0: logging.error('Must be run as root') sys.exit(1) Server(diag=asdiag).clusterize(conf_file, hosts, import_jobs=import_jobs)