blob: d07ff178f87cf6568fee5f5ab5295c7b69bfdf13 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.ducc.common;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.uima.ducc.common.utils.DuccLogger;
import org.apache.uima.ducc.common.utils.DuccProperties;
import org.apache.uima.ducc.common.utils.IllegalConfigurationException;
import org.apache.uima.ducc.common.utils.SystemPropertyResolver;
import org.apache.uima.ducc.common.utils.id.DuccId;
/**
* This class reads and parses a node configuration file. It is used primarily by RM for scheduling
* and by the web server to present the configuration.
*/
public class NodeConfiguration
{
String config_file_name = null;
String ducc_nodes = null;
String ducc_users = null;
BufferedReader in;
int lineno = 0;
DuccProperties defaultFairShareClass = new DuccProperties();
DuccProperties defaultFixedShareClass = new DuccProperties();
DuccProperties defaultReserveClass = new DuccProperties();
DuccProperties defaultNodepool = new DuccProperties();
DuccProperties defaultUser = new DuccProperties(); // UIMA-4275
Map<String, DuccProperties> nodepools = new HashMap<String, DuccProperties>(); // all nodepools, by name
ArrayList<DuccProperties> independentNodepools = new ArrayList<DuccProperties>(); // the top-level node pools
// UIMA-4142 Move all these declarations to the top and comment them
List<DuccProperties> classes = new ArrayList<DuccProperties>(); // all classes, during parsing, a discard
Map<String, DuccProperties> clmap = new HashMap<String, DuccProperties>(); // all classes, by name
Map<String, DuccProperties> usermap = new HashMap<String, DuccProperties>(); // all users, by name UIMA-4275
ArrayList<String> independentClasses = new ArrayList<String>(); // all classes that don't derive from something
Map<String, String> allNodes = new HashMap<String, String>(); // map node -> nodefile name, map for dup checking
Map<String, DuccProperties> poolsByNodefile = new HashMap<String, DuccProperties>(); // nodepool node file -> nodepool props
Map<String, DuccProperties> poolsByNodeName = new HashMap<String, DuccProperties>(); // Nodepools, by node
Map<String, String> allImports = new HashMap<String, String>(); // map nodefile -> importer, map for dup checking
Map<String, String> referrers = new HashMap<String, String>(); // map nodefile -> referring nodepool, for dup checking
DuccLogger logger;
String defaultDomain = null;
int defaultQuantum = 15;
String firstNodepool = null;
boolean fairShareExists = false;
boolean fixedExists = false;
boolean reserveExists = false;
DuccProperties fairShareDefault = null;
DuccProperties fixedDefault = null;
DuccProperties reserveDefault = null;
String ducc_home = null;
public NodeConfiguration(String config_file_name, String ducc_nodes, String ducc_users, DuccLogger logger)
{
this.config_file_name = config_file_name;
this.ducc_nodes = ducc_nodes;
this.ducc_users = ducc_users;
this.logger = logger;
ducc_home = System.getProperty("DUCC_HOME");
defaultFairShareClass.put("type", "class");
defaultFairShareClass.put("name", "defaultFairShareClass");
defaultFairShareClass.put("policy", "FAIR_SHARE");
defaultFairShareClass.put("weight", "100");
defaultFairShareClass.put("priority", "10");
defaultFairShareClass.put("cap", Integer.toString(Integer.MAX_VALUE));
defaultFairShareClass.put("expand-by-doubling", ""+SystemPropertyResolver.getBooleanProperty("ducc.rm.expand.by.doubling", true));
defaultFairShareClass.put("initialization-cap", ""+SystemPropertyResolver.getIntProperty("ducc.rm.initialization.cap", 2));
defaultFairShareClass.put("use-prediction", ""+SystemPropertyResolver.getBooleanProperty("ducc.rm.prediction", true));
defaultFairShareClass.put("prediction-fudge", ""+SystemPropertyResolver.getIntProperty("ducc.rm.prediction.fudge", 60000));
defaultFairShareClass.put("max-processes", "<optional>"); // this is deprecated. it remains here to ease migration to DUCC 2.0. It has no effect.
defaultFairShareClass.put("nodepool", "<required>");
defaultFairShareClass.put("users", "<optional>");
defaultFairShareClass.put("debug", "fixed");
defaultFairShareClass.put("abstract", "<optional>");
defaultFairShareClass.put("children", "<optional>");
defaultFairShareClass.put("parent", "<optional>");
defaultFairShareClass.put("debug", "<optional>");
defaultFairShareClass.put("default", "<optional>");
defaultFairShareClass.put("name", "<required>"); // required, but always filled in by the parser. needed here for validation.
defaultFixedShareClass.put("type", "class");
defaultFixedShareClass.put("name", "defaultFixedShareClass");
defaultFixedShareClass.put("abstract", "<optional>");
defaultFixedShareClass.put("children", "<optional>");
defaultFixedShareClass.put("parent", "<optional>");
defaultFixedShareClass.put("policy", "FIXED_SHARE");
defaultFixedShareClass.put("priority", "5");
defaultFixedShareClass.put("default", "<optional>");
defaultFixedShareClass.put("max-processes", "<optional>"); // this is deprecated. it remains here to ease migration to DUCC 2.0. It has no effect.
defaultFixedShareClass.put("cap", "<optional>"); // this is deprecated. it remains here to ease migration to DUCC 2.0. It has no effect.
defaultFixedShareClass.put("nodepool", "<required>");
defaultFixedShareClass.put("users", "<optional>");
defaultReserveClass.put("type", "class");
defaultReserveClass.put("name", "defaultReserveClass");
defaultReserveClass.put("abstract", "<optional>");
defaultReserveClass.put("children", "<optional>");
defaultReserveClass.put("parent", "<optional>");
defaultReserveClass.put("policy", "RESERVE");
defaultReserveClass.put("priority", "1");
defaultReserveClass.put("default", "<optional>");
defaultReserveClass.put("max-machines", "<optional>"); // this is deprecated. it remains here to ease migration to DUCC 2.0. It has no effect.
defaultReserveClass.put("cap", "<optional>"); // this is deprecated. it remains here to ease migration to DUCC 2.0. It has no effect.
defaultReserveClass.put("nodepool", "<required>");
defaultReserveClass.put("users", "<optional>");
defaultReserveClass.put("enforce", "true");
defaultNodepool.put("type", "nodepool");
defaultNodepool.put("name", "<optional>");
defaultNodepool.put("nodefile", "<optional>");
defaultNodepool.put("parent", "<optional>");
defaultNodepool.put("share-quantum", "<optional>");
defaultNodepool.put("domain", "<optional>");
defaultNodepool.put("search-order", "100"); // temporary. UIMA-4324
defaultUser.put("type", "user");
defaultUser.put("name", "<optional>");
defaultUser.put("max-allotment", Integer.toString(Integer.MAX_VALUE));
}
/**
* Resolve a filename relative to DUCC_HOME
*/
String resolve(String file)
throws IllegalConfigurationException
{
if ( file == null ) return null;
if ( !file.startsWith("/") ) {
file = ducc_home + "/resources/" + file;
}
File f = new File(file);
if ( ! f.exists() ) {
return null; // UIMA-4275 Defer crash to caller, making it optional.
}
return file;
}
void logInfo(String methodName, String message)
{
if ( logger == null ) {
System.out.println(message);
} else {
logger.info(methodName, null, message);
}
}
void logWarn(String methodName, String message)
{
if ( logger == null ) {
System.out.println(message);
} else {
logger.warn(methodName, null, message);
}
}
void logError(String methodName, String message)
{
if ( logger == null ) {
System.out.println(message);
} else {
logger.error(methodName, null, message);
}
}
/**
* Use the NodeIdentity to infer my the domain name.
*
* Itertate through the possible names - if one of them has a '.'
* the we have to assume the following stuff is the domain name.
* We only get one such name, so we give up the search if we find
* it.
*/
private String getDomainName()
{
String location = "getDomainName";
DuccId jobid = null;
if ( defaultDomain != null ) return defaultDomain;
InetAddress me = null;
try {
me = InetAddress.getLocalHost();
String my_happy_name = me.getHostName();
String my_canonical_name = me.getCanonicalHostName();
if ( my_canonical_name.startsWith(my_happy_name) ) {
int ndx = my_canonical_name.indexOf(".");
return my_canonical_name.substring(ndx+1);
}
} catch (UnknownHostException e1) {
logger.error(location, jobid, e1);
}
return null;
}
/**
* Provide a continual stream of lines, removing empty lines and comment lines.
*/
String readLine()
throws IOException
{
String line = null;
while ( (line = in.readLine()) != null ) {
lineno++;
// System.out.println("Line[" + lineno + "]: " + line);
line = line.trim();
if ( line.equals("") ) continue;
if ( line.startsWith("#") ) continue;
return line + ";"; // convert linend into ; so we can do lists
}
return null;
}
/**
* Fill up the token buffer.
*/
StringTokenizer buf = null;
boolean fillBuf()
throws IOException
{
while ( (buf == null) || !buf.hasMoreTokens() ) {
String line = readLine();
if ( line == null ) return false;
buf = new StringTokenizer(line, "\n\t\r\f{} =,;", true);
}
return true;
}
/**
* Provide a continual stream of tokens, throwing out whitespace and semocolons
*/
String pushback = null;
String nextToken()
throws IOException
{
if ( pushback != null ) {
// System.out.println("Return " + pushback + " from pushback.");
String ret = pushback;
pushback = null;
return ret;
}
while ( fillBuf() ) {
String tok = null;
while ( buf.hasMoreTokens() ) {
tok = buf.nextToken();
// System.out.println("Token: " + tok);
if ( tok.equals(" ") ) continue; // ignoring whitespace
if ( tok.equals("\t") ) continue; // ignoring whitespace
if ( tok.equals(",") ) continue; // ignoring commas as whitespace
return tok;
}
}
return null;
}
/**
* Consume the token stream up to the next delimeter.
*/
String consume()
throws IOException
{
String tok = nextToken();
if ( tok.equals("=") ) tok = nextToken(); // optional '='
if ( tok.equals("}") ) return tok; // start of stream is }, probably invalid
if ( tok.equals("{") ) return tok; // start of stream is {, probably invalid
String ret = null;
while ( tok != null ) {
if ( tok.equals(";") ) return ret; // logical eol
if ( tok.equals("}") || tok.equals("{") ) { // start or begin of stanza, not at start of stream
pushback = tok; // we allow 1 token pushback
return ret;
}
if ( ret == null ) {
ret = tok;
} else {
ret = ret + " " + tok;
}
tok = nextToken();
}
return ret;
}
void parseInternal(DuccProperties props)
throws IOException,
IllegalConfigurationException
{
String tok = null;
while ( (tok = nextToken() ) != null ) {
if ( tok.equals(";") ) continue;
if ( tok.equals("}") ) return;
String k = tok;
if ( k.equals("{") ) {
throw new IllegalConfigurationException("Missing '}' near line " + lineno + " in " + config_file_name);
}
String v = consume();
if ( v.equals("}") ) {
throw new IllegalConfigurationException("Missing value near line " + lineno + " in " + config_file_name);
}
if ( v.equals("{") ) {
throw new IllegalConfigurationException("Missing '}' near line " + lineno + " in " + config_file_name);
}
if ( ! props.containsKey(k) ) {
props.put(k, v);
} else {
throw new IllegalConfigurationException("Duplicate property not allowed near line " + lineno + " in " + config_file_name + ": " + k);
}
}
return;
}
DuccProperties parseNodepool(String name, String parent)
throws IOException,
IllegalConfigurationException
{
if ( firstNodepool == null ) {
firstNodepool = name;
}
DuccProperties ret = new DuccProperties();
ret.put("type", "nodepool");
ret.put("name", name);
if ( parent != null ) {
throw new IllegalConfigurationException("Illegal inheritance (inheritance not supported for nodepools) near line " + lineno + " in " + config_file_name);
}
// put into global map
if ( nodepools.containsKey(name) ) {
throw new IllegalConfigurationException("Duplicate nodepool: " + name);
}
nodepools.put(name, ret);
parseInternal(ret);
String dd = ret.getProperty("domain");
if ( name.equals(firstNodepool) && (dd != null) ) {
defaultDomain = dd;
} else {
if ( dd != null ) {
throw new IllegalConfigurationException("Default domain specified in nodepool other than first nodepool \"" + firstNodepool + "\", not allowed, near line " + lineno + " in " + config_file_name);
}
}
supplyDefaults(ret, defaultNodepool);
if ( ! ret.containsKey("nodefile") && (ducc_nodes != null) ) {
// duplicates will be checked later
ret.put("nodefile", ducc_nodes);
}
if ( ! ret.containsKey("parent") ) {
// System.out.println("Add top level nodepool: " + name);
independentNodepools.add(ret);
}
return ret;
}
DuccProperties parseClass(String name, String parent)
throws IOException,
IllegalConfigurationException
{
DuccProperties ret = new DuccProperties();
ret.put("type", "class");
ret.put("name", name);
if ( parent != null ) {
ret.put("parent", parent);
}
parseInternal(ret);
return ret;
}
// UIMA-4275
DuccProperties parseUser(String name, String parent)
throws IOException,
IllegalConfigurationException
{
DuccProperties ret = new DuccProperties();
ret.put("type", "user");
ret.put("name", name);
if ( parent != null ) {
ret.put("parent", parent);
}
parseInternal(ret);
return ret;
}
DuccProperties parseStanzas()
throws IOException,
IllegalConfigurationException
{
String tok;
while ( (tok = nextToken()) != null ) {
if ( tok.equals(";") ) continue; // logical EOL, ignore here
String type = tok; // stanza type
String name = nextToken(); // stanza name
if ( name == null ) {
throw new IllegalConfigurationException("Missing stanza name near line " + lineno + " in " + config_file_name);
}
String parent = nextToken(); // who to inherit from, or "{"
String start = null;
if ( parent.equals("{") ) {
start = parent;
parent = null;
} else {
start = nextToken();
}
if ( ! start.equals("{") ) {
throw new IllegalConfigurationException("Missing '{' near line " + lineno + " in " + config_file_name);
}
if ( type.equals("Nodepool") ) nodepools.put(name, parseNodepool(name, parent));
if ( type.equals("Class") ) classes.add(parseClass(name, parent));
if ( type.equals("User") ) usermap.put(name, parseUser(name, parent)); // UIMA-4275
}
return null;
}
/**
* Given the 'in' properties, look through the 'model' propertis and make sure there are
* no unsupported properties, and that all required properties are filled in. Works for both
* Class and Nodepool properties.
*/
void supplyDefaults(DuccProperties in, DuccProperties model)
throws IllegalConfigurationException
{
// first make sure all properties for the input object are valid
String name = in.getProperty("name");
String type = in.getProperty("type");
for (Object o : in.keySet()) {
String k = (String) o;
if ( model.get(k) == null ) { // key not in model is illegal
throw new IllegalConfigurationException("Illegal property \"" + k + "\" in " + type + " " + name);
}
}
// now make sure all required fields are supplied and fill in defaults
for ( Object o : model.keySet() ) {
String k = (String) o;
String vm = model.getProperty(k);
String vi = in.getProperty(k);
if ( vi == null) {
if ( vm.equals("<required>" ) ) {
throw new IllegalConfigurationException("Missing required property " + k + " in " + type + " " + name);
}
if ( vm.contains("<optional>") ) { // its optional but there is no meaningful default
continue;
}
// System.out.println("Object " + name + " inherits " + k + "," + vm + " from " + model.get("name"));
in.put(k, vm); // fill in the default
}
}
}
/**
* Propogate my properties to all my kids and their kids, etc. Classes only, Nodepools don't
* do any sort of inheritance.
*/
void propogateDown(String clname)
{
DuccProperties cl = clmap.get(clname);
String children = cl.getStringProperty("children", null);
if ( children == null ) return;
String[] kids = children.split("\\s+");
for ( String kid : kids ) {
DuccProperties kp = clmap.get(kid);
for ( Object o : cl.keySet() ) {
if ( ! kp.containsKey(o) ) {
String k = (String) o;
if ( k.equals("abstract" ) ) continue; // don't propogate down abstractness
if ( k.equals("children" ) ) continue; // don't propogate down children
if ( k.equals("default" ) ) continue; // don't propogate down default
String v = cl.getStringProperty(k);
// System.out.println("Object " + kp.get("name") + " inherits " + k + "," + v + " from " + cl.get("name"));
kp.put(k, v);
}
}
propogateDown(kid);
}
}
void handleDefault(DuccProperties p, DuccProperties def, String policy)
throws IllegalConfigurationException
{
String dflt = p.getProperty("default");
if ( dflt != null ) {
if ( def != null ) {
throw new IllegalConfigurationException("Class " + p.getProperty("name")
+ ": Only one " + policy + " default allowed. Already defined in class \""
+ def.getProperty("name")
+ "\"");
} else {
if ( policy.equals("FAIR_SHARE" ) ) {
fairShareDefault = p;
} else if ( policy.equals("FIXED_SHARE") ) {
fixedDefault = p;
} else {
reserveDefault = p;
}
}
}
}
/**
* Map all the classes and do inheritance.
* Check values for correctness.
*/
void doClassInheritance()
throws IllegalConfigurationException
{
// map the clases, crash on duplicates
for ( DuccProperties p : classes ) {
String name = p.getStringProperty("name");
if ( clmap.containsKey(name) ) {
throw new IllegalConfigurationException("Duplicate class: " + name);
}
clmap.put(name, p);
}
// now establish the parent -> child relationships
for ( DuccProperties p : clmap.values() ) {
String parent = p.getProperty("parent");
String name = p.getProperty("name");
if ( (p.getProperty("abstract") != null) &&
(p.getProperty("default") != null ) ) {
throw new IllegalConfigurationException("Class " + name + ": Abstract class is not allowed to specify \"default\"");
}
if ( parent == null ) {
independentClasses.add(name);
} else {
DuccProperties par_cl = clmap.get(parent);
if ( par_cl == null ) {
throw new IllegalConfigurationException("Class " + name + " parent pool " + parent + " cannot be found.");
}
String children = par_cl.getStringProperty("children", null);
if ( children == null ) {
children = name;
} else {
children = children + " " + name;
}
par_cl.put("children", children);
}
}
// now starting at every root, propogate stuff down
for ( String s : independentClasses ) {
propogateDown(s);
}
// must fill in defaults, which we couldn't do until we finished inheritance
for ( DuccProperties p : clmap.values() ) {
String policy = p.getStringProperty("policy", null);
String name = p.getProperty("name");
if ( policy == null ) {
throw new IllegalConfigurationException("Class " + name + " is missing scheduling policy ");
}
if ( policy.equals("FAIR_SHARE") ) {
fairShareExists = true;
handleDefault(p, fairShareDefault, policy);
supplyDefaults(p, defaultFairShareClass);
} else
if ( policy.equals("FIXED_SHARE") ) {
fixedExists = true;
handleDefault(p, fixedDefault, policy);
supplyDefaults(p, defaultFixedShareClass);
} else
if ( policy.equals("RESERVE") ) {
reserveExists = true;
handleDefault(p, reserveDefault, policy);
supplyDefaults(p, defaultReserveClass);
} else {
throw new IllegalConfigurationException("Unknown scheduling policy \"" + policy + "\" in class " + name);
}
}
// remove the abstract classes as they are no longer needed and we don't want to let them leak out
// where somebody might think they're ok to use
Iterator<String> iter = clmap.keySet().iterator();
while ( iter.hasNext() ) {
String k = iter.next();
DuccProperties p = clmap.get(k);
if ( p.containsKey("abstract") ) {
// System.out.println("---------------- Remove " + p.get("name"));
iter.remove();
}
}
}
/**
* Look at all top-level nodepools and insure the scheduling quantum is set. If not, inherit it from
* ducc.properties.
*/
void setShareQuantum()
throws IllegalConfigurationException
{
for (DuccProperties props : independentNodepools ) {
String q = props.getProperty("share-quantum");
if ( q == null ) {
props.setProperty("share-quantum", "" + defaultQuantum);
} else {
try {
Integer.parseInt(q); // insure it's a number
} catch (NumberFormatException e) {
throw new IllegalConfigurationException("Value for \"share-quantum\" in nodepool " + props.getProperty("name") + " is not a number.");
}
}
}
}
/**
* (Recursively) walk up parental chain to find the top-level np for this guy.
*/
DuccProperties getTopLevel(DuccProperties child)
{
String parent = child.getStringProperty("parent", null);
if ( parent == null ) return child;
return getTopLevel(nodepools.get(parent));
}
/**
* Find all the top-level nodepools.
* Make sure every parent nodepool exists.
* Set the names of the child nodepools into each parent.
* Set the names of the classes managed in each nodepool into the nodepools.
*/
void connectNodepools()
throws IllegalConfigurationException
{
// Insure default is set right
setShareQuantum();
// Map the child nodepools into their parents
for ( DuccProperties p : nodepools.values() ) {
String parent = p.getStringProperty("parent", null);
String name = p.getStringProperty("name");
if ( parent != null ) {
// Insure no scheduling quantum is set
if ( p.getProperty("share-quantum") != null ) {
throw new IllegalConfigurationException("Nodepool " + name + ": \"share-quantum\" is legal only for top-level nodepools.");
}
// Now pull down the parent's scheduling quantum
DuccProperties tl = getTopLevel(p);
p.setProperty("share-quantum", tl.getProperty("share-quantum")); // (guaranteed non-null by setSchedulingQuantum)
// And now connect children and parents
DuccProperties par_pool = nodepools.get(parent);
if ( par_pool == null ) {
throw new IllegalConfigurationException("Nodepool " + name+ " parent pool " + parent + " cannot be found.");
}
@SuppressWarnings("unchecked")
List<DuccProperties> children = (List<DuccProperties>) par_pool.get("children");
if ( children == null ) {
children = new ArrayList<DuccProperties>();
par_pool.put("children", children);
}
children.add(p);
}
}
// connect the classes into their nodepools
for ( DuccProperties p : classes ) {
if ( p.containsKey("abstract") ) continue; // don't propogate these out
String name = p.getStringProperty("name");
String npname = p.getStringProperty("nodepool", null);
if ( npname == null ) {
throw new IllegalConfigurationException("Class " + name + " is not assigned to a nodepool.");
}
DuccProperties np = nodepools.get(npname);
if ( np == null ) {
throw new IllegalConfigurationException("Class " + name + " assigned to nodepool " + npname + " but nodepool does not exist.");
}
@SuppressWarnings("unchecked")
List<DuccProperties> class_set = (List<DuccProperties>) np.get("classes");
if ( class_set == null ) {
class_set = new ArrayList<DuccProperties>();
np.put("classes", class_set);
}
class_set.add(p);
}
}
void readNodepoolNodes(String nodefile, DuccProperties p, String domain)
throws IllegalConfigurationException
{
String methodName = "readnodepoolFiles";
@SuppressWarnings("unchecked")
Map<String, String> nodes = (Map<String, String>) p.get("nodes");
if ( nodes == null ) {
nodes = new HashMap<String, String>();
p.put("nodes", nodes);
}
BufferedReader br = null;
try {
String tnodefile = resolve(nodefile);
if ( tnodefile == null ) {
throw new IllegalConfigurationException("File " + nodefile + " does not exist.");
}
nodefile = tnodefile;
br = new BufferedReader(new FileReader(nodefile));
String node = "";
while ( (node = br.readLine()) != null ) {
int ndx = node.indexOf("#");
if ( ndx >= 0 ) {
node = node.substring(0, ndx);
}
node = node.trim();
if (node.equals("") ) {
continue;
}
// UIMA-4142 Domain no longer supported
if ( node.startsWith("domain") ) {
logInfo(methodName, "The \"domain\" keyword is no long supported in node files. Found in file " + nodefile);
continue;
}
if ( node.startsWith("import") ) {
// if it's an import and it's not in the set of nodepool files, read it in3
// otherwise ignore it because it will get read and associated with it's correct
// nodepool
String[] tmp = node.split("\\s+");
String importfile = tmp[1];
if ( ! poolsByNodefile.containsKey(importfile) ) {
readNodepoolNodes(importfile, p, domain);
}
continue;
}
if ( allNodes.containsKey(node) ) {
throw new IllegalConfigurationException("Duplicate node found in " + nodefile + ": " + node + "; first occurance in " + allNodes.get(node));
}
allNodes.put(node, nodefile); // for dup checking - we only get to read a node once
nodes.put(node, nodefile); // UIMA-4142 map host -> domain
poolsByNodeName.put(node, p); // So we can find pool-related things for the node
// include fully and non-fully qualified names to allow sloppiness of config
ndx = node.indexOf(".");
String dnode = null;
if ( ndx >= 0 ) { // strip domain to get just the name
dnode = node.substring(0, ndx);
nodes.put(dnode, nodefile); // UIMA-4142 map host -> domain
} else if ( domain != null ) { // or add the domain, if found, to get fully-qualified
dnode = node + "." + domain;
nodes.put(dnode, nodefile); // UIMA-4142 map host -> domain
}
if( dnode != null ) {
if ( allNodes.containsKey(dnode) ) {
throw new IllegalConfigurationException("Duplicate node found in " + nodefile + ": " + dnode + "; first occurance in " + allNodes.get(dnode));
}
allNodes.put(dnode, nodefile); // UIMA-4142 map host -> domain
}
}
} catch (FileNotFoundException e) {
throw new IllegalConfigurationException("Cannot open NodePool file \"" + nodefile + "\": file not found.");
} catch (IOException e) {
throw new IllegalConfigurationException("Cannot read NodePool file \"" + nodefile + "\": I/O Error.");
} catch (IllegalConfigurationException e) {
throw e;
} catch ( Exception e ) {
e.printStackTrace();
} finally {
if ( br != null ) {
try { br.close(); } catch (IOException e) { }
}
}
}
/**
* If nothing throws then allNodePools has a map of all node pool files to read and the nodepool props file to attach them to
* @param p
*/
void checkForDuplicatePoolFiles()
throws IllegalConfigurationException
{
for ( DuccProperties dp : nodepools.values() ) {
String npfile = dp.getProperty("nodefile");
if ( poolsByNodefile.containsKey(npfile) ) {
throw new IllegalConfigurationException("Duplicate nodepool file reference to " + npfile + " from " + dp.getProperty("name") + " not allowed "
+ " first reference was from " + poolsByNodefile.get(npfile));
}
if ( npfile != null ) { // pools are not required to have nodes associated, e.g. --default--
poolsByNodefile.put(npfile, dp);
}
}
}
void checkForMissingNodeFile()
throws IllegalConfigurationException
{
List<String> missing = new ArrayList<String>();
for ( DuccProperties dp : nodepools.values() ) {
if ( ! dp.containsKey("nodefile") ) {
missing.add(dp.getProperty("name")); // remember, for possible exception below
// No nodefile, assign it ducc_nodes
// it will crash in a while if this is a conflict
if ( ducc_nodes != null ) {
dp.setProperty("nodefile", ducc_nodes);
}
}
}
if ( missing.size() > 1 ) {
StringBuffer sb = new StringBuffer("Multiple nodepools with no associated node file, not allowed: ");
for (String s : missing) {
sb.append(" ");
sb.append(s);
}
throw new IllegalConfigurationException(sb.toString());
}
}
void checkForCycles()
throws IllegalConfigurationException
{
Map<String, String> visited = new HashMap<String, String>();
List<String> pools = new ArrayList<String>();
for ( DuccProperties dp : nodepools.values() ) {
visited.clear();
pools.clear();
DuccProperties current = dp;
String currentName = current.getProperty("name");
while ( current != null ) {
if ( visited.containsKey(currentName) ) {
StringBuffer sb = new StringBuffer();
for ( String s : pools ) {
sb.append(s);
sb.append(" <-- ");
}
sb.append(currentName);
throw new IllegalConfigurationException("Nodepool cycle detected: " + sb.toString());
} else {
visited.put(currentName, currentName);
pools.add(currentName);
currentName = current.getProperty("parent");
if ( currentName != null ) {
current = (DuccProperties) nodepools.get(currentName);
} else {
current = null;
}
}
}
}
}
// UIMA-4930 Enforce the NodepoolScheduler restriction that classes with the same priority MUST have the same scheduling policy
void checkPriorities() throws IllegalConfigurationException {
HashMap<String, String> policyMap = new HashMap<String,String>();
for ( DuccProperties p : classes ) {
String priority = p.getProperty("priority");
String policy = p.getProperty("policy");
String p4p = policyMap.get(priority);
if (p4p == null) {
policyMap.put(priority, policy);
} else {
if (!p4p.equals(policy)) {
throw new IllegalConfigurationException("Class " + p.getProperty("name") + " has the same priority (" + priority +
") as another but with a different scheduling policy (" + policy + ")");
}
}
}
}
/**
* Make sure any classes specified in the user registry exist and specify a number.
* Expected format:
* max_allotment.classname = number
*
* UIMA-4275
*/
void verifyUserLimits()
throws IllegalConfigurationException
{
for (Object o : usermap.keySet() ) {
DuccProperties dp = usermap.get(o);
for ( Object l : dp.keySet() ) {
if ( defaultUser.containsKey(l) ) continue;
String k = (String) l;
String val = ((String) dp.get(k)).trim();
if ( k.indexOf(".") <= 0 ) {
throw new IllegalConfigurationException("User " + o + ": allotment incorrectly specified, cannot determine class. " + k + " = " + val);
}
String[] tmp = k.split("\\.");
if ( ! clmap.containsKey(tmp[1]) ) {
throw new IllegalConfigurationException("User " + o + ": allotment incorrectly specified, class not defined. " + k + " = " + val);
}
try {
Integer.parseInt(val);
} catch ( NumberFormatException e ) {
throw new IllegalConfigurationException("User " + o + ": allotment incorrectly specified, value not a number. " + k + " = " + val);
}
}
}
}
/**
* Read all the node pool files recursively down from the properties file (p) and
* create a map node -> nodepoolname checking for duplicates.
* @param p Properties file representing a nodepool
* @param domain Default network domain
*/
void readNpNodes(String domain)
throws IllegalConfigurationException
{
checkForMissingNodeFile(); // only one nodepool may be missing its node file
// also fills in default nodefile if needed
checkForDuplicatePoolFiles(); // only one pool may reference any single file
// if we get here without crash the node pool files are not inconsistent
for ( String k : poolsByNodefile.keySet() ) {
readNodepoolNodes(k, (DuccProperties) poolsByNodefile.get(k), domain);
}
// TODO: Test above procedures
// Assign ducc.nodes to the one allowable top level np with no pool file
}
/**
* Read the complete node configuration as defined in. Intended for use from command line, not
* usually elsewhere.
*/
public void fullValidation(String global_nodefile)
throws IllegalConfigurationException
{
//global_nodefile = resolve(global_nodefile);
//if ( allNodefiles.containsKey(global_nodefile) ) return; // already passed if is's there and we're here
//readNodepoolFile(global_nodefile, defaultDomain, true); // will throw if there's an issue
}
/**
* get first (default) nodepool
*/
public String getFirstNodepool() {
return firstNodepool;
}
public DuccProperties getDefaultFairShareClass()
{
return fairShareDefault;
}
public DuccProperties getDefaultFixedClass()
{
return fixedDefault;
}
public DuccProperties getDefaultReserveClass()
{
return reserveDefault;
}
public DuccProperties getNodePoolForNode(String node) {
DuccProperties retVal = null;
if(node != null) {
retVal = poolsByNodeName.get(node);
}
return retVal;
}
public String getNodePoolNameForNode(String node) {
String retVal = null;
if(node !=null) {
DuccProperties np = poolsByNodeName.get(node);
String key = "name";
if(np != null) {
retVal = np.getProperty(key);
}
}
return retVal;
}
public int getQuantumForNode(String node)
{
DuccProperties np = poolsByNodeName.get(node);
if ( np == null ) {
// it has to be the default np, so use the default quantum
return defaultQuantum;
}
// otherwise it's required that the quantum for the nodepool is set to something
return Integer.parseInt(np.getProperty("share-quantum"));
}
public int getQuantumForClass(String classname)
throws IllegalConfigurationException
{
// to find the quantum for a class -
// -- look up the class to get the nodepool name
// -- look in the nodepool for the quantum
// must throw on invalid classname which could be a tupo on the part of whoever calls this
DuccProperties props = clmap.get(classname);
if ( props == null ) {
throw new IllegalConfigurationException("Class " + classname + " is not configured.");
}
String npname = props.getProperty("nodepool");
DuccProperties np = nodepools.get(npname);
return Integer.parseInt(np.getProperty("share-quantum"));
}
public DuccProperties[] getToplevelNodepools()
{
return independentNodepools.toArray(new DuccProperties[independentNodepools.size()]);
}
public DuccProperties getClass(String name)
{
if ( clmap == null ) return null;
return clmap.get(name);
}
// UIMA-4142 return domain in API
public String getDefaultDomain()
{
return defaultDomain;
}
public Map<String, DuccProperties> getClasses()
{
return clmap;
}
public Map<String, DuccProperties> getUsers()
{
return usermap;
}
public void readConfiguration()
throws FileNotFoundException,
IOException,
IllegalConfigurationException
{
if ( ducc_home == null ) {
throw new IllegalConfigurationException("DUCC_HOME must be defined as a system property.");
}
defaultDomain = getDomainName();
defaultQuantum = SystemPropertyResolver.getIntProperty("ducc.rm.share.quantum", 15);
try {
String tconfig_file_name = resolve(config_file_name);
if ( tconfig_file_name == null ) {
throw new IllegalConfigurationException("File " + config_file_name + " does not exist.");
}
config_file_name = tconfig_file_name;
in = new BufferedReader(new FileReader(config_file_name));
parseStanzas();
doClassInheritance();
connectNodepools();
readNpNodes(defaultDomain);
checkForCycles();
checkPriorities();
} finally {
if ( in != null ) {
try { in.close();
} catch (IOException e) {
// nothing ... who cares if we got this far, and what can we do anyway ?
}
}
}
//for (DuccProperties p : independentNodepools) { // walk the tree and read the node files
// readNpNodes(p, defaultDomain);
//}
String msg = "";
String sep = "";
if ( fairShareExists && (fairShareDefault == null) ) {
msg = "Definition for Default FAIR_SHARE is missing.";
sep = "\n";
}
if ( fixedExists && (fixedDefault == null) ) {
msg = msg + sep + "Definition for Default FIXED_SHARE class is missing.";
sep = "\n";
}
if ( reserveExists && (reserveDefault == null) ) {
msg = msg + sep + "Definition for Default RESERVE class is missing.";
}
if ( !msg.equals("") ) {
throw new IllegalConfigurationException(msg);
}
// UIMA-4275 if classes are ok do the user registry, if it exists
try {
ducc_users = resolve(ducc_users);
if ( ducc_users != null ) {
in = new BufferedReader(new FileReader(ducc_users));
parseStanzas();
verifyUserLimits(); // insure the specified classes exist
}
} finally {
if ( in != null ) {
try { in.close();
} catch (IOException e) {
// nothing ... who cares if we got this far, and what can we do anyway ?
}
}
}
}
String formatNodes(Map<String, String> nodes, int indent)
{
int MAX_WIDTH = 100;
int cur_width = indent;
StringBuffer sb = new StringBuffer();
String leader = String.format("%" + indent + "s", " ");
if ( nodes == null || nodes.size() == 0 ) return leader + "<None>";
sb.append(leader);
for ( String s : nodes.keySet() ) {
if ( s.indexOf(".") >= 0 ) continue; // skip domains to make it more readable
if ( cur_width + s.length() + 1 > MAX_WIDTH) {
sb.append("\n");
sb.append(leader);
cur_width = indent;
}
sb.append(s);
sb.append(" ");
cur_width += s.length() + 1;
}
return sb.toString();
}
void printNodepool(DuccProperties p, String indent)
{
String methodName = "printNodepool";
logInfo(methodName, indent + "Nodepool " + p.getProperty("name"));
logInfo(methodName, indent + " Scheduling quantum: " + p.getProperty("share-quantum"));
logInfo(methodName, indent + " Search Order: " + p.getProperty("search-order"));
String nodefile = p.getProperty("nodefile");
String nfheader = " Node File: ";
logInfo(methodName, indent + nfheader + (nodefile == null ? "None" : nodefile));
@SuppressWarnings("unchecked")
Map<String, String> nodes = (Map<String, String>) p.get("nodes");
logInfo(methodName, formatNodes(nodes, indent.length() + nfheader.length()));
@SuppressWarnings("unchecked")
List<DuccProperties> class_set = (List<DuccProperties>) p.get("classes");
if ( class_set == null ) {
logInfo(methodName, indent + " No classes defined.");
} else {
StringBuffer buf = new StringBuffer();
buf.append(indent + " Classes:");
for (DuccProperties cp : class_set ) {
buf.append(" " + cp.get("name"));
}
logInfo(methodName, buf.toString());
}
@SuppressWarnings("unchecked")
List<DuccProperties> children = (List<DuccProperties>) p.get("children");
if ( children == null ) {
logInfo(methodName, indent + " No subpools.\n");
} else {
StringBuffer buf = new StringBuffer();
buf.append(indent + " Subpools:");
for ( DuccProperties cp : children ) {
buf.append(" " + cp.get("name"));
}
logInfo(methodName, buf.toString());
for ( DuccProperties cp : children ) {
printNodepool(cp, indent + indent);
}
}
}
void printProperty(String k, Object v)
{
String methodName = "printProperty";
if ( v == null ) return; // ignore non-existant properties
logInfo(methodName, String.format(" %-20s: %s", k, v));
}
void printDeprecatedProperty(String k, Object v, String msg)
{
String methodName = "printProperty";
if ( v == null ) return; // ignore non-existant properties
logInfo(methodName, String.format(" %-20s: Deprecated property - %s", k, msg));
}
/**
* Print class values in controlled order and format, not to be confused
* with what you'd get just iterating the map.
*/
void printClass(DuccProperties cl)
{
String methodName = "printClass";
logInfo(methodName, "Class " + cl.get("name"));
printProperty("Policy", cl.get("policy"));
printProperty("Nodepool", cl.get("nodepool"));
printProperty("Priority", cl.get("priority"));
printProperty("Weight", cl.get("weight"));
printProperty("Debug", cl.get("debug"));
printProperty("Cap", cl.get("cap"));
printProperty("Expand By Doubling", cl.get("expand-by-doubling"));
printProperty("Initialization Cap", cl.get("initialization-cap"));
printProperty("Use Prediction", cl.get("use-prediction"));
printProperty("Prediction Fudge", cl.get("prediction-fudge"));
printDeprecatedProperty("Max Processes", cl.get("max-processes"), "IGNORED Use max-allotment = [mem in GB] instead.");
printDeprecatedProperty("Max Machines" , cl.get("max-machines") , "IGNORED Use max-allotment = [mem in GB] instead.");
printProperty("Max Allotment", cl.get("max-allotment"));
printProperty("Enforce Memory Size", cl.get("enforce"));
printProperty("Authorized Users" , cl.get("users"));
logInfo(methodName, "");
}
void printUser(DuccProperties cl)
{
String methodName = "printUser";
logInfo(methodName, "User " + cl.get("name"));
for (Object o : cl.keySet() ) {
String k = (String) o;
if ( k.startsWith("max-allotment") ) {
printProperty(k, cl.get(k));
}
}
logInfo(methodName, "");
}
public void printConfiguration()
{
String methodName = "printConfiguration";
if ( independentNodepools == null || clmap == null ) {
logError(methodName, "Configuration has not been generated. (Run readConfiguration first.)");
}
// First iterate nodepools
for ( DuccProperties p : independentNodepools ) { // it's a tree, use recursion
printNodepool(p, " ");
}
DuccProperties[] class_set = clmap.values().toArray(new DuccProperties[clmap.size()]);
Arrays.sort(class_set, new ClassSorter());
for ( DuccProperties p : class_set ) {
printClass(p);
}
DuccProperties[] user_set = usermap.values().toArray(new DuccProperties[usermap.size()]);
Arrays.sort(user_set, new UserSorter());
for ( DuccProperties p : user_set ) {
printUser(p);
}
}
static void usage(String msg)
{
if ( msg != null ) {
System.out.println(msg);
System.out.println("");
}
System.out.println("Usage:");
System.out.println(" NodeConfiguration [-c class-definitions] [-n nodefile] [-u userfile] [-p] <configfile>");
System.out.println("Where:");
System.out.println(" -c <class-definitions> is the class definition file, usually ducc.classes.");
System.out.println(" -n <nodefile> is nodefile used with tye system, defaults to ducc.nodes");
System.out.println(" -u <userfile> is the user registry.");
System.out.println(" -p Prints the parsed configuration, for verification.");
System.out.println(" -? show this help.");
System.out.println("");
System.exit(1);
}
/**
* Testing and verification of the file
*/
public static void main(String[] args)
{
boolean doprint = false;
String nodefile = null;
String userfile = null;
String config = null;
int i = 0;
// (simplistic, expects to be called from a script that does rigorous argument checking
// and setup)
for ( i = 0; i < args.length; i++ ) {
if ( args[i].equals("-p") ) {
doprint = true;
continue;
}
if ( args[i].equals("-n") ) {
nodefile = args[i+1];
i++;
continue;
}
if ( args[i].equals("-u") ) {
userfile = args[i+1];
i++;
continue;
}
if ( args[i].equals("-c") ) {
config = args[i+1];
i++;
continue;
}
if ( args[i].equals("-?") ) {
usage(null);
}
}
if ( config == null ) {
usage("Class configuration file not specified.");
}
if ( nodefile == null ) {
usage("Nodefile not specified.");
}
NodeConfiguration nc = new NodeConfiguration(config, nodefile, userfile, null);
int rc = 0;
try {
nc.readConfiguration(); // if it doesn't crash it must have worked
if ( doprint ) {
nc.printConfiguration();
}
} catch (FileNotFoundException e) {
System.out.println("Configuration file " + config + " does not exist or cannot be read.");
rc = 1;
} catch (IOException e) {
System.out.println("IOError reading configuration file " + config + ": " + e.toString());
rc = 1;
} catch (IllegalConfigurationException e) {
System.out.println(e.getMessage());
rc = 1;
}
System.exit(rc);
}
static public class ClassSorter
implements Comparator<DuccProperties>
{
public int compare(DuccProperties pr1, DuccProperties pr2)
{
// primary sort thus: FAIR_SHARE, FIXED_SHARE, RESERVE
// seconddary sort on class name
if ( pr1.equals(pr2) ) return 0; // deal with the 'equals' contract
String p1 = pr1.getProperty("policy");
String p2 = pr2.getProperty("policy");
// happily, the string values sort the way we want so we can eliminate a lot of stuff
// by just checking equality.
if ( ! p1.equals(p2) ) {
return p1.compareTo(p2);
}
// and if they're the same, just tiebreak on the name
String n1 = pr1.getProperty("name");
String n2 = pr2.getProperty("name");
return n1.compareTo(n2);
}
}
static public class UserSorter
implements Comparator<DuccProperties>
{
public int compare(DuccProperties u1, DuccProperties u2)
{
String n1 = u1.getProperty("name");
String n2 = u2.getProperty("name");
return n1.compareTo(n2);
}
}
}