From dadad27f60921fc68d34e5e8361bc0d7c7f0ae2e Mon Sep 17 00:00:00 2001 From: Sylvain Lesage Date: Tue, 17 May 2022 11:09:28 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20the=20admin=20serv?= =?UTF-8?q?ice=20(to=20run=20admin=20scripts)=20(#275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add the admin service (to run admin scripts) * feat: 🎸 add admin service to kube * docs: ✏️ fix doc * docs: ✏️ run the scripts directly using docker or kubectl exec --- .github/workflows/docker.yml | 2 +- .github/workflows/quality.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- .vscode/monorepo.code-workspace | 6 +- Makefile | 6 + deprecated/Makefile | 32 - deprecated/README.md | 39 - deprecated/cancel_started_jobs.py | 16 - docker-compose.yml | 10 + infra/charts/datasets-server/env/dev.yaml | 9 + infra/charts/datasets-server/env/prod.yaml | 15 + .../datasets-server/templates/_helpers.tpl | 5 + .../templates/admin/_container.tpl | 41 + .../templates/admin/deployment.yaml | 38 + infra/charts/datasets-server/values.yaml | 24 +- libs/libqueue/src/libqueue/queue.py | 24 +- services/admin/.env.example | 14 + services/admin/.flake8 | 5 + services/admin/.python-version | 1 + services/admin/Dockerfile | 30 + services/admin/INSTALL.md | 47 + services/admin/Makefile | 27 + services/admin/README.md | 43 + services/admin/poetry.lock | 1990 +++++++++++++++++ services/admin/poetry.toml | 2 + services/admin/pyproject.toml | 44 + services/admin/src/admin/__init__.py | 0 services/admin/src/admin/config.py | 21 + services/admin/src/admin/constants.py | 5 + services/admin/src/admin/py.typed | 0 services/admin/src/admin/scripts/__init__.py | 0 .../scripts/cancel_started_dataset_jobs.py | 13 + .../scripts/cancel_started_split_jobs.py | 13 + .../admin/src/admin/scripts/warm_cache.py | 37 +- services/admin/tests/__init__.py | 0 services/admin/tests/docker-compose.yml | 10 + .../admin/tests/scripts/test_warm_cache.py | 8 + 37 files changed, 2456 insertions(+), 125 deletions(-) delete mode 100644 deprecated/Makefile delete mode 100644 deprecated/README.md delete mode 100644 deprecated/cancel_started_jobs.py create mode 100644 infra/charts/datasets-server/templates/admin/_container.tpl create mode 100644 infra/charts/datasets-server/templates/admin/deployment.yaml create mode 100644 services/admin/.env.example create mode 100644 services/admin/.flake8 create mode 100644 services/admin/.python-version create mode 100644 services/admin/Dockerfile create mode 100644 services/admin/INSTALL.md create mode 100644 services/admin/Makefile create mode 100644 services/admin/README.md create mode 100644 services/admin/poetry.lock create mode 100644 services/admin/poetry.toml create mode 100644 services/admin/pyproject.toml create mode 100644 services/admin/src/admin/__init__.py create mode 100644 services/admin/src/admin/config.py create mode 100644 services/admin/src/admin/constants.py create mode 100644 services/admin/src/admin/py.typed create mode 100644 services/admin/src/admin/scripts/__init__.py create mode 100644 services/admin/src/admin/scripts/cancel_started_dataset_jobs.py create mode 100644 services/admin/src/admin/scripts/cancel_started_split_jobs.py rename deprecated/warm.py => services/admin/src/admin/scripts/warm_cache.py (56%) create mode 100644 services/admin/tests/__init__.py create mode 100644 services/admin/tests/docker-compose.yml create mode 100644 services/admin/tests/scripts/test_warm_cache.py diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index de5f1ede12..ead86f58ea 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - service: [api, worker] + service: [admin, api, worker] runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a6f4410ac2..e1a929167a 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -10,7 +10,7 @@ jobs: poetry-version: [1.1.7] os: [ubuntu-latest] working-directory: - [e2e, services/api, libs/libcache, libs/libqueue, libs/libutils] + [e2e, services/admin, services/api, libs/libcache, libs/libqueue, libs/libutils] defaults: run: shell: bash diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 60cef47ae3..884f0321d9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -11,7 +11,7 @@ jobs: poetry-version: [1.1.7] os: [ubuntu-latest] working-directory: - [services/api, libs/libcache, libs/libqueue, libs/libutils] + [services/admin, services/api, libs/libcache, libs/libqueue, libs/libutils] defaults: run: shell: bash diff --git a/.vscode/monorepo.code-workspace b/.vscode/monorepo.code-workspace index f1d9cb3265..8bf3ee71d1 100644 --- a/.vscode/monorepo.code-workspace +++ b/.vscode/monorepo.code-workspace @@ -16,6 +16,10 @@ "name": "libs/libutils", "path": "../libs/libutils" }, + { + "name": "services/admin", + "path": "../services/admin" + }, { "name": "services/api", "path": "../services/api" @@ -37,7 +41,7 @@ "python.formatting.provider": "black", "python.linting.enabled": true, "python.linting.mypyEnabled": true, - "python.linting.flake8Enabled": true, + "python.linting.flake8Enabled": true }, "extensions": { "recommendations": [ diff --git a/Makefile b/Makefile index b29f978eac..4ccbb0a360 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ install: $(MAKE) -C services/worker/ install $(MAKE) -C services/api/ install + $(MAKE) -C services/admin/ install .PHONY: lock lock: @@ -11,6 +12,7 @@ lock: $(MAKE) -C libs/libcache/ lock $(MAKE) -C services/worker/ lock $(MAKE) -C services/api/ lock + $(MAKE) -C services/admin/ lock .PHONY: api api: @@ -22,6 +24,7 @@ worker: .PHONY: test test: + $(MAKE) -C services/admin/ test $(MAKE) -C services/worker/ test $(MAKE) -C services/api/ test $(MAKE) -C libs/libcache/ test @@ -30,6 +33,7 @@ test: .PHONY: coverage coverage: + $(MAKE) -C services/admin/ coverage $(MAKE) -C services/worker/ coverage $(MAKE) -C services/api/ coverage $(MAKE) -C libs/libcache/ coverage @@ -43,6 +47,7 @@ quality: $(MAKE) -C infra/charts/datasets-server/ quality $(MAKE) -C services/worker/ quality $(MAKE) -C services/api/ quality + $(MAKE) -C services/admin/ quality $(MAKE) -C libs/libcache/ quality $(MAKE) -C libs/libqueue/ quality $(MAKE) -C libs/libutils/ quality @@ -53,6 +58,7 @@ style: $(MAKE) -C e2e/ style $(MAKE) -C services/worker/ style $(MAKE) -C services/api/ style + $(MAKE) -C services/admin/ style $(MAKE) -C libs/libcache/ style $(MAKE) -C libs/libqueue/ style $(MAKE) -C libs/libutils/ style diff --git a/deprecated/Makefile b/deprecated/Makefile deleted file mode 100644 index 398f40568b..0000000000 --- a/deprecated/Makefile +++ /dev/null @@ -1,32 +0,0 @@ - -.PHONY: warm -warm: - poetry run python src/datasets_server/warm.py - -.PHONY: worker -worker: - poetry run python src/datasets_server/worker.py - -.PHONY: force-refresh-cache -force-refresh-cache: - poetry run python src/datasets_server/force_refresh_cache.py - -.PHONY: cancel-started-jobs -cancel-started-jobs: - poetry run python src/datasets_server/cancel_started_jobs.py - -.PHONY: cancel-waiting-jobs -cancel-waiting-jobs: - poetry run python src/datasets_server/cancel_waiting_jobs.py - -.PHONY: clean-queues -clean-queues: - poetry run python src/datasets_server/clean_queues.py - -.PHONY: clean-cache -clean-cache: - poetry run python src/datasets_server/clean_cache.py -# TODO: remove the assets too - -.PHONY: clean -clean: clean-queues clean-cache diff --git a/deprecated/README.md b/deprecated/README.md deleted file mode 100644 index e8f8c7468a..0000000000 --- a/deprecated/README.md +++ /dev/null @@ -1,39 +0,0 @@ -TODO: add the scripts - -To warm the cache, ie. add all the missing Hugging Face datasets to the queue: - -```bash -make warm -``` - -Warm the cache with: - -```bash -pm2 start --no-autorestart --name warm make -- -C /home/hf/datasets-server/ warm -``` - -To empty the databases: - -```bash -make clean -``` - -or individually: - -```bash -make clean-cache -make clean-queues # delete all the jobs -``` - -See also: - -```bash -make cancel-started-jobs -make cancel-waiting-jobs -``` - ---- - -how to monitor the workers and the queue? - -grafana doesn't have any data (see links in INSTALL.md) diff --git a/deprecated/cancel_started_jobs.py b/deprecated/cancel_started_jobs.py deleted file mode 100644 index 435657a8bc..0000000000 --- a/deprecated/cancel_started_jobs.py +++ /dev/null @@ -1,16 +0,0 @@ -import logging - -from libutils.logger import init_logger -from libqueue.queue import ( - cancel_started_dataset_jobs, - cancel_started_split_jobs, - connect_to_queue, -) - -if __name__ == "__main__": - init_logger("INFO", "cancel_started_jobs") - logger = logging.getLogger("cancel_started_jobs") - connect_to_queue() - cancel_started_dataset_jobs() - cancel_started_split_jobs() - logger.info("all the started jobs in the queues have been cancelled") diff --git a/docker-compose.yml b/docker-compose.yml index 259922df95..fee6cf546e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,6 +66,16 @@ services: mongodb: condition: service_started restart: always + admin: + build: + context: . + dockerfile: ./services/admin/Dockerfile + environment: + MONGO_URL: "mongodb://mongodb" + depends_on: + mongodb: + condition: service_started + restart: always mongodb: image: mongo volumes: diff --git a/infra/charts/datasets-server/env/dev.yaml b/infra/charts/datasets-server/env/dev.yaml index eb4ed3f54d..4b5789601e 100644 --- a/infra/charts/datasets-server/env/dev.yaml +++ b/infra/charts/datasets-server/env/dev.yaml @@ -61,3 +61,12 @@ splitsWorker: cpu: 0.01 limits: cpu: 1 + +admin: + replicas: 1 + + resources: + requests: + cpu: 0.01 + limits: + cpu: 1 diff --git a/infra/charts/datasets-server/env/prod.yaml b/infra/charts/datasets-server/env/prod.yaml index 072c7687bd..6d7848cfc1 100644 --- a/infra/charts/datasets-server/env/prod.yaml +++ b/infra/charts/datasets-server/env/prod.yaml @@ -113,3 +113,18 @@ splitsWorker: # Log level logLevel: "DEBUG" + +admin: + replicas: 1 + + nodeSelector: + role-datasets-server: 'true' + + resources: + requests: + cpu: 0.01 + limits: + cpu: 1 + + # Log level + logLevel: "DEBUG" diff --git a/infra/charts/datasets-server/templates/_helpers.tpl b/infra/charts/datasets-server/templates/_helpers.tpl index 114c421213..9bd0a11d59 100644 --- a/infra/charts/datasets-server/templates/_helpers.tpl +++ b/infra/charts/datasets-server/templates/_helpers.tpl @@ -62,6 +62,11 @@ app: "{{ include "release" . }}-datasets-worker" app: "{{ include "release" . }}-splits-worker" {{- end -}} +{{- define "labels.admin" -}} +{{ include "labels" . }} +app: "{{ include "release" . }}-admin" +{{- end -}} + {{/* The assets/ subpath in the NFS - in a subdirectory named as the chart (datasets-server/), and below it, diff --git a/infra/charts/datasets-server/templates/admin/_container.tpl b/infra/charts/datasets-server/templates/admin/_container.tpl new file mode 100644 index 0000000000..59c51c2aa3 --- /dev/null +++ b/infra/charts/datasets-server/templates/admin/_container.tpl @@ -0,0 +1,41 @@ +{{- define "containerAdmin" -}} +- name: "{{ include "name" . }}-admin" + env: + - name: ASSETS_DIRECTORY + value: {{ .Values.splitsWorker.assetsDirectory | quote }} + - name: LOG_LEVEL + value: {{ .Values.splitsWorker.logLevel | quote }} + - name: MONGO_CACHE_DATABASE + value: {{ .Values.mongodb.cacheDatabase | quote }} + - name: MONGO_QUEUE_DATABASE + value: {{ .Values.mongodb.queueDatabase | quote }} + - name: MONGO_URL + {{- if .Values.mongodb.enabled }} + value: mongodb://{{.Release.Name}}-mongodb + {{- else }} + valueFrom: + secretKeyRef: + name: {{ .Values.secrets.mongoUrl | quote }} + key: MONGO_URL + optional: false + {{- end }} + image: "{{ .Values.admin.image.repository }}/{{ .Values.admin.image.name }}:{{ .Values.docker.tag }}" + imagePullPolicy: {{ .Values.admin.image.pullPolicy }} + volumeMounts: + - mountPath: {{ .Values.admin.assetsDirectory | quote }} + mountPropagation: None + name: nfs + subPath: "{{ include "assets.subpath" . }}" + readOnly: false + securityContext: + allowPrivilegeEscalation: false + # TODO: provide readiness and liveness probes + # readinessProbe: + # tcpSocket: + # port: {{ .Values.admin.readinessPort }} + # livenessProbe: + # tcpSocket: + # port: {{ .Values.admin.readinessPort }} + resources: + {{ toYaml .Values.admin.resources | nindent 4 }} +{{- end -}} diff --git a/infra/charts/datasets-server/templates/admin/deployment.yaml b/infra/charts/datasets-server/templates/admin/deployment.yaml new file mode 100644 index 0000000000..2ec7213456 --- /dev/null +++ b/infra/charts/datasets-server/templates/admin/deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{ include "labels.admin" . | nindent 4 }} + name: "{{ include "release" . }}-admin" + namespace: {{ .Release.Namespace }} +spec: + progressDeadlineSeconds: 600 + replicas: {{ .Values.admin.replicas }} + revisionHistoryLimit: 10 + selector: + matchLabels: + {{ include "labels.admin" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + {{ include "labels.admin" . | nindent 8 }} + spec: + initContainers: + {{ include "initContainerAssets" . | nindent 8 }} + containers: + {{ include "containerAdmin" . | nindent 8 }} + nodeSelector: + {{ toYaml .Values.admin.nodeSelector | nindent 8 }} + tolerations: + {{ toYaml .Values.admin.tolerations | nindent 8 }} + volumes: + - name: nfs + nfs: + server: {{ .Values.storage.nfs.server }} + path: {{ .Values.storage.nfs.path }} + securityContext: + runAsUser: {{ .Values.uid }} + runAsGroup: {{ .Values.gid }} + runAsNonRoot: true diff --git a/infra/charts/datasets-server/values.yaml b/infra/charts/datasets-server/values.yaml index a7813e19ae..33adf1aca2 100644 --- a/infra/charts/datasets-server/values.yaml +++ b/infra/charts/datasets-server/values.yaml @@ -28,7 +28,7 @@ gid: 3000 datasetsBlocklist: "" docker: - tag: sha-8a7c036 + tag: sha-b1ed584 reverseProxy: image: @@ -192,3 +192,25 @@ splitsWorker: rowsMinNumber: 10 # Number of seconds a worker will sleep before trying to process a new job workerSleepSeconds: 15 + + +admin: + image: + repository: 707930574880.dkr.ecr.us-east-1.amazonaws.com + name: hub-datasets-server-admin + pullPolicy: IfNotPresent + + replicas: 1 + + resources: + requests: + cpu: 1 + limits: + cpu: 1 + nodeSelector: {} + tolerations: [] + + # Directory of assets (audio files and images that will be served for the web) + assetsDirectory: "/assets" + # Log level + logLevel: "INFO" diff --git a/libs/libqueue/src/libqueue/queue.py b/libs/libqueue/src/libqueue/queue.py index 60defd29db..e5209b1abb 100644 --- a/libs/libqueue/src/libqueue/queue.py +++ b/libs/libqueue/src/libqueue/queue.py @@ -262,14 +262,6 @@ def finish_started_job(jobs: QuerySet[AnyJob], job_id: str, success: bool) -> No job.update(finished_at=datetime.utcnow(), status=status) -def cancel_started_jobs(jobs: QuerySet[AnyJob]) -> None: - get_started(jobs).update(finished_at=datetime.utcnow(), status=Status.CANCELLED) - - -def cancel_waiting_jobs(jobs: QuerySet[AnyJob]) -> None: - get_waiting(jobs).update(finished_at=datetime.utcnow(), status=Status.CANCELLED) - - def finish_dataset_job(job_id: str, success: bool) -> None: finish_started_job(DatasetJob.objects, job_id, success) @@ -284,19 +276,15 @@ def clean_database() -> None: def cancel_started_dataset_jobs() -> None: - cancel_started_jobs(DatasetJob.objects) + for job in get_started(DatasetJob.objects): + job.update(finished_at=datetime.utcnow(), status=Status.CANCELLED) + add_dataset_job(dataset_name=job.dataset_name) def cancel_started_split_jobs() -> None: - cancel_started_jobs(SplitJob.objects) - - -def cancel_waiting_dataset_jobs() -> None: - cancel_waiting_jobs(DatasetJob.objects) - - -def cancel_waiting_split_jobs() -> None: - cancel_waiting_jobs(SplitJob.objects) + for job in get_started(SplitJob.objects): + job.update(finished_at=datetime.utcnow(), status=Status.CANCELLED) + add_split_job(dataset_name=job.dataset_name, config_name=job.config_name, split_name=job.split_name) # special reports diff --git a/services/admin/.env.example b/services/admin/.env.example new file mode 100644 index 0000000000..4f462cca42 --- /dev/null +++ b/services/admin/.env.example @@ -0,0 +1,14 @@ +# Assets directory +# ASSETS_DIRECTORY= + +# Log level +# LOG_LEVEL = "INFO" + +# Name of the mongo db database used to cache the datasets +# MONGO_CACHE_DATABASE="datasets_server_cache" + +# Name of the mongo db database used to store the jobs queue +# MONGO_QUEUE_DATABASE="datasets_server_queue" + +# URL to connect to mongo db +# MONGO_URL="mongodb://localhost:27017" diff --git a/services/admin/.flake8 b/services/admin/.flake8 new file mode 100644 index 0000000000..f7d6157c5c --- /dev/null +++ b/services/admin/.flake8 @@ -0,0 +1,5 @@ +[flake8] +# Recommend matching the black line length (119), +# rather than using the flake8 default of 79: +max-line-length = 119 +extend-ignore = "E203" diff --git a/services/admin/.python-version b/services/admin/.python-version new file mode 100644 index 0000000000..1635d0f5a1 --- /dev/null +++ b/services/admin/.python-version @@ -0,0 +1 @@ +3.9.6 diff --git a/services/admin/Dockerfile b/services/admin/Dockerfile new file mode 100644 index 0000000000..7b0bcbde11 --- /dev/null +++ b/services/admin/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.9.6-slim + +ENV PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + POETRY_NO_INTERACTION=1 \ + # Versions: + POETRY_VERSION=1.1.12 \ + POETRY_VIRTUALENVS_IN_PROJECT=true + +# System deps: +RUN apt-get update \ + && apt-get install -y build-essential python3-dev make \ + && rm -rf /var/lib/apt/lists/* +RUN pip install -U --no-cache-dir pip +RUN pip install "poetry==$POETRY_VERSION" + +WORKDIR /src +COPY libs ./libs/ +COPY services ./services/ +COPY tools ./tools/ +WORKDIR /src/services/admin/ +RUN poetry install + +# https://stackoverflow.com/a/55734437/7351594 +# do nothing: the user has to login to the machine to run the scripts with make +CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait" diff --git a/services/admin/INSTALL.md b/services/admin/INSTALL.md new file mode 100644 index 0000000000..c658572a62 --- /dev/null +++ b/services/admin/INSTALL.md @@ -0,0 +1,47 @@ +# Install guide + +Follow the [general INSTALL](../INSTALL.md) to be sure to setup the assets directory and the databases. + +## Requirements + +The requirements are: + +- Python 3.9.6+ (consider [pyenv](https://github.com/pyenv/pyenv)) +- Poetry 1.1.7+ +- make + +We assume a machine running Ubuntu. Install packages: + +```bash +sudo apt install python-is-python3 make +``` + +Also install node and npm (with [nvm](https://github.com/nvm-sh/nvm)), then: + +```bash +npm i -g pm2@latest +``` + +Also [install poetry](https://python-poetry.org/docs/master/#installation). Don't forget to add `poetry` to the `PATH` environment variable. + +## Install and configure + +Install the API service: + +```bash +cd +# See https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git for authentication +git clone https://github.com/huggingface/datasets-server.git +cd datasets-server/services/admin +make install +``` + +Copy and edit the environment variables file: + +```bash +cd datasets-server/services/admin +cp .env.example .env +vi .env +``` + +Note that we assume `ASSETS_DIRECTORY=/data` in the nginx configuration. If you set the assets directory to another place, or let the default, ensure the nginx configuration is setup accordingly. Beware: the default directory inside `/home/hf/.cache` is surely not readable by the nginx user. diff --git a/services/admin/Makefile b/services/admin/Makefile new file mode 100644 index 0000000000..d6b4f949b1 --- /dev/null +++ b/services/admin/Makefile @@ -0,0 +1,27 @@ +include ../../tools/Common.mk + +.PHONY: cancel-started-split-jobs +cancel-started-split-jobs: + poetry run python src/admin/scripts/cancel_started_split_jobs.py + +.PHONY: cancel-started-dataset-jobs +cancel-started-dataset-jobs: + poetry run python src/admin/scripts/cancel_started_dataset_jobs.py + +.PHONY: warm-cache +warm-cache: + poetry run python src/admin/scripts/warm_cache.py + +# Ensure to specify HF_TOKEN when calling make test, ie HF_TOKEN=hf_app_xxx make test +.PHONY: test +test: + docker-compose -f tests/docker-compose.yml up -d + MONGO_CACHE_DATABASE="datasets_server_cache_test" MONGO_QUEUE_DATABASE="datasets_server_queue_test" MONGO_URL="mongodb://localhost:27019" poetry run python -m pytest -x tests + docker-compose -f tests/docker-compose.yml down + +# Ensure to specify HF_TOKEN when calling make coverage, ie HF_TOKEN=hf_app_xxx make coverage +.PHONY: coverage +coverage: + docker-compose -f tests/docker-compose.yml up -d + MONGO_CACHE_DATABASE="datasets_server_cache_test" MONGO_QUEUE_DATABASE="datasets_server_queue_test" MONGO_URL="mongodb://localhost:27019" poetry run python -m pytest -s --cov --cov-report xml:coverage.xml --cov-report=term tests + docker-compose -f tests/docker-compose.yml down diff --git a/services/admin/README.md b/services/admin/README.md new file mode 100644 index 0000000000..04c24346ac --- /dev/null +++ b/services/admin/README.md @@ -0,0 +1,43 @@ +# Datasets server admin machine + +> Admin scripts + +## Install + +See [INSTALL](./INSTALL.md#Install) + +## Run + +Launch the scripts with: + +```shell +make