001    /*
002     * PBSResourceStrategy.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.pbs;
024    
025    import gov.bnl.star.offline.scheduler.*;
026    import gov.bnl.star.offline.scheduler.catalog.PhysicalFile;
027    
028    import java.util.Hashtable;
029    import java.util.Iterator;
030    import java.util.Set;
031    import org.apache.log4j.Logger;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    
035    
036    /**A dispatcher for the SGE batch system.  
037     * @author Levente Hajdu
038     * @version July 24, 2003
039     */
040    public class PBSResourceStrategy {
041        static private Logger log = Logger.getLogger(PBSResourceStrategy.class.getName());
042    
043        /** Base value for the resource value formula. */
044        protected int base = 0;
045        
046        /** Returns the base value for the resource value formula. */
047        public int getBase() {
048            return base;
049        }
050        
051        /** Changes the base value for the resource value formula. */
052        public void setBase(int base) {
053            this.base = base;
054        }
055    
056        /** FileFactor value for the resource value formula. */
057        protected int fileFactor = 2;
058        
059        /** Returns the fileFactor value for the resource value formula. */
060        public int getFileFactor() {
061            return fileFactor;
062        }
063        
064        /** Changes the fileFactor value for the resource value formula. */
065        public void setFileFactor(int fileFactor) {
066            this.fileFactor = fileFactor;
067        }
068    
069        /** Max parameter for the standard increaseResourceUsage implementation. */
070        protected int max = 50;
071        
072        /** Returns the max value for the resource value formula. */
073        public int getMax() {
074            return max;
075        }
076        
077        /** Changes the max value for the resource value formula. */
078        public void setMax(int max) {
079            this.max = max;
080        }
081        
082        private Hashtable resources;
083    
084        private String dataVaultPrefix;
085        
086        /** Returns the prefix with which a file on NFS identifies on which file server or
087         * data vault is residing. For example, "/star/data07/muDST/..." represents a file
088         * on data vault "07" (that is the data vault number or id), therefore the prefix is "/star/data".
089         * @return The prefix that identifies data vaults on NFS.
090         */
091        public String getDataVaultPrefix() {
092            return dataVaultPrefix;
093        }
094        
095        /** Changes the prefix of a NFS file. The resource identifies the data vault number
096         * by looking at a prefix in the file name. For example "/star/data07/minbias/..."
097         * would have "/star/data" as a prefix.
098         */
099        public void setDataVaultPrefix(String dataVaultPrefix) {
100            this.dataVaultPrefix = dataVaultPrefix;
101        }
102    
103        private String resourceNameSyntax;
104        
105        /** Returns the syntax to be used to convert a data vault number to the PBS resource.
106         */
107        public String getResourceNameSyntax() {
108            return resourceNameSyntax;
109        }
110        
111        /** Changes the syntax to be used to convert a data vault number to the PBS resource.
112         * It can be any string, and it should contain the $vaultNumber variable, which will
113         * be substituted with the particular vault number. For example, to have something
114         * like "dv7io", you should have "dv$vaultNumberio".
115         */
116        public void setResourceNameSyntax(String resourceNameSyntax) {
117            this.resourceNameSyntax = resourceNameSyntax;
118        }
119        
120        private boolean trimVaultNumberLeadingZeros;
121        
122        /** Returns true if the leading zeros of a vault number will be removed when
123         * creating the corresponding PBS resource.
124         */
125        public boolean isTrimVaultNumberLeadingZeros() {
126            return trimVaultNumberLeadingZeros;
127        }
128        
129        /** Set to true if the leading zeros of a vault number will be removed when
130         * creating the corresponding PBS resource. Basically, 07 will be changed to 7.
131         */
132        public void setTrimVaultNumberLeadingZeros(boolean trimVaultNumberLeadingZeros) {
133            this.trimVaultNumberLeadingZeros = trimVaultNumberLeadingZeros;
134        }
135        
136        /** Given the vault id, returns the PBS resource name to be used in the -R
137         * parameter.
138         * @param vaultNumber the vault id, as identified in <CODE>getDataVaultPrefix</CODE>
139         * @return the PBS resource name for the vault
140         */
141        protected String prepareResourceName(String vaultNumber) {
142            if ((removeCharactersFromVaultNumber) && (!vaultNumber.matches("[a-zA-Z]+"))) {
143                Pattern pattern = Pattern.compile("[^a-zA-Z]+");
144                Matcher matcher = pattern.matcher(vaultNumber);
145                StringBuffer buf = new StringBuffer(vaultNumber.length());
146                StringBuffer vaultNumberBuf = new StringBuffer(vaultNumber);
147                while (matcher.find()) {
148                    buf.append(vaultNumberBuf.subSequence(matcher.start(), matcher.end()));
149                }
150                vaultNumber = buf.toString();
151            }
152            
153            if (trimVaultNumberLeadingZeros) {
154                while (vaultNumber.startsWith("0") && (vaultNumber.length() != 1)) {
155                    vaultNumber = vaultNumber.substring(1);
156                }
157            }
158    
159            if (trimVaultNumberDecimal) {
160                int dec = vaultNumber.indexOf('.');
161                if (dec != -1) vaultNumber = vaultNumber.substring(0, dec);
162            }
163            
164            String name = resourceNameSyntax.replaceAll("\\$vaultNumber", vaultNumber);
165    
166            return name;
167        }
168        
169        private String switchSyntax;
170        
171        /** Holds value of property trimVaultNumberDecimal. */
172        private boolean trimVaultNumberDecimal;
173        
174        /** Holds value of property removeCharactersFromVaultNumber. */
175        private boolean removeCharactersFromVaultNumber;
176        
177        /** Returns the syntax that will be used to prepare the final switch
178         */
179        public String getSwitchSyntax() {
180            return switchSyntax;
181        }
182        
183        /** Changes the syntax that will be used to prepare the final switch.
184         * You can use the $definedNames and the $nameEqualValueCommaSeparated
185         * variables in you switch. For example, the following switch: 
186         * "\"select[$definedNames] rusage[$nameEqualValueCommaSeparated]\""
187         * will produce something like: "select[defined(01)] rusage[01=50]"
188         */
189        public void setSwitchSyntax(String switchSyntax) {
190            this.switchSyntax = switchSyntax;
191        }
192        
193        /** Prepares the -R parameters to give to PBS at the end of the resource
194         * distribution. It will use <CODE>definedNames()</CODE> and
195         * <CODE>nameEqualValueCommaSeparated()</CODE> to prepare it more easily.
196         * @return the -R parameter for the current job
197         */
198        protected String prepareSwitch() {
199            if (determineResourceNotUsed()) {
200                return null;
201            }
202            
203            String fullSwitch = switchSyntax.replaceAll("\\$definedNames", definedNames());
204            fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueCommaSeparated", nameEqualValueCommaSeparated());
205    
206            return fullSwitch;
207        }
208        
209        /** Increase the resource usage as part of the standard implementation. The standard
210         * formula starts counting from <CODE>base</CODE>, adds <CODE>fileFactor</CODE> for
211         * each usage until <CODE>max</CODE> is reached. In pseudo-code:
212         * <CODE>if (nRes == 0) nRes = base;
213         * nRes += nRes;
214         * if (nRes > max) nRes = max;
215         * </CODE>
216         * <p>
217         * One can modify the three protected parameters (base, fileFactor, max) to tune
218         * the usage.
219         * @param resource the resource for which to increase the counting.
220         */
221        protected void increaseResourceUsage(String resource) {
222            increaseResourceUptoLimit(resource, base, fileFactor, max);
223        }
224    
225        /** Addes a new resource, and initializes the value to <CODE>base</CODE>.
226         * @param resName the name of the resource
227         * @param base the initial value for the resource
228         */
229        protected void addResource(String resName, int base) {
230            if (!resources.containsKey(resName)) {
231                resources.put(resName, new Integer(base));
232            } else {
233                throw new RuntimeException("Can't add resource " + resName +
234                    " because it's already present.");
235            }
236        }
237    
238        /** Increases the value associated to the given resource by a given amount. If the
239         * resource doesn't exists, first it will be created and initialized.
240         * @param resName the name of the resource
241         * @param base the initial value if the resource has to be created
242         * @param amount the amount to increase the resource by
243         * @return the new value associated to the resource
244         */
245        protected int increaseResource(String resName, int base, int amount) {
246            if (!resources.containsKey(resName)) {
247                addResource(resName, base);
248            }
249    
250            int returnValue = prepareResourceValue(resName) + amount;
251            Integer newValue = new Integer(returnValue);
252    
253            resources.remove(resName);
254            resources.put(resName, newValue);
255    
256            return returnValue;
257        }
258    
259        /** Increases the value associated to the given resource by a given amount. If the
260         * resource doesn't exists, first it will be created and initialized. If the
261         * increased amount is over the limit, the value will be set to the limit.
262         * @return the new value associated to the resource
263         * @param limit upper limit to which the resource value will be set
264         * @param resName the name of the resource
265         * @param base the initial value if the resource has to be created
266         * @param amount the amount to increase the resource by
267         */
268        protected int increaseResourceUptoLimit(String resName, int base,
269            int amount, int limit) {
270            if (!resources.containsKey(resName)) {
271                addResource(resName, base);
272            }
273    
274            int returnValue = prepareResourceValue(resName) + amount;
275    
276            if (returnValue > limit) {
277                return limit;
278            }
279    
280            Integer newValue = new Integer(returnValue);
281    
282            resources.remove(resName);
283            resources.put(resName, newValue);
284    
285            return returnValue;
286        }
287    
288        /** Returns the value currently associated to the resource.
289         * @param resName resource name
290         * @return resource value
291         */
292        protected int prepareResourceValue(String resName) {
293            return ((Integer) resources.get(resName)).intValue();
294        }
295    
296        /** Returns the -R parameter to be used to dispatch the given job.
297         * @param job the job to be dispatched
298         * @return the -R parameter to use
299         */
300        public String prepareResourceUsageSwitch(Job job) {
301            initResources();
302            calculatePBSResources(job);
303    
304            return prepareSwitch();
305        }
306    
307        /** Initializes the resource table. */
308        protected void initResources() {
309            resources = new Hashtable();
310        }
311    
312        /** Returns a string already formatted containing names and values comma separated
313         * (i.e. "res1=34,res2=12").
314         * @return resource name/value pairs comma separated (i.e. "res1=34,res2=12")
315         */
316        protected String nameEqualValueCommaSeparated() {
317            String rusage = "";
318            Set resNames = resources.keySet();
319            Iterator iter = resNames.iterator();
320            boolean first = true;
321    
322            while (iter.hasNext()) {
323                if (!first) {
324                    rusage += ",";
325                }
326    
327                Object key = iter.next();
328                rusage += (key + "=" + resources.get(key));
329                first = false;
330            }
331    
332            return rusage;
333        }
334    
335        /** A list of names, comma separated, of all the resources used.
336         * @return (i.e. "res1,res2")
337         */
338        protected String definedNames() {
339            String defined = "";
340            Set resNames = resources.keySet();
341            Iterator iter = resNames.iterator();
342            boolean first = true;
343    
344            while (iter.hasNext()) {
345                if (!first) {
346                    defined += " && ";
347                }
348    
349                Object key = iter.next();
350                defined += ("defined(" + key + ")");
351                first = false;
352            }
353    
354            return defined;
355        }
356    
357        /** Filles the resource table with all the resources. It basically iterates over the
358         * input files and calls <CODE>increaseResourceUsage</CODE> for each file that map
359         * to a resource.
360         * @param job the job
361         */
362        protected void calculatePBSResources(Job job) {
363            log.debug("Calculating PBS resources");
364    
365            for (int i = 0; i < job.getInput().size(); i++) {
366                PhysicalFile file = (PhysicalFile) job.getInput().get(i);
367                String res = resourceRequired(file);
368    
369                if (res != null) {
370                    increaseResourceUsage(res);
371                }
372            }
373        }
374    
375        /** Determines which resource the job might need to access the file.
376         * @param file input file the job will be using
377         * @return the resource needed
378         */
379        protected String resourceRequired(PhysicalFile file) {
380            if ("NFS".equals(file.getStorage())) {
381                log.debug("Found NFS file: " + file);
382    
383                String path = file.getPath();
384    
385                if (path.startsWith(getDataVaultPrefix())) {
386                    int bar = path.indexOf('/', getDataVaultPrefix().length());
387                    String serverNumber = path.substring(getDataVaultPrefix()
388                                                             .length(), bar);
389    
390                    return prepareResourceName(serverNumber);
391                }
392            }
393    
394            return null;
395        }
396    
397        /** Determines whether any resources at all have been assigned.
398         * @return true if any resource is used.
399         */
400        protected boolean determineResourceNotUsed() {
401            return resources.isEmpty();
402        }
403        
404        /** Getter for property trimVaultNumberDecimal.
405         * @return Value of property trimVaultNumberDecimal.
406         *
407         */
408        public boolean isTrimVaultNumberDecimal() {
409            return this.trimVaultNumberDecimal;
410        }
411        
412        /** Setter for property trimVaultNumberDecimal.
413         * @param trimVaultNumberDecimal New value of property trimVaultNumberDecimal.
414         *
415         */
416        public void setTrimVaultNumberDecimal(boolean trimVaultNumberDecimal) {
417            this.trimVaultNumberDecimal = trimVaultNumberDecimal;
418        }
419        
420        /** If true removes all the characters from the vault number.
421         * @return Value of property removeCharactersFromVaultNumber.
422         *
423         */
424        public boolean isRemoveCharactersFromVaultNumber() {
425            return this.removeCharactersFromVaultNumber;
426        }
427        
428        /** Set to true removes all the characters from the vault number
429         * @param removeCharactersFromVaultNumber New value of property removeCharactersFromVaultNumber.
430         *
431         */
432        public void setRemoveCharactersFromVaultNumber(boolean removeCharactersFromVaultNumber) {
433            this.removeCharactersFromVaultNumber = removeCharactersFromVaultNumber;
434        }
435        
436    }