Skip to content

Commit 2fec77c

Browse files
Merge pull request #135 from matyasselmeci/pr/SOFTWARE-4895.ceattrgen
Add osg-ce-attributes-generator (SOFTWARE-4895)
2 parents 0a94e4f + a960c42 commit 2fec77c

File tree

9 files changed

+345
-69
lines changed

9 files changed

+345
-69
lines changed

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,87 @@ This section is contained in `/etc/osg/config.d/40-siteinfo.ini` which is provid
475475
| **host\_name** | String | This should be set to be hostname of the CE that is being configured |
476476
| **resource** | String | The resource name of this CE endpoint as registered in Topology. |
477477
| **resource\_group** | String | The resource\_group of this CE as registered in Topology. |
478+
479+
480+
OSG CE Attributes Generator
481+
===========================
482+
483+
The CE Attributes Generator is a standalone tool to create resource catalog attributes for a CE, that will be uploaded to the CE collector.
484+
It is a lightweight alternative to OSG-Configure, and it prints the attributes instead of modifying your CE's config files.
485+
486+
It uses `*.ini` files from OSG-Configure as the source of the data.
487+
488+
Installation
489+
------------
490+
491+
The CE Attributes Generator is typically installed via RPMs from the OSG repositories.
492+
See [OSG documentation for how to enable the repositories](https://opensciencegrid.org/docs/common/yum/);
493+
once the repos are enabled, install by running
494+
495+
yum install osg-ce-attributes-generator
496+
497+
Note: `osg-ce-attributes-generator` is not available in the OSG 3.5 series.
498+
499+
500+
Configuration
501+
-------------
502+
503+
The CE Attributes Generator uses the same config files that OSG-Configure uses.
504+
See the [31-cluster.ini](#31-clusterini--subcluster-and-resource-entry-sections),
505+
[35-pilot.ini](#35-pilotini--pilot) for attributes.
506+
507+
You will also need `resource` and `resource_group` from the `Site Information` section
508+
([40-siteinfo.ini](#40-siteinfoini--site-information-section)).
509+
You can also use `--resource` and `--resource-group` on the command line to specify these
510+
instead.
511+
512+
In addition, you need to specify at least one batch system.
513+
The recognized batch systems are "Condor", "LSF", "PBS", "SGE", and "SLURM".
514+
You can specify available batch systems in one of three ways:
515+
516+
1. Have a batch system section (one of the `20-*.ini` files) with the attribute `enabled=True`, e.g.
517+
```ini
518+
[Condor]
519+
enabled=True
520+
```
521+
2. If you are using BOSCO, specify your batch system in the `batch` attribute in `20-bosco.ini`.
522+
523+
3. Specify a comma-separated list with `--batch-systems` on the command line.
524+
525+
526+
Batch systems specified on the command line take precedence. If you have both a BOSCO section
527+
and an enabled batch system section, all of them will be listed in the attributes file.
528+
529+
530+
Invocation
531+
----------
532+
533+
osg-ce-attributes-generator [<options>] [<config_location>] [<output>]
534+
535+
- `config_location` is a file or directory to load configuration from.
536+
If this is a directory, load every `*.ini` file in that directory.
537+
If `-`, read from STDIN. The default is to read `/etc/osg/config.d`,
538+
same as `osg-configure`.
539+
540+
- `output` is a file to write attributes to.
541+
If `-` or unspecified, write to STDOUT.
542+
543+
544+
### Options
545+
546+
- `--resource RESOURCE_NAME`
547+
548+
The Resource name to use, which should match your Topology registration.
549+
Equivalent to `resource` in the `Site Information` section.
550+
551+
- `--resource-group RESOURCE_GROUP_NAME`
552+
553+
The Resource Group name to use, which should match your Topology registration.
554+
Equivalent to `resource_group` in the `Site Information` section.
555+
556+
- `--batch-systems BATCH_SYSTEMS_LIST`
557+
558+
A comma-separated list of batch systems used by the resource.
559+
Recognized batch systems are: `Condor`, `LSF`, `PBS`, `SGE`, and `SLURM`.
560+
Equivalent to enabling the batch system sections in the
561+
`20-*.ini` files, or, if using BOSCO, setting `batch` in the `BOSCO` section.

osg_configure/configure_modules/infoservices.py

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
for CE collector info services"""
33

44
import re
5-
import configparser as ConfigParser
5+
from configparser import ConfigParser
66
import subprocess
77
import logging
88

99
from osg_configure.modules import exceptions
1010
from osg_configure.modules import utilities
1111
from osg_configure.modules import configfile
1212
from osg_configure.modules.baseconfiguration import BaseConfiguration
13+
from osg_configure.modules import ce_attributes
1314
from osg_configure.modules import subcluster
1415
from osg_configure.modules import reversevomap
1516

@@ -22,10 +23,6 @@
2223
BAN_VOMS_MAPFILE = reversevomap.BAN_MAPFILE
2324
BAN_MAPFILE = '/etc/grid-security/ban-mapfile'
2425

25-
# BATCH_SYSTEMS here is both the config sections for the batch systems
26-
# and the values in the OSG_BatchSystems attribute since they are
27-
# coincidentally the same. If they ever change, make a mapping.
28-
BATCH_SYSTEMS = ['Condor', 'LSF', 'PBS', 'SGE', 'SLURM']
2926

3027
try:
3128
import classad
@@ -55,13 +52,10 @@ def __init__(self, *args, **kwargs):
5552

5653
self.ce_collectors = []
5754
self.ce_collector_required_rpms_installed = utilities.rpm_installed('htcondor-ce')
58-
self.osg_resource = ""
59-
self.osg_resource_group = ""
60-
self.enabled_batch_systems = []
6155
self.htcondor_gateway_enabled = None
62-
self.resource_catalog = None
6356
self.authorization_method = None
64-
self.subcluster_sections = None
57+
self.configuration = None
58+
self.ce_attributes_str = ""
6559

6660
self.log("InfoServicesConfiguration.__init__ completed")
6761

@@ -80,7 +74,7 @@ def _parse_ce_collectors(self, val):
8074
else:
8175
return val.split(',')
8276

83-
def parse_configuration(self, configuration):
77+
def parse_configuration(self, configuration: ConfigParser):
8478
"""
8579
Try to get configuration information from ConfigParser or SafeConfigParser object given
8680
by configuration and write recognized settings to attributes dict
@@ -113,27 +107,9 @@ def parse_configuration(self, configuration):
113107

114108
self.ce_collectors = self._parse_ce_collectors(self.options['ce_collectors'].value)
115109

116-
def csg(section, option):
117-
return utilities.config_safe_get(configuration, section, option, None)
110+
self.htcondor_gateway_enabled = configuration.get('Gateway', 'htcondor_gateway_enabled', fallback=False)
118111

119-
def csgbool(section, option):
120-
return utilities.config_safe_getboolean(configuration, section, option, False)
121-
122-
# We get some values for HTCondor-CE from the Site Information section
123-
self.osg_resource = csg('Site Information', 'resource')
124-
self.osg_resource_group = csg('Site Information', 'resource_group')
125-
# and the enabled batch systems from their respective sections
126-
self.enabled_batch_systems = [bs for bs in BATCH_SYSTEMS if csgbool(bs, 'enabled')]
127-
128-
self.htcondor_gateway_enabled = csgbool('Gateway', 'htcondor_gateway_enabled')
129-
130-
self.subcluster_sections = ConfigParser.SafeConfigParser()
131-
132-
for section in configuration.sections():
133-
if subcluster.is_subcluster_like(section):
134-
self.subcluster_sections.add_section(section)
135-
for key, value in configuration.items(section):
136-
self.subcluster_sections.set(section, key, value)
112+
self.configuration = configuration # save for later: the ce_attributes module reads the whole config.
137113

138114
if utilities.ce_installed() and not subcluster.check_config(configuration):
139115
self.log("On a CE but no valid 'Subcluster', 'Resource Entry', or 'Pilot' sections defined."
@@ -147,7 +123,7 @@ def csgbool(section, option):
147123
# configure(), but at this point we don't have a way of knowing what
148124
# default_allowed_vos should be.
149125
if self.ce_collector_required_rpms_installed and self.htcondor_gateway_enabled and classad is not None:
150-
subcluster.resource_catalog_from_config(self.subcluster_sections, default_allowed_vos=["*"])
126+
subcluster.resource_catalog_from_config(configuration, default_allowed_vos=["*"])
151127

152128
self.log('InfoServicesConfiguration.parse_configuration completed')
153129

@@ -171,14 +147,12 @@ def configure(self, attributes):
171147
if classad is None:
172148
self.log("Cannot configure HTCondor CE info services: unable to import HTCondor Python bindings."
173149
"\nEnsure the 'classad' Python module is installed and accessible to Python scripts."
174-
"\nIf using HTCondor from RPMs, install the 'condor-python' RPM."
150+
"\nIf using HTCondor from RPMs, install the 'python3-condor' RPM."
175151
"\nIf not, you may need to add the directory containing the Python bindings to PYTHONPATH."
176152
"\nHTCondor version must be at least 8.2.0.", level=logging.WARNING)
177153
else:
178154
try:
179-
self.resource_catalog = subcluster.resource_catalog_from_config(
180-
self.subcluster_sections,
181-
default_allowed_vos=["*"])
155+
self.ce_attributes_str = ce_attributes.get_ce_attributes_str(self.configuration)
182156
except exceptions.SettingError as err:
183157
self.log("Error in info services configuration: %s" % err, level=logging.ERROR)
184158
return False
@@ -230,27 +204,10 @@ def _write_ce_collector_attributes_file(self, attributes_file):
230204
CE-Collector to advertise
231205
232206
"""
233-
schedd_attrs_list = ["$(SCHEDD_ATTRS)"]
234-
attributes_file_lines = []
235-
236-
for name, value in [
237-
('OSG_Resource', self.osg_resource),
238-
('OSG_ResourceGroup', self.osg_resource_group),
239-
('OSG_BatchSystems', ",".join(self.enabled_batch_systems))
240-
]:
241-
attributes_file_lines.append("%s = %s" % (name, utilities.classad_quote(value)))
242-
schedd_attrs_list.append(name)
243-
244-
if self.resource_catalog:
245-
attributes_file_lines.append(self.resource_catalog.compose_text())
246-
schedd_attrs_list.append('OSG_ResourceCatalog')
247-
248207
attributes_file_contents = (
249208
"# Do not edit - file generated by osg-configure\n"
250-
+ "\n".join(attributes_file_lines) + "\n"
251-
+ "SCHEDD_ATTRS = " + " ".join(schedd_attrs_list) + "\n"
209+
+ self.ce_attributes_str + "\n"
252210
)
253-
254211
return utilities.atomic_write(attributes_file, attributes_file_contents)
255212

256213
def _write_ce_collector_file(self, info_services_file):

osg_configure/configure_modules/siteinformation.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ def __init__(self, *args, **kwargs):
4040
mapping='OSG_SITE_NAME'),
4141
'resource_group':
4242
configfile.Option(name='resource_group',
43-
required=MANDATORY_ON_CE)}
43+
required=MANDATORY_ON_CE),
44+
'batch_systems':
45+
configfile.Option(name='batch_systems',
46+
required=OPTIONAL,
47+
default_value=None)}
4448

4549
self.config_section = "Site Information"
4650
self.enabled = True
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# BATCH_SYSTEMS here is both the config sections for the batch systems
2+
# and the values in the OSG_BatchSystems attribute since they are
3+
# coincidentally the same. If they ever change, make a mapping.
4+
from configparser import ConfigParser
5+
from typing import Dict
6+
7+
from osg_configure.modules import utilities, subcluster
8+
from osg_configure.modules.exceptions import SettingError
9+
10+
BATCH_SYSTEMS_CASE_MAP = {
11+
'condor': 'Condor',
12+
'lsf': 'LSF',
13+
'pbs': 'PBS',
14+
'sge': 'SGE',
15+
'slurm': 'SLURM',
16+
}
17+
BATCH_SYSTEMS = list(BATCH_SYSTEMS_CASE_MAP.values())
18+
19+
20+
def empty_if_blank(value: str) -> str:
21+
return "" if utilities.blank(value) else value
22+
23+
24+
def get_resource_from_config(config: ConfigParser) -> str:
25+
return utilities.classad_quote(
26+
empty_if_blank(
27+
config.get("Site Information", "resource", fallback="")
28+
)
29+
)
30+
31+
32+
def get_resource_group_from_config(config: ConfigParser) -> str:
33+
return utilities.classad_quote(
34+
empty_if_blank(
35+
config.get("Site Information", "resource_group", fallback="")
36+
)
37+
)
38+
39+
40+
def get_batch_systems_from_config(config: ConfigParser) -> str:
41+
batch_systems = set()
42+
43+
siteinfo_batch_systems = config.get("Site Information", "batch_systems", fallback=None)
44+
if siteinfo_batch_systems is not None:
45+
# Site Information.batch_systems specified -- this one wins
46+
split_batch_systems = utilities.split_comma_separated_list(siteinfo_batch_systems)
47+
for batch_system in split_batch_systems:
48+
try:
49+
batch_systems.add(BATCH_SYSTEMS_CASE_MAP[batch_system.lower()])
50+
except KeyError:
51+
raise SettingError("Unrecognized batch system %s" % batch_system)
52+
else:
53+
# Add each batch system that's enabled from the sections in the 20-*.ini files.
54+
for batch_system in BATCH_SYSTEMS:
55+
if batch_system in config:
56+
if config.getboolean(section=batch_system, option="enabled", fallback=None):
57+
batch_systems.add(batch_system)
58+
59+
# Special case: BOSCO (see SOFTWARE-3720); use the BOSCO.batch argument.
60+
if config.getboolean("BOSCO", "enabled", fallback=False):
61+
bosco_batch = config.get("BOSCO", "batch", fallback=None)
62+
if bosco_batch:
63+
try:
64+
batch_systems.add(BATCH_SYSTEMS_CASE_MAP[bosco_batch.lower()])
65+
except KeyError:
66+
raise SettingError("Unrecognized batch system %s in Bosco section" % bosco_batch)
67+
68+
return utilities.classad_quote(",".join(sorted(batch_systems)))
69+
70+
71+
def get_resource_catalog_from_config(config: ConfigParser) -> str:
72+
return subcluster.resource_catalog_from_config(config, default_allowed_vos=[]).format_value()
73+
74+
75+
def get_attributes(config: ConfigParser) -> Dict[str, str]:
76+
"""Turn config from .ini files into a dict of condor settings.
77+
78+
"""
79+
attributes = {}
80+
81+
resource = get_resource_from_config(config)
82+
if resource and resource != '""':
83+
attributes["OSG_Resource"] = resource
84+
85+
resource_group = get_resource_group_from_config(config)
86+
if resource_group and resource_group != '""':
87+
attributes["OSG_ResourceGroup"] = resource_group
88+
89+
batch_systems = get_batch_systems_from_config(config)
90+
if batch_systems and batch_systems != '""':
91+
attributes["OSG_BatchSystems"] = batch_systems
92+
93+
resource_catalog = get_resource_catalog_from_config(config)
94+
if resource_catalog and resource_catalog != "{}":
95+
attributes["OSG_ResourceCatalog"] = resource_catalog
96+
97+
return attributes
98+
99+
100+
def get_ce_attributes_str(
101+
config: ConfigParser,
102+
) -> str:
103+
attributes = get_attributes(config)
104+
attributes["SCHEDD_ATTRS"] = "$(SCHEDD_ATTRS), " + ", ".join(attributes.keys())
105+
return "\n".join(f"{key} = {value}" for key, value in attributes.items())

osg_configure/modules/resourcecatalog.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ def _to_classad_list(a_list):
2121
return "{ " + ", ".join([utilities.classad_quote(it) for it in a_list if it]) + " }"
2222

2323

24-
def _str_to_classad_list(a_str):
25-
return _to_classad_list(utilities.split_comma_separated_list(a_str))
26-
27-
2824
class RCAttribute(namedtuple("RCAttribute", "rce_field classad_attr format_fn")):
2925
"""The mapping of an RCEntry field to a classad attribute, with a format function"""
3026

@@ -140,7 +136,6 @@ def as_attributes(self):
140136
return attributes
141137

142138

143-
144139
class ResourceCatalog(object):
145140
"""Class for building an OSG_ResourceCatalog attribute in condor-ce configs for the ce-collector"""
146141

@@ -152,7 +147,7 @@ def add_rcentry(self, rcentry):
152147

153148
return self
154149

155-
def compose_text(self):
150+
def format_value(self) -> str:
156151
"""Return the OSG_ResourceCatalog classad attribute made of all the entries in this object"""
157152
if not self.entries:
158153
catalog = '{}'
@@ -168,5 +163,8 @@ def compose_text(self):
168163
catalog = ('{ \\\n'
169164
+ ', \\\n'.join(entry_texts)
170165
+ ' \\\n}')
166+
return catalog
171167

168+
def compose_text(self):
169+
catalog = self.format_value()
172170
return 'OSG_ResourceCatalog = ' + catalog

0 commit comments

Comments
 (0)