Skip to content

Commit 2950498

Browse files
authored
Merge pull request #344 from xylar/get-hostname-re-from-config
Use config options for hostname re in machine discovery
2 parents a15499d + 04e0c2a commit 2950498

File tree

12 files changed

+298
-23
lines changed

12 files changed

+298
-23
lines changed

mache/discover.py

Lines changed: 165 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,76 @@
1+
import configparser
2+
import importlib
13
import os
24
import re
35
import socket
6+
import sys
7+
from dataclasses import dataclass
8+
from importlib import resources as importlib_resources
9+
from typing import Iterable, List, Optional
410

511

6-
def discover_machine(quiet=False):
7-
"""
8-
Figure out the machine from the host name
12+
def discover_machine(
13+
quiet: bool = False,
14+
package: Optional[str] = None,
15+
path: Optional[str] = None,
16+
):
17+
"""Figure out the machine from the host name.
918
1019
Parameters
1120
----------
1221
quiet : bool, optional
1322
Whether to print warnings if the machine name is ambiguous
1423
24+
package : str, optional
25+
An additional Python package to search for machine config files
26+
(``*.cfg``) that include a ``[discovery] hostname_re`` entry.
27+
28+
path : str, optional
29+
An additional directory to search for machine config files (``*.cfg``)
30+
that include a ``[discovery] hostname_re`` entry.
31+
1532
Returns
1633
-------
1734
machine : str
1835
The name of the current machine
1936
"""
37+
2038
hostname = socket.gethostname()
2139
machine = None
22-
machines_by_host_re = {
23-
r'^andes': 'andes',
24-
r'^aurora': 'aurora',
25-
r'^x\d{4}c\d{1}s\d{1}b0n0': 'aurora',
26-
r'^blueslogin': 'anvil',
27-
r'^b\d{3}': 'anvil',
28-
r'^ch-fe': 'chicoma-cpu',
29-
r'^chrlogin': 'chrysalis',
30-
r'^chr-\d{4}': 'chrysalis',
31-
r'^compy': 'compy',
32-
r'^n\d{4}': 'anvil',
33-
r'^polaris': 'polaris',
34-
r'^dane\d{1,4}': 'dane',
35-
r'^ruby\d{1,4}': 'ruby',
36-
}
37-
for host_re, mach in machines_by_host_re.items():
38-
p = re.compile(host_re)
39-
if p.match(hostname):
40-
machine = mach
41-
break
40+
41+
rules = _get_discovery_rules(package=package, path=path)
42+
matches: List[_DiscoveryRule] = []
43+
for rule in rules:
44+
try:
45+
pattern = re.compile(rule.hostname_re)
46+
except re.error:
47+
if not quiet:
48+
print(
49+
f'Warning: invalid hostname_re {rule.hostname_re!r} '
50+
f'for machine {rule.machine!r} from {rule.source}',
51+
file=sys.stderr,
52+
)
53+
continue
54+
if pattern.match(hostname):
55+
matches.append(rule)
56+
57+
if matches:
58+
machine = matches[0].machine
59+
if len(matches) > 1 and not quiet:
60+
others = ', '.join(sorted({rule.machine for rule in matches[1:]}))
61+
print(
62+
f'Warning: hostname {hostname!r} matches multiple machines; '
63+
f'choosing {machine!r}. Other matches: {others}',
64+
file=sys.stderr,
65+
)
66+
4267
if machine is None and 'LMOD_SYSTEM_NAME' in os.environ:
4368
hostname = os.environ['LMOD_SYSTEM_NAME']
4469
if hostname == 'frontier':
4570
# frontier's hostname is too generic to detect, so relying on
4671
# LMOD_SYSTEM_NAME
4772
machine = 'frontier'
73+
4874
if machine is None and 'NERSC_HOST' in os.environ:
4975
hostname = os.environ['NERSC_HOST']
5076
if hostname == 'perlmutter':
@@ -74,3 +100,119 @@ def discover_machine(quiet=False):
74100
machine = fp.read().replace('\n', '').strip()
75101

76102
return machine
103+
104+
105+
@dataclass(frozen=True)
106+
class _DiscoveryRule:
107+
machine: str
108+
hostname_re: str
109+
source: str
110+
111+
112+
def _parse_hostname_re_value(hostname_re: str) -> List[str]:
113+
"""Parse one or more hostname regex patterns from a config value.
114+
115+
We support comma-separated and/or newline-separated entries.
116+
"""
117+
patterns: List[str] = []
118+
for line in hostname_re.splitlines():
119+
line = line.strip()
120+
if not line:
121+
continue
122+
# Split only on comma+whitespace so patterns like `{1,4}` are safe.
123+
for entry in re.split(r',\s+', line):
124+
entry = entry.strip()
125+
if entry:
126+
patterns.append(entry)
127+
return patterns
128+
129+
130+
def _read_discovery_rules_from_cfg(
131+
cfg_path: str, machine: str, source: str
132+
) -> List[_DiscoveryRule]:
133+
# Do NOT enable interpolation here: regex patterns commonly contain '$'.
134+
config = configparser.RawConfigParser()
135+
config.read(cfg_path)
136+
if not config.has_option('discovery', 'hostname_re'):
137+
return []
138+
raw_value = config.get(
139+
'discovery', 'hostname_re', raw=True, fallback=''
140+
).strip()
141+
if not raw_value:
142+
return []
143+
return [
144+
_DiscoveryRule(machine=machine, hostname_re=pattern, source=source)
145+
for pattern in _parse_hostname_re_value(raw_value)
146+
]
147+
148+
149+
def _iter_cfgs_in_package(package: str) -> Iterable[tuple[str, str]]:
150+
"""Yield (machine_name, cfg_path) from a package containing *.cfg files."""
151+
module = importlib.import_module(package)
152+
root = importlib_resources.files(module)
153+
for child in root.iterdir():
154+
if not child.is_file():
155+
continue
156+
name = child.name
157+
if not name.endswith('.cfg'):
158+
continue
159+
machine = os.path.splitext(name)[0]
160+
yield machine, str(child)
161+
162+
163+
def _iter_cfgs_in_path(path: str) -> Iterable[tuple[str, str]]:
164+
"""Yield (machine_name, cfg_path) from a directory of ``*.cfg`` files."""
165+
if not os.path.isdir(path):
166+
return
167+
for name in sorted(os.listdir(path)):
168+
if not name.endswith('.cfg'):
169+
continue
170+
machine = os.path.splitext(name)[0]
171+
yield machine, os.path.join(path, name)
172+
173+
174+
def _get_discovery_rules(
175+
*,
176+
package: Optional[str] = None,
177+
path: Optional[str] = None,
178+
builtin_package: str = 'mache.machines',
179+
) -> List[_DiscoveryRule]:
180+
"""Get hostname discovery rules.
181+
182+
Precedence is:
183+
1) rules from ``path`` (if provided)
184+
2) rules from ``package`` (if provided)
185+
3) rules from the built-in machines package
186+
"""
187+
rules: List[_DiscoveryRule] = []
188+
189+
if path is not None:
190+
for machine, cfg_path in _iter_cfgs_in_path(path):
191+
rules.extend(
192+
_read_discovery_rules_from_cfg(
193+
cfg_path=cfg_path,
194+
machine=machine,
195+
source=f'path:{path}',
196+
)
197+
)
198+
199+
if package is not None:
200+
for machine, cfg_path in _iter_cfgs_in_package(package):
201+
rules.extend(
202+
_read_discovery_rules_from_cfg(
203+
cfg_path=cfg_path,
204+
machine=machine,
205+
source=f'package:{package}',
206+
)
207+
)
208+
209+
for machine, cfg_path in _iter_cfgs_in_package(builtin_package):
210+
rules.extend(
211+
_read_discovery_rules_from_cfg(
212+
cfg_path=cfg_path,
213+
machine=machine,
214+
source=f'package:{builtin_package}',
215+
)
216+
)
217+
218+
return rules

mache/machines/andes.cfg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ partitions = batch
4545

4646
# the full hostname of the machine
4747
hostname = andes.olcf.ornl.gov
48+
49+
# Options related to machine discovery
50+
[discovery]
51+
52+
# a regular expression used to identify this machine from its hostname
53+
hostname_re = ^andes

mache/machines/anvil.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,10 @@ public_diags = /lcrc/group/e3sm/public_html/diagnostics
7474

7575
# private diagnostics directory
7676
private_diags = /lcrc/group/e3sm/diagnostics_private
77+
78+
79+
# Options related to machine discovery
80+
[discovery]
81+
82+
# regular expression(s) used to identify this machine from its hostname
83+
hostname_re = ^blueslogin, ^b\d{3}, ^n\d{4}

mache/machines/aurora.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,10 @@ queues = prod, debug
5757

5858
# the full hostname of the machine
5959
hostname = aurora.alcf.anl.gov
60+
61+
62+
# Options related to machine discovery
63+
[discovery]
64+
65+
# regular expression(s) used to identify this machine from its hostname
66+
hostname_re = ^aurora, ^x\d{4}c\d{1}s\d{1}b0n0

mache/machines/chicoma-cpu.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,10 @@ hostname = wtrw.lanl.gov
4848

4949
# tunnel command
5050
tunnel_hostname = ch-fe
51+
52+
53+
# Options related to machine discovery
54+
[discovery]
55+
56+
# a regular expression used to identify this machine from its hostname
57+
hostname_re = ^ch-fe

mache/machines/chrysalis.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,10 @@ public_diags = /lcrc/group/e3sm/public_html/diagnostics
6767

6868
# private diagnostics directory
6969
private_diags = /lcrc/group/e3sm/diagnostics_private
70+
71+
72+
# Options related to machine discovery
73+
[discovery]
74+
75+
# regular expression(s) used to identify this machine from its hostname
76+
hostname_re = ^chrlogin, ^chr-\d{4}

mache/machines/compy.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,10 @@ qos = regular
6868

6969
# the full hostname of the machine
7070
hostname = compy.pnl.gov
71+
72+
73+
# Options related to machine discovery
74+
[discovery]
75+
76+
# a regular expression used to identify this machine from its hostname
77+
hostname_re = ^compy

mache/machines/dane.cfg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,9 @@ partitions = pbatch, pdebug
5858

5959
# the full hostname of the machine
6060
hostname = dane.llnl.gov
61+
62+
# Options related to machine discovery
63+
[discovery]
64+
65+
# a regular expression used to identify this machine from its hostname
66+
hostname_re = ^dane\d{1,4}

mache/machines/frontier.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,10 @@ cray_compilers = True
6565

6666
# the full hostname of the machine
6767
hostname = frontier.olcf.ornl.gov
68+
69+
70+
# Options related to machine discovery
71+
[discovery]
72+
73+
# a regular expression used to identify this machine from its hostname
74+
hostname_re = ^frontier

mache/machines/polaris.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,10 @@ partitions = prod
4545

4646
# the full hostname of the machine
4747
hostname = polaris.alcf.anl.gov
48+
49+
50+
# Options related to machine discovery
51+
[discovery]
52+
53+
# a regular expression used to identify this machine from its hostname
54+
hostname_re = ^polaris

0 commit comments

Comments
 (0)