diff --git a/debian/castor-dbtools.install.perm b/debian/castor-dbtools.install.perm index b5394580f5a9d5ed6dfed9ee8feaa24de625aeb8..e37c9c41e5bdfc0e15f79b6b732522b022cd49bd 100644 --- a/debian/castor-dbtools.install.perm +++ b/debian/castor-dbtools.install.perm @@ -8,6 +8,10 @@ %attr(0755,root,root) usr/bin/enterdiskpool %attr(0755,root,root) usr/bin/deletediskpool %attr(0755,root,root) usr/bin/printdiskpool +%attr(0755,root,root) usr/bin/enterrecallgroup +%attr(0755,root,root) usr/bin/modifyrecallgroup +%attr(0755,root,root) usr/bin/deleterecallgroup +%attr(0755,root,root) usr/bin/printrecallgroup %attr(0755,root,root) usr/bin/entertapepool %attr(0755,root,root) usr/bin/modifytapepool %attr(0755,root,root) usr/bin/deletetapepool diff --git a/debian/castor-dbtools.manpages b/debian/castor-dbtools.manpages index 07a2ee3bdfc70f1aeae00cf5e2d4eb2e2d364c73..5d28e863e3646513dbd5244709cbd17330fb13b0 100644 --- a/debian/castor-dbtools.manpages +++ b/debian/castor-dbtools.manpages @@ -8,6 +8,10 @@ debian/castor/usr/share/man/man1/printsvcclass.1castor.gz debian/castor/usr/share/man/man1/enterdiskpool.1castor.gz debian/castor/usr/share/man/man1/deletediskpool.1castor.gz debian/castor/usr/share/man/man1/printdiskpool.1castor.gz +debian/castor/usr/share/man/man1/enterrecallgroup.1castor.gz +debian/castor/usr/share/man/man1/modifyrecallgroup.1castor.gz +debian/castor/usr/share/man/man1/deleterecallgroup.1castor.gz +debian/castor/usr/share/man/man1/printrecallgroup.1castor.gz debian/castor/usr/share/man/man1/entertapepool.1castor.gz debian/castor/usr/share/man/man1/modifytapepool.1castor.gz debian/castor/usr/share/man/man1/deletetapepool.1castor.gz diff --git a/hsmtools/Imakefile b/hsmtools/Imakefile index 28cc5ad6181a188921380677ab1f311a9336324b..3b486fc8199213fab2498b7dd9319b3ac0fde499 100644 --- a/hsmtools/Imakefile +++ b/hsmtools/Imakefile @@ -42,6 +42,11 @@ InstallTarget(enterdiskpool,755) InstallTarget(deletediskpool,755) InstallTarget(printdiskpool,755) +InstallTarget(enterrecallgroup,755) +InstallTarget(modifyrecallgroup,755) +InstallTarget(deleterecallgroup,755) +InstallTarget(printrecallgroup,755) + InstallTarget(entertapepool,755) InstallTarget(modifytapepool,755) InstallTarget(deletetapepool,755) @@ -80,6 +85,11 @@ EXEMANPAGE(enterdiskpool) EXEMANPAGE(deletediskpool) EXEMANPAGE(printdiskpool) +EXEMANPAGE(enterrecallgroup) +EXEMANPAGE(modifyrecallgroup) +EXEMANPAGE(deleterecallgroup) +EXEMANPAGE(printrecallgroup) + EXEMANPAGE(entertapepool) EXEMANPAGE(modifytapepool) EXEMANPAGE(deletetapepool) diff --git a/hsmtools/deleterecallgroup b/hsmtools/deleterecallgroup new file mode 100755 index 0000000000000000000000000000000000000000..3035c3a5ce9cc7866b4f39643c35d4517c65489d --- /dev/null +++ b/hsmtools/deleterecallgroup @@ -0,0 +1,129 @@ +#!/usr/bin/python +#/****************************************************************************** +# * deleterecallgroup +# * +# * This file is part of the Castor project. +# * See http://castor.web.cern.ch/castor +# * +# * Copyright (C) 2003 CERN +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License +# * as published by the Free Software Foundation; either version 2 +# * of the License, or (at your option) any later version. +# * This program 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 General Public License for more details. +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# * +# * @author Castor Dev team, castor-dev@cern.ch +# *****************************************************************************/ + +'''command line deleting the recall group(s)''' + +import sys +import getopt +import castor_tools + +# usage function +def usage(exitCode): + '''prints usage''' + print 'Usage : ' + sys.argv[0] + ' [-h|--help] <recallGroupName>[:...]' + sys.exit(exitCode) + +# first parse the options +try: + options, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'verbose']) +except Exception, e: + print e + usage(1) +verbose = False +for f, v in options: + if f == '-h' or f == '--help': + usage(0) + elif f == '-v' or f == '--verbose': + verbose = True + else: + print "unknown option : " + f + usage(1) + +# Deal with arguments +recallGroups = set([]) +if len(args) != 0: + recallGroups = set(args[0].split(':')) +else: + print "Missing arguments" + usage(1) + +try: + # connect to stager + stconn = castor_tools.connectToStager() + stcur = stconn.cursor() + # check tape pool existence + stcur.execute("SELECT name FROM RecallGroup WHERE name IN ('" + "', '".join(recallGroups) + "')") + existingRecallGroups = set([row[0] for row in stcur.fetchall()]) + nonexitingRecallGroups = recallGroups - existingRecallGroups + # forbid deletion of the default group + if 'default' in existingRecallGroups: + print 'Recall group default should be deleted. Giving up' + sys.exit(1) + # check that the recall groups are not containing any user + stcur.execute('''SELECT RecallGroup.name, count(*) + FROM RecallUser, RecallGroup + WHERE RecallUser.recallGroup = RecallGroup.id + AND RecallGroup.name IN (\'''' + "', '".join(existingRecallGroups) + "') " + ''' + GROUP BY RecallGroup.name''') + rows = stcur.fetchall() + if len(rows) > 0: + print 'RecallGroups cannot be dropped when they still contain users :' + for rg, nb in rows: + print ' - recall group %s contains %d user(s)' % (rg, nb) + print 'You may want to use deleterecalluser or modifyrecalluser to correct this' + sys.exit(1) + # check that the tape pools are not used by any ongoing RecallJob + stcur.execute('''SELECT RecallGroup.name, count(*) + FROM RecallJob, RecallGroup + WHERE RecallJob.recallGroup = RecallGroup.id + AND RecallGroup.name IN (\'''' + "', '".join(existingRecallGroups) + '''\') + GROUP BY RecallGroup.name''') + rows = stcur.fetchall() + if len(rows) > 0: + print 'RecallGroups cannot be dropped when used by some recall jobs :' + for tp, nb in rows: + print ' - recall group %s is in use by %d recall jobs' % (tp, nb) + print 'You probably have to wait that the recall of these files is over' + sys.exit(1) + # check that the tape pools are not used by any ongoing RecallMount + stcur.execute('''SELECT RecallGroup.name, count(*) + FROM RecallMount, RecallGroup + WHERE RecallMount.recallGroup = RecallGroup.id + AND RecallGroup.name IN (\'''' + "', '".join(existingRecallGroups) + '''\') + GROUP BY RecallGroup.name''') + rows = stcur.fetchall() + if len(rows) > 0: + print 'RecallGroups cannot be dropped when used by some recall mount :' + for tp, nb in rows: + print ' - recall group %s is in use by %d recall mounts' % (tp, nb) + print 'You probably have to wait that the mount is over. Note that there was no RecallJob left so it should be a matter of seconds' + sys.exit(1) + # drop recall group + if existingRecallGroups: + stcur.execute("DELETE FROM RecallGroup WHERE name IN ('" + "', '".join(existingRecallGroups) + "')") + stconn.commit() + print 'successfully dropped the following recall groups : ' + ', '.join(existingRecallGroups) + # warn for non existing tape pools + if nonexitingRecallGroups: + print 'WARNING : some recall group(s) did not exist : ' + ', '.join(nonexitingRecallGroups) + # close DB connections + try: + castor_tools.disconnectDB(stconn) + except Exception: + pass +except Exception, e: + print e + if verbose: + import traceback + traceback.print_exc() + sys.exit(-1) diff --git a/hsmtools/deleterecallgroup.man b/hsmtools/deleterecallgroup.man new file mode 100644 index 0000000000000000000000000000000000000000..6c30f05c9e77e2793c2fc313db9fcc937502a972 --- /dev/null +++ b/hsmtools/deleterecallgroup.man @@ -0,0 +1,77 @@ +.TH DELETERECALLGROUP 1 "2011" CASTOR "stager catalogue administrative commands" +.SH NAME +deleterecallgroup \- deletes existing recall groups from the stager catalogue + +.SH SYNOPSIS +.B deleterecallgroup +[ +.BI -h +] +.BI <recallGroupName> +[:...] + +.SH DESCRIPTION +.B deleterecallgroup +deletes existing recall groups from the CASTOR stager catalog +.LP +.BI \-h,\ \-\-help +Get usage information +.TP +.BI <recallGroupName> +name of recall groups to delete. Several names can be given, colon separated + +.SH EXAMPLES +.nf +.ft CW +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +----------------------------------------------------------------------------------------------------------------------- + newrecallgroup 0 100GiB 1000 12h 0 263333 root 30-May-2012 17:29:12 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 +testrecallgroup 2 23MiB 1000 12h23mn 456 263419 root 30-May-2012 17:31:11 + +# deleterecallgroup newrecallgroup +successfully dropped the following recall groups : newrecallgroup + +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +--------------------------------------------------------------------------------------------------------------------------- + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 +testrecallgroup 2 23MiB 1000 12h23mn 456 263419 root 30-May-2012 17:31:11 + +# deleterecallgroup newrecallgroup:testrecallgroup +successfully dropped the following recall groups : testrecallgroup +WARNING : some recall group(s) did not exist : newrecallgroup + +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +------------------------------------------------------------------------------------------------------------------- +default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 + +# deleterecallgroup default +Recall group default should be deleted. Giving up + +In case a user still belongs to a recall group, you may get this : + +# deleterecallgroup testrecallgroup +RecallGroups cannot be dropped when they still contain users : + - recall group testrecallgroup contains 1 users +You may want to use deleterecalluser or modifyrecalluser to correct this + +.SH NOTES +This command requires database client access to the stager catalogue. +Configuration for the database access is taken from castor.conf. + +.SH SEE ALSO +.BR enterrecallgroup, +.BR modifyrecallgroup, +.BR printrecallgroup, +.BR deletediskpool, +.BR deletesvcclass, +.BR deletetapepool, +.BR deletefileclass, +.BR deletemigrationroute, +.BR adminMultiInstance + +.SH AUTHOR +\fBCASTOR\fP Team <castor.support@cern.ch> diff --git a/hsmtools/enterrecallgroup b/hsmtools/enterrecallgroup new file mode 100755 index 0000000000000000000000000000000000000000..647aaed16aee06a60f758685b095c0e5b088b9e2 --- /dev/null +++ b/hsmtools/enterrecallgroup @@ -0,0 +1,161 @@ +#!/usr/bin/python +#/****************************************************************************** +# * enterrecallgroup +# * +# * This file is part of the Castor project. +# * See http://castor.web.cern.ch/castor +# * +# * Copyright (C) 2003 CERN +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License +# * as published by the Free Software Foundation; either version 2 +# * of the License, or (at your option) any later version. +# * This program 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 General Public License for more details. +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# * +# * @author castor dev team +# *****************************************************************************/ + +'''allows to enter a new recall group into the castor stager''' + +import sys, re, os, pwd +import getopt +import castor_tools + +# usage function +def usage(exitCode): + '''prints usage''' + print 'Usage : ' + sys.argv[0] + ' [-h|--help] [--nbdrives <nbDrives>] ' + \ + '[--minamountdata <minAmountDataForAMount>] [--minnbfiles <minNbFilesForAMount>] ' + \ + '[--maxfileage <maxFileAgeBeforeForcedMount>] [--priority <VDQMPriority>] <recallGroupName>' + sys.exit(exitCode) + +def parsePositiveInt(name, svalue): + '''parses a positive int value and exits with proper error message in case the value does not fit''' + try: + value = int(svalue) + if value < 0: + raise ValueError + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +def parseDataAmount(name, svalue): + '''parses a value describing an amount of data. Exits with proper error message in case the value does not fit''' + try: + exts = {'K' : 1000, 'M' : 1000*1000, 'G' : 1000*1000*1000, 'T' : 1000*1000*1000*1000, + 'Ki' : 1024, 'Mi' : 1024*1024, 'Gi' : 1024*1024*1024, 'Ti' : 1024*1024*1024*1024} + regexp = re.compile('^(?P<nb>\d+)(?P<ext>(?:[KMGT]i?)?)B?$') + m = regexp.match(svalue) + if not m: + raise ValueError + value = int(m.group('nb')) + if m.group('ext'): + value *= exts[m.group('ext')] + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +def parseTimeDuration(name, svalue): + '''parses a value describing a time duration. Exits with proper error message in case the value does not fit''' + try: + # check validity + if not re.compile('^(\d+(s|mn?|h|d)?)+$').match(svalue): + raise ValueError + # parse + exts = {'s' : 1, 'm' : 60, 'mn' : 60, 'h' : 3600, 'd' : 86400} + regexp = re.compile('(\d+)(s|mn?|h|d)?') + value = 0 + for nb, ext in regexp.findall(svalue): + partvalue = int(nb) + if ext: + partvalue *= exts[ext] + value += partvalue + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +# first parse the options +try: + options, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'verbose', 'nbdrives=', 'minamountdata=', + 'minnbfiles=', 'maxfileage=', 'priority=']) +except Exception, e: + print e + usage(1) +verbose = False +nbdrives = 0 +minamountdata = 100*1024*1024*1024 # 100GiB +minnbfiles = 1000 +maxfileage = 43200 # 12h +priority = 0 +for f, v in options: + if f == '-h' or f == '--help': + usage(0) + elif f == '-v' or f == '--verbose': + verbose = True + elif f == '--nbdrives': + nbdrives = parsePositiveInt('nbdrives', v) + elif f == '--minamountdata': + minamountdata = parseDataAmount('minamountdata', v) + elif f == '--minnbfiles': + minnbfiles = parsePositiveInt('minnbfiles', v) + elif f == '--maxfileage': + maxfileage = parseTimeDuration('maxfileage', v) + elif f == '--priority': + priority = parsePositiveInt('priority', v) + else: + print "unknown option : " + f + usage(1) + +# Deal with arguments +if len(args) == 0: + print "Missing arguments" + usage(1) +elif len(args) > 1: + print "Too many arguments" + usage(1) +recallGroupName = args[0] + +try: + # connect to stager + stconn = castor_tools.connectToStager() + stcur = stconn.cursor() + # check whether the recall group already exists + stcur.execute('SELECT id FROM RecallGroup WHERE name=:name', name=recallGroupName) + rows = stcur.fetchall() + if len(rows) != 0: + print 'RecallGroup %s already exists in the stager DB' % recallGroupName + print 'You may want to use modifyrecallgroup' + sys.exit(1) + # get info on user running this command + lasteditor = pwd.getpwuid(os.getuid())[0] + # insert new recallGroup + stcur.execute('''INSERT INTO RecallGroup + (name, nbDrives, minAmountDataForMount, minNbFilesForMount, maxFileAgeBeforeMount, + VDQMPriority, lastEditor, lastEditionTime, id) + VALUES + (:name, :nbdrives, :minamountdata, :minnbfiles, :maxfileage, + :priority, :lasteditor, gettime(), ids_seq.nextval)''', + name=recallGroupName, nbdrives=nbdrives, minamountdata=minamountdata, + minnbfiles=minnbfiles, maxfileage=maxfileage, priority=priority, lasteditor=lasteditor) + stconn.commit() + print 'inserted recall group %s successfully' % recallGroupName + # close DB connection + try: + castor_tools.disconnectDB(stconn) + except Exception: + pass +except Exception, e: + print e + if verbose: + import traceback + traceback.print_exc() + sys.exit(-1) diff --git a/hsmtools/enterrecallgroup.man b/hsmtools/enterrecallgroup.man new file mode 100644 index 0000000000000000000000000000000000000000..5e0c7ac30a138ed6f529229a609af7a6065292b5 --- /dev/null +++ b/hsmtools/enterrecallgroup.man @@ -0,0 +1,108 @@ +.TH ENTERRECALLGROUP 1 "2011" CASTOR "stager catalog administrative commands" +.SH NAME +enterrecallgroup \- enter a new tape pool in the stager catalog +.SH SYNOPSIS +.B enterrecallgroup +[ +.BI -h +] +[ +.BI --nbdrives +<nbDrives> +] +[ +.BI --minamountdata +.B <minAmountDataForAMount> +] +[ +.BI --minnbfiles +.B <minNbFilesForAMount> +] +[ +.BI --maxfileage +.B <maxFileAgeBeforeForcedMount> +] +[ +.BI --priority +.B <VDQMPriority> +] +.BI <recallGroupName> +.SH DESCRIPTION +.B enterrecallgroup +enters a new recall group in the CASTOR stager catalog. + +The parameters of the recall group define the policy used to trigger tape mounts in case users of this group want to recall some files. +The behavior is the following : + - never more than <nbDrives> drives can be used concurrently + - a new tape will be mounted whenever we have either more than <minAmountDataForAMount> of data or <minNbFilesForAMount> files waiting for recall. In practice, if some migrations are already running, a new one is trigerred if one of these 2 numbers will still be exceeded per mount, even with the new mount. + - a tape mount will be forced if nothing is mounted and one file becomes older than maxFileAgeBeforeForcedMount + - if a tape mount is triggered, VDQM will be called to get a drive with the given priority +.TP +.BI \-h,\ \-\-help +Get usage information +.TP +.BI \-\-nbdrives <nbDrives> +The maximum number of tape drives this tape pool is allowed to use concurrently +for migration. Default is 0 is not provided +.TP +.BI \-\-minamountdata <minAmountDataForAMount> +The minimum amount of data needed to trigger a new mount in bytes. Default is 100GiB if not provided. +The value can be given using the standard K/M/G/T extensions, with or without B (i.e. both KB and K are accepted). +These have the ISO meaning of powers of 10. Ki/Mi/Gi/Ti[B] extensions are also accepted and deal with powers of 2. +.TP +.BI \-\-minnbfiles <minNbFilesForAMount> +The minimum number of files needed to trigger a new mount. Default is 1000 if not provided. +.TP +.BI \-\-maxfileage <maxFileAgeBeforeForcedMount> +The maximum age of a file before a new mount is triggered. Default is 12h if not provided. +The value can be given using the extensions s/m/mn/h/d for seconds, minutes(both m and mn), hours and days. +.TP +.BI \-\-priority <VDQMpriority> +The priority to be used when accessing VDQM to get a drive. Default is 0 if not provided. +.TP +.BI <recallGroupName> +name of a tape pool to create. + +.SH EXAMPLES +.nf +.ft CW +# enterrecallgroup newrecallgroup +inserted recall group newrecallgroup successfully + +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +---------------------------------------------------------------------------------------------------------------------- +newrecallgroup 0 100GiB 1000 12h 0 263333 root 30-May-2012 17:29:12 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 + +# enterrecallgroup --nbdrives 2 newrecallgroup +Recallgroup newrecallgroup already exists in the stager DB +You may want to use modifyrecallgroup + +# enterrecallgroup --nbdrives 2 --minamountdata 23MiB --maxfileage 12h23mn --priority 456 testrecallgroup +inserted recall group testrecallgroup successfully + +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +----------------------------------------------------------------------------------------------------------------------- + newrecallgroup 0 100GiB 1000 12h 0 263333 root 30-May-2012 17:29:12 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 +testrecallgroup 2 23MiB 1000 12h23mn 456 263419 root 30-May-2012 17:31:11 + +.SH NOTES +This command requires database client access to the stager catalog and nameserver DBs. +Configuration for the database accesses is taken from castor.conf. + +.SH SEE ALSO +.BR deleterecallgroup, +.BR modifyrecallgroup, +.BR printrecallgroup, +.BR enterdiskpool, +.BR entersvcclass, +.BR enterfileclass, +.BR entertapepool, +.BR entermigrationroute, +.BR adminMultiInstance + +.SH AUTHOR +\fBCASTOR\fP Team <castor.support@cern.ch> diff --git a/hsmtools/modifyrecallgroup b/hsmtools/modifyrecallgroup new file mode 100755 index 0000000000000000000000000000000000000000..861d22c07c70a4249532efbb2ced93988c099ff3 --- /dev/null +++ b/hsmtools/modifyrecallgroup @@ -0,0 +1,172 @@ +#!/usr/bin/python +#/****************************************************************************** +# * modifyrecallgroup +# * +# * This file is part of the Castor project. +# * See http://castor.web.cern.ch/castor +# * +# * Copyright (C) 2003 CERN +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License +# * as published by the Free Software Foundation; either version 2 +# * of the License, or (at your option) any later version. +# * This program 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 General Public License for more details. +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# * +# * @author castor dev team +# *****************************************************************************/ + +'''allows to modify an existing recall group in the castor stager''' + +import sys, re, os, pwd +import getopt +import castor_tools + +# usage function +def usage(exitCode): + '''prints usage''' + print 'Usage : ' + sys.argv[0] + ' [-h|--help] [--nbdrives <nbDrives>] ' + \ + '[--minamountdata <minAmountDataForAMount>] [--minnbfiles <minNbFilesForAMount>] ' + \ + '[--maxfileage <maxFileAgeBeforeForcedMount>] [--priority <VDQMPriority>] <recallGroupName>' + sys.exit(exitCode) + +def parsePositiveInt(name, svalue): + '''parses a positive int value and exits with proper error message in case the value does not fit''' + try: + value = int(svalue) + if value < 0: + raise ValueError + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +def parseDataAmount(name, svalue): + '''parses a value describing an amount of data. Exits with proper error message in case the value does not fit''' + try: + exts = {'K' : 1000, 'M' : 1000*1000, 'G' : 1000*1000*1000, 'T' : 1000*1000*1000*1000, + 'Ki' : 1024, 'Mi' : 1024*1024, 'Gi' : 1024*1024*1024, 'Ti' : 1024*1024*1024*1024} + regexp = re.compile('^(?P<nb>\d+)(?P<ext>(?:[KMGT]i?)?)B?$') + m = regexp.match(svalue) + if not m: + raise ValueError + value = int(m.group('nb')) + if m.group('ext'): + value *= exts[m.group('ext')] + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +def parseTimeDuration(name, svalue): + '''parses a value describing a time duration. Exits with proper error message in case the value does not fit''' + try: + # check validity + if not re.compile('^(\d+(s|mn?|h|d)?)+$').match(svalue): + raise ValueError + # parse + exts = {'s' : 1, 'm' : 60, 'mn' : 60, 'h' : 3600, 'd' : 86400} + regexp = re.compile('(\d+)(s|mn?|h|d)?') + value = 0 + for nb, ext in regexp.findall(svalue): + partvalue = int(nb) + if ext: + partvalue *= exts[ext] + value += partvalue + return value + except ValueError: + print 'Invalid %s %s' % (name, svalue) + usage(1) + +# first parse the options +try: + options, args = getopt.getopt(sys.argv[1:], 'hvm:', ['help', 'verbose', 'nbdrives=', 'minamountdata=', + 'minnbfiles=', 'maxfileage=', 'priority=']) +except Exception, e: + print e + usage(1) +verbose = False +nbdrives = None +minamountdata = None +minnbfiles = None +maxfileage = None +priority = None +for f, v in options: + if f == '-h' or f == '--help': + usage(0) + elif f == '-v' or f == '--verbose': + verbose = True + elif f == '--nbdrives': + nbdrives = parsePositiveInt('nbdrives', v) + elif f == '--minamountdata': + minamountdata = parseDataAmount('minamountdata', v) + elif f == '--minnbfiles': + minnbfiles = parsePositiveInt('minnbfiles', v) + elif f == '--maxfileage': + maxfileage = parseTimeDuration('maxfileage', v) + elif f == '--priority': + priority = parsePositiveInt('priority', v) + else: + print "unknown option : " + f + usage(1) + +# Deal with arguments +if len(args) == 0: + print "Missing arguments" + usage(1) +elif len(args) > 1: + print "Too many arguments" + usage(1) +else: + recallgroupname = args[0] + +# check we have something to do +if nbdrives == None and minamountdata == None and minnbfiles == None and maxfileage == None: + print 'Nothing to modify. Did you forget some argument ?' + usage(1) + +try: + # connect to stager + stconn = castor_tools.connectToStager() + stcur = stconn.cursor() + # check that the recall group exists + stcur.execute('SELECT id FROM RecallGroup WHERE name=:name', name=recallgroupname) + rows = stcur.fetchall() + if len(rows) == 0: + print 'RecallGroup %s does not exist in the stager DB' % recallgroupname + print 'Please use enterrecallgroup if you would like to create it' + sys.exit(1) + recallGroupId = rows[0][0] + # deal with the update + if nbdrives != None: + stcur.execute('UPDATE RecallGroup SET nbDrives=:value WHERE id = :recallGroupId', value=nbdrives, recallGroupId=recallGroupId) + if minamountdata != None: + stcur.execute('UPDATE RecallGroup SET minAmountdataForMount=:value WHERE id = :recallGroupId', value=minamountdata, recallGroupId=recallGroupId) + if minnbfiles != None: + stcur.execute('UPDATE RecallGroup SET minNbFilesForMount=:value WHERE id = :recallGroupId', value=minnbfiles, recallGroupId=recallGroupId) + if maxfileage != None: + stcur.execute('UPDATE RecallGroup SET maxFileAgeBeforeMount=:value WHERE id = :recallGroupId', value=maxfileage, recallGroupId=recallGroupId) + if priority != None: + stcur.execute('UPDATE RecallGroup SET VDQMPriority=:value WHERE id = :recallGroupId', value=priority, recallGroupId=recallGroupId) + # in all cases, update the lastEditor and lastEditionTime + lasteditor = pwd.getpwuid(os.getuid())[0] + stcur.execute('UPDATE RecallGroup SET lastEditor=:lasteditor, lastEditionTime=gettime() WHERE id = :recallGroupId', lasteditor=lasteditor, recallGroupId=recallGroupId) + # commit insertion and tell user + stconn.commit() + print 'modified recall group %s successfully' % recallgroupname + # close DB connections + try: + castor_tools.disconnectDB(stconn) + except Exception: + pass +except Exception, e: + print e + if verbose: + import traceback + traceback.print_exc() + sys.exit(-1) diff --git a/hsmtools/modifyrecallgroup.man b/hsmtools/modifyrecallgroup.man new file mode 100644 index 0000000000000000000000000000000000000000..4f50b1c045eee97fa5ebab5269d2481206b87554 --- /dev/null +++ b/hsmtools/modifyrecallgroup.man @@ -0,0 +1,99 @@ +.TH MODIFYRECALLGROUP 1 "2011" CASTOR "stager catalogue administrative commands" +.SH NAME +modifyrecallgroup \- modifies an existing recall group in the stager catalog +.SH SYNOPSIS +.B modifyrecallgroup +[ +.BI -h +] +[ +.BI --nbdrives +<nbDrives> +] +[ +.BI --minamountdata +.B <minAmountDataForAMount> +] +[ +.BI --minnbfiles +.B <minNbFilesForAMount> +] +[ +.BI --maxfileage +.B <maxFileAgeBeforeForcedMount> +] +[ +.BI --priority +.B <VDQMpriority> +] +<recallGroupName> + +.SH DESCRIPTION +.B modifyrecallgroup +modifies an existing recall group in the CASTOR stager catalog + +The parameters of the recallgroup define the policy used to trigger tape mounts for recalls. +The behavior is the following : + - never more than <nbDrives> drives can be used concurrently + - a new tape will be mounted whenever we have either more than <minAmountDataForAMount> of data or <minNbFilesForAMount> files waiting for recall. In practice, if some migrations are already running, a new one is trigerred if one of these 2 numbers will still be exceeded per mount, even with the new mount. + - a tape mount will be forced if nothing is mounted and one file becomes older than maxFileAgeBeforeForcedMount + - if a tape mount is triggered, VDQM will be called to get a drive with the given priority +.TP +.BI \-h,\ \-\-help +Get usage information +.TP +.BI \-\-nbdrives <nbDrives> +The maximum number of tape drives this recall group is allowed to use concurrently +for migration. +.TP +.BI \-\-minamountdata <minAmountDataForAMount> +The minimum amount of data needed to trigger a new mount in bytes. +The value can be given using the standard K/M/G/T extensions, with or without B (i.e. both KB and K are accepted). +These have the ISO meaning of powers of 10. Ki/Mi/Gi/Ti[B] extensions are also accepted and deal with powers of 2. +.TP +.BI \-\-minnbfiles <minNbFilesForAMount> +The minimum number of files needed to trigger a new mount. +.TP +.BI \-\-maxfileage <maxFileAgeBeforeForcedMount> +The maximum age of a file before a new mount is triggered. +The value can be given using the extensions s/m/mn/h/d for seconds, minutes(both m and mn), hours and days. +.TP +.BI \-\-priority <VDQMpriority> +The priority to be used when accessing VDQM to get a drive. +.TP +.BI <tapePoolName> +name of the recall group to modify. + +.SH EXAMPLES +.nf +.ft CW +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +---------------------------------------------------------------------------------------------------------------------- +newrecallgroup 0 100GiB 1000 12h 0 263333 root 30-May-2012 17:29:12 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 + +# modifyrecallgroup --nbdrives 1 --minamountdata 57MiB --minnbfiles 234 --maxfileage 1d5h --priority 2 newrecallgroup +modified recall group newrecallgroup successfully + +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +---------------------------------------------------------------------------------------------------------------------- +newrecallgroup 1 57MiB 234 1d5h 2 263333 root 06-Jun-2012 09:35:11 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 + +.SH NOTES +This command requires database client access to the stager catalog and nameserver DBs. +Configuration for the database accesses is taken from castor.conf. + +.SH SEE ALSO +.BR deleterecallgroup, +.BR enterrecallgroup, +.BR printrecallgroup, +.BR modifytapepool +.BR modifysvcclass, +.BR modifymigrationroute, +.BR adminMultiInstance + +.SH AUTHOR +\fBCASTOR\fP Team <castor.support@cern.ch> diff --git a/hsmtools/printrecallgroup b/hsmtools/printrecallgroup new file mode 100755 index 0000000000000000000000000000000000000000..3a6c35d027646d10c6162f3942caf7fe69da2d38 --- /dev/null +++ b/hsmtools/printrecallgroup @@ -0,0 +1,137 @@ +#!/usr/bin/python +#/****************************************************************************** +# * printrecallgroup +# * +# * This file is part of the Castor project. +# * See http://castor.web.cern.ch/castor +# * +# * Copyright (C) 2003 CERN +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License +# * as published by the Free Software Foundation; either version 2 +# * of the License, or (at your option) any later version. +# * This program 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 General Public License for more details. +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# * +# * @author Castor Dev team, castor-dev@cern.ch +# *****************************************************************************/ + +'''command line printing the given recall group(s)''' + +import sys +import getopt, time +import castor_tools + +# usage function +def usage(exitCode): + '''prints usage''' + print 'Usage : ' + sys.argv[0] + ' [-h|--help] [<recallGroupName> [...]]' + sys.exit(exitCode) + +def secsToDate(s): + '''converts number of seconds since the epoch into readable date''' + return time.strftime('%d-%b-%Y %H:%M:%S', time.localtime(s)) + +def nbToDataAmount(n): + '''converts a number into a readable amount of data''' + ext = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] + magn = 0 + while n / 1024 > 5: + magn += 1 + n = n / 1024 + return str(n) + ext[magn] + +def nbToAge(n): + '''converts a number of seconds into a readable age''' + s = '' + if n >= 86400: + s = s + str(n/86400) + 'd' + n = n % 86400 + if n >= 3600: + s = s + str(n/3600) + 'h' + n = n % 3600 + if n >= 60: + s = s + str(n/60) + 'mn' + n = n % 60 + if n > 0: + s = s + str(n) + 's' + if len(s) == 0: + s = '0s' + return s + +# first parse the options +try: + options, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'verbose']) +except Exception, e: + print e + usage(1) +verbose = False +for f, v in options: + if f == '-h' or f == '--help': + usage(0) + elif f == '-v' or f == '--verbose': + verbose = True + else: + print "unknown option : " + f + usage(1) + +# Deal with arguments +recallGroups = None +if len(args) != 0: + recallGroups = set(args) + +try: + # connect to stager and prepare statements + stconn = castor_tools.connectToStager() + stcur = stconn.cursor() + sqlStatement = ''' + SELECT name, nbDrives, minAmountDataForMount, minNbFilesForMount, maxFileAgeBeforeMount, VDQMPriority, + lastEditor, lastEditionTime, id + FROM RecallGroup''' + if recallGroups: + sqlStatement = sqlStatement + " WHERE name IN ('" + "', '".join(recallGroups) + "')" + stcur.execute(sqlStatement) + # get results + rows = stcur.fetchall() + existingRecallGroups = set([row[0] for row in rows]) + if recallGroups: + unknownRecallGroups = recallGroups - existingRecallGroups + # loop over tape pools and print them + if existingRecallGroups: + maxNameLen = max([4] + [len(row[0]) for row in rows]) + maxMinAmountDataLen = max([13] + [len(nbToDataAmount(row[2])) for row in rows]) + maxMinNbFilesLen = max([10] + [len(str(row[3])) for row in rows]) + maxMaxFileAgeLen = max([10] + [len(nbToAge(row[4])) for row in rows]) + maxLastEditorLen = max([10] + [len(row[6]) for row in rows if row[6]]) + maxLastEditionTimeLen = max([11] + [len(secsToDate(row[7])) for row in rows]) + maxIdLen = max([2] + [len(str(row[8])) for row in rows]) + print '%*s NBDRIVES %*s %*s %*s VDQMPRIORITY %*s %*s %*s' % \ + (maxNameLen, 'NAME', maxMinAmountDataLen, 'MINAMOUNTDATA', maxMinNbFilesLen, \ + 'MINNBFILES', maxMaxFileAgeLen, 'MAXFILEAGE', maxIdLen, 'ID', \ + maxLastEditorLen, 'LASTEDITOR', maxLastEditionTimeLen, 'LASTEDITION') + print '-' * (28 + maxNameLen + maxMinAmountDataLen + maxMinNbFilesLen + maxMaxFileAgeLen + \ + maxLastEditorLen + maxLastEditionTimeLen + maxIdLen) + for name, nbdrives, minamountdata, minnbfiles, maxfileage, priority, lastEditor, lastEdition, tpid in rows: + print '%*s %8d %*s %*d %*s %12d %*d %*s %*s' % \ + (maxNameLen, name, nbdrives, maxMinAmountDataLen, nbToDataAmount(minamountdata), \ + maxMinNbFilesLen, minnbfiles, maxMaxFileAgeLen, nbToAge(maxfileage), priority, maxIdLen, tpid, \ + maxLastEditorLen, lastEditor, maxLastEditionTimeLen, secsToDate(lastEdition)) + # check unknown tape pools + if recallGroups and unknownRecallGroups: + print 'WARNING : the following recall groups do not exist : ' + ', '.join(unknownRecallGroups) + # close DB connections + try: + castor_tools.disconnectDB(stconn) + except Exception: + pass +except Exception, e: + print e + if verbose: + import traceback + traceback.print_exc() + sys.exit(-1) diff --git a/hsmtools/printrecallgroup.man b/hsmtools/printrecallgroup.man new file mode 100644 index 0000000000000000000000000000000000000000..60fe713d14082be19e5b9451de5b797a0aed03eb --- /dev/null +++ b/hsmtools/printrecallgroup.man @@ -0,0 +1,57 @@ +.TH PRINTRECALLGROUP 1 "2011" CASTOR "Prints out the given recall group(s)" +.SH NAME +printrecallgroup +.SH SYNOPSIS +.B printrecallgroup +[ +.BI -h +] +[ +<recallGroupName> +[...] +] + +.SH DESCRIPTION +.B printrecallgroup +prints out definitions of the given tape pools +.LP +.BI \-h,\ \-\-help +Get usage information +.TP +.BI <RecallGroupName> +when recall group names are given, only the definitions of the listed recall groups are printed. +If no recall group name is given, all recall group definitions are printed. + +.SH EXAMPLES +.nf +.ft CW +# printrecallgroup + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +--------------------------------------------------------------------------------------------------------------------------- + newrecallgroup 0 100GiB 1000 12h 0 263333 root 30-May-2012 17:29:12 + default 20 100GiB 1000 12h 0 133068 2.1.13 upgrade script 03-May-2012 16:44:50 +testrecallgroup 2 23MiB 1000 12h23mn 456 263419 root 30-May-2012 17:31:11 + +# printrecallgroup testrecallgroup nonexisting + NAME NBDRIVES MINAMOUNTDATA MINNBFILES MAXFILEAGE VDQMPRIORITY ID LASTEDITOR LASTEDITION +--------------------------------------------------------------------------------------------------------------------------- +testrecallgroup 2 23MiB 1000 12h23mn 456 263419 root 30-May-2012 17:31:11 +WARNING : the following recall groups do not exist : nonexisting + +.SH NOTES +This command requires database client access to the stager catalogue. +Configuration for the database access is taken from castor.conf. + +.SH SEE ALSO +.BR enterrecallgroup, +.BR modifyrecallgroup, +.BR deleterecallgroup, +.BR printdiskpool, +.BR printsvcclass, +.BR printfileclass, +.BR printtapepool, +.BR printmigrationroute, +.BR adminMultiInstance + +.SH AUTHOR +\fBCASTOR\fP Team <castor.support@cern.ch>