# 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 sys import os import socket import pwd import grp import logging import time import re import random import string import tempfile import cPickle import copy import datetime import traceback import threading from operator import itemgetter from collections import OrderedDict from distutils.version import LooseVersion try: import psycopg2 PSYCOPG = True except: PSYCOPG = False try: from ptl.lib.pbs_ifl import * API_OK = True except: try: from ptl.lib.pbs_ifl_mock import * except: sys.stderr.write("failed to import pbs_ifl, run pbs_swigify " + "to make it\n") raise ImportError API_OK = False from ptl.lib.pbs_api_to_cli import api_to_cli from ptl.utils.pbs_dshutils import DshUtils from ptl.utils.pbs_procutils import ProcUtils from ptl.utils.pbs_cliutils import CliUtils from ptl.utils.pbs_fileutils import FileUtils, FILE_TAIL # suppress logging exceptions logging.raiseExceptions = False # Various mappings and aliases MGR_OBJ_VNODE = MGR_OBJ_NODE VNODE = MGR_OBJ_VNODE NODE = MGR_OBJ_NODE HOST = MGR_OBJ_HOST JOB = MGR_OBJ_JOB RESV = MGR_OBJ_RESV SERVER = MGR_OBJ_SERVER QUEUE = MGR_OBJ_QUEUE SCHED = MGR_OBJ_SCHED HOOK = MGR_OBJ_HOOK RSC = MGR_OBJ_RSC PBS_HOOK = MGR_OBJ_PBS_HOOK # the order of these symbols matters, see pbs_ifl.h (SET, UNSET, INCR, DECR, EQ, NE, GE, GT, LE, LT, MATCH, MATCH_RE, NOT, DFLT) = range(14) (PTL_OR, PTL_AND) = [0, 1] (IFL_SUBMIT, IFL_SELECT, IFL_TERMINATE, IFL_ALTER, IFL_MSG, IFL_DELETE, IFL_RALTER) = [0, 1, 2, 3, 4, 5, 6] (PTL_API, PTL_CLI) = ['api', 'cli'] (PTL_COUNTER, PTL_FILTER) = [0, 1] PTL_STR_TO_OP = { '<': LT, '<=': LE, '=': EQ, '>=': GE, '>': GT, '!=': NE, ' set ': SET, ' unset ': UNSET, ' match ': MATCH, '~': MATCH_RE, '!': NOT } PTL_OP_TO_STR = { LT: '<', LE: '<=', EQ: '=', GE: '>=', GT: '>', SET: ' set ', NE: '!=', UNSET: ' unset ', MATCH: ' match ', MATCH_RE: '~', NOT: 'is not' } PTL_ATTROP_TO_STR = {PTL_AND: '&&', PTL_OR: '||'} (RESOURCES_AVAILABLE, RESOURCES_TOTAL) = [0, 1] EXPECT_MAP = { UNSET: 'Unset', SET: 'Set', EQ: 'Equal', NE: 'Not Equal', LT: 'Less Than', GT: 'Greater Than', LE: 'Less Equal Than', GE: 'Greater Equal Than', MATCH_RE: 'Matches regexp', MATCH: 'Matches', NOT: 'Not' } PBS_CMD_MAP = { MGR_CMD_CREATE: 'create', MGR_CMD_SET: 'set', MGR_CMD_DELETE: 'delete', MGR_CMD_UNSET: 'unset', MGR_CMD_IMPORT: 'import', MGR_CMD_EXPORT: 'export', MGR_CMD_LIST: 'list', } PBS_CMD_TO_OP = { MGR_CMD_SET: SET, MGR_CMD_UNSET: UNSET, MGR_CMD_DELETE: UNSET, MGR_CMD_CREATE: SET, } PBS_OBJ_MAP = { MGR_OBJ_NONE: 'none', SERVER: 'server', QUEUE: 'queue', JOB: 'job', NODE: 'node', RESV: 'reservation', RSC: 'resource', SCHED: 'sched', HOST: 'host', HOOK: 'hook', VNODE: 'node', PBS_HOOK: 'pbshook' } PTL_TRUE = ('1', 'true', 't', 'yes', 'y', 'enable', 'enabled', 'True', True) PTL_FALSE = ('0', 'false', 'f', 'no', 'n', 'disable', 'disabled', 'False', False) PTL_NONE = ('None', None) PTL_FORMULA = '__formula__' PTL_NOARG = '__noarg__' PTL_ALL = '__ALL__' CMD_ERROR_MAP = { 'alterjob': 'PbsAlterError', 'holdjob': 'PbsHoldError', 'sigjob': 'PbsSignalError', 'msgjob': 'PbsMessageError', 'rlsjob': 'PbsReleaseError', 'rerunjob': 'PbsRerunError', 'orderjob': 'PbsOrderError', 'runjob': 'PbsRunError', 'movejob': 'PbsMoveError', 'delete': 'PbsDeleteError', 'deljob': 'PbsDeljobError', 'delresv': 'PbsDelresvError', 'status': 'PbsStatusError', 'manager': 'PbsManagerError', 'submit': 'PbsSubmitError', 'terminate': 'PbsQtermError', 'alterresv': 'PbsResvAlterError' } class PtlConfig(object): """ Holds configuration options The options can be stored in a file as well as in the OS environment variables.When set, the environment variables will override definitions in the file.By default, on Unix like systems, the file read is ``/etc/ptl.conf``, the environment variable ``PTL_CONF_FILE`` can be used to set the path to the file to read. The format of the file is a series of `` = `` properties. A line that starts with a '#' is ignored and can be used for comments :param conf: Path to PTL configuration file :type conf: str or None """ logger = logging.getLogger(__name__) def __init__(self, conf=None): self.options = { 'PTL_SUDO_CMD': 'sudo -H', 'PTL_RSH_CMD': 'ssh', 'PTL_CP_CMD': 'scp -p', 'PTL_EXPECT_MAX_ATTEMPTS': 60, 'PTL_EXPECT_INTERVAL': 0.5, 'PTL_UPDATE_ATTRIBUTES': True, } self.handlers = { 'PTL_SUDO_CMD': DshUtils.set_sudo_cmd, 'PTL_RSH_CMD': DshUtils.set_rsh_cmd, 'PTL_CP_CMD': DshUtils.set_copy_cmd, 'PTL_EXPECT_MAX_ATTEMPTS': Server.set_expect_max_attempts, 'PTL_EXPECT_INTERVAL': Server.set_expect_interval, 'PTL_UPDATE_ATTRIBUTES': Server.set_update_attributes } if conf is None: conf = os.environ.get('PTL_CONF_FILE', '/etc/ptl.conf') try: lines = open(conf).readlines() except IOError: lines = [] for line in lines: line = line.strip() if (line.startswith('#') or (line == '')): continue try: k, v = line.split('=', 1) k = k.strip() v = v.strip() self.options[k] = v except: self.logger.error('Error parsing line ' + line) for k, v in self.options.items(): if k in os.environ: v = os.environ[k] else: os.environ[k] = str(v) if k in self.handlers: self.handlers[k](v) class PtlException(Exception): """ Generic errors raised by PTL operations. Sets a ``return value``, a ``return code``, and a ``message`` A post function and associated positional and named arguments are available to perform any necessary cleanup. :param rv: Return value set for the error occured during PTL operation :type rv: int or None. :param rc: Return code set for the error occured during PTL operation :type rc: int or None. :param msg: Message set for the error occured during PTL operation :type msg: str or None. :param post: Execute given post callable function if not None. :type post: callable or None. :raises: PTL exceptions """ def __init__(self, rv=None, rc=None, msg=None, post=None, *args, **kwargs): self.rv = rv self.rc = rc self.msg = msg if post is not None: post(*args, **kwargs) def __str__(self): return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) + ', msg=' + str(self.msg)) def __repr__(self): return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' + str(self.rv) + ', msg=' + str(self.msg) + ')') class PtlFailureException(AssertionError): """ Generic failure exception raised by PTL operations. Sets a ``return value``, a ``return code``, and a ``message`` A post function and associated positional and named arguments are available to perform any necessary cleanup. :param rv: Return value set for the failure occured during PTL operation :type rv: int or None. :param rc: Return code set for the failure occured during PTL operation :type rc: int or None. :param msg: Message set for the failure occured during PTL operation :type msg: str or None. :param post: Execute given post callable function if not None. :type post: callable or None. :raises: PTL exceptions """ def __init__(self, rv=None, rc=None, msg=None, post=None, *args, **kwargs): self.rv = rv self.rc = rc self.msg = msg if post is not None: post(*args, **kwargs) def __str__(self): return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) + ', msg=' + str(self.msg)) def __repr__(self): return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' + str(self.rv) + ', msg=' + str(self.msg) + ')') class PbsServiceError(PtlException): pass class PbsConnectError(PtlException): pass class PbsStatusError(PtlException): pass class PbsSubmitError(PtlException): pass class PbsManagerError(PtlException): pass class PbsDeljobError(PtlException): pass class PbsDelresvError(PtlException): pass class PbsDeleteError(PtlException): pass class PbsRunError(PtlException): pass class PbsSignalError(PtlException): pass class PbsMessageError(PtlException): pass class PbsHoldError(PtlException): pass class PbsReleaseError(PtlException): pass class PbsOrderError(PtlException): pass class PbsRerunError(PtlException): pass class PbsMoveError(PtlException): pass class PbsAlterError(PtlException): pass class PbsResourceError(PtlException): pass class PbsSelectError(PtlException): pass class PbsSchedConfigError(PtlException): pass class PbsMomConfigError(PtlException): pass class PbsFairshareError(PtlException): pass class PbsQdisableError(PtlException): pass class PbsQenableError(PtlException): pass class PbsQstartError(PtlException): pass class PbsQstopError(PtlException): pass class PtlExpectError(PtlFailureException): pass class PbsInitServicesError(PtlException): pass class PbsQtermError(PtlException): pass class PtlLogMatchError(PtlFailureException): pass class PbsResvAlterError(PtlException): pass class PbsTypeSize(str): """ Descriptor class for memory as a numeric entity. Units can be one of ``b``, ``kb``, ``mb``, ``gb``, ``tb``, ``pt`` :param unit: The unit type associated to the memory value :type unit: str :param value: The numeric value of the memory :type value: int or None :raises: ValueError and TypeError """ def __init__(self, value=None): if value is None: return if len(value) < 2: raise ValueError if value[-1:] in ('b', 'B') and value[:-1].isdigit(): self.unit = 'b' self.value = int(int(value[:-1]) / 1024) return # lower() applied to ignore case unit = value[-2:].lower() self.value = value[:-2] if not self.value.isdigit(): raise ValueError if unit == 'kb': self.value = int(self.value) elif unit == 'mb': self.value = int(self.value) * 1024 elif unit == 'gb': self.value = int(self.value) * 1024 * 1024 elif unit == 'tb': self.value = int(self.value) * 1024 * 1024 * 1024 elif unit == 'pb': self.value = int(self.value) * 1024 * 1024 * 1024 * 1024 else: raise TypeError self.unit = 'kb' def encode(self, value=None, valtype='kb', precision=1): """ Encode numeric memory input in kilobytes to a string, including unit :param value: The numeric value of memory to encode :type value: int or None. :param valtype: The unit of the input value, defaults to kb :type valtype: str :param precision: Precision of the encoded value, defaults to 1 :type precision: int :returns: Encoded memory in kb to string """ if value is None: value = self.value if valtype == 'b': val = value elif valtype == 'kb': val = value * 1024 elif valtype == 'mb': val = value * 1024 * 1024 elif valtype == 'gb': val = value * 1024 * 1024 * 1024 * 1024 elif valtype == 'tb': val = value * 1024 * 1024 * 1024 * 1024 * 1024 elif valtype == 'pt': val = value * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 m = ( (1 << 50, 'pb'), (1 << 40, 'tb'), (1 << 30, 'gb'), (1 << 20, 'mb'), (1 << 10, 'kb'), (1, 'b') ) for factor, suffix in m: if val >= factor: break return '%.*f%s' % (precision, float(val) / factor, suffix) def __cmp__(self, other): if self.value < other.value: return -1 if self.value == other.value: return 0 return 1 def __lt__(self, other): if self.value < other.value: return True return False def __le__(self, other): if self.value <= other.value: return True return False def __gt__(self, other): if self.value > other.value: return True return False def __ge__(self, other): if self.value < other.value: return True return False def __eq__(self, other): if self.value == other.value: return True return False def __get__(self): return self.value def __add__(self, other): if isinstance(other, int): self.value += other else: self.value += other.value return self def __mul__(self, other): if isinstance(other, int): self.value *= other else: self.value *= other.value return self def __floordiv__(self, other): self.value /= other.value return self def __sub__(self, other): self.value -= other.value return self def __repr__(self): return self.__str__() def __str__(self): return self.encode(valtype=self.unit) class PbsTypeDuration(str): """ Descriptor class for a duration represented as ``hours``, ``minutes``, and ``seconds``,in the form of ``[HH:][MM:]SS`` :param as_seconds: HH:MM:SS represented in seconds :type as_seconds: int :param as_str: duration represented in HH:MM:SS :type as_str: str """ def __init__(self, val): if isinstance(val, str): if ':' in val: s = val.split(':') l = len(s) if l > 3: raise ValueError hr = mn = sc = 0 if l >= 2: sc = s[l - 1] mn = s[l - 2] if l == 3: hr = s[0] self.duration = int(hr) * 3600 + int(mn) * 60 + int(sc) elif val.isdigit(): self.duration = int(val) elif isinstance(val, int) or isinstance(val, float): self.duration = val def __add__(self, other): self.duration += other.duration return self def __sub__(self, other): self.duration -= other.duration return self def __cmp__(self, other): if self.duration < other.duration: return -1 if self.duration == other.duration: return 0 return 1 def __lt__(self, other): if self.duration < other.duration: return True return False def __le__(self, other): if self.duration <= other.duration: return True return False def __gt__(self, other): if self.duration > other.duration: return True return False def __ge__(self, other): if self.duration < other.duration: return True return False def __eq__(self, other): if self.duration == other.duration: return True return False def __get__(self): return self.as_str def __repr__(self): return self.__str__() def __int__(self): return int(self.duration) def __str__(self): return str(datetime.timedelta(seconds=self.duration)) class PbsTypeArray(list): """ Descriptor class for a PBS array list type, e.g. String array :param value: Array value to be passed :param sep: Separator for two array elements :type sep: str :returns: List """ def __init__(self, value=None, sep=','): self.separator = sep self = list.__init__(self, value.split(sep)) def __str__(self): return self.separator.join(self) class PbsTypeList(dict): """ Descriptor class for a generic PBS list that are key/value pairs delimited :param value: List value to be passed :param sep: Separator for two key/value pair :type sep: str :param kvsep: Separator for key and value :type kvsep: str :returns: Dictionary """ def __init__(self, value=None, sep=',', kvsep='='): self.kvsep = kvsep self.separator = sep d = {} as_list = map(lambda v: v.split(kvsep), value.split(sep)) if as_list: for k, v in as_list: d[k] = v del as_list dict.__init__(self, d) def __str__(self): s = [] for k, v in self.items(): s += [str(k) + self.kvsep + str(v)] return self.separator.join(s) class PbsTypeLicenseCount(PbsTypeList): """ Descriptor class for a PBS license_count attribute. It is a specialized list where key/values are ':' delimited, separated by a ' ' (space) :param value: PBS license_count attribute value :returns: Specialized list """ def __init__(self, value=None): super(PbsTypeLicenseCount, self).__init__(value, sep=' ', kvsep=':') class PbsTypeVariableList(PbsTypeList): """ Descriptor class for a PBS Variable_List attribute It is a specialized list where key/values are '=' delimited, separated by a ',' (space) :param value: PBS Variable_List attribute value :returns: Specialized list """ def __init__(self, value=None): super(PbsTypeVariableList, self).__init__(value, sep=',', kvsep='=') class PbsTypeSelect(list): """ Descriptor class for PBS select/schedselect specification. Select is of the form: ``"+"