diff --git a/.dockerignore b/.dockerignore index d0d9e8e37c4840a9cd49cc90b21142ba7f6c80f6..34bd8d4ebe562836a82028affccc26a2b34a9eb2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,7 +41,9 @@ gui/public/env.js !examples/plugins/parser/nomad.yaml # Ignore built gui and docs artufacts -nomad/app/static/ +nomad/app/static/.gui_configured +nomad/app/static/docs +nomad/app/static/gui # Ignore files created at nomad runtime run/ diff --git a/.gitignore b/.gitignore index 98aad82dcb783db470fbf0558ae087d682e35af1..750bb9d46975bd201d1d9b8c17b37ea659ef33f7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,9 @@ gui/public/artifacts.js !ops/docker-compose/nomad-oasis-with-keycloak/configs/nomad.yaml # Ignore built gui and docs artifacts -nomad/app/static/ +nomad/app/static/.gui_configured +nomad/app/static/docs +nomad/app/static/gui nomad/normalizing/data/*.db nomad/normalizing/data/*.msg @@ -81,6 +83,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +nexus.obj # PyInstaller # Usually these files are written by a python script from a template diff --git a/nomad/app/main.py b/nomad/app/main.py index e8304c542d5e8711040b27f5cb5c95808d2406b8..2d3dd00ff420b0696e6e03d840b885c542c69db7 100644 --- a/nomad/app/main.py +++ b/nomad/app/main.py @@ -16,31 +16,21 @@ # limitations under the License. # -import re -import os import hashlib import json -from fastapi import FastAPI, Request, Response, status +from fastapi import FastAPI, Response, status from fastapi.exception_handlers import ( http_exception_handler as default_http_exception_handler, ) from starlette.exceptions import HTTPException as StarletteHTTPException from fastapi.responses import HTMLResponse, JSONResponse -from starlette.staticfiles import ( - StaticFiles as StarletteStaticFiles, - NotModifiedResponse, -) from starlette.middleware.base import BaseHTTPMiddleware -from starlette.responses import PlainTextResponse -from starlette.datastructures import Headers from pydantic import BaseModel from nomad import config, infrastructure from .v1.main import app as v1_app - -from nomad.cli.dev import get_gui_artifacts_js -from nomad.cli.dev import get_gui_config +from .static import app as static_files_app, GuiFiles class OasisAuthenticationMiddleware(BaseHTTPMiddleware): @@ -69,6 +59,18 @@ class OasisAuthenticationMiddleware(BaseHTTPMiddleware): app = FastAPI() app_base = config.services.api_base_path + + +@app.get(f'{app_base}/alive') +async def alive(): + return 'I am, alive!' + + +@app.get('/-/health', status_code=status.HTTP_200_OK) +async def health(): + return {'healthcheck': 'ok'} + + app.mount(f'{app_base}/api/v1', v1_app) if config.services.optimade_enabled: @@ -93,111 +95,8 @@ if config.resources.enabled: app.mount(f'{app_base}/resources', resources_app) -dist_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dist')) -docs_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/docs')) -gui_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/gui')) -if not os.path.exists(gui_folder): - os.makedirs(gui_folder) - -configured_gui_folder = os.path.join( - config.fs.working_directory, 'run', 'gui_configured' -) -if os.path.exists(configured_gui_folder): - gui_folder = configured_gui_folder - - -class StaticFiles(StarletteStaticFiles): - etag_re = r'^(W/)?"?([^"]*)"?$' - - def is_not_modified( - self, response_headers: Headers, request_headers: Headers - ) -> bool: - # The starlette etag implementation is not considering the "..." and W/"..." etag - # RFC syntax used by browsers. - try: - if_none_match = request_headers['if-none-match'] - match = re.match(StaticFiles.etag_re, if_none_match) - if_none_match = match.group(2) - etag = response_headers['etag'] - if if_none_match == etag: - return True - except KeyError: - pass - - return super().is_not_modified(response_headers, request_headers) - - -class GuiFiles(StaticFiles): - gui_artifacts_data = None - gui_env_data = None - gui_data_etag = None - - async def get_response(self, path: str, scope) -> Response: - if path not in ['env.js', 'artifacts.js']: - response = await super().get_response(path, scope) - else: - assert ( - GuiFiles.gui_data_etag is not None - ), 'Etag for gui data was not initialized' - response = PlainTextResponse( - GuiFiles.gui_env_data - if path == 'env.js' - else GuiFiles.gui_artifacts_data, - media_type='application/javascript', - headers=dict(etag=GuiFiles.gui_data_etag), - ) - - request_headers = Headers(scope=scope) - if self.is_not_modified(response.headers, request_headers): - return NotModifiedResponse(response.headers) - return response - - -app.mount( - f'{app_base}/dist', - StaticFiles(directory=dist_folder, check_dir=False), - name='dist', -) -app.mount( - f'{app_base}/docs', StaticFiles(directory=docs_folder, check_dir=False), name='docs' -) -app.mount( - f'{app_base}/gui', GuiFiles(directory=gui_folder, check_dir=False), name='gui' -) - - -@app.on_event('startup') -async def startup_event(): - from nomad.cli.dev import get_gui_artifacts_js - from nomad.cli.dev import get_gui_config - from nomad import infrastructure - from nomad.parsing.parsers import import_all_parsers - from nomad.metainfo.elasticsearch_extension import entry_type - - import_all_parsers() - - # each subprocess is supposed disconnect and - # connect again: https://jira.mongodb.org/browse/PYTHON-2090 - try: - from mongoengine import disconnect - - disconnect() - except Exception: - pass - - entry_type.reload_quantities_dynamic() - GuiFiles.gui_artifacts_data = get_gui_artifacts_js() - GuiFiles.gui_env_data = get_gui_config() - - data = { - 'artifacts': GuiFiles.gui_artifacts_data, - 'gui_config': GuiFiles.gui_env_data, - } - GuiFiles.gui_data_etag = hashlib.md5( - json.dumps(data).encode(), usedforsecurity=False - ).hexdigest() - - infrastructure.setup() +# Make sure to mount this last, as it is a catch-all routes that are not yet mounted. +app.mount(app_base, static_files_app) @app.exception_handler(StarletteHTTPException) @@ -255,45 +154,35 @@ async def http_exception_handler(request, exc): ) -@app.get(f'{app_base}/alive') -async def alive(): - """Simple endpoint to utilize kubernetes liveness/readiness probing.""" - return 'I am, alive!' - - -@app.get('/-/health', status_code=status.HTTP_200_OK) -async def health(): - return {'healthcheck': 'ok'} - - -max_cache_ages = { - r'\.[a-f0-9]+\.chunk\.(js|css)$': 3600 * 24 * 7, - r'\.(html|js|css)$': config.services.html_resource_http_max_age, - r'\.(png|jpg|gif|jpeg|ico)$': config.services.image_resource_http_max_age, -} +@app.on_event('startup') +async def startup_event(): + from nomad.cli.dev import get_gui_artifacts_js + from nomad.cli.dev import get_gui_config + from nomad.parsing.parsers import import_all_parsers + from nomad import infrastructure + from nomad.metainfo.elasticsearch_extension import entry_type + import_all_parsers() -@app.middleware('http') -async def add_header(request: Request, call_next): - response = await call_next(request) + # each subprocess is supposed disconnect and + # connect again: https://jira.mongodb.org/browse/PYTHON-2090 + try: + from mongoengine import disconnect - max_age = None - for key, value in max_cache_ages.items(): - if re.search(key, str(request.url)): - max_age = value - break + disconnect() + except Exception: + pass - if max_age is not None: - response.headers['Cache-Control'] = f'max-age={max_age}, must-revalidate' - else: - response.headers[ - 'Cache-Control' - ] = f'max-age=0, no-cache, no-store, must-revalidate' + entry_type.reload_quantities_dynamic() + GuiFiles.gui_artifacts_data = get_gui_artifacts_js() + GuiFiles.gui_env_data = get_gui_config() - # The etags that we and starlette produce do not follow the RFC, because they do not - # start with a " as the RFC specifies. Nginx considers them weak etags and will strip - # these if gzip is enabled. - if not response.headers.get('etag', '"').startswith('"'): - response.headers['etag'] = f'"{response.headers.get("etag")}"' + data = { + 'artifacts': GuiFiles.gui_artifacts_data, + 'gui_config': GuiFiles.gui_env_data, + } + GuiFiles.gui_data_etag = hashlib.md5( + json.dumps(data).encode(), usedforsecurity=False + ).hexdigest() - return response + infrastructure.setup() diff --git a/nomad/app/static/__init__.py b/nomad/app/static/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..877bd1f8f12195f961e0a69e97d8f572fd0edb31 --- /dev/null +++ b/nomad/app/static/__init__.py @@ -0,0 +1,115 @@ +import re +import os + +from fastapi import FastAPI, Request, Response +from fastapi.exception_handlers import ( + http_exception_handler as default_http_exception_handler, +) +from starlette.staticfiles import ( + StaticFiles as StarletteStaticFiles, + NotModifiedResponse, +) +from starlette.responses import PlainTextResponse +from starlette.datastructures import Headers + +from nomad import config + + +app = FastAPI() + +docs_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'docs')) +gui_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gui')) +if not os.path.exists(gui_folder): + os.makedirs(gui_folder) + +configured_gui_folder = os.path.join( + config.fs.working_directory, 'run', 'gui_configured' +) +if os.path.exists(configured_gui_folder): + gui_folder = configured_gui_folder + + +class StaticFiles(StarletteStaticFiles): + etag_re = r'^(W/)?"?([^"]*)"?$' + + def is_not_modified( + self, response_headers: Headers, request_headers: Headers + ) -> bool: + # The starlette etag implementation is not considering the "..." and W/"..." etag + # RFC syntax used by browsers. + try: + if_none_match = request_headers['if-none-match'] + match = re.match(StaticFiles.etag_re, if_none_match) + if_none_match = match.group(2) + etag = response_headers['etag'] + if if_none_match == etag: + return True + except KeyError: + pass + + return super().is_not_modified(response_headers, request_headers) + + +app.mount(f'/docs', StaticFiles(directory=docs_folder, check_dir=False), name='docs') + + +class GuiFiles(StaticFiles): + gui_artifacts_data = None + gui_env_data = None + gui_data_etag = None + + async def get_response(self, path: str, scope) -> Response: + if path not in ['env.js', 'artifacts.js']: + response = await super().get_response(path, scope) + else: + assert ( + GuiFiles.gui_data_etag is not None + ), 'Etag for gui data was not initialized' + response = PlainTextResponse( + GuiFiles.gui_env_data + if path == 'env.js' + else GuiFiles.gui_artifacts_data, + media_type='application/javascript', + headers=dict(etag=GuiFiles.gui_data_etag), + ) + + request_headers = Headers(scope=scope) + if self.is_not_modified(response.headers, request_headers): + return NotModifiedResponse(response.headers) + return response + + +gui_files_app = GuiFiles(directory=gui_folder, check_dir=False) +app.mount(f'/gui', gui_files_app, name='gui') + +max_cache_ages = { + r'\.[a-f0-9]+\.chunk\.(js|css)$': 3600 * 24 * 7, + r'\.(html|js|css)$': config.services.html_resource_http_max_age, + r'\.(png|jpg|gif|jpeg|ico)$': config.services.image_resource_http_max_age, +} + + +@app.middleware('http') +async def add_header(request: Request, call_next): + response = await call_next(request) + + max_age = None + for key, value in max_cache_ages.items(): + if re.search(key, str(request.url)): + max_age = value + break + + if max_age is not None: + response.headers['Cache-Control'] = f'max-age={max_age}, must-revalidate' + else: + response.headers[ + 'Cache-Control' + ] = f'max-age=0, no-cache, no-store, must-revalidate' + + # The etags that we and starlette produce do not follow the RFC, because they do not + # start with a " as the RFC specifies. Nginx considers them weak etags and will strip + # these if gzip is enabled. + if not response.headers.get('etag', '"').startswith('"'): + response.headers['etag'] = f'"{response.headers.get("etag")}"' + + return response diff --git a/nomad/app/v1/main.py b/nomad/app/v1/main.py index ee31bb1acea0c31ca0c8d9fb97f2a94e559c5135..2cdcd5e3a61eb8ff7127f31ba560839de5c396ee 100644 --- a/nomad/app/v1/main.py +++ b/nomad/app/v1/main.py @@ -16,10 +16,12 @@ # limitations under the License. # -from typing import Any +from typing import Any, cast from fastapi import FastAPI, status, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, RedirectResponse +from starlette.types import ASGIApp, Message, Receive, Scope, Send +from fastapi.routing import APIRoute import traceback import orjson @@ -90,10 +92,16 @@ async def redirect_to_docs(req: Request): app.add_route('/', redirect_to_docs, include_in_schema=False) -@app.middleware('http') -async def log_request_time(request: Request, call_next): - with utils.timer(logger, 'request handled', url=request.url.path): - return await call_next(request) +class LoggingMiddleware: + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + with utils.timer(logger, 'request handled', path=scope.get('path')): + await self.app(scope, receive, send) + + +app.add_middleware(LoggingMiddleware) @app.exception_handler(Exception) diff --git a/nomad/app/v1/routers/entries.py b/nomad/app/v1/routers/entries.py index 7d3ccdd32b10ae60a744ce4b0330de9a9e45c315..acf99304a23e9ac0cefcc44efb0443533f61f416 100644 --- a/nomad/app/v1/routers/entries.py +++ b/nomad/app/v1/routers/entries.py @@ -38,7 +38,6 @@ import json import orjson from pydantic.main import create_model from starlette.responses import Response -from joblib import Parallel, delayed, parallel_backend from nomad import files, config, utils, metainfo, processing as proc from nomad import datamodel @@ -857,7 +856,7 @@ def _validate_required(required: ArchiveRequired, user) -> RequiredReader: def _read_entry_from_archive(entry: dict, uploads, required_reader: RequiredReader): entry_id, upload_id = entry['entry_id'], entry['upload_id'] - # all other exceptions are handled by the caller `_read_entries_from_archive` + # all other exceptions are handled by the caller `_answer_entries_archive_request` try: upload_files = uploads.get_upload_files(upload_id) @@ -872,27 +871,8 @@ def _read_entry_from_archive(entry: dict, uploads, required_reader: RequiredRead return None -def _read_entries_from_archive( - entries: Union[list, dict], required: ArchiveRequired, user -): - """ - Takes pickleable arguments so that it can be offloaded to worker processes. - - It is important to ensure the return values are also pickleable. - """ - with _Uploads() as uploads: - required_reader = _validate_required(required, user) - - if isinstance(entries, dict): - return _read_entry_from_archive(entries, uploads, required_reader) - - return [ - _read_entry_from_archive(entry, uploads, required_reader) - for entry in entries - ] - - -def _answer_entries_archive_request( +async def _answer_entries_archive_request( + request: Request, owner: Owner, query: Query, pagination: MetadataPagination, @@ -928,27 +908,29 @@ def _answer_entries_archive_request( for entry in search_response.data ] - # fewer than config.archive.min_entries_per_process entries per process is not useful - # more than config.max_process_number processes is too much for the server - number: int = min( - int(math.ceil(len(entries) / config.archive.min_entries_per_process)), - config.archive.max_process_number, - ) + required_reader = _validate_required(required, user) + response_data = [] + if isinstance(entries, dict): + entries = [entries] - if number <= 1: - request_data: list = _read_entries_from_archive(entries, required, user) - else: - with parallel_backend('threading', n_jobs=number): - request_data = Parallel()( - delayed(_read_entries_from_archive)(i, required, user) for i in entries - ) + with _Uploads() as uploads: + for entry in entries: + disconnected = await request.is_disconnected() + if disconnected: + logger.info('client disconnected', endpoint='entries/archive') + break + + entry_archive = _read_entry_from_archive(entry, uploads, required_reader) + response_data.append(entry_archive) + + logger.info('read all archives', endpoint='entries/archive') return EntriesArchiveResponse( owner=search_response.owner, query=search_response.query, pagination=search_response.pagination, required=required, - data=list(filter(None, request_data)), + data=list(filter(None, response_data)), ) @@ -977,7 +959,8 @@ async def post_entries_archive_query( data: EntriesArchive, user: User = Depends(create_user_dependency()), ): - return _answer_entries_archive_request( + return await _answer_entries_archive_request( + request=request, owner=data.owner, query=data.query, pagination=data.pagination, @@ -1002,7 +985,8 @@ async def get_entries_archive_query( pagination: MetadataPagination = Depends(metadata_pagination_parameters), user: User = Depends(create_user_dependency()), ): - res = _answer_entries_archive_request( + res = await _answer_entries_archive_request( + request=request, owner=with_query.owner, query=with_query.query, pagination=pagination, diff --git a/nomad/app/v1/routers/metainfo.py b/nomad/app/v1/routers/metainfo.py index 798c85386c474e82cec7bf58dd3a38295ca6aed0..2d9471828698ae3be582c3efec4b33539112fc5a 100644 --- a/nomad/app/v1/routers/metainfo.py +++ b/nomad/app/v1/routers/metainfo.py @@ -103,7 +103,7 @@ def store_package_definition(package: Package, **kwargs): ).count() > 0 ): - logger.info(f'Package {package.definition_id} already exists. Skipping.') + logger.info(f'Package already exists.', package_id={package.definition_id}) return mongo_package = PackageDefinition(package, **kwargs) diff --git a/nomad/app/v1/routers/north.py b/nomad/app/v1/routers/north.py index 1ca98b1343d80671237b7531e70d05189750610b..be57d7eab21fca13596818e7d5118e6b70543ce8 100644 --- a/nomad/app/v1/routers/north.py +++ b/nomad/app/v1/routers/north.py @@ -255,7 +255,7 @@ async def start_tool( 'external_mounts': external_mounts, } - logger.info('body of the post call', body=body) + logger.info('post tool start to jupyterhub', body=body) response = requests.post(url, json=body, headers=hub_api_headers) diff --git a/nomad/app/v1/routers/uploads.py b/nomad/app/v1/routers/uploads.py index 8bbecd4900dca3a6b628fac5124d54db687e9173..95d476877c3aa4e150310a0eb2c4b57208163b9e 100644 --- a/nomad/app/v1/routers/uploads.py +++ b/nomad/app/v1/routers/uploads.py @@ -2515,11 +2515,11 @@ async def _get_files_if_provided( f.write(chunk) if uploaded_bytes > next_log_at: logger.info( - 'Large upload in progress - uploaded: ' - f'{uploaded_bytes // log_interval} {log_unit}' + 'large upload in progress', + uploaded_bytes=uploaded_bytes, ) next_log_at += log_interval - logger.info(f'Uploaded {uploaded_bytes} bytes') + logger.info(f'upload completed', uploaded_bytes={uploaded_bytes}) except Exception as e: if not (isinstance(e, RuntimeError) and 'Stream consumed' in str(e)): if os.path.exists(tmp_dir): @@ -2536,7 +2536,7 @@ async def _get_files_if_provided( shutil.rmtree(tmp_dir) return [], None - logger.info(f'received {len(upload_paths)} uploaded file(s)') + logger.info(f'received uploaded file(s)') if method == 2 and no_file_name_info_provided: # Only ok if uploaded file is a zip or a tar archive. ext = ( diff --git a/nomad/client/archive.py b/nomad/client/archive.py index 75dc689c42b3adae23dc2d32e1e93653bb0141b3..911c7734c9e65b74de341becd004d665b533abf5 100644 --- a/nomad/client/archive.py +++ b/nomad/client/archive.py @@ -21,6 +21,8 @@ import asyncio from asyncio import Semaphore from itertools import islice from typing import Any, Union +from math import floor +from time import monotonic import threading from click import progressbar @@ -116,6 +118,9 @@ class ArchiveQuery: Setting a high value for `semaphore` may cause the server to return 500, 502, 504 errors. + Use `max_requests_per_second` to control the number of requests per second in case the server + has a rate limit. + Params: owner (str): ownership scope query (dict): query @@ -131,6 +136,7 @@ class ArchiveQuery: retry (int): number of retry when fetching uploads, default: 4 sleep_time (float): sleep time for retry, default: 4. semaphore (int): number of concurrent downloads, this depends on server settings, default: 4 + max_requests_per_second (int): maximum requests per second, default: 999999 """ def __init__( @@ -145,10 +151,11 @@ class ArchiveQuery: batch_size: int = 10, username: str = None, password: str = None, - retry: int = 4, + retry: int = 1, sleep_time: float = 4.0, from_api: bool = False, - semaphore: int = 4, + semaphore: int = 8, + max_requests_per_second: int = 20, ): self._owner: str = owner self._required = required if required else dict(run='*') @@ -168,6 +175,10 @@ class ArchiveQuery: self._sleep_time: float = sleep_time if sleep_time > 0.0 else 4.0 self._semaphore = min(10, semaphore) if semaphore > 0 else 4 self._results_actual: int = 0 + self._max_requests_per_second: int = max_requests_per_second + + self._start_time: float = 0.0 + self._accumulated_requests: int = 0 from nomad.client import Auth @@ -262,6 +273,15 @@ class ArchiveQuery: return self._results_max + @property + def _allowed_requests(self) -> float: + """ + The number of requests allowed since the start of the download. + This is controlled by the maximum number of requests per second. + """ + duration: float = monotonic() - self._start_time + return self._max_requests_per_second * duration + async def _fetch_async(self, number: int) -> int: """ There is no need to perform fetching asynchronously as the required number of uploads @@ -371,6 +391,9 @@ class ArchiveQuery: actual_number: int = min(number, len(self._entries)) + self._start_time = monotonic() + self._accumulated_requests = 0 + def batched(iterable, chunk_size): iterator = iter(iterable) while chunk := list(islice(iterator, chunk_size)): @@ -413,6 +436,11 @@ class ArchiveQuery: request = self._download_request(entry_ids) async with semaphore: + while self._accumulated_requests > self._allowed_requests: + await asyncio.sleep(0.1) + + self._accumulated_requests += 1 + response = await session.post( self._download_url, json=request, headers=self._auth.headers() ) diff --git a/nomad/config/models.py b/nomad/config/models.py index 177cbdd397bd996b96e205b31fbb34e28a0de41d..f30c8c79dd96c0846547b1f86b51d051fe469321 100644 --- a/nomad/config/models.py +++ b/nomad/config/models.py @@ -917,13 +917,6 @@ class Archive(NomadSettings): description='GPFS needs at least 256K to achieve decent performance.', ) toc_depth = Field(6, description='Depths of table of contents in the archive.') - max_process_number = Field( - 20, - description='Maximum number of processes can be assigned to process archive query.', - ) - min_entries_per_process = Field( - 20, description='Minimum number of entries per process.' - ) use_new_writer = False # todo: to be removed small_obj_optimization_threshold = Field( 256 * 1024, diff --git a/ops/kubernetes/nomad-prod-develop.yaml b/ops/kubernetes/nomad-prod-develop.yaml index 489d39e57cb9297e8da2fec252909c3917058dc1..52e600b2d05638b355a537091919e74b28a64034 100644 --- a/ops/kubernetes/nomad-prod-develop.yaml +++ b/ops/kubernetes/nomad-prod-develop.yaml @@ -6,10 +6,6 @@ nomad: usesBetaData: false officialUrl: "https://nomad-lab.eu/prod/v1/gui" - proxy: - external: - path: "/prod/v1/develop" - gui: debug: true @@ -29,16 +25,8 @@ nomad: image: tag: "prod" - ingress: - hosts: - - host: cloud.nomad-lab.eu - paths: - - path: /prod/v1/develop/ - pathType: ImplementationSpecific - - host: nomad-lab.eu - paths: - - path: /prod/v1/develop/ - pathType: ImplementationSpecific + proxy: + path: "/prod/v1/develop" app: replicaCount: 4 diff --git a/ops/kubernetes/nomad-prod-staging.yaml b/ops/kubernetes/nomad-prod-staging.yaml index da4e78fe8562f1ef1e023181b1a411b71e090a14..97ffefc57dbd64f99cf0a5f16f7eac34a3b21314 100644 --- a/ops/kubernetes/nomad-prod-staging.yaml +++ b/ops/kubernetes/nomad-prod-staging.yaml @@ -6,10 +6,6 @@ nomad: usesBetaData: false officialUrl: "https://nomad-lab.eu/prod/v1/gui" - proxy: - external: - path: "/prod/v1/staging" - gui: debug: true @@ -29,22 +25,14 @@ nomad: image: tag: "prod" - ingress: - hosts: - - host: cloud.nomad-lab.eu - paths: - - path: /prod/v1/staging/ - pathType: ImplementationSpecific - - host: nomad-lab.eu - paths: - - path: /prod/v1/staging/ - pathType: ImplementationSpecific + proxy: + path: "/prod/v1/staging" app: replicaCount: 8 worker: - replicaCount: 1 + replicaCount: 2 processes: 12 resources: limits: diff --git a/ops/kubernetes/nomad-prod-test.yaml b/ops/kubernetes/nomad-prod-test.yaml index b9a7651c073ada2bac63f45a7f862364c1f409f2..e5666d1f468feb6029f3929459fedb19acde5238 100644 --- a/ops/kubernetes/nomad-prod-test.yaml +++ b/ops/kubernetes/nomad-prod-test.yaml @@ -6,10 +6,6 @@ nomad: usesBetaData: true officialUrl: "https://nomad-lab.eu/prod/v1/gui" - proxy: - external: - path: "/prod/v1/test" - gui: debug: true @@ -38,16 +34,8 @@ nomad: image: tag: "prod" - ingress: - hosts: - - host: cloud.nomad-lab.eu - paths: - - path: /prod/v1/test/ - pathType: ImplementationSpecific - - host: nomad-lab.eu - paths: - - path: /prod/v1/test/ - pathType: ImplementationSpecific + proxy: + path: "/prod/v1/test" app: replicaCount: 4 diff --git a/ops/kubernetes/nomad-prod-util.yaml b/ops/kubernetes/nomad-prod-util.yaml index d05a929d453a421789e63497bcfae4d32d00b425..4966a53dd7266b7f7ab5e660a3fa48358a86c684 100644 --- a/ops/kubernetes/nomad-prod-util.yaml +++ b/ops/kubernetes/nomad-prod-util.yaml @@ -6,10 +6,6 @@ nomad: usesBetaData: false officialUrl: "https://nomad-lab.eu/prod/v1/gui" - proxy: - external: - path: "/prod/v1/util" - gui: debug: true @@ -26,22 +22,24 @@ nomad: north: enabled: false + archive: + use_new_writer: true + image: tag: "prod" - ingress: - hosts: - - host: nomad-lab.eu - paths: - - path: /prod/v1/util/ - pathType: ImplementationSpecific + proxy: + path: "/prod/v1/util" app: replicaCount: 1 + resources: + limits: + memory: "8Gi" worker: - replicaCount: 1 - processes: 4 + replicaCount: 2 + processes: 8 resources: limits: memory: "256Gi" diff --git a/ops/kubernetes/nomad-prod.yaml b/ops/kubernetes/nomad-prod.yaml index 50b5bce2630a54b862c977ffdbf544c31741cb45..4bfa0a6853080f091309acf1b86daeb45c17a3ca 100644 --- a/ops/kubernetes/nomad-prod.yaml +++ b/ops/kubernetes/nomad-prod.yaml @@ -1,9 +1,5 @@ nomad: config: - proxy: - external: - path: "/prod/v1" - dbname: nomad_prod_v1 uploadurl: "https://nomad-lab.eu/prod/v1/api/uploads" @@ -20,19 +16,13 @@ nomad: image: tag: "prod" + proxy: + path: "/prod/v1" + ingress: annotations: nginx.ingress.kubernetes.io/limit-rps: "25" nginx.ingress.kubernetes.io/denylist-source-range: "141.35.40.36/32, 141.35.40.52/32" - hosts: - - host: cloud.nomad-lab.eu - paths: - - path: /prod/v1/ - pathType: ImplementationSpecific - - host: nomad-lab.eu - paths: - - path: /prod/v1/ - pathType: ImplementationSpecific app: replicaCount: 18 diff --git a/ops/kubernetes/nomad/templates/NOTES.txt b/ops/kubernetes/nomad/templates/NOTES.txt index ec8177584e0a6f66444c2b5f05b1705933db8fff..b53ec5b87e23b5481c81dabe86916a2adeda8ae9 100644 --- a/ops/kubernetes/nomad/templates/NOTES.txt +++ b/ops/kubernetes/nomad/templates/NOTES.txt @@ -1,20 +1,19 @@ +{{- $path := .Values.nomad.proxy.path -}} 1. Get the application URL by running these commands: {{- if .Values.nomad.ingress.enabled }} {{- range $host := .Values.nomad.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.nomad.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} + http{{ if $.Values.nomad.ingress.tls }}s{{ end }}://{{ $host }}{{ $path }} {{- end }} -{{- else if contains "NodePort" .Values.nomad.proxy.service.type }} +{{- else if contains "NodePort" .Values.nomad.service.type }} export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nomad.fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.nomad.proxy.service.type }} +{{- else if contains "LoadBalancer" .Values.nomad.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nomad.fullname" . }}' export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nomad.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.nomad.proxy.service.port }} -{{- else if contains "ClusterIP" .Values.nomad.proxy.service.type }} + echo http://$SERVICE_IP:{{ .Values.nomad.service.port }} +{{- else if contains "ClusterIP" .Values.nomad.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nomad.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" diff --git a/ops/kubernetes/nomad/templates/app/deployment.yaml b/ops/kubernetes/nomad/templates/app/deployment.yaml index f02c6287f6a39a377bfd6ef7e805da6883153bbf..42c6f154c40fbd04860276db854770bc74ec34c2 100644 --- a/ops/kubernetes/nomad/templates/app/deployment.yaml +++ b/ops/kubernetes/nomad/templates/app/deployment.yaml @@ -56,18 +56,18 @@ spec: protocol: TCP livenessProbe: httpGet: - path: "{{ .Values.nomad.config.proxy.external.path }}/alive" + path: "{{ .Values.nomad.proxy.path }}/alive" port: 8000 - initialDelaySeconds: 60 - periodSeconds: 30 - timeoutSeconds: 5 + initialDelaySeconds: 90 + periodSeconds: 10 + timeoutSeconds: {{ add .Values.nomad.proxy.timeout 10}} readinessProbe: httpGet: - path: "{{ .Values.nomad.config.proxy.external.path }}/alive" + path: "{{ .Values.nomad.proxy.path }}/alive" port: 8000 - initialDelaySeconds: 60 - periodSeconds: 15 - timeoutSeconds: 5 + initialDelaySeconds: 90 + periodSeconds: 3 + timeoutSeconds: {{ add .Values.nomad.proxy.timeout 3 }} {{- with .Values.nomad.app.resources }} resources: {{- . | toYaml | nindent 12 }} diff --git a/ops/kubernetes/nomad/templates/configmap.yml b/ops/kubernetes/nomad/templates/configmap.yml index f1983e7e6fb83ed0aad93eac06b57894a12ff7a5..68df7ef8572f51c3f91fe1c2a6c95941a3fd1fb6 100644 --- a/ops/kubernetes/nomad/templates/configmap.yml +++ b/ops/kubernetes/nomad/templates/configmap.yml @@ -46,11 +46,11 @@ data: host: "{{ .Values.nomad.config.logstash.host }}" tcp_port: {{ .Values.nomad.config.logstash.port }} services: - api_host: "{{ .Values.nomad.config.proxy.external.host }}" - api_port: {{ .Values.nomad.config.proxy.external.port }} - api_base_path: "{{ .Values.nomad.config.proxy.external.path }}" + api_host: "{{ index .Values.nomad.ingress.hosts 0 }}" + api_port: {{ .Values.nomad.service.port }} + api_base_path: "{{ .Values.nomad.proxy.path }}" api_secret: "{{ .Values.nomad.config.api.secret }}" - https: {{ .Values.nomad.config.proxy.external.https }} + https: true upload_limit: {{ .Values.nomad.config.api.uploadLimit }} admin_user_id: {{ .Values.nomad.config.keycloak.admin_user_id }} aitoolkit_enabled: {{ .Values.nomad.config.services.aitoolkit.enabled }} @@ -120,16 +120,16 @@ data: {{ end }} north: enabled: {{ .Values.nomad.config.north.enabled }} - hub_host: "{{ .Values.nomad.config.proxy.external.host }}" - hub_port: {{ .Values.nomad.config.proxy.external.port }} + hub_host: "{{ index .Values.nomad.ingress.hosts 0 }}" + hub_port: {{ .Values.nomad.service.port }} hub_service_api_token: "{{ .Values.nomad.config.north.hubServiceApiToken }}" - {{ if .Values.nomad.archive }} - archive: {{ .Values.nomad.archive | toYaml | nindent 6 }} + {{ if .Values.nomad.config.archive }} + archive: {{ .Values.nomad.config.archive | toYaml | nindent 6 }} {{ end }} - {{ if .Values.nomad.plugins }} - plugins: {{ .Values.nomad.plugins | toYaml | nindent 6 }} + {{ if .Values.nomad.config.plugins }} + plugins: {{ .Values.nomad.config.plugins | toYaml | nindent 6 }} {{ end }} - {{ if .Values.nomad.normalize }} - normalize: {{ .Values.nomad.normalize | toYaml | nindent 6 }} + {{ if .Values.nomad.config.normalize }} + normalize: {{ .Values.nomad.config.normalize | toYaml | nindent 6 }} {{ end }} {{- end }} \ No newline at end of file diff --git a/ops/kubernetes/nomad/templates/ingress-api.yaml b/ops/kubernetes/nomad/templates/ingress-api.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5766492abc33a0de33ee5ee089c50393c4663d8f --- /dev/null +++ b/ops/kubernetes/nomad/templates/ingress-api.yaml @@ -0,0 +1,63 @@ +{{- if .Values.nomad.ingress.enabled -}} +{{- $fullName := include "nomad.fullname" . -}} +{{- $svcPort := .Values.nomad.service.port -}} +{{- $path := .Values.nomad.proxy.path -}} +{{- if and .Values.nomad.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.nomad.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.nomad.ingress.annotations "kubernetes.io/ingress.class" .Values.nomad.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-api + labels: + {{- include "nomad.labels" . | nindent 4 }} + annotations: + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" + nginx.ingress.kubernetes.io/proxy-send-timeout: "{{ .Values.nomad.proxy.timeout }}" + nginx.ingress.kubernetes.io/proxy-read-timeout: "{{ .Values.nomad.proxy.timeout }}" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "{{ .Values.nomad.proxy.connectionTimeout }}" + nginx.ingress.kubernetes.io/limit-connections: "{{ .Values.nomad.ingress.limitConnectionsApi }}" + {{- with .Values.nomad.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.nomad.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.nomad.ingress.className }} + {{- end }} + {{- if .Values.nomad.ingress.tls }} + tls: + {{- range .Values.nomad.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.nomad.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ trimSuffix "/" $path }}/api + pathType: ImplementationSpecific + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-proxy + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }}-proxy + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/ops/kubernetes/nomad/templates/ingress.yaml b/ops/kubernetes/nomad/templates/ingress.yaml index 9bb37fa0e0c5e3d568fc7de29c5e068144bff5f6..bd4fa2ac99384cb3d1fc4516821d8754b56a9b97 100644 --- a/ops/kubernetes/nomad/templates/ingress.yaml +++ b/ops/kubernetes/nomad/templates/ingress.yaml @@ -1,6 +1,7 @@ {{- if .Values.nomad.ingress.enabled -}} {{- $fullName := include "nomad.fullname" . -}} {{- $svcPort := .Values.nomad.service.port -}} +{{- $path := .Values.nomad.proxy.path -}} {{- if and .Values.nomad.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} {{- if not (hasKey .Values.nomad.ingress.annotations "kubernetes.io/ingress.class") }} {{- $_ := set .Values.nomad.ingress.annotations "kubernetes.io/ingress.class" .Values.nomad.ingress.className}} @@ -18,10 +19,15 @@ metadata: name: {{ $fullName }} labels: {{- include "nomad.labels" . | nindent 4 }} - {{- with .Values.nomad.ingress.annotations }} annotations: + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" + nginx.ingress.kubernetes.io/proxy-send-timeout: "{{ .Values.nomad.proxy.timeout }}" + nginx.ingress.kubernetes.io/proxy-read-timeout: "{{ .Values.nomad.proxy.timeout }}" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "{{ .Values.nomad.proxy.connectionTimeout }}" + nginx.ingress.kubernetes.io/limit-connections: "{{ .Values.nomad.ingress.limitConnections }}" + {{- with .Values.nomad.ingress.annotations }} {{- toYaml . | nindent 4 }} - {{- end }} + {{- end }} spec: {{- if and .Values.nomad.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} ingressClassName: {{ .Values.nomad.ingress.className }} @@ -38,14 +44,11 @@ spec: {{- end }} rules: {{- range .Values.nomad.ingress.hosts }} - - host: {{ .host | quote }} + - host: {{ . | quote }} http: paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} + - path: {{ $path }} + pathType: ImplementationSpecific backend: {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: @@ -56,6 +59,5 @@ spec: serviceName: {{ $fullName }}-proxy servicePort: {{ $svcPort }} {{- end }} - {{- end }} {{- end }} {{- end }} diff --git a/ops/kubernetes/nomad/templates/proxy/configmap.yml b/ops/kubernetes/nomad/templates/proxy/configmap.yml index c1992939edb5748ee6d46f127b8d9c70dfdcd73a..81ce9788023eb3bbb2a3392a8159f1abaab0d674 100644 --- a/ops/kubernetes/nomad/templates/proxy/configmap.yml +++ b/ops/kubernetes/nomad/templates/proxy/configmap.yml @@ -23,8 +23,8 @@ data: server_name www.example.com; proxy_set_header Host $host; - proxy_connect_timeout {{ .Values.nomad.config.proxy.timeout }}; - proxy_read_timeout {{ .Values.nomad.config.proxy.timeout }}; + proxy_connect_timeout {{ .Values.nomad.proxy.connectionTimeout }}; + proxy_read_timeout {{ .Values.nomad.proxy.timeout }}; proxy_pass_request_headers on; underscores_in_headers on; @@ -50,29 +50,29 @@ data: proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } - location ~ {{ .Values.nomad.config.proxy.external.path }}\/?(gui)?$ { - rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/gui/ permanent; + location ~ {{ .Values.nomad.proxy.path }}\/?(gui)?$ { + rewrite ^ {{ .Values.nomad.proxy.path }}/gui/ permanent; } - location {{ .Values.nomad.config.proxy.external.path }}/gui/ { + location {{ .Values.nomad.proxy.path }}/gui/ { proxy_intercept_errors on; error_page 404 = @redirect_to_index; proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } location @redirect_to_index { - rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/gui/index.html break; + rewrite ^ {{ .Values.nomad.proxy.path }}/gui/index.html break; proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } - location {{ .Values.nomad.config.proxy.external.path }}/docs/ { + location {{ .Values.nomad.proxy.path }}/docs/ { proxy_intercept_errors on; error_page 404 = @redirect_to_index_docs; proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } location @redirect_to_index_docs { - rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/docs/index.html break; + rewrite ^ {{ .Values.nomad.proxy.path }}/docs/index.html break; proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } @@ -98,12 +98,12 @@ data: location ~ /api/v1/entries/edit { proxy_buffering off; - proxy_read_timeout {{ .Values.nomad.config.proxy.editTimeout }}; + proxy_read_timeout {{ .Values.nomad.proxy.editTimeout }}; proxy_pass http://{{ include "nomad.fullname" . }}-app:8000; } {{- if .Values.nomad.config.north.enabled }} - location {{ .Values.nomad.config.proxy.external.path }}/north/ { + location {{ .Values.nomad.proxy.path }}/north/ { client_max_body_size 500m; proxy_pass http://{{ include "jupyterhub.fullname" . }}-proxy-public; @@ -120,6 +120,5 @@ data: proxy_buffering off; } {{- end }} - } {{- end}} \ No newline at end of file diff --git a/ops/kubernetes/nomad/templates/proxy/deployment.yaml b/ops/kubernetes/nomad/templates/proxy/deployment.yaml index bcbebd6045238e1b6236ef0072bc13cd2a863c54..ee1f7526f061dd50e785d83e5c3989fb7c795155 100644 --- a/ops/kubernetes/nomad/templates/proxy/deployment.yaml +++ b/ops/kubernetes/nomad/templates/proxy/deployment.yaml @@ -60,16 +60,18 @@ spec: protocol: TCP livenessProbe: httpGet: - path: "{{ .Values.nomad.config.proxy.external.path }}/gui/index.html" + path: "{{ .Values.nomad.proxy.path }}/gui/index.html" port: http - initialDelaySeconds: 60 - periodSeconds: 15 + initialDelaySeconds: 90 + periodSeconds: 10 + timeoutSeconds: {{ add .Values.nomad.proxy.timeout 10 }} readinessProbe: httpGet: - path: "{{ .Values.nomad.config.proxy.external.path }}/gui/index.html" + path: "{{ .Values.nomad.proxy.path }}/gui/index.html" port: http - initialDelaySeconds: 60 + initialDelaySeconds: 90 periodSeconds: 3 + timeoutSeconds: {{ add .Values.nomad.proxy.timeout 3}} {{- with .Values.nomad.proxy.resources }} resources: {{- . | toYaml | nindent 12 }} diff --git a/ops/kubernetes/nomad/templates/worker/deployment.yaml b/ops/kubernetes/nomad/templates/worker/deployment.yaml index 10a987d189034d2591f018500df657a374f5956b..71e8a2c20f338ff64ba8c8ca25ea1d98d8b69258 100644 --- a/ops/kubernetes/nomad/templates/worker/deployment.yaml +++ b/ops/kubernetes/nomad/templates/worker/deployment.yaml @@ -103,29 +103,23 @@ spec: {{- end }} command: ["python", "-m", "celery", "-A", "nomad.processing", "worker", "-n", "$(NOMAD_CELERY_NODE_NAME)" {{ if .Values.nomad.worker.processes }}, "-c", "{{ .Values.nomad.worker.processes }}"{{ end }}{{ if .Values.nomad.worker.maxTasksPerChild }}, "--max-tasks-per-child", "{{ .Values.nomad.worker.maxTasksPerChild }}"{{ end }}] livenessProbe: - # httpGet: - # path: / - # port: http exec: command: - bash - -c - NOMAD_LOGSTASH_LEVEL=WARNING python -m celery -A nomad.processing status | grep "${NOMAD_CELERY_NODE_NAME}:.*OK" initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 30 + periodSeconds: 120 + timeoutSeconds: 60 readinessProbe: - # httpGet: - # path: / - # port: http exec: command: - bash - -c - NOMAD_LOGSTASH_LEVEL=WARNING python -m celery -A nomad.processing status | grep "${NOMAD_CELERY_NODE_NAME}:.*OK" - initialDelaySeconds: 15 - periodSeconds: 30 - timeoutSeconds: 30 + initialDelaySeconds: 30 + periodSeconds: 120 + timeoutSeconds: 60 volumes: {{- with .Values.nomad.volumes }} {{- toYaml . | nindent 8 }} diff --git a/ops/kubernetes/nomad/values.yaml b/ops/kubernetes/nomad/values.yaml index ccd1e0b213c386104e2ea8160b0854aeb9efca96..ac2cba4fee1aa8045b283998904535d7a5fad996 100644 --- a/ops/kubernetes/nomad/values.yaml +++ b/ops/kubernetes/nomad/values.yaml @@ -148,31 +148,17 @@ nomad: ## automatically gz based on header gzip: true - proxy: - # Set a nodePort to create a NodePort service instead of ClusterIP. Also set a nodeIP for the externalIP. - timeout: 120 - editTimeout: 1800 - external: - host: "nomad-lab.eu" - port: 80 - path: "/fairdi/nomad/latest" - https: true - - ingress: enabled: false + limitConnections: 32 + limitConnectionsApi: 8 + hosts: + - nomad-lab.eu className: "" annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific + nginx.ingress.kubernetes.io/proxy-body-size: "32g" tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local # Additional volumes on the output Deployment definition. volumes: [] @@ -196,6 +182,11 @@ nomad: ## Everything concerning the nginx that serves the gui, proxies the api # It is run via NodePort service proxy: + path: "/fairdi/nomad/latest" + timeout: 60 + editTimeout: 60 + connectionTimeout: 10 + replicaCount: 1 # Set a nodePort to create a NodePort service instead of ClusterIP. Also set a nodeIP for the externalIP. diff --git a/ops/kubernetes/values.yaml b/ops/kubernetes/values.yaml index b16203492d192e4c75a142b36f3786fb24e45bc6..42bb25c5d6bde49c937d58f5497039b30810c3aa 100644 --- a/ops/kubernetes/values.yaml +++ b/ops/kubernetes/values.yaml @@ -11,11 +11,6 @@ nomad: isBeta: false usesBetaData: false - proxy: - external: - host: "nomad-lab.eu" - path: "/prod/v1" - gui: debug: false encyclopediaBase: "https://nomad-lab.eu/prod/rae/encyclopedia/#" @@ -84,34 +79,25 @@ nomad: ingress: enabled: true + limitConnections: 32 + limitConnectionsApi: 8 className: "nginx" annotations: cert-manager.io/cluster-issuer: "letsencrypt-production" - nginx.ingress.kubernetes.io/proxy-body-size: "32g" - nginx.ingress.kubernetes.io/proxy-request-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "10" - nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - nginx.ingress.kubernetes.io/limit-rps: "25" + nginx.ingress.kubernetes.io/limit-rps: "32" nginx.ingress.kubernetes.io/denylist-source-range: "141.35.40.36/32, 141.35.40.52/32" hosts: - - host: cloud.nomad-lab.eu - paths: - - path: /prod/v1/ - pathType: ImplementationSpecific - - host: nomad-lab.eu - paths: - - path: /prod/v1/ - pathType: ImplementationSpecific + - nomad-lab.eu tls: - - secretName: cloud-nomad-lab-eu-tls - hosts: - - cloud.nomad-lab.eu - secretName: nomad-lab-eu-tls hosts: - nomad-lab.eu proxy: + timeout: 60 + editTimeout: 60 + host: "nomad-lab.eu" + path: "/prod/v1" nodeSelector: environment: prod "nomad-lab.eu/app": "" diff --git a/pyproject.toml b/pyproject.toml index c3084d18867ac7f6db40ed3c2627a4c834340661..d3dcfb2c8131bf9f77bb7bfe8a1317588c34bcf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,6 @@ infrastructure = [ 'dockerspawner==12.1.0', 'oauthenticator==15.1.0', 'validators==0.18.2', - 'joblib>=1.1.0', 'gunicorn>=21.2.0,<22.0.0', 'importlib-metadata~=4.13.0' # Needed because of https://github.com/python/importlib_metadata/issues/411 ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 1127633b87cba713389724f9401b4a59b532840f..5be4fda0fcb64af24d4e7d85f8d85aa0f8fd1cf6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -36,7 +36,7 @@ bs4==0.0.1 # via -r requirements.txt, nomad-lab, nomad-lab (pypro build==0.9.0 # via nomad-lab (pyproject.toml), pip-tools cachetools==4.2.4 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) cattrs==22.2.0 # via -r requirements.txt, requests-cache -celery[redis]==5.2.7 # via celery, nomad-lab, nomad-lab (pyproject.toml) +celery[redis]==5.2.7 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) certifi==2022.12.7 # via -r requirements.txt, elasticsearch, httpcore, httpx, requests certipy==0.1.3 # via -r requirements.txt, jupyterhub cffi==1.15.1 # via -r requirements.txt, bcrypt, cryptography @@ -53,7 +53,7 @@ commonmark==0.9.1 # via -r requirements.txt, recommonmark coverage==6.5.0 # via pytest-cov cryptography==39.0.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml), pyjwt, pyopenssl, rfc3161ng, secretstorage cycler==0.11.0 # via -r requirements.txt, matplotlib -dask[array]==2022.2.0 # via -r requirements.txt, dask, hyperspy, kikuchipy, orix, pyxem +dask[array]==2022.2.0 # via -r requirements.txt, hyperspy, kikuchipy, orix, pyxem debugpy==1.6.5 # via -r requirements.txt, ipykernel decorator==5.1.1 # via -r requirements.txt, ipyparallel, ipython, validators devtools==0.8.0 # via nomad-lab (pyproject.toml) @@ -95,7 +95,7 @@ griddataformats==0.7.0 # via -r requirements.txt, mdanalysis gsd==2.7.0 # via -r requirements.txt, mdanalysis gunicorn==21.2.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) h11==0.14.0 # via -r requirements.txt, httpcore, uvicorn -h5grove[fastapi]==1.3.0 # via h5grove, nomad-lab, nomad-lab (pyproject.toml) +h5grove[fastapi]==1.3.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) h5py==3.6.0 # via -r requirements.txt, h5grove, hyperspy, ifes-apt-tc-data-modeling, kikuchipy, nionswift, nomad-lab, nomad-lab (pyproject.toml), orix, phonopy, pyfai, silx hjson==3.0.2 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) html5lib==1.1 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) @@ -122,9 +122,9 @@ jedi==0.18.2 # via -r requirements.txt, ipython jeepney==0.8.0 # via keyring, secretstorage jinja2==3.0.3 # via -r requirements.txt, flask, hyperspy, jupyterhub, mkdocs, mkdocs-macros-plugin, mkdocs-material, sphinx jmespath==0.10.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) -joblib==1.1.0 # via -r requirements.txt, mdanalysis, nomad-lab, nomad-lab (pyproject.toml), pymatgen, scikit-learn +joblib==1.1.0 # via -r requirements.txt, mdanalysis, nomad-lab, pymatgen, scikit-learn jsonpointer==2.3 # via -r requirements.txt, jsonschema -jsonschema[format]==4.17.3 # via jsonschema, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator +jsonschema[format]==4.17.3 # via -r requirements.txt, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator jupyter-client==7.4.8 # via -r requirements.txt, ipykernel, ipyparallel jupyter-core==4.12.0 # via -r requirements.txt, jupyter-client jupyter-telemetry==0.1.0 # via -r requirements.txt, jupyterhub @@ -194,7 +194,7 @@ numpy-quaternion==2022.4.3 # via -r requirements.txt, orix oauthenticator==15.1.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) oauthlib==3.2.2 # via -r requirements.txt, jupyterhub openpyxl==3.1.2 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) -optimade[mongo]==0.22.1 # via nomad-lab, nomad-lab (pyproject.toml), optimade +optimade[mongo]==0.22.1 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) orix==0.11.1 # via -r requirements.txt, diffsims, kikuchipy, pyxem orjson==3.9.4 # via -r requirements.txt, h5grove, nomad-lab, nomad-lab (pyproject.toml) packaging==24.0 # via -r requirements.txt, build, dask, docker, gunicorn, hyperspy, ipykernel, matplotlib, mdanalysis, mkdocs, mongomock, pint, pooch, pytest, scikit-image, sphinx @@ -234,7 +234,7 @@ pycparser==2.21 # via -r requirements.txt, cffi pydantic==1.10.9 # via -r requirements.txt, fastapi, nomad-lab, nomad-lab (pyproject.toml), optimade pyfai==2023.9.0 # via -r requirements.txt, pyxem pygments==2.14.0 # via -r requirements.txt, ipython, mkdocs-material, readme-renderer, sphinx -pyjwt[crypto]==2.6.0 # via nomad-lab, nomad-lab (pyproject.toml), pyjwt +pyjwt[crypto]==2.6.0 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) pylint==2.13.9 # via nomad-lab (pyproject.toml), pylint-mongoengine, pylint-plugin-utils pylint-mongoengine==0.4.0 # via nomad-lab (pyproject.toml) pylint-plugin-utils==0.7 # via nomad-lab (pyproject.toml), pylint-mongoengine @@ -277,7 +277,7 @@ requests-cache==1.0.1 # via -r requirements.txt requests-toolbelt==0.10.1 # via python-gitlab, twine rfc3161ng==2.1.3 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) rfc3339-validator==0.1.4 # via -r requirements.txt, jsonschema -rfc3986[idna2008]==1.5.0 # via -r requirements.txt, httpx, rfc3986, twine +rfc3986[idna2008]==1.5.0 # via -r requirements.txt, httpx, twine rfc3987==1.3.8 # via -r requirements.txt, jsonschema rope==0.21.0 # via nomad-lab (pyproject.toml) rsa==4.9 # via -r requirements.txt, python-jose @@ -335,7 +335,7 @@ unidecode==1.3.2 # via -r requirements.txt, nomad-lab, nomad-lab (pypro uri-template==1.2.0 # via -r requirements.txt, jsonschema url-normalize==1.4.3 # via -r requirements.txt, requests-cache urllib3==1.26.14 # via -r requirements.txt, docker, elasticsearch, pybis, requests, requests-cache -uvicorn[standard]==0.20.0 # via h5grove, nomad-lab, nomad-lab (pyproject.toml), uvicorn +uvicorn[standard]==0.20.0 # via -r requirements.txt, h5grove, nomad-lab, nomad-lab (pyproject.toml) uvloop==0.17.0 # via -r requirements.txt, uvicorn validators==0.18.2 # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml) vine==5.0.0 # via -r requirements.txt, amqp, celery, kombu diff --git a/requirements.txt b/requirements.txt index 9490fca55fa745ff9a51b1d2011689386bddaeaa..6761ecddc37b3039890e3d087c4c86394929537c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ bitarray==2.3.5 # via nomad-lab, nomad-lab (pyproject.toml), nomad_dos bs4==0.0.1 # via nomad-lab, nomad-lab (pyproject.toml) cachetools==4.2.4 # via nomad-lab, nomad-lab (pyproject.toml) cattrs==22.2.0 # via requests-cache -celery[redis]==5.2.7 # via celery, nomad-lab, nomad-lab (pyproject.toml) +celery[redis]==5.2.7 # via nomad-lab, nomad-lab (pyproject.toml) certifi==2022.12.7 # via elasticsearch, httpcore, httpx, requests certipy==0.1.3 # via jupyterhub cffi==1.15.1 # via bcrypt, cryptography @@ -45,7 +45,7 @@ comm==0.1.4 # via ipywidgets commonmark==0.9.1 # via recommonmark cryptography==39.0.0 # via nomad-lab, nomad-lab (pyproject.toml), pyjwt, pyopenssl, rfc3161ng cycler==0.11.0 # via matplotlib -dask[array]==2022.2.0 # via dask, hyperspy, kikuchipy, orix, pyxem +dask[array]==2022.2.0 # via hyperspy, kikuchipy, orix, pyxem debugpy==1.6.5 # via ipykernel decorator==5.1.1 # via ipyparallel, ipython, validators diffpy-structure==3.1.0 # via diffsims, kikuchipy, orix @@ -82,7 +82,7 @@ griddataformats==0.7.0 # via mdanalysis gsd==2.7.0 # via mdanalysis gunicorn==21.2.0 # via nomad-lab, nomad-lab (pyproject.toml) h11==0.14.0 # via httpcore, uvicorn -h5grove[fastapi]==1.3.0 # via h5grove, nomad-lab, nomad-lab (pyproject.toml) +h5grove[fastapi]==1.3.0 # via nomad-lab, nomad-lab (pyproject.toml) h5py==3.6.0 # via electronicparsers (dependencies/parsers/electronic/pyproject.toml), h5grove, hyperspy, ifes-apt-tc-data-modeling, kikuchipy, nionswift, nomad-lab, nomad-lab (pyproject.toml), orix, phonopy, pyfai, silx hjson==3.0.2 # via nomad-lab, nomad-lab (pyproject.toml) html5lib==1.1 # via nomad-lab, nomad-lab (pyproject.toml) @@ -106,9 +106,9 @@ itsdangerous==2.1.2 # via flask, nomad-lab, nomad-lab (pyproject.toml) jedi==0.18.2 # via ipython jinja2==3.0.3 # via flask, hyperspy, jupyterhub, sphinx jmespath==0.10.0 # via nomad-lab, nomad-lab (pyproject.toml) -joblib==1.1.0 # via mdanalysis, nomad-lab, nomad-lab (pyproject.toml), pymatgen, scikit-learn +joblib==1.1.0 # via mdanalysis, nomad-lab, pymatgen, scikit-learn jsonpointer==2.3 # via jsonschema -jsonschema[format]==4.17.3 # via jsonschema, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator +jsonschema[format]==4.17.3 # via jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator jupyter-client==7.4.8 # via ipykernel, ipyparallel jupyter-core==4.12.0 # via jupyter-client jupyter-telemetry==0.1.0 # via jupyterhub @@ -150,7 +150,7 @@ nionswift==0.16.8 # via pynxtools (dependencies/parsers/nexus/pyproject. nionswift-io==0.15.1 # via nionswift nionui==0.6.11 # via nionswift nionutils==0.4.8 # via niondata, nionswift, nionswift-io, nionui -nomad-lab @ git+https://github.com/nomad-coe/nomad.git@develop # via nomad-lab, nomad-schema-plugin-run, nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml) +nomad-lab @ git+https://github.com/nomad-coe/nomad.git@develop # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), nomad-schema-plugin-run, nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml) nomad-schema-plugin-run @ git+https://github.com/nomad-coe/nomad-schema-plugin-run.git@develop # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml) nomad-schema-plugin-simulation-workflow @ git+https://github.com/nomad-coe/nomad-schema-plugin-simulation-workflow.git@develop # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), workflowparsers (dependencies/parsers/workflow/pyproject.toml) nptyping==1.4.4 # via nomad-lab, nomad-lab (pyproject.toml) @@ -162,7 +162,7 @@ numpy-quaternion==2022.4.3 # via orix oauthenticator==15.1.0 # via nomad-lab, nomad-lab (pyproject.toml) oauthlib==3.2.2 # via jupyterhub openpyxl==3.1.2 # via nomad-lab, nomad-lab (pyproject.toml) -optimade[mongo]==0.22.1 # via nomad-lab, nomad-lab (pyproject.toml), optimade +optimade[mongo]==0.22.1 # via nomad-lab, nomad-lab (pyproject.toml) orix==0.11.1 # via diffsims, kikuchipy, pyxem orjson==3.9.4 # via h5grove, nomad-lab, nomad-lab (pyproject.toml) packaging==24.0 # via dask, docker, gunicorn, hyperspy, ipykernel, matplotlib, mdanalysis, mongomock, pint, pooch, scikit-image, sphinx @@ -196,7 +196,7 @@ pycparser==2.21 # via cffi pydantic==1.10.9 # via fastapi, nomad-lab, nomad-lab (pyproject.toml), optimade pyfai==2023.9.0 # via pyxem pygments==2.14.0 # via ipython, sphinx -pyjwt[crypto]==2.6.0 # via nomad-lab, nomad-lab (pyproject.toml), pyjwt +pyjwt[crypto]==2.6.0 # via nomad-lab, nomad-lab (pyproject.toml) pymatgen==2023.9.25 # via asr, nomad-lab, nomad-lab (pyproject.toml) pymongo==4.3.3 # via mongoengine, nomad-lab, nomad-lab (pyproject.toml), optimade pyopenssl==23.0.0 # via certipy @@ -226,7 +226,7 @@ requests==2.28.2 # via docker, eelsdbconverter (dependencies/parsers/ee requests-cache==1.0.1 # via pynxtools (dependencies/parsers/nexus/pyproject.toml) rfc3161ng==2.1.3 # via nomad-lab, nomad-lab (pyproject.toml) rfc3339-validator==0.1.4 # via jsonschema -rfc3986[idna2008]==1.5.0 # via httpx, rfc3986 +rfc3986[idna2008]==1.5.0 # via httpx rfc3987==1.3.8 # via jsonschema rsa==4.9 # via python-jose ruamel-yaml==0.17.21 # via jupyter-telemetry, oauthenticator, pymatgen