Skip to content

Commit

Permalink
Feature/SK-1262 | Add graphql server to FEDn (#777)
Browse files Browse the repository at this point in the history
  • Loading branch information
niklastheman authored Jan 8, 2025
1 parent 1875fec commit e7ea3b6
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 28 deletions.
54 changes: 54 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Base image
ARG BASE_IMG=python:3.10-slim
FROM $BASE_IMG

ARG GRPC_HEALTH_PROBE_VERSION=""

# Requirements (use MNIST Keras as default)
ARG REQUIREMENTS=""

# Add FEDn and default configs
COPY . /app
COPY config/settings-client.yaml.template /app/config/settings-client.yaml
COPY config/settings-combiner.yaml.template /app/config/settings-combiner.yaml
COPY config/settings-hooks.yaml.template /app/config/settings-hooks.yaml
COPY config/settings-reducer.yaml.template /app/config/settings-reducer.yaml
COPY $REQUIREMENTS /app/config/requirements.txt

# Install developer tools (needed for psutil)
RUN apt-get update && apt-get install -y python3-dev gcc

# Install grpc health probe checker
RUN if [ ! -z "$GRPC_HEALTH_PROBE_VERSION" ]; then \
apt-get install -y wget && \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe && \
apt-get remove -y wget && apt autoremove -y; \
else \
echo "No grpc_health_probe version specified, skipping installation"; \
fi

# Setup working directory
WORKDIR /app

# Create FEDn app directory
SHELL ["/bin/bash", "-c"]
RUN mkdir -p /app \
&& mkdir -p /app/client \
&& mkdir -p /app/certs \
&& mkdir -p /app/client/package \
&& mkdir -p /app/certs \
#
# Install FEDn and requirements
&& python -m venv /venv \
&& /venv/bin/pip install --upgrade pip \
&& /venv/bin/pip install --no-cache-dir 'setuptools>=65' \
&& /venv/bin/pip install --no-cache-dir -e . \
&& if [[ ! -z "$REQUIREMENTS" ]]; then \
/venv/bin/pip install --no-cache-dir -r /app/config/requirements.txt; \
fi \
#
# Clean up
&& rm -r /app/config/requirements.txt

ENTRYPOINT [ "/venv/bin/fedn" ]
163 changes: 163 additions & 0 deletions docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Compose schema version
version: '3.4'

# Setup network
networks:
default:
name: fedn_default

services:
# Base services
minio:
image: minio/minio:14128-5ee91dc
hostname: minio
environment:
- GET_HOSTS_FROM=dns
- MINIO_HOST=minio
- MINIO_PORT=9000
- MINIO_ROOT_USER=fedn_admin
- MINIO_ROOT_PASSWORD=password
command: server /data --console-address minio:9001
healthcheck:
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
interval: 30s
timeout: 20s
retries: 3
ports:
- 9000:9000
- 9001:9001

mongo:
image: mongo:7.0
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=fedn_admin
- MONGO_INITDB_ROOT_PASSWORD=password
ports:
- 6534:6534
command: mongod --port 6534

mongo-express:
image: mongo-express:latest
restart: always
depends_on:
- "mongo"
environment:
- ME_CONFIG_MONGODB_SERVER=mongo
- ME_CONFIG_MONGODB_PORT=6534
- ME_CONFIG_MONGODB_ADMINUSERNAME=fedn_admin
- ME_CONFIG_MONGODB_ADMINPASSWORD=password
- ME_CONFIG_BASICAUTH_USERNAME=fedn_admin
- ME_CONFIG_BASICAUTH_PASSWORD=password
ports:
- 8081:8081

api-server:
environment:
- GET_HOSTS_FROM=dns
- USER=test
- PROJECT=project
- FLASK_DEBUG=1
- STATESTORE_CONFIG=/app/config/settings-reducer.yaml.template
- MODELSTORAGE_CONFIG=/app/config/settings-reducer.yaml.template
- FEDN_COMPUTE_PACKAGE_DIR=/app
- TMPDIR=/app/tmp
build:
context: .
dockerfile: Dockerfile.dev
args:
BASE_IMG: ${BASE_IMG:-python:3.12-slim}
working_dir: /app
volumes:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
depends_on:
- minio
- mongo
command:
- controller
- start
ports:
- 8092:8092

# Combiner
combiner:
environment:
- PYTHONUNBUFFERED=0
- GET_HOSTS_FROM=dns
- STATESTORE_CONFIG=/app/config/settings-combiner.yaml.template
- MODELSTORAGE_CONFIG=/app/config/settings-combiner.yaml.template
- HOOK_SERVICE_HOST=hook:12081
- TMPDIR=/app/tmp
build:
context: .
dockerfile: Dockerfile.dev
args:
BASE_IMG: ${BASE_IMG:-python:3.12-slim}
GRPC_HEALTH_PROBE_VERSION: v0.4.35
working_dir: /app
volumes:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
command:
- combiner
- start
- --init
- config/settings-combiner.yaml.template
ports:
- 12080:12080
healthcheck:
test: [ "CMD", "/app/grpc_health_probe", "-addr=localhost:12080" ]
interval: 20s
timeout: 10s
retries: 5
depends_on:
- api-server
- hooks
# Hooks
hooks:
container_name: hook
environment:
- GET_HOSTS_FROM=dns
- TMPDIR=/app/tmp
build:
context: .
dockerfile: Dockerfile.dev
args:
BASE_IMG: ${BASE_IMG:-python:3.12-slim}
GRPC_HEALTH_PROBE_VERSION: v0.4.35
working_dir: /app
volumes:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
entrypoint: [ "sh", "-c" ]
command:
- "/venv/bin/pip install --no-cache-dir -e . && /venv/bin/fedn hooks start"
ports:
- 12081:12081
healthcheck:
test: [ "CMD", "/bin/grpc_health_probe", "-addr=localhost:12081" ]
interval: 20s
timeout: 10s
retries: 5

# Client
client:
environment:
- GET_HOSTS_FROM=dns
- FEDN_PACKAGE_EXTRACT_DIR=package
build:
context: .
dockerfile: Dockerfile.dev
args:
BASE_IMG: ${BASE_IMG:-python:3.10-slim}
working_dir: /app
volumes:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
command:
- client
- start
- --api-url
- http://api-server:8092
deploy:
replicas: 0
depends_on:
combiner:
condition: service_healthy
33 changes: 31 additions & 2 deletions fedn/network/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from flask import Flask, jsonify, request

from fedn.common.config import get_controller_config
from fedn.network.api import gunicorn_app
from fedn.network.api.auth import jwt_auth_required
from fedn.network.api.interface import API
from fedn.network.api.shared import control, statestore
from fedn.network.api.v1 import _routes
from fedn.network.api import gunicorn_app
from fedn.network.api.v1.graphql.schema import schema

custom_url_prefix = os.environ.get("FEDN_CUSTOM_URL_PREFIX", False)
# statestore_config,modelstorage_config,network_id,control=set_statestore_config()
Expand All @@ -28,6 +29,32 @@ def health_check():
app.add_url_rule(f"{custom_url_prefix}/health", view_func=health_check, methods=["GET"])


@app.route("/api/v1/graphql", methods=["POST"])
def graphql_endpoint():
data = request.get_json()

if not data or "query" not in data:
return jsonify({"error": "Missing query in request"}), 400

# Execute the GraphQL query
result = schema.execute(
data["query"],
variables=data.get("variables"),
context_value={"request": request}, # Pass Flask request object as context if needed
)

# Format the result as a JSON response
response = {"data": result.data}
if result.errors:
response["errors"] = [str(error) for error in result.errors]

return jsonify(response)


if custom_url_prefix:
app.add_url_rule(f"{custom_url_prefix}/api/v1/graphql", view_func=graphql_endpoint, methods=["POST"])


@app.route("/get_model_trail", methods=["GET"])
@jwt_auth_required(role="admin")
def get_model_trail():
Expand Down Expand Up @@ -638,7 +665,9 @@ def start_server_api():
if debug:
app.run(debug=debug, port=port, host=host)
else:
workers=os.cpu_count()
workers = os.cpu_count()
gunicorn_app.run_gunicorn(app, host, port, workers)


if __name__ == "__main__":
start_server_api()
5 changes: 1 addition & 4 deletions fedn/network/api/v1/combiner_routes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from flask import Blueprint, jsonify, request

from fedn.network.api.auth import jwt_auth_required
from fedn.network.api.v1.shared import api_version, client_store, get_post_data_to_kwargs, get_typed_list_headers, mdb
from fedn.network.storage.statestore.stores.combiner_store import CombinerStore
from fedn.network.api.v1.shared import api_version, client_store, combiner_store, get_post_data_to_kwargs, get_typed_list_headers
from fedn.network.storage.statestore.stores.shared import EntityNotFound

bp = Blueprint("combiner", __name__, url_prefix=f"/api/{api_version}/combiners")

combiner_store = CombinerStore(mdb, "network.combiners")


@bp.route("/", methods=["GET"])
@jwt_auth_required(role="admin")
Expand Down
Empty file.
Loading

0 comments on commit e7ea3b6

Please sign in to comment.