001    /*
002     * LSFResourceStrategy.java
003     *
004     * Created on April 1, 2003, 3:30 PM
005     *
006     * This file is part of the STAR Scheduler.
007     * Copyright (c) 2002-2006 STAR Collaboration - Brookhaven National Laboratory
008     *
009     * STAR Scheduler is free software; you can redistribute it and/or modify
010     * it under the terms of the GNU General Public License as published by
011     * the Free Software Foundation; either version 2 of the License, or
012     * (at your option) any later version.
013     *
014     * STAR Scheduler is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017     * GNU General Public License for more details.
018     *
019     * You should have received a copy of the GNU General Public License
020     * along with STAR Scheduler; if not, write to the Free Software
021     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
022     */
023    package gov.bnl.star.offline.scheduler.Dispatchers.lsf;
024    
025    import gov.bnl.star.offline.scheduler.*;
026    import gov.bnl.star.offline.scheduler.Dispatchers.AbstractResourceStrategy;
027    import gov.bnl.star.offline.scheduler.catalog.PhysicalFile;
028    
029    import java.util.Hashtable;
030    import java.util.Iterator;
031    import java.util.Set;
032    import org.apache.log4j.Logger;
033    import java.util.regex.Matcher;
034    import java.util.regex.Pattern;
035    
036    
037    /** Encapsulate the resource usage parameter (-R) for an LSF farm. Essentially,
038     * its a placeholder for the prepareResourceUsageSwitch() which will calculate the
039     * appriate string. The administrator should be able to create different
040     * subclasses for each farm as needed.
041     * <p>
042     * This class provides a scheleton for a typical resource usage for files residing
043     * on different NFS servers. It was designed to fit STAR needs at two sites. In case
044     * it doesn't fit the needs, the class can be subclassed to define new behaviour.
045     * <p>
046     * The resource switch is prepared in this way:
047     * Takes the list of input files and iterates over them.<br>
048     * Checks if each file needs a resource (<CODE>resourceRequired()</CODE>).<br>
049     * To determine that, checks if the file is on NFS, and looks for the typical data
050     * vault prefix (<CODE>getDataVaultPrefix</CODE>). For example, "/star/dataxx/...";
051     * it extract the vaultNumber and it substitute it in the resourceSyntax <br>
052     * Increases the count of the resource (<CODE>increaseResourceUsage</CODE>)<br>
053     * Prepares the switch (<CODE>prepareSwitch</CODE>)
054     * <p>
055     * The resource value for each resource will the be given by the following formula: <br>
056     * <code>min (base+nCount*fileFactor, max)</code>
057     * <p>
058     * The subclass need to implement the abstract methods, which defines things that
059     * always change between different farms.
060     * <p>
061     * TODO Further work should be done to be able to configure the ResourceStrategy
062     * depending on the farm on which the job will be dispatched. This should be
063     * included in the GRID dispatchers.
064     **
065     * @author Gabriele Carcassi, Jerome Lauret, Levente Hajdu
066     * @version July 24, 2003
067     */
068    public class LSFResourceStrategy implements AbstractResourceStrategy, java.io.Serializable{
069        static private Logger log = Logger.getLogger(LSFResourceStrategy.class.getName());
070    
071        /** Base value for the resource value formula. */
072        protected int base = 0;
073        
074        /** Returns the base value for the resource value formula. */
075        public int getBase() {
076            return base;
077        }
078        
079        /** Changes the base value for the resource value formula. */
080        public void setBase(int base) {
081            this.base = base;
082        }
083    
084        /** FileFactor value for the resource value formula. */
085        protected int fileFactor = 2;
086        
087        /** Returns the fileFactor value for the resource value formula. */
088        public int getFileFactor() {
089            return fileFactor;
090        }
091        
092        /** Changes the fileFactor value for the resource value formula. */
093        public void setFileFactor(int fileFactor) {
094            this.fileFactor = fileFactor;
095        }
096    
097        /** Max parameter for the standard increaseResourceUsage implementation. */
098        protected int max = 50;
099        
100        /** Returns the max value for the resource value formula. */
101        public int getMax() {
102            return max;
103        }
104        
105        /** Changes the max value for the resource value formula. */
106        public void setMax(int max) {
107            this.max = max;
108        }
109        
110        private Hashtable resources;
111    
112        private String dataVaultPrefix;
113        
114        /** Returns the prefix with which a file on NFS identifies on which file server or
115         * data vault is residing. For example, "/star/data07/muDST/..." represents a file
116         * on data vault "07" (that is the data vault number or id), therefore the prefix is "/star/data".
117         * @return The prefix that identifies data vaults on NFS.
118         */
119        public String getDataVaultPrefix() {
120            return dataVaultPrefix;
121        }
122        
123        /** Changes the prefix of a NFS file. The resource identifies the data vault number
124         * by looking at a prefix in the file name. For example "/star/data07/minbias/..."
125         * would have "/star/data" as a prefix.
126         */
127        public void setDataVaultPrefix(String dataVaultPrefix) {
128            this.dataVaultPrefix = dataVaultPrefix;
129        }
130    
131        private String resourceNameSyntax;
132        
133        /** Returns the syntax to be used to convert a data vault number to the LSF resource.
134         */
135        public String getResourceNameSyntax() {
136            return resourceNameSyntax;
137        }
138        
139        /** Changes the syntax to be used to convert a data vault number to the LSF resource.
140         * It can be any string, and it should contain the $vaultNumber variable, which will
141         * be substituted with the particular vault number. For example, to have something
142         * like "dv7io", you should have "dv$vaultNumberio".
143         */
144        public void setResourceNameSyntax(String resourceNameSyntax) {
145            this.resourceNameSyntax = resourceNameSyntax;
146        }
147        
148        private boolean trimVaultNumberLeadingZeros;
149        
150        /** Returns true if the leading zeros of a vault number will be removed when
151         * creating the corresponding LSF resource.
152         */
153        public boolean isTrimVaultNumberLeadingZeros() {
154            return trimVaultNumberLeadingZeros;
155        }
156        
157        /** Set to true if the leading zeros of a vault number will be removed when
158         * creating the corresponding LSF resource. Basically, 07 will be changed to 7.
159         */
160        public void setTrimVaultNumberLeadingZeros(boolean trimVaultNumberLeadingZeros) {
161            this.trimVaultNumberLeadingZeros = trimVaultNumberLeadingZeros;
162        }
163        
164        /** Given the vault id, returns the LSF resource name to be used in the -R
165         * parameter.
166         * @param vaultNumber the vault id, as identified in <CODE>getDataVaultPrefix</CODE>
167         * @return the LSF resource name for the vault
168         */
169        protected String prepareResourceName(String vaultNumber) {
170            if ((removeCharactersFromVaultNumber) && (!vaultNumber.matches("[a-zA-Z]+"))) {
171                Pattern pattern = Pattern.compile("[^a-zA-Z]+");
172                Matcher matcher = pattern.matcher(vaultNumber);
173                StringBuffer buf = new StringBuffer(vaultNumber.length());
174                StringBuffer vaultNumberBuf = new StringBuffer(vaultNumber);
175                while (matcher.find()) {
176                    buf.append(vaultNumberBuf.subSequence(matcher.start(), matcher.end()));
177                }
178                vaultNumber = buf.toString();
179            }
180            
181            if (trimVaultNumberLeadingZeros) {
182                while (vaultNumber.startsWith("0") && (vaultNumber.length() != 1)) {
183                    vaultNumber = vaultNumber.substring(1);
184                }
185            }
186    
187            if (trimVaultNumberDecimal) {
188                int dec = vaultNumber.indexOf('.');
189                if (dec != -1) vaultNumber = vaultNumber.substring(0, dec);
190            }
191            
192            String name = resourceNameSyntax.replaceAll("\\$vaultNumber", vaultNumber);
193    
194            return name;
195        }
196        
197        private String switchSyntax;
198        
199        /** Holds value of property trimVaultNumberDecimal. */
200        private boolean trimVaultNumberDecimal;
201        
202        /** Holds value of property removeCharactersFromVaultNumber. */
203        private boolean removeCharactersFromVaultNumber;
204        
205        /** Returns the syntax that will be used to prepare the final switch
206         */
207        public String getSwitchSyntax() {
208            return switchSyntax;
209        }
210        
211        /** Changes the syntax that will be used to prepare the final switch.
212         * You can use the $definedNames, $nameEqualValueCommaSeparated
213         * or $nameEqualValueColumnSeparated variables in your switch. 
214         * For example, the following switch: 
215         *  "\"select[$definedNames] rusage[$nameEqualValueCommaSeparated]\""
216         * will produce something like: "select[defined(01)] rusage[01=50]"
217         */
218        public void setSwitchSyntax(String switchSyntax) {
219            this.switchSyntax = switchSyntax;
220        }
221        
222        /** Prepares the -R parameters to give to LSF at the end of the resource
223         * distribution. It will use <CODE>definedNames()</CODE>,
224         * <CODE>nameEqualValueCommaSeparated()</CODE> or
225         * <CODE>nameEqualValueColumnSeparated()</CODE> to prepare it more easily.
226         * @return the -R parameter for the current job
227         */
228        protected String prepareSwitch() {
229            if (determineResourceNotUsed()) {
230                return null;
231            }
232            
233            String fullSwitch = switchSyntax.replaceAll("\\$definedNames", definedNames());
234            fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueCommaSeparated", nameEqualValueCommaSeparated());
235            fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueColumnSeparated", nameEqualValueColumnSeparated());
236    
237            return fullSwitch;
238        }
239        
240        /** Increase the resource usage as part of the standard implementation. The standard
241         * formula starts counting from <CODE>base</CODE>, adds <CODE>fileFactor</CODE> for
242         * each usage until <CODE>max</CODE> is reached. In pseudo-code:
243         * <CODE>if (nRes == 0) nRes = base;
244         * nRes += nRes;
245         * if (nRes > max) nRes = max;
246         * </CODE>
247         * <p>
248         * One can modify the three protected parameters (base, fileFactor, max) to tune
249         * the usage.
250         * @param resource the resource for which to increase the counting.
251         */
252        protected void increaseResourceUsage(String resource) {
253            increaseResourceUptoLimit(resource, base, fileFactor, max);
254        }
255    
256        /** Addes a new resource, and initializes the value to <CODE>base</CODE>.
257         * @param resName the name of the resource
258         * @param base the initial value for the resource
259         */
260        protected void addResource(String resName, int base) {
261            if (!resources.containsKey(resName)) {
262                resources.put(resName, new Integer(base));
263            } else {
264                throw new RuntimeException("Can't add resource " + resName +
265                    " because it's already present.");
266            }
267        }
268    
269        /** Increases the value associated to the given resource by a given amount. If the
270         * resource doesn't exists, first it will be created and initialized.
271         * @param resName the name of the resource
272         * @param base the initial value if the resource has to be created
273         * @param amount the amount to increase the resource by
274         * @return the new value associated to the resource
275         */
276        protected int increaseResource(String resName, int base, int amount) {
277            if (!resources.containsKey(resName)) {
278                addResource(resName, base);
279            }
280    
281            int returnValue = prepareResourceValue(resName) + amount;
282            Integer newValue = new Integer(returnValue);
283    
284            resources.remove(resName);
285            resources.put(resName, newValue);
286    
287            return returnValue;
288        }
289    
290        /** Increases the value associated to the given resource by a given amount. If the
291         * resource doesn't exists, first it will be created and initialized. If the
292         * increased amount is over the limit, the value will be set to the limit.
293         * @return the new value associated to the resource
294         * @param limit upper limit to which the resource value will be set
295         * @param resName the name of the resource
296         * @param base the initial value if the resource has to be created
297         * @param amount the amount to increase the resource by
298         */
299        protected int increaseResourceUptoLimit(String resName, int base,
300            int amount, int limit) {
301            if (!resources.containsKey(resName)) {
302                addResource(resName, base);
303            }
304    
305            int returnValue = prepareResourceValue(resName) + amount;
306    
307            if (returnValue > limit) {
308                return limit;
309            }
310    
311            Integer newValue = new Integer(returnValue);
312    
313            resources.remove(resName);
314            resources.put(resName, newValue);
315    
316            return returnValue;
317        }
318    
319        /** Returns the value currently associated to the resource.
320         * @param resName resource name
321         * @return resource value
322         */
323        protected int prepareResourceValue(String resName) {
324            return ((Integer) resources.get(resName)).intValue();
325        }
326    
327        /** Returns the -R parameter to be used to dispatch the given job.
328         * @param job the job to be dispatched
329         * @return the -R parameter to use
330         */
331        public String prepareResourceUsageSwitch(Job job) {
332            initResources();
333            calculateLSFResources(job);
334    
335            return prepareSwitch();
336        }
337    
338        /** Initializes the resource table. */
339        protected void initResources() {
340            resources = new Hashtable();
341        }
342    
343        /** Returns a string already formatted containing names and values comma separated
344         * (i.e. "res1=34,res2=12").
345         * @return resource name/value pairs comma separated (i.e. "res1=34,res2=12")
346         */
347        protected String nameEqualValueCommaSeparated() {
348            String rusage = "";
349            Set resNames = resources.keySet();
350            Iterator iter = resNames.iterator();
351            boolean first = true;
352    
353            while (iter.hasNext()) {
354                if (!first) {
355                    rusage += ",";
356                }
357    
358                Object key = iter.next();
359                rusage += (key + "=" + resources.get(key));
360                first = false;
361            }
362    
363            return rusage;
364        }
365        
366        /** Returns a string already formatted containing names and values column 
367         * separated (i.e. "res1=34:res2=12").
368         * @return resource name/value pairs column separated (i.e. "res1=34:res2=12")
369         */
370        
371          protected String nameEqualValueColumnSeparated() {
372            String rusage = "";
373            Set resNames = resources.keySet();
374            Iterator iter = resNames.iterator();
375            boolean first = true;
376    
377            while (iter.hasNext()) {
378                if (!first) {
379                    rusage += ":";
380                }
381    
382                Object key = iter.next();
383                rusage += (key + "=" + resources.get(key));
384                first = false;
385            }
386    
387            return rusage;
388        }
389      
390        
391        
392        /** A list of names, comma separated, of all the resources used.
393         * @return (i.e. "res1,res2")
394         */
395        protected String definedNames() {
396            String defined = "";
397            Set resNames = resources.keySet();
398            Iterator iter = resNames.iterator();
399            boolean first = true;
400    
401            while (iter.hasNext()) {
402                if (!first) {
403                    defined += " && ";
404                }
405    
406                Object key = iter.next();
407                defined += ("defined(" + key + ")");
408                first = false;
409            }
410    
411            return defined;
412        }
413    
414        /** Filles the resource table with all the resources. It basically iterates over the
415         * input files and calls <CODE>increaseResourceUsage</CODE> for each file that map
416         * to a resource.
417         * @param job the job
418         */
419        protected void calculateLSFResources(Job job) {
420            log.debug("Calculating LSF resources");
421            for (int i = 0; i < job.getInput().size(); i++) {
422                PhysicalFile file = (PhysicalFile) job.getInput().get(i);
423                String res = resourceRequired(file);
424    
425                if (res != null) {
426                    increaseResourceUsage(res);
427                }
428            }
429        }
430    
431        /** Determines which resource the job might need to access the file.
432         * @param file input file the job will be using
433         * @return the resource needed
434         */
435        protected String resourceRequired(PhysicalFile file) {
436            if ("NFS".equals(file.getStorage())) {
437                log.debug("Found NFS file: " + file);
438    
439                String path = file.getPath();
440    
441                if (path.startsWith(getDataVaultPrefix())) {
442                    int bar = path.indexOf('/', getDataVaultPrefix().length());
443                    String serverNumber = path.substring(getDataVaultPrefix().length(), bar);
444    
445                    return prepareResourceName(serverNumber);
446                }
447            }
448    
449            return null;
450        }
451    
452        /** Determines whether any resources at all have been assigned.
453         * @return true if any resource is used.
454         */
455        protected boolean determineResourceNotUsed() {
456            return resources.isEmpty();
457        }
458        
459        /** Getter for property trimVaultNumberDecimal.
460         * @return Value of property trimVaultNumberDecimal.
461         *
462         */
463        public boolean isTrimVaultNumberDecimal() {
464            return this.trimVaultNumberDecimal;
465        }
466        
467        /** Setter for property trimVaultNumberDecimal.
468         * @param trimVaultNumberDecimal New value of property trimVaultNumberDecimal.
469         *
470         */
471        public void setTrimVaultNumberDecimal(boolean trimVaultNumberDecimal) {
472            this.trimVaultNumberDecimal = trimVaultNumberDecimal;
473        }
474        
475        /** If true removes all the characters from the vault number.
476         * @return Value of property removeCharactersFromVaultNumber.
477         *
478         */
479        public boolean isRemoveCharactersFromVaultNumber() {
480            return this.removeCharactersFromVaultNumber;
481        }
482        
483        /** Set to true removes all the characters from the vault number
484         * @param removeCharactersFromVaultNumber New value of property removeCharactersFromVaultNumber.
485         *
486         */
487        public void setRemoveCharactersFromVaultNumber(boolean removeCharactersFromVaultNumber) {
488            this.removeCharactersFromVaultNumber = removeCharactersFromVaultNumber;
489        }
490        
491        
492        private String delimiterCharacter = ",";
493        
494        public String getDelimiterCharacter() { return delimiterCharacter;}
495        public void setDelimiterCharacter(String delimiterCharacter) {this.delimiterCharacter = delimiterCharacter;} 
496        
497    }