Skip to content

Commit 262eab1

Browse files
authored
Merge pull request #7143 from chaen/getToken_integration
[v8.1] [diracx] Get token and run integration tests
2 parents 4d534a3 + 973bb72 commit 262eab1

File tree

14 files changed

+339
-104
lines changed

14 files changed

+339
-104
lines changed

docs/diracdoctools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"_arc",
1010
"arc",
1111
"cmreslogging",
12+
"diracx",
1213
"fts3",
1314
"gfal2",
1415
"git",

docs/source/AdministratorGuide/ServerInstallations/environment_variable_configuration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ DIRAC_DEPRECATED_FAIL
1818
If set, the use of functions or objects that are marked ``@deprecated`` will fail. Useful for example in continuous
1919
integration tests against future versions of DIRAC
2020

21+
DIRAC_ENABLE_DIRACX_JOB_MONITORING
22+
If set, calls the diracx job monitoring service. Off by default.
23+
24+
DIRAC_ENABLE_DIRACX_LOGIN
25+
If set, retrieve a DiracX token when calling dirac-proxy-init or dirac-login
26+
2127
DIRAC_FEWER_CFG_LOCKS
2228
If ``true`` or ``yes`` or ``on`` or ``1`` or ``y`` or ``t``, DIRAC will reduce the number of locks used when accessing the CS for better performance (default, ``no``).
2329

integration_tests.py

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@
2929
"DIRACOSVER": "master",
3030
"DIRACOS_TARBALL_PATH": None,
3131
"TEST_HTTPS": "Yes",
32+
"TEST_DIRACX": "No",
3233
"DIRAC_FEWER_CFG_LOCKS": None,
3334
"DIRAC_USE_JSON_ENCODE": None,
3435
"INSTALLATION_BRANCH": "",
3536
}
36-
DEFAULT_MODULES = {
37-
"DIRAC": Path(__file__).parent.absolute(),
38-
}
37+
DIRACX_OPTIONS = (
38+
"DIRAC_ENABLE_DIRACX_LOGIN",
39+
"DIRAC_ENABLE_DIRACX_JOB_MONITORING",
40+
)
41+
DEFAULT_MODULES = {"DIRAC": Path(__file__).parent.absolute()}
3942

4043
# Static configuration
4144
DB_USER = "Dirac"
@@ -180,7 +183,7 @@ def destroy():
180183
with _gen_docker_compose(DEFAULT_MODULES) as docker_compose_fn:
181184
os.execvpe(
182185
"docker-compose",
183-
["docker-compose", "-f", docker_compose_fn, "down", "--remove-orphans", "-t", "0"],
186+
["docker-compose", "-f", docker_compose_fn, "down", "--remove-orphans", "-t", "0", "--volumes"],
184187
_make_env({}),
185188
)
186189

@@ -193,7 +196,6 @@ def prepare_environment(
193196
release_var: Optional[str] = None,
194197
):
195198
"""Prepare the local environment for installing DIRAC."""
196-
197199
_check_containers_running(is_up=False)
198200
if editable is None:
199201
editable = sys.stdout.isatty()
@@ -224,7 +226,7 @@ def prepare_environment(
224226
typer.secho("Running docker-compose to create containers", fg=c.GREEN)
225227
with _gen_docker_compose(modules) as docker_compose_fn:
226228
subprocess.run(
227-
["docker-compose", "-f", docker_compose_fn, "up", "-d"],
229+
["docker-compose", "-f", docker_compose_fn, "up", "-d", "dirac-server", "dirac-client"],
228230
check=True,
229231
env=docker_compose_env,
230232
)
@@ -313,6 +315,27 @@ def prepare_environment(
313315
)
314316
subprocess.run(command, check=True, shell=True)
315317

318+
docker_compose_fn_final = Path(tempfile.mkdtemp()) / "ci"
319+
typer.secho("Running docker-compose to create DiracX containers", fg=c.GREEN)
320+
typer.secho(f"Will leave a folder behind: {docker_compose_fn_final}", fg=c.YELLOW)
321+
322+
with _gen_docker_compose(modules) as docker_compose_fn:
323+
# We cannot use the temporary directory created in the context manager because
324+
# we don't stay in the contect manager (Popen)
325+
# So we need something that outlives it.
326+
shutil.copytree(docker_compose_fn.parent, docker_compose_fn_final, dirs_exist_ok=True)
327+
# We use Popen because we don't want to wait for this command to finish.
328+
# It is going to start all the diracx containers, including one which waits
329+
# for the DIRAC installation to be over.
330+
subprocess.Popen(
331+
["docker-compose", "-f", docker_compose_fn_final / "docker-compose.yml", "up", "-d", "diracx"],
332+
env=docker_compose_env,
333+
stdin=None,
334+
stdout=None,
335+
stderr=None,
336+
close_fds=True,
337+
)
338+
316339

317340
@app.command()
318341
def install_server():
@@ -326,6 +349,15 @@ def install_server():
326349
check=True,
327350
)
328351

352+
# This runs a continuous loop that exports the config in yaml
353+
# for the diracx container to use
354+
typer.secho("Starting configuration export loop for diracx", fg=c.GREEN)
355+
base_cmd = _build_docker_cmd("server", tty=False, daemon=True)
356+
subprocess.run(
357+
base_cmd + ["bash", "/home/dirac/LocalRepo/ALTERNATIVE_MODULES/DIRAC/tests/CI/exportCSLoop.sh"],
358+
check=True,
359+
)
360+
329361
typer.secho("Copying credentials and certificates", fg=c.GREEN)
330362
base_cmd = _build_docker_cmd("client", tty=False)
331363
subprocess.run(
@@ -508,13 +540,24 @@ def _gen_docker_compose(modules):
508540
# Load the docker-compose configuration and mount the necessary volumes
509541
input_fn = Path(__file__).parent / "tests/CI/docker-compose.yml"
510542
docker_compose = yaml.safe_load(input_fn.read_text())
543+
# diracx-wait-for-db needs the volume to be able to run the witing script
544+
for ctn in ("dirac-server", "dirac-client", "diracx-wait-for-db"):
545+
if "volumes" not in docker_compose["services"][ctn]:
546+
docker_compose["services"][ctn]["volumes"] = []
511547
volumes = [f"{path}:/home/dirac/LocalRepo/ALTERNATIVE_MODULES/{name}" for name, path in modules.items()]
512548
volumes += [f"{path}:/home/dirac/LocalRepo/TestCode/{name}" for name, path in modules.items()]
513-
docker_compose["services"]["dirac-server"]["volumes"] = volumes[:]
514-
docker_compose["services"]["dirac-client"]["volumes"] = volumes[:]
549+
docker_compose["services"]["dirac-server"]["volumes"].extend(volumes[:])
550+
docker_compose["services"]["dirac-client"]["volumes"].extend(volumes[:])
551+
docker_compose["services"]["diracx-wait-for-db"]["volumes"].extend(volumes[:])
552+
553+
module_configs = _load_module_configs(modules)
554+
if "diracx" in module_configs:
555+
docker_compose["services"]["diracx"]["volumes"].append(
556+
f"{modules['diracx']}/src/diracx:{module_configs['diracx']['install-location']}"
557+
)
515558

516559
# Add any extension services
517-
for module_name, module_configs in _load_module_configs(modules).items():
560+
for module_name, module_configs in module_configs.items():
518561
for service_name, service_config in module_configs["extra-services"].items():
519562
typer.secho(f"Adding service {service_name} for {module_name}", err=True, fg=c.GREEN)
520563
docker_compose["services"][service_name] = service_config.copy()
@@ -981,6 +1024,8 @@ def _make_config(modules, flags, release_var, editable):
9811024
"CLIENT_HOST": "client",
9821025
# Test specific variables
9831026
"WORKSPACE": "/home/dirac",
1027+
# DiracX variable
1028+
"DIRACX_URL": "http://diracx:8000",
9841029
}
9851030

9861031
if editable:
@@ -1006,6 +1051,12 @@ def _make_config(modules, flags, release_var, editable):
10061051
except KeyError:
10071052
typer.secho(f"Required feature variable {key!r} is missing", err=True, fg=c.RED)
10081053
raise typer.Exit(code=1)
1054+
1055+
# If we test DiracX, enable all the options
1056+
if config["TEST_DIRACX"].lower() in ("yes", "true"):
1057+
for key in DIRACX_OPTIONS:
1058+
config[key] = "Yes"
1059+
10091060
config["TESTREPO"] = [f"/home/dirac/LocalRepo/TestCode/{name}" for name in modules]
10101061
config["ALTERNATIVE_MODULES"] = [f"/home/dirac/LocalRepo/ALTERNATIVE_MODULES/{name}" for name in modules]
10111062

@@ -1027,7 +1078,7 @@ def _load_module_configs(modules):
10271078
return module_ci_configs
10281079

10291080

1030-
def _build_docker_cmd(container_name, *, use_root=False, cwd="/home/dirac", tty=True):
1081+
def _build_docker_cmd(container_name, *, use_root=False, cwd="/home/dirac", tty=True, daemon=False):
10311082
if use_root or os.getuid() == 0:
10321083
user = "root"
10331084
else:
@@ -1042,6 +1093,8 @@ def _build_docker_cmd(container_name, *, use_root=False, cwd="/home/dirac", tty=
10421093
err=True,
10431094
fg=c.YELLOW,
10441095
)
1096+
if daemon:
1097+
cmd += ["-d"]
10451098
cmd += [
10461099
"-e=TERM=xterm-color",
10471100
"-e=INSTALLROOT=/home/dirac",

src/DIRAC/ConfigurationSystem/Client/CSAPI.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -612,25 +612,15 @@ def getOpsSection():
612612
Where is the shifters section?
613613
"""
614614
vo = CSGlobals.getVO()
615-
setup = CSGlobals.getSetup()
616615

617616
if vo:
618-
res = gConfig.getSections(f"/Operations/{vo}/{setup}/Shifter")
617+
res = gConfig.getSections(f"/Operations/{vo}/Shifter")
619618
if res["OK"]:
620-
return S_OK(f"/Operations/{vo}/{setup}/Shifter")
619+
return S_OK(f"/Operations/{vo}/Shifter")
621620

622-
res = gConfig.getSections(f"/Operations/{vo}/Defaults/Shifter")
623-
if res["OK"]:
624-
return S_OK(f"/Operations/{vo}/Defaults/Shifter")
625-
626-
else:
627-
res = gConfig.getSections(f"/Operations/{setup}/Shifter")
628-
if res["OK"]:
629-
return S_OK(f"/Operations/{setup}/Shifter")
630-
631-
res = gConfig.getSections("/Operations/Defaults/Shifter")
632-
if res["OK"]:
633-
return S_OK("/Operations/Defaults/Shifter")
621+
res = gConfig.getSections("/Operations/Defaults/Shifter")
622+
if res["OK"]:
623+
return S_OK("/Operations/Defaults/Shifter")
634624

635625
return S_ERROR("No shifter section")
636626

@@ -671,7 +661,7 @@ def getOpsSection():
671661
gLogger.info("Adding shifter section")
672662
vo = CSGlobals.getVO()
673663
if vo:
674-
section = f"/Operations/{vo}/Defaults/Shifter"
664+
section = f"/Operations/{vo}/Shifter"
675665
else:
676666
section = "/Operations/Defaults/Shifter"
677667
res = self.__csMod.createSection(section)

src/DIRAC/ConfigurationSystem/Client/Helpers/Operations.py

Lines changed: 10 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,19 @@
66
77
Operations/
88
Defaults/
9+
someOption = someValue
10+
aSecondOption = aSecondValue
11+
specificVo/
912
someSection/
10-
someOption = someValue
11-
aSecondOption = aSecondValue
12-
Production/
13-
someSection/
14-
someOption = someValueInProduction
15-
aSecondOption = aSecondValueInProduction
16-
Certification/
17-
someSection/
18-
someOption = someValueInCertification
13+
someOption = someValueInVO
1914
2015
The following calls would give different results based on the setup::
2116
2217
Operations().getValue('someSection/someOption')
23-
- someValueInProduction if we are in 'Production' setup
24-
- someValueInCertification if we are in 'Certification' setup
25-
26-
Operations().getValue('someSection/aSecondOption')
27-
- aSecondValueInProduction if we are in 'Production' setup
28-
- aSecondValue if we are in 'Certification' setup <- looking in Defaults
29-
since there's no Certification/someSection/aSecondOption
30-
18+
- someValueInVO if we are in 'specificVo' vo
19+
- someValue if we are in any other VO
3120
32-
At the same time, for multi-VO installations, it is also possible to specify different options per-VO,
33-
like the following::
34-
35-
Operations/
36-
aVOName/
37-
Defaults/
38-
someSection/
39-
someOption = someValue
40-
aSecondOption = aSecondValue
41-
Production/
42-
someSection/
43-
someOption = someValueInProduction
44-
aSecondOption = aSecondValueInProduction
45-
Certification/
46-
someSection/
47-
someOption = someValueInCertification
48-
anotherVOName/
49-
Defaults/
50-
someSectionName/
51-
someOptionX = someValueX
52-
aSecondOption = aSecondValue
53-
setupName/
54-
someSection/
55-
someOption = someValueInProduction
56-
aSecondOption = aSecondValueInProduction
57-
58-
For this case it becomes then important for the Operations() objects to know the VO name
21+
It becomes then important for the Operations() objects to know the VO name
5922
for which we want the information, and this can be done in the following ways.
6023
6124
1. by specifying the VO name directly::
@@ -98,9 +61,7 @@ def __init__(self, vo=False, group=False, setup=False):
9861
"""
9962
self.__uVO = vo
10063
self.__uGroup = group
101-
self.__uSetup = setup
10264
self.__vo = False
103-
self.__setup = False
10465
self.__discoverSettings()
10566

10667
def __discoverSettings(self):
@@ -119,12 +80,6 @@ def __discoverSettings(self):
11980
result = getVOfromProxyGroup()
12081
if result["OK"]:
12182
self.__vo = result["Value"]
122-
# Set the setup
123-
self.__setup = False
124-
if self.__uSetup:
125-
self.__setup = self.__uSetup
126-
else:
127-
self.__setup = CSGlobals.getSetup()
12883

12984
def __getCache(self):
13085
Operations.__cacheLock.acquire()
@@ -134,7 +89,7 @@ def __getCache(self):
13489
Operations.__cache = {}
13590
Operations.__cacheVersion = currentVersion
13691

137-
cacheKey = (self.__vo, self.__setup)
92+
cacheKey = (self.__vo,)
13893
if cacheKey in Operations.__cache:
13994
return Operations.__cache[cacheKey]
14095

@@ -155,14 +110,13 @@ def __getCache(self):
155110
pass
156111

157112
def __getSearchPaths(self):
158-
paths = ["/Operations/Defaults", f"/Operations/{self.__setup}"]
113+
paths = ["/Operations/Defaults"]
159114
if not self.__vo:
160115
globalVO = CSGlobals.getVO()
161116
if not globalVO:
162117
return paths
163118
self.__vo = CSGlobals.getVO()
164-
paths.append(f"/Operations/{self.__vo}/Defaults")
165-
paths.append(f"/Operations/{self.__vo}/{self.__setup}")
119+
paths.append(f"/Operations/{self.__vo}/")
166120
return paths
167121

168122
def getValue(self, optionPath, defaultValue=None):
@@ -202,26 +156,6 @@ def getOptionsDict(self, sectionPath):
202156
data[opName] = sectionCFG[opName]
203157
return S_OK(data)
204158

205-
def getPath(self, option, vo=False, setup=False):
206-
"""
207-
Generate the CS path for an option:
208-
209-
- if vo is not defined, the helper's vo will be used for multi VO installations
210-
- if setup evaluates False (except None) -> The helpers setup will be used
211-
- if setup is defined -> whatever is defined will be used as setup
212-
- if setup is None -> Defaults will be used
213-
214-
:param option: path with respect to the Operations standard path
215-
:type option: string
216-
"""
217-
218-
for path in self.__getSearchPaths():
219-
optionPath = os.path.join(path, option)
220-
value = gConfig.getValue(optionPath, "NoValue")
221-
if value != "NoValue":
222-
return optionPath
223-
return ""
224-
225159
def getMonitoringBackends(self, monitoringType=None):
226160
"""
227161
Chooses the type of backend to use (Monitoring and/or Accounting) depending on the MonitoringType.

0 commit comments

Comments
 (0)