"""
Interface to the config_machines.xml file. This class inherits from GenericXML.py
"""
from CIME.XML.standard_module_setup import *
from CIME.XML.generic_xml import GenericXML
from CIME.XML.files import Files
from CIME.utils import convert_to_unknown_type, get_cime_config, get_all_cime_models
import socket
logger = logging.getLogger(__name__)
[docs]class Machines(GenericXML):
def __init__(self, infile=None, files=None, machine=None, extra_machines_dir=None):
"""
initialize an object
if a filename is provided it will be used,
otherwise if a files object is provided it will be used
otherwise create a files object from default values
If extra_machines_dir is provided, it should be a string giving a path to an
additional directory that will be searched for a config_machines.xml file; if
found, the contents of this file will be appended to the standard
config_machines.xml. An empty string is treated the same as None.
"""
self.machine_node = None
self.machine = None
self.machines_dir = None
self.custom_settings = {}
schema = None
supported_models = []
if files is None:
files = Files()
if infile is None:
infile = files.get_value("MACHINES_SPEC_FILE")
schema = files.get_schema("MACHINES_SPEC_FILE")
logger.debug("Verifying using schema {}".format(schema))
self.machines_dir = os.path.dirname(infile)
GenericXML.__init__(self, infile, schema)
# Append the contents of $HOME/.cime/config_machines.xml if it exists.
#
# Also append the contents of a config_machines.xml file in the directory given by
# extra_machines_dir, if present.
#
# This could cause problems if node matches are repeated when only one is expected.
local_infile = os.path.join(os.environ.get("HOME"),".cime","config_machines.xml")
logger.debug("Infile: {}".format(local_infile))
if os.path.exists(local_infile):
GenericXML.read(self, local_infile, schema)
if extra_machines_dir:
local_infile = os.path.join(extra_machines_dir, "config_machines.xml")
logger.debug("Infile: {}".format(local_infile))
if os.path.exists(local_infile):
GenericXML.read(self, local_infile, schema)
if machine is None:
if "CIME_MACHINE" in os.environ:
machine = os.environ["CIME_MACHINE"]
else:
cime_config = get_cime_config()
if cime_config.has_option("main", "machine"):
machine = cime_config.get("main", "machine")
if machine is None:
machine = self.probe_machine_name()
if machine is None:
for potential_model in get_all_cime_models():
local_infile = os.path.join(get_cime_root(), "config",potential_model,"machines","config_machines.xml")
if local_infile != infile:
GenericXML.read(self, local_infile, schema)
if self.probe_machine_name() is not None:
supported_models.append(potential_model)
GenericXML.change_file(self, infile, schema)
expect(machine is not None, "Could not initialize machine object from {} or {}. This machine is not available for the target CIME_MODEL. The supported CIME_MODELS that can be used are: {}".format(infile, local_infile, supported_models))
self.set_machine(machine)
[docs] def get_child(self, name=None, attributes=None, root=None, err_msg=None):
if root is None:
root = self.machine_node
return super(Machines, self).get_child(name, attributes, root, err_msg)
[docs] def get_machines_dir(self):
"""
Return the directory of the machines file
"""
return self.machines_dir
[docs] def get_machine_name(self):
"""
Return the name of the machine
"""
return self.machine
[docs] def get_node_names(self):
"""
Return the names of all the child nodes for the target machine
"""
nodes = self.get_children(root=self.machine_node)
node_names = []
for node in nodes:
node_names.append(self.name(node))
return node_names
[docs] def get_first_child_nodes(self, nodename):
"""
Return the names of all the child nodes for the target machine
"""
nodes = self.get_children(nodename, root=self.machine_node)
return nodes
[docs] def list_available_machines(self):
"""
Return a list of machines defined for a given CIME_MODEL
"""
machines = []
nodes = self.get_children("machine")
for node in nodes:
mach = self.get(node, "MACH")
machines.append(mach)
return machines
[docs] def probe_machine_name(self, warn=True):
"""
Find a matching regular expression for hostname
in the NODENAME_REGEX field in the file. First match wins.
"""
names_not_found = []
nametomatch = socket.getfqdn()
machine = self._probe_machine_name_one_guess(nametomatch)
if machine is None:
names_not_found.append(nametomatch)
nametomatch = socket.gethostname()
machine = self._probe_machine_name_one_guess(nametomatch)
if machine is None:
names_not_found.append(nametomatch)
names_not_found_quoted = ["'" + name + "'" for name in names_not_found]
names_not_found_str = ' or '.join(names_not_found_quoted)
if warn:
logger.warning("Could not find machine match for {}".format(names_not_found_str))
return machine
def _probe_machine_name_one_guess(self, nametomatch):
"""
Find a matching regular expression for nametomatch in the NODENAME_REGEX
field in the file. First match wins. Returns None if no match is found.
"""
machine = None
nodes = self.get_children("machine")
for node in nodes:
machtocheck = self.get(node, "MACH")
logger.debug("machine is " + machtocheck)
regex_str_node = self.get_optional_child("NODENAME_REGEX", root=node)
regex_str = machtocheck if regex_str_node is None else self.text(regex_str_node)
if regex_str is not None:
logger.debug("machine regex string is " + regex_str)
regex = re.compile(regex_str)
if regex.match(nametomatch):
logger.debug("Found machine: {} matches {}".format(machtocheck, nametomatch))
machine = machtocheck
break
return machine
[docs] def set_machine(self, machine):
"""
Sets the machine block in the Machines object
>>> machobj = Machines(machine="melvin")
>>> machobj.get_machine_name()
'melvin'
>>> machobj.set_machine("trump") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
CIMEError: ERROR: No machine trump found
"""
if machine == "Query":
self.machine = machine
elif self.machine != machine or self.machine_node is None:
self.machine_node = super(Machines,self).get_child("machine", {"MACH" : machine}, err_msg="No machine {} found".format(machine))
self.machine = machine
return machine
#pylint: disable=arguments-differ
[docs] def get_value(self, name, attributes=None, resolved=True, subgroup=None):
"""
Get Value of fields in the config_machines.xml file
"""
expect(self.machine_node is not None, "Machine object has no machine defined")
expect(subgroup is None, "This class does not support subgroups")
value = None
if name in self.custom_settings:
return self.custom_settings[name]
# COMPILER and MPILIB are special, if called without arguments they get the default value from the
# COMPILERS and MPILIBS lists in the file.
if name == "COMPILER":
value = self.get_default_compiler()
elif name == "MPILIB":
value = self.get_default_MPIlib(attributes)
else:
node = self.get_optional_child(name, root=self.machine_node, attributes=attributes)
if node is not None:
value = self.text(node)
if resolved:
if value is not None:
value = self.get_resolved_value(value)
elif name in os.environ:
value = os.environ[name]
value = convert_to_unknown_type(value)
return value
[docs] def get_field_from_list(self, listname, reqval=None, attributes=None):
"""
Some of the fields have lists of valid values in the xml, parse these
lists and return the first value if reqval is not provided and reqval
if it is a valid setting for the machine
"""
expect(self.machine_node is not None, "Machine object has no machine defined")
supported_values = self.get_value(listname, attributes=attributes)
# if no match with attributes, try without
if supported_values is None:
supported_values = self.get_value(listname, attributes=None)
expect(supported_values is not None,
"No list found for " + listname + " on machine " + self.machine)
supported_values = supported_values.split(",") #pylint: disable=no-member
if reqval is None or reqval == "UNSET":
return supported_values[0]
for val in supported_values:
if val == reqval:
return reqval
return None
[docs] def get_default_compiler(self):
"""
Get the compiler to use from the list of COMPILERS
"""
cime_config = get_cime_config()
if cime_config.has_option('main','COMPILER'):
value = cime_config.get('main', 'COMPILER')
expect(self.is_valid_compiler(value), "User-selected compiler {} is not supported on machine {}".format(value, self.machine))
else:
value = self.get_field_from_list("COMPILERS")
return value
[docs] def get_default_MPIlib(self, attributes=None):
"""
Get the MPILIB to use from the list of MPILIBS
"""
return self.get_field_from_list("MPILIBS", attributes=attributes)
[docs] def is_valid_compiler(self,compiler):
"""
Check the compiler is valid for the current machine
>>> machobj = Machines(machine="cori-knl")
>>> machobj.get_default_compiler()
'intel'
>>> machobj.is_valid_compiler("gnu")
True
>>> machobj.is_valid_compiler("nag")
False
"""
return self.get_field_from_list("COMPILERS", reqval=compiler) is not None
[docs] def is_valid_MPIlib(self, mpilib, attributes=None):
"""
Check the MPILIB is valid for the current machine
>>> machobj = Machines(machine="cori-knl")
>>> machobj.is_valid_MPIlib("mpi-serial")
True
>>> machobj.is_valid_MPIlib("fake-mpi")
False
"""
return mpilib == "mpi-serial" or \
self.get_field_from_list("MPILIBS", reqval=mpilib, attributes=attributes) is not None
[docs] def has_batch_system(self):
"""
Return if this machine has a batch system
>>> machobj = Machines(machine="cori-knl")
>>> machobj.has_batch_system()
True
>>> machobj.set_machine("melvin")
'melvin'
>>> machobj.has_batch_system()
False
"""
result = False
batch_system = self.get_optional_child("BATCH_SYSTEM", root=self.machine_node)
if batch_system is not None:
result = (self.text(batch_system) is not None and self.text(batch_system) != "none")
logger.debug("Machine {} has batch: {}".format(self.machine, result))
return result
[docs] def get_suffix(self, suffix_type):
node = self.get_optional_child("default_run_suffix")
if node is not None:
suffix_node = self.get_optional_child(suffix_type, root=node)
if suffix_node is not None:
return self.text(suffix_node)
return None
[docs] def set_value(self, vid, value, subgroup=None, ignore_type=True):
# A temporary cache only
self.custom_settings[vid] = value
[docs] def print_values(self):
# write out machines
machines = self.get_children("machine")
logger.info("Machines")
for machine in machines:
name = self.get(machine, "MACH")
desc = self.get_child("DESC", root=machine)
os_ = self.get_child("OS", root=machine)
compilers = self.get_child("COMPILERS", root=machine)
max_tasks_per_node = self.get_child("MAX_TASKS_PER_NODE", root=machine)
max_mpitasks_per_node = self.get_child("MAX_MPITASKS_PER_NODE", root=machine)
print( " {} : {} ".format(name , self.text(desc)))
print( " os ", self.text(os_))
print( " compilers ",self.text(compilers))
if max_mpitasks_per_node is not None:
print(" pes/node ",self.text(max_mpitasks_per_node))
if max_tasks_per_node is not None:
print(" max_tasks/node ",self.text(max_tasks_per_node))
[docs] def return_values(self):
""" return a dictionary of machine info
This routine is used by external tools in https://github.com/NCAR/CESM_xml2html
"""
machines = self.get_children("machine")
mach_dict = dict()
logger.debug("Machines return values")
for machine in machines:
name = self.get(machine, "MACH")
desc = self.get_child("DESC", root=machine)
mach_dict[(name,"description")] = self.text(desc)
os_ = self.get_child("OS", root=machine)
mach_dict[(name,"os")] = self.text(os_)
compilers = self.get_child("COMPILERS", root=machine)
mach_dict[(name,"compilers")] = self.text(compilers)
max_tasks_per_node = self.get_child("MAX_TASKS_PER_NODE", root=machine)
mach_dict[(name,"max_tasks_per_node")] = self.text(max_tasks_per_node)
max_mpitasks_per_node = self.get_child("MAX_MPITASKS_PER_NODE", root=machine)
mach_dict[(name,"max_mpitasks_per_node")] = self.text(max_mpitasks_per_node)
return mach_dict