diff --git a/Script/CastorScript.py b/Script/CastorScript.py index bd397dd52deb9506edea888e25fce39f5f3dee70..3584adb4fbd92a58640d02e38710b10d2d4f9a9c 100755 --- a/Script/CastorScript.py +++ b/Script/CastorScript.py @@ -21,6 +21,7 @@ from time import time import threading import os.path from os import environ, getenv, umask +import datetime import signal import logging, logging.handlers from utils import set_log_level,formatter @@ -193,10 +194,16 @@ def main(conf): if conf.ERSenabled: check.start() logger.info('Manager,Copy, Delete and Check Threads started') + + ##### Setup Kerberos if needed ##### + if conf.keytab: + + krb_setcache(conf.krbcache,logger) + krb_exp = krb_updatetoken(conf.keytab, conf.krbuser, logger) ##### Every DBTimeout check if connection to Oracle database is still good ##### while not exitFlag: - #signal.pause() + logger.info('Check for connection to Metadata Database') #Keep always a fresh connection @@ -215,6 +222,7 @@ def main(conf): db = checkDB(db,logger,dblogger,parser) event.wait(DBTimeout) + ##### If update signal, update configuration ##### if confFlag: conf = Conf.Conf(sys.argv[1]) @@ -233,7 +241,12 @@ def main(conf): logger.info('Configuration updated') # end if - + # Update KRB token, if needed: + if conf.keytab: + now = datetime.datetime.now() + if now >= krb_exp or (krb_exp-now).seconds < 3600: + krb_exp = krb_updatetoken(conf.keytab, conf.krbuser, logger) + #Check worker states if not (manager.isAlive() and copy.isAlive() and delete.isAlive()): logger.warning('Inconsistent worker states. Manager-->%s Copy-->%s Delete-->%s. Exiting!' \ @@ -306,6 +319,49 @@ def checkDB(db,logger,dblogger,parser): # end checkDB() +def krb_setcache(krbcachefile, logger): + try: + os.environ['KRB5CCNAME'] = krbcachefile + except TypeError as ex: + logger.warning('KRB Cache file setting failed: %s ' % str(ex)) + + +def krb_updatetoken(keytab, user, logger): + kinit = Popen(['kinit','-kt',keytab,user], #'-l','3900s'], + stdout = PIPE, stderr = STDOUT) + ret = kinit.wait() + + if ret: + logger.warning('kinit failed. Ret code: %d Output: %s' % \ + (ret, kinit.stdout.read())) + return None + else: + logger.debug('kinit succeeded. Output: %s' % kinit.stdout.read()) + return krb_tokenexpiration(logger) + + + +def krb_tokenexpiration(logger): + + klist = Popen(['klist',], + stdout = PIPE, stderr = STDOUT) + ret = klist.wait() + + out = klist.stdout.read() + logger.debug('klist done. Return code: %d Output: %s' % (ret,out)) + + out = out.split('\n') + for idx,l in enumerate(out): + if 'Valid' in l: + ticket = out[idx+1] + break + + ticket = ticket.split() + logger.debug('Ticket expiration: %s' % ticket) + return datetime.datetime.strptime(' '.join(ticket[2:4]), \ + '%m/%d/%y %H:%M:%S') + + if __name__ == '__main__': main(conf) diff --git a/Script/Conf.cfg b/Script/Conf.cfg index b649013f825cb1ae9177f87c5db7b493905c55ff..0b6fa590ef2a5ab24db4f4fae8e5ea5caff5cd7c 100755 --- a/Script/Conf.cfg +++ b/Script/Conf.cfg @@ -78,6 +78,20 @@ BackendModule: 'castorstorage' # Backend specific parameters BackendModuleConf: {} +# Keytab file for Kerberos auth. None to skip +#A keytab file can be generated with: +#$> /usr/kerberos/sbin/ktutil +#ktutil: add_entry -password -p <username>@CERN.CH -k 1 -e arcfour-hmac-md5 +#ktutil: add_entry -password -p <username>@CERN.CH -k 1 -e aes256-cts +#ktutil: wkt <filename> +Keytab: None + +# Kerberos user corresponding to the above keytab +KrbUser: 'atlascdr' + +# Path to custom Kerberos cache file. None to use the default one +KrbCache: '/tmp/castor_krbcache' + #[Manager] ########## MANAGER THREAD ########## diff --git a/Script/Conf.py b/Script/Conf.py index 5dfe485fd4881b963cbb6ba7ac7fa6c269f9265b..9dc07a2a0ac20177a558f2b38a2c0ea09fbfd570 100755 --- a/Script/Conf.py +++ b/Script/Conf.py @@ -119,6 +119,7 @@ class Conf: try: self.backend = __import__(cfg.BackendModule, globals(), locals()) except AttributeError: + #Just for configuration backcompatibility self.backend = __import__('castorstorage', globals(), locals()) #Backend params @@ -127,6 +128,22 @@ class Conf: except AttributeError: pass + #Kerberos Auth + try: + self.keytab = cfg.Keytab + except AttributeError: + self.keytab = None + + try: + self.krbuser = cfg.KrbUser + except AttributeError: + self.krbuser = None + + try: + self.krbcache = cfg.KrbCache + except AttributeError: + self.krbcache = None + ########## MANAGER THREAD ########## diff --git a/Script/eosstorage.py b/Script/eosstorage.py index 7e566774b8d3212fa91e785231379adca718462c..7f23f28625876943ce222877a79954b7c6dd3a1d 100644 --- a/Script/eosstorage.py +++ b/Script/eosstorage.py @@ -2,18 +2,7 @@ """ Provides EOS backend functionalities. -The 'init' function requires one parameter - -'keytab':</path/to/keytabfile> - -A second paramter: - -'ticketfile':</path/to/keytabfile> - -is optional and indicates the location of the ticket file. - -See the 'init' documentation for more details about the keytab naming convention and generation. -The kerberos authentication is automatically renewed using the keytab file. The token expiration is fetched using kinit. +Tape migration is not supported by EOS. """ @@ -24,146 +13,58 @@ import castorstorage import sys import os.path import datetime - -def init(conf): - """ - Initialize the EOS backend module. Requires one parameter: - 'keytab':</path/to/keytabfile> - - The keytab file must follow this naming convention: - /path/to/<username>.keytab - - A keytab file can be generated with: - $> /usr/kerberos/sbin/ktutil - ktutil: add_entry -password -p <username>@CERN.CH -k 1 -e arcfour-hmac-md5 - ktutil: add_entry -password -p <username>@CERN.CH -k 1 -e aes256-cts - ktutil: wkt <username>.keytab - - A second, optional parameter: - - 'ticketfile':</path/to/keytabfile> - - indicates the desired location of the ticket storage file. - """ - - ticketfile = conf.get('ticketfile', None) - if ticketfile: - import os - os.environ['KRB5CCNAME'] = ticketfile - - keytabfile = conf['keytab'] - eos = eosstorage(keytabfile) - - globals()['_eos'] = eos - globals()['sizechecksum'] = eos._sizechecksum - globals()['remove'] = eos._remove - globals()['backgroundcopy'] = eos._backgroundcopy - -def _authcheck(method): - def wrapped(self, *args, **kwargs): - now = datetime.datetime.now() - diff = self.expiration - now - if now >= self.expiration or diff.seconds < 3600: - self._updateauth() - - return method(self,*args, **kwargs) - - return wrapped - - -class eosstorage(object): - - def __init__(self, keytab): - self.keytabfile = keytab - #keytabfile --> <username>.keytab - self.user = os.path.splitext(os.path.basename(self.keytabfile))[0] - self.expiration = None - self._updateauth() - +def sizechecksum(filename, stager, pool, logger=None): - def _updateauth(self): - #kinit -kt /where/ever/<user>.keytab <user> - kinit = Popen(['kinit','-kt',self.keytabfile, self.user],#'-l','100s', - stdout = PIPE, stderr = STDOUT) - ret = kinit.wait() + if logger: + logger.debug('Fetching checksum from eos for: ' + + filename) - if ret: - print 'kinit failed. Ret code: %d Output: %s' % \ - (ret, kinit.stdout.read()) - sys.exit(1) + + cmd=['xrdcp', 'root://%s//proc/user/' % stager, + '-', + '-OSmgm.cmd=fileinfo&mgm.path=%s&mgm.file.info.option=--checksum-m' % filename, + '-np'] + + xrd = Popen(cmd, stdout = PIPE, stderr = STDOUT) + ret = xrd.wait() + out = xrd.stdout.read() + + if not ret: + splitted = out.split(' ') + + size = int(splitted[1].split('=')[1]) + checksum = splitted[10].split('=')[1][:8] + else: + checksum=size=None + if logger: + logger.warning('Adler32 checksum or size not found for ' + + filename + ': ' + + out) - self._fetchexpiration() + return size,checksum - - def _fetchexpiration(self): - klist = Popen(['klist',], - stdout = PIPE, stderr = STDOUT) - ret = klist.wait() - - out = klist.stdout.read() - out = out.split('\n') - for idx,l in enumerate(out): - if 'Valid' in l: - ticket = out[idx+1] - - ticket = ticket.split() - self.expiration = datetime.datetime.strptime(' '.join(ticket[2:4]), \ - '%m/%d/%y %H:%M:%S') - - @_authcheck - def _sizechecksum(self, filename, stager, pool, logger=None): - if logger: - logger.debug('Fetching checksum from eos for: ' - + filename) +def remove(dstfile, stager, pool, logger=None): + xrd = Popen(['xrd',stager,'rm',dstfile], stdout = PIPE, + stderr = STDOUT) + ret = xrd.wait() - cmd=['xrdcp', 'root://%s//proc/user/' % stager, - '-', - '-OSmgm.cmd=fileinfo&mgm.path=%s&mgm.file.info.option=--checksum-m' % filename, - '-np'] - - xrd = Popen(cmd, stdout = PIPE, stderr = STDOUT) - ret = xrd.wait() - out = xrd.stdout.read() - - if not ret: - splitted = out.split(' ') - - size = int(splitted[1].split('=')[1]) - checksum = splitted[10].split('=')[1][:8] - else: - checksum=size=None - if logger: - logger.warning('Adler32 checksum or size not found for ' - + filename + ': ' - + out) - - return size,checksum - - @_authcheck - def _remove(self, dstfile, stager, pool, logger=None): - - xrd = Popen(['xrd',stager,'rm',dstfile], stdout = PIPE, - stderr = STDOUT)#, env=castorstorage._castorenv(stager,pool)) - ret = xrd.wait() - - if logger: - logger.debug(xrd.stdout.read()) + if logger: + logger.debug(xrd.stdout.read()) - return ret + return ret - @_authcheck - def _backgroundcopy(self, srcfile, dstfile, stager, pool, logger=None): +def backgroundcopy(srcfile, dstfile, stager, pool, logger=None): - dstfile = 'root://%s/%s' % (stager, dstfile) - cmd = ['xrdcp', '-f', srcfile, dstfile,'-ODsvcClass=%s' % pool] - xrdcp = Popen(cmd, stdout = PIPE, stderr = STDOUT) + dstfile = 'root://%s/%s' % (stager, dstfile) + cmd = ['xrdcp', '-f', srcfile, dstfile,'-ODsvcClass=%s' % pool] + xrdcp = Popen(cmd, stdout = PIPE, stderr = STDOUT) - return xrdcp, xrdcp.pid + return xrdcp, xrdcp.pid def mkdir(directory, stager, pool, logger=None):