diff --git a/integration_tests.py b/integration_tests.py index 6377a48fc9c..0079d3c9680 100755 --- a/integration_tests.py +++ b/integration_tests.py @@ -146,12 +146,13 @@ def create( flags: Optional[list[str]] = typer.Argument(None), editable: Optional[bool] = None, extra_module: Optional[list[str]] = None, + diracx_dist_dir: Optional[str] = None, release_var: Optional[str] = None, run_server_tests: bool = True, run_client_tests: bool = True, ): """Start a local instance of the integration tests""" - prepare_environment(flags, editable, extra_module, release_var) + prepare_environment(flags, editable, extra_module, diracx_dist_dir, release_var) install_server() install_client() exit_code = 0 @@ -191,6 +192,7 @@ def prepare_environment( flags: Optional[list[str]] = typer.Argument(None), editable: Optional[bool] = None, extra_module: Optional[list[str]] = None, + diracx_dist_dir: Optional[str] = None, release_var: Optional[str] = None, ): """Prepare the local environment for installing DIRAC.""" @@ -227,7 +229,7 @@ def prepare_environment( extra_services = list(chain(*[config["extra-services"] for config in module_configs.values()])) typer.secho("Running docker-compose to create containers", fg=c.GREEN) - with _gen_docker_compose(modules) as docker_compose_fn: + with _gen_docker_compose(modules, diracx_dist_dir=diracx_dist_dir) as docker_compose_fn: subprocess.run( ["docker-compose", "-f", docker_compose_fn, "up", "-d", "dirac-server", "dirac-client"] + extra_services, check=True, @@ -322,7 +324,7 @@ def prepare_environment( typer.secho("Running docker-compose to create DiracX containers", fg=c.GREEN) typer.secho(f"Will leave a folder behind: {docker_compose_fn_final}", fg=c.YELLOW) - with _gen_docker_compose(modules) as docker_compose_fn: + with _gen_docker_compose(modules, diracx_dist_dir=diracx_dist_dir) as docker_compose_fn: # We cannot use the temporary directory created in the context manager because # we don't stay in the contect manager (Popen) # So we need something that outlives it. @@ -545,7 +547,7 @@ class TestExit(typer.Exit): @contextmanager -def _gen_docker_compose(modules): +def _gen_docker_compose(modules, *, diracx_dist_dir=None): # Load the docker-compose configuration and mount the necessary volumes input_fn = Path(__file__).parent / "tests/CI/docker-compose.yml" docker_compose = yaml.safe_load(input_fn.read_text()) @@ -560,10 +562,12 @@ def _gen_docker_compose(modules): docker_compose["services"]["diracx-wait-for-db"]["volumes"].extend(volumes[:]) module_configs = _load_module_configs(modules) - if "diracx" in module_configs: - docker_compose["services"]["diracx"]["volumes"].append( - f"{modules['diracx']}/src/diracx:{module_configs['diracx']['install-location']}" - ) + if diracx_dist_dir is not None: + for container_name in ["dirac-client", "dirac-server", "diracx-init-cs", "diracx-wait-for-db", "diracx"]: + docker_compose["services"][container_name]["volumes"].append(f"{diracx_dist_dir}:/diracx_sources") + docker_compose["services"][container_name].setdefault("environment", []).append( + "DIRACX_CUSTOM_SOURCE_PREFIXES=/diracx_sources" + ) # Add any extension services for module_name, module_configs in module_configs.items(): diff --git a/setup.cfg b/setup.cfg index d523b4b0625..fee00b153b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,8 @@ install_requires = cachetools certifi diraccfg + diracx-client + diracx-core db12 fts3 gfal2-python @@ -160,6 +162,7 @@ console_scripts = # FrameworkSystem dirac-login = DIRAC.FrameworkSystem.scripts.dirac_login:main dirac-logout = DIRAC.FrameworkSystem.scripts.dirac_logout:main + dirac-diracx-whoami = DIRAC.FrameworkSystem.scripts.dirac_diracx_whoami:main dirac-admin-get-CAs = DIRAC.FrameworkSystem.scripts.dirac_admin_get_CAs:main [server] dirac-admin-get-proxy = DIRAC.FrameworkSystem.scripts.dirac_admin_get_proxy:main [admin] dirac-admin-proxy-upload = DIRAC.FrameworkSystem.scripts.dirac_admin_proxy_upload:main [admin] diff --git a/src/DIRAC/Core/Security/DiracX.py b/src/DIRAC/Core/Security/DiracX.py new file mode 100644 index 00000000000..d5f8214436d --- /dev/null +++ b/src/DIRAC/Core/Security/DiracX.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +__all__ = ( + "DiracXClient", + "diracxTokenFromPEM", +) + +import base64 +import json +import re +import textwrap +from contextlib import contextmanager +from pathlib import Path +from tempfile import NamedTemporaryFile +from typing import Any + +from diracx.client import DiracClient as _DiracClient +from diracx.core.models import TokenResponse +from diracx.core.preferences import DiracxPreferences +from diracx.core.utils import serialize_credentials + +from DIRAC import gConfig, S_ERROR +from DIRAC.ConfigurationSystem.Client.Helpers import Registry +from DIRAC.Core.Security.Locations import getDefaultProxyLocation +from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue, returnValueOrRaise + + +PEM_BEGIN = "-----BEGIN DIRACX-----" +PEM_END = "-----END DIRACX-----" +RE_DIRACX_PEM = re.compile(rf"{PEM_BEGIN}\n(.*)\n{PEM_END}", re.MULTILINE | re.DOTALL) + + +@convertToReturnValue +def addTokenToPEM(pemPath, group): + from DIRAC.Core.Base.Client import Client + + vo = Registry.getVOMSVOForGroup(group) + disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", []) + if vo and vo not in disabledVOs: + token_content = returnValueOrRaise( + Client(url="Framework/ProxyManager", proxyLocation=pemPath).exchangeProxyForToken() + ) + + token = TokenResponse( + access_token=token_content["access_token"], + expires_in=token_content["expires_in"], + token_type=token_content.get("token_type"), + refresh_token=token_content.get("refresh_token"), + ) + + token_pem = f"{PEM_BEGIN}\n" + data = base64.b64encode(serialize_credentials(token).encode("utf-8")).decode() + token_pem += textwrap.fill(data, width=64) + token_pem += f"\n{PEM_END}\n" + + with open(pemPath, "a") as f: + f.write(token_pem) + + +def diracxTokenFromPEM(pemPath) -> dict[str, Any] | None: + """Extract the DiracX token from the proxy PEM file""" + pem = Path(pemPath).read_text() + if match := RE_DIRACX_PEM.search(pem): + match = match.group(1) + return json.loads(base64.b64decode(match).decode("utf-8")) + + +@contextmanager +def DiracXClient() -> _DiracClient: + """Get a DiracX client instance with the current user's credentials""" + diracxUrl = gConfig.getValue("/DiracX/URL") + if not diracxUrl: + raise ValueError("Missing mandatory /DiracX/URL configuration") + + proxyLocation = getDefaultProxyLocation() + diracxToken = diracxTokenFromPEM(proxyLocation) + + with NamedTemporaryFile(mode="wt") as token_file: + token_file.write(json.dumps(diracxToken)) + token_file.flush() + token_file.seek(0) + + pref = DiracxPreferences(url=diracxUrl, credentials_path=token_file.name) + with _DiracClient(diracx_preferences=pref) as api: + yield api diff --git a/src/DIRAC/Core/Security/ProxyInfo.py b/src/DIRAC/Core/Security/ProxyInfo.py index c5ead407150..e221cfb7b91 100644 --- a/src/DIRAC/Core/Security/ProxyInfo.py +++ b/src/DIRAC/Core/Security/ProxyInfo.py @@ -8,6 +8,7 @@ from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error from DIRAC.Core.Security.VOMS import VOMS from DIRAC.Core.Security import Locations +from DIRAC.Core.Security.DiracX import diracxTokenFromPEM from DIRAC.ConfigurationSystem.Client.Helpers import Registry @@ -25,6 +26,7 @@ def getProxyInfo(proxy=False, disableVOMS=False): * 'validDN' : Valid DN in DIRAC * 'validGroup' : Valid Group in DIRAC * 'secondsLeft' : Seconds left + * 'hasDiracxToken' * values that can be there * 'path' : path to the file, * 'group' : DIRAC group @@ -67,6 +69,11 @@ def getProxyInfo(proxy=False, disableVOMS=False): infoDict["VOMS"] = retVal["Value"] else: infoDict["VOMSError"] = retVal["Message"].strip() + + infoDict["hasDiracxToken"] = False + if proxyLocation: + infoDict["hasDiracxToken"] = bool(diracxTokenFromPEM(proxyLocation)) + return S_OK(infoDict) @@ -94,6 +101,7 @@ def formatProxyInfoAsString(infoDict): "subproxyUser", ("secondsLeft", "timeleft"), ("group", "DIRAC group"), + ("hasDiracxToken", "DiracX"), "rfc", "path", "username", diff --git a/src/DIRAC/Core/Tornado/Client/private/TornadoBaseClient.py b/src/DIRAC/Core/Tornado/Client/private/TornadoBaseClient.py index cd204bc44cc..f1acb62c404 100644 --- a/src/DIRAC/Core/Tornado/Client/private/TornadoBaseClient.py +++ b/src/DIRAC/Core/Tornado/Client/private/TornadoBaseClient.py @@ -511,10 +511,12 @@ def _request(self, retry=0, outputFile=None, **kwargs): # getting certificate # Do we use the server certificate ? if self.kwargs[self.KW_USE_CERTIFICATES]: + # TODO: make this code path work with DiracX for Agents and possibly webapp ? auth = {"cert": Locations.getHostCertificateAndKeyLocation()} # Use access token? elif self.__useAccessToken: + # TODO: Remove this code path? from DIRAC.FrameworkSystem.private.authorization.utils.Tokens import ( getLocalTokenDict, writeTokenDictToTokenFile, @@ -543,13 +545,13 @@ def _request(self, retry=0, outputFile=None, **kwargs): auth = {"headers": {"Authorization": f"Bearer {token['access_token']}"}} elif self.kwargs.get(self.KW_PROXY_STRING): + # TODO: This code path cannot work with DiracX tmpHandle, cert = tempfile.mkstemp() fp = os.fdopen(tmpHandle, "w") fp.write(self.kwargs[self.KW_PROXY_STRING]) fp.close() - - # CHRIS 04.02.21 - # TODO: add proxyLocation check ? + elif self.kwargs.get(self.KW_PROXY_LOCATION): + auth = {"cert": self.kwargs[self.KW_PROXY_LOCATION]} else: auth = {"cert": Locations.getProxyLocation()} if not auth["cert"]: diff --git a/src/DIRAC/FrameworkSystem/Client/ProxyManagerClient.py b/src/DIRAC/FrameworkSystem/Client/ProxyManagerClient.py index 597cda99353..5da4b09e528 100755 --- a/src/DIRAC/FrameworkSystem/Client/ProxyManagerClient.py +++ b/src/DIRAC/FrameworkSystem/Client/ProxyManagerClient.py @@ -10,6 +10,7 @@ from DIRAC.ConfigurationSystem.Client.Helpers import Registry from DIRAC.Core.Utilities import ThreadSafe, DIRACSingleton from DIRAC.Core.Utilities.DictCache import DictCache +from DIRAC.Core.Security.DiracX import addTokenToPEM from DIRAC.Core.Security.ProxyFile import multiProxyArgument, deleteMultiProxy from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error from DIRAC.Core.Security.X509Request import X509Request # pylint: disable=import-error @@ -547,6 +548,10 @@ def dumpProxyToFile(self, chain, destinationFile=None, requiredTimeLeft=600): if not retVal["OK"]: return retVal filename = retVal["Value"] + if not (result := chain.getDIRACGroup())["OK"]: + return result + if not (result := addTokenToPEM(filename, result["Value"]))["OK"]: # pylint: disable=unsubscriptable-object + return result self.__filesCache.add(cHash, chain.getRemainingSecs()["Value"], filename) return S_OK(filename) @@ -655,7 +660,14 @@ def renewProxy(self, proxyToBeRenewed=None, minLifeTime=3600, newProxyLifeTime=4 chain = retVal["Value"] if not proxyToRenewDict["tempFile"]: - return chain.dumpAllToFile(proxyToRenewDict["file"]) + filename = proxyToRenewDict["file"] + if not (result := chain.dumpAllToFile(filename))["OK"]: + return result + if not (result := chain.getDIRACGroup())["OK"]: + return result + if not (result := addTokenToPEM(filename, result["Value"]))["OK"]: # pylint: disable=unsubscriptable-object + return result + return S_OK(filename) return S_OK(chain) diff --git a/src/DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py b/src/DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py index a8cf1130a17..88b20b4ce7e 100644 --- a/src/DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py +++ b/src/DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py @@ -12,7 +12,7 @@ from DIRAC.Core.Security import Properties from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader from DIRAC.ConfigurationSystem.Client.Helpers import Registry - +from DIRAC.FrameworkSystem.Utilities.diracx import get_token DEFAULT_MAIL_FROM = "proxymanager@diracgrid.org" @@ -412,7 +412,6 @@ def export_getVOMSProxyWithToken(self, userDN, userGroup, requestPem, requiredLi @convertToReturnValue def export_exchangeProxyForToken(self): """Exchange a proxy for an equivalent token to be used with diracx""" - from DIRAC.FrameworkSystem.Utilities.diracx import get_token credDict = self.getRemoteCredentials() return get_token( diff --git a/src/DIRAC/FrameworkSystem/Utilities/diracx.py b/src/DIRAC/FrameworkSystem/Utilities/diracx.py index 946ee47e423..594cc120678 100644 --- a/src/DIRAC/FrameworkSystem/Utilities/diracx.py +++ b/src/DIRAC/FrameworkSystem/Utilities/diracx.py @@ -1,4 +1,3 @@ -# pylint: disable=import-error import requests from cachetools import TTLCache, cached diff --git a/src/DIRAC/FrameworkSystem/scripts/dirac_admin_get_proxy.py b/src/DIRAC/FrameworkSystem/scripts/dirac_admin_get_proxy.py index daec2ed1707..c787fb31576 100755 --- a/src/DIRAC/FrameworkSystem/scripts/dirac_admin_get_proxy.py +++ b/src/DIRAC/FrameworkSystem/scripts/dirac_admin_get_proxy.py @@ -15,6 +15,7 @@ import DIRAC from DIRAC import gLogger, S_OK, S_ERROR from DIRAC.Core.Base.Script import Script +from DIRAC.Core.Security.DiracX import addTokenToPEM from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager from DIRAC.ConfigurationSystem.Client.Helpers import Registry @@ -159,6 +160,10 @@ def main(): if not result["OK"]: gLogger.notice(f"Proxy file cannot be written to {params.proxyPath}: {result['Message']}") DIRAC.exit(2) + if not (result := chain.getDIRACGroup())["OK"]: + return result + if not (result := addTokenToPEM(params.proxyPath, result["Value"]))["OK"]: # pylint: disable=unsubscriptable-object + return result gLogger.notice(f"Proxy downloaded to {params.proxyPath}") DIRAC.exit(0) diff --git a/src/DIRAC/FrameworkSystem/scripts/dirac_diracx_whoami.py b/src/DIRAC/FrameworkSystem/scripts/dirac_diracx_whoami.py new file mode 100644 index 00000000000..7f425a879ea --- /dev/null +++ b/src/DIRAC/FrameworkSystem/scripts/dirac_diracx_whoami.py @@ -0,0 +1,22 @@ +"""Query DiracX for information about the current user + +This is a stripped down version of the "dirac whoami" script from DiracX. +It primarily exists as a method of validating the current user's credentials are functional. +""" +import json + +from DIRAC.Core.Base.Script import Script +from DIRAC.Core.Security.DiracX import DiracXClient + + +@Script() +def main(): + Script.parseCommandLine() + + with DiracXClient() as api: + user_info = api.auth.userinfo() + print(json.dumps(user_info.as_dict(), indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/DIRAC/FrameworkSystem/scripts/dirac_login.py b/src/DIRAC/FrameworkSystem/scripts/dirac_login.py index d0be47e34ec..5e503ebe72b 100644 --- a/src/DIRAC/FrameworkSystem/scripts/dirac_login.py +++ b/src/DIRAC/FrameworkSystem/scripts/dirac_login.py @@ -25,6 +25,7 @@ from DIRAC import gConfig, gLogger, S_OK, S_ERROR from DIRAC.Core.Security.Locations import getDefaultProxyLocation, getCertificateAndKeyLocation from DIRAC.Core.Security.VOMS import VOMS +from DIRAC.Core.Security.DiracX import addTokenToPEM from DIRAC.Core.Security.ProxyFile import writeToProxyFile from DIRAC.Core.Security.ProxyInfo import getProxyInfo, formatProxyInfoAsString from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error @@ -314,32 +315,8 @@ def loginWithCertificate(self): return res # Get a token for use with diracx - vo = getVOMSVOForGroup(self.group) - disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", []) - if vo not in disabledVOs: - from diracx.core.utils import write_credentials # pylint: disable=import-error - from diracx.core.models import TokenResponse # pylint: disable=import-error - from diracx.core.preferences import DiracxPreferences # pylint: disable=import-error - - res = Client(url="Framework/ProxyManager").exchangeProxyForToken() - if not res["OK"]: - return res - token_content = res["Value"] - - diracxUrl = gConfig.getValue("/DiracX/URL") - if not diracxUrl: - return S_ERROR("Missing mandatory /DiracX/URL configuration") - - preferences = DiracxPreferences(url=diracxUrl) - write_credentials( - TokenResponse( - access_token=token_content["access_token"], - expires_in=token_content["expires_in"], - token_type=token_content.get("token_type"), - refresh_token=token_content.get("refresh_token"), - ), - location=preferences.credentials_path, - ) + if not (result := addTokenToPEM(self.outputFile, self.group))["OK"]: + return result return S_OK() diff --git a/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_info.py b/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_info.py index 5d8e5a6a07f..4efa9a84fd4 100755 --- a/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_info.py +++ b/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_info.py @@ -77,6 +77,7 @@ def main(): from DIRAC.Core.Security import VOMS from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager from DIRAC.ConfigurationSystem.Client.Helpers import Registry + from DIRAC.Core.Security.DiracX import DiracXClient if params.csEnabled: retVal = Script.enableCS() @@ -151,6 +152,12 @@ def invalidProxy(msg): invalidProxy(f"Cannot determine life time of VOMS attributes: {result['Message']}") if int(result["Value"].strip()) == 0: invalidProxy("VOMS attributes are expired") + # Ensure the proxy is working with DiracX + try: + with DiracXClient() as api: + api.auth.userinfo() + except Exception as e: + invalidProxy(f"Failed to access DiracX: {e}") sys.exit(0) diff --git a/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py b/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py index 02e994dddce..cb41e3c4543 100755 --- a/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +++ b/src/DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py @@ -18,10 +18,10 @@ from DIRAC.Core.Base.Script import Script from DIRAC.FrameworkSystem.Client import ProxyGeneration, ProxyUpload from DIRAC.Core.Security import X509Chain, ProxyInfo, VOMS -from DIRAC.Core.Security.Locations import getCAsLocation +from DIRAC.Core.Security.DiracX import addTokenToPEM +from DIRAC.Core.Security.Locations import getCAsLocation, getDefaultProxyLocation from DIRAC.ConfigurationSystem.Client.Helpers import Registry from DIRAC.FrameworkSystem.Client.BundleDeliveryClient import BundleDeliveryClient -from DIRAC.Core.Base.Client import Client class Params(ProxyGeneration.CLIParams): @@ -221,6 +221,11 @@ def doTheMagic(self): self.checkCAs() pI.certLifeTimeCheck() resultProxyWithVOMS = pI.addVOMSExtIfNeeded() + + proxyLoc = self.__piParams.proxyLoc or getDefaultProxyLocation() + if not (result := addTokenToPEM(proxyLoc, self.__piParams.diracGroup))["OK"]: + return result + if not resultProxyWithVOMS["OK"]: if "returning a valid AC for the user" in resultProxyWithVOMS["Message"]: gLogger.error(resultProxyWithVOMS["Message"]) @@ -238,33 +243,6 @@ def doTheMagic(self): if self.__piParams.strict: return resultProxyUpload - vo = Registry.getVOMSVOForGroup(self.__piParams.diracGroup) - disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", []) - if vo and vo not in disabledVOs: - from diracx.core.utils import write_credentials # pylint: disable=import-error - from diracx.core.models import TokenResponse # pylint: disable=import-error - from diracx.core.preferences import DiracxPreferences # pylint: disable=import-error - - res = Client(url="Framework/ProxyManager").exchangeProxyForToken() - if not res["OK"]: - return res - - diracxUrl = gConfig.getValue("/DiracX/URL") - if not diracxUrl: - return S_ERROR("Missing mandatory /DiracX/URL configuration") - - token_content = res["Value"] - preferences = DiracxPreferences(url=diracxUrl) - write_credentials( - TokenResponse( - access_token=token_content["access_token"], - expires_in=token_content["expires_in"], - token_type=token_content.get("token_type"), - refresh_token=token_content.get("refresh_token"), - ), - location=preferences.credentials_path, - ) - return S_OK() diff --git a/src/DIRAC/Interfaces/Utilities/DCommands.py b/src/DIRAC/Interfaces/Utilities/DCommands.py index 8891b0d5198..d7f2f8226b6 100644 --- a/src/DIRAC/Interfaces/Utilities/DCommands.py +++ b/src/DIRAC/Interfaces/Utilities/DCommands.py @@ -15,6 +15,7 @@ from DIRAC import S_OK, S_ERROR, gConfig, gLogger from DIRAC.Core.Security.Locations import getCAsLocation from DIRAC.Core.Security import Locations, VOMS +from DIRAC.Core.Security.DiracX import addTokenToPEM from DIRAC.Core.Utilities.PrettyPrint import printTable from DIRAC.FrameworkSystem.Client.BundleDeliveryClient import BundleDeliveryClient from DIRAC.ConfigurationSystem.Client.Helpers import Registry @@ -495,16 +496,21 @@ def proxyInit(self): params.diracGroup = retVal["Value"] result = ProxyGeneration.generateProxy(params) - if not result["OK"]: raise Exception(result["Message"]) + filename = result["Value"] + self.checkCAs() + try: - self.addVomsExt(result["Value"]) + self.addVomsExt(filename) except: # silently skip VOMS errors pass + if not (result := addTokenToPEM(filename, params.diracGroup))["OK"]: # pylint: disable=unsubscriptable-object + raise Exception(result["Message"]) # pylint: disable=unsubscriptable-object + def addVomsExt(self, proxy): retVal = self.getEnv("group_name") if not retVal["OK"]: diff --git a/src/DIRAC/WorkloadManagementSystem/FutureClient/JobMonitoringClient.py b/src/DIRAC/WorkloadManagementSystem/FutureClient/JobMonitoringClient.py index f7ec91ac1ca..a6f303bf5b6 100644 --- a/src/DIRAC/WorkloadManagementSystem/FutureClient/JobMonitoringClient.py +++ b/src/DIRAC/WorkloadManagementSystem/FutureClient/JobMonitoringClient.py @@ -1,6 +1,5 @@ -# pylint: disable=import-error from diracx.client import DiracClient -from diracx.client.models import JobSearchParams + from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue diff --git a/src/DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py b/src/DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py index bf617f3326c..64fcc985cad 100755 --- a/src/DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +++ b/src/DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py @@ -22,6 +22,7 @@ from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader from DIRAC.DataManagementSystem.Client.DataManager import DataManager from DIRAC.DataManagementSystem.Service.StorageElementHandler import getDiskSpace +from DIRAC.FrameworkSystem.Utilities.diracx import TheImpersonator from DIRAC.RequestManagementSystem.Client.File import File from DIRAC.RequestManagementSystem.Client.Operation import Operation from DIRAC.RequestManagementSystem.Client.ReqClient import ReqClient @@ -29,6 +30,8 @@ from DIRAC.Resources.Storage.StorageElement import StorageElement from DIRAC.Core.Utilities.File import getGlobbedTotalSize +from diracx.client.models import SandboxInfo + class SandboxStoreHandlerMixin: __purgeCount = -1 @@ -113,9 +116,6 @@ def _getFromClient(self, fileId, token, fileSize, fileHelper=None, data=""): disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", []) if self._useDiracXBackend and vo not in disabledVOs: - from DIRAC.FrameworkSystem.Utilities.diracx import TheImpersonator - from diracx.client.models import SandboxInfo # pylint: disable=import-error - gLogger.info("Forwarding to DiracX") with tempfile.TemporaryFile(mode="w+b") as tar_fh: result = fileHelper.networkToDataSink(tar_fh, maxFileSize=self._maxUploadBytes) @@ -490,8 +490,6 @@ def _sendToClient(self, fileID, token, fileHelper=None, raw=False): # If the PFN starts with S3, we know it has been uploaded to the # S3 sandbox store, so download it from there before sending it if filePath.startswith("/S3"): - from DIRAC.FrameworkSystem.Utilities.diracx import TheImpersonator - with TheImpersonator(credDict) as client: res = client.jobs.get_sandbox_file(pfn=filePath) r = requests.get(res.url) diff --git a/tests/CI/docker-compose.yml b/tests/CI/docker-compose.yml index 97c4dadaf5d..e35437bd0bb 100644 --- a/tests/CI/docker-compose.yml +++ b/tests/CI/docker-compose.yml @@ -120,15 +120,20 @@ services: - DIRACX_SERVICE_AUTH_TOKEN_KEY="file:///signing-key/rs256.key" volumes: - diracx-key-store:/signing-key/ + # As the diracx images don't run as root we need to change the permissions of the /cs_store/ directory as well + - diracx-cs-store:/cs_store/ # We need to allow everybody to read the private keys # Because the users are different between the DIRAC and DiracX containers entrypoint: | - bash -c "ssh-keygen -P '' -trsa -b4096 -mPEM -f/signing-key/rs256.key && chmod o+r /signing-key/rs256.*" + bash -xc "ssh-keygen -P '' -trsa -b4096 -mPEM -f/signing-key/rs256.key && chmod o+r /signing-key/rs256.* && chmod -R o=u /cs_store" pull_policy: always diracx-init-cs: - image: ghcr.io/diracgrid/diracx/server + image: ghcr.io/diracgrid/diracx/client:dev container_name: diracx-init-cs + depends_on: + diracx-init-key: + condition: service_completed_successfully # Let the init container set the permission on /cs_store/ environment: - DIRACX_CONFIG_BACKEND_URL=git+file:///cs_store/initialRepo - DIRACX_SERVICE_AUTH_TOKEN_KEY=file:///signing-key/rs256.key @@ -140,7 +145,7 @@ services: pull_policy: always diracx-init-db: - image: ghcr.io/diracgrid/diracx/server + image: ghcr.io/diracgrid/diracx/services:dev container_name: diracx-init-db depends_on: mysql: @@ -152,7 +157,7 @@ services: pull_policy: always diracx: - image: ghcr.io/diracgrid/diracx/server + image: ghcr.io/diracgrid/diracx/services:dev container_name: diracx environment: - DIRACX_CONFIG_BACKEND_URL=git+file:///cs_store/initialRepo @@ -175,6 +180,8 @@ services: volumes: - diracx-cs-store:/cs_store/ - diracx-key-store:/signing-key/ + entrypoint: | + /entrypoint.sh bash -xc 'uvicorn --factory diracx.routers:create_app --host=0.0.0.0' healthcheck: test: ["CMD", "/entrypoint.sh", "curl", "-f", "http://localhost:8000/.well-known/openid-configuration"] diff --git a/tests/CI/envs/opensearchproject/opensearch:2.1.0.env b/tests/CI/envs/opensearchproject/opensearch:2.1.0.env index 2e19968618a..0eb9691f87f 100644 --- a/tests/CI/envs/opensearchproject/opensearch:2.1.0.env +++ b/tests/CI/envs/opensearchproject/opensearch:2.1.0.env @@ -6,4 +6,4 @@ cluster.routing.allocation.disk.watermark.high=300mb plugins.security.disabled=true # Elasticsearch allocates 1GB of memory by default. As resources are limited # and elasticsearch performance isn't critical in CI, limit this to 256MB -"OPENSEARCH_JAVA_OPTS=-Xms256m -Xmx256m" # minimum and maximum Java heap size +OPENSEARCH_JAVA_OPTS=-Xms256m -Xmx256m diff --git a/tests/CI/exportCSLoop.sh b/tests/CI/exportCSLoop.sh index 093794bdbd6..7a9ba5b4946 100755 --- a/tests/CI/exportCSLoop.sh +++ b/tests/CI/exportCSLoop.sh @@ -13,6 +13,9 @@ source /home/dirac/ServerInstallDIR/bashrc git config --global user.name "DIRAC Server CI" git config --global user.email "dirac-server-ci@invalid" +mkdir -p /home/dirac/TestCode/diracx/tests/cli/legacy/cs_sync/ +curl -L https://raw.githubusercontent.com/DIRACGrid/diracx/main/diracx-cli/tests/legacy/cs_sync/convert_integration_test.yaml > /home/dirac/TestCode/diracx/tests/cli/legacy/cs_sync/convert_integration_test.yaml + while true; do DIRAC_COMPAT_ENABLE_CS_CONVERSION=x dirac internal legacy cs-sync \ "$DIRACOS/etc/Production.cfg" \ diff --git a/tests/Jenkins/dirac_ci.sh b/tests/Jenkins/dirac_ci.sh index e11843bace1..a4ce86982e3 100644 --- a/tests/Jenkins/dirac_ci.sh +++ b/tests/Jenkins/dirac_ci.sh @@ -133,8 +133,13 @@ installSite() { cd - - echo "==> Done installing, now configuring" + echo "==> Sourcing bashrc" source "${SERVERINSTALLDIR}/bashrc" + + echo "==> Installing main branch of diracx" + installDIRACX core client cli + + echo "==> Done installing, now configuring" configureArgs=() if [[ "${TEST_DIRACX:-}" = "Yes" ]]; then configureArgs+=("--LegacyExchangeApiKey=diracx:legacy:InsecureChangeMe") diff --git a/tests/Jenkins/utilities.sh b/tests/Jenkins/utilities.sh index 1e5b2cb36c1..dad55f9bcba 100644 --- a/tests/Jenkins/utilities.sh +++ b/tests/Jenkins/utilities.sh @@ -263,6 +263,11 @@ installDIRAC() { done fi + + + echo "==> Installing main branch of diracx" + installDIRACX core client + echo "$DIRAC" echo "$PATH" @@ -276,6 +281,25 @@ installDIRAC() { echo '==> Done installDIRAC' } +############################################################################## +# Install DiracX either from wheels or from github +# Arguments: list of DiracX submodule module names to install (core, client, etc.) + +function installDIRACX() { + for wheel_name in "$@"; do + if [[ -n "${DIRACX_CUSTOM_SOURCE_PREFIXES:-}" ]]; then + wheels=( $(find "${DIRACX_CUSTOM_SOURCE_PREFIXES}" -name "diracx_${wheel_name}-*.whl") ) + if [[ ! ${#wheels[@]} -eq 1 ]]; then + echo "ERROR: Multiple wheels found for ${package_name} in ${dir}" + exit 1 + fi + pip install "${wheels[0]}" + else + pip install "git+https://github.com/DIRACGrid/diracx.git@main#egg=diracx-${wheel_name}&subdirectory=diracx-${wheel_name}" + fi + done +} + ############################################################################## # This function submits a job or more (it assumes a DIRAC client is installed) # it needs the following environment variables: