From dee93134ae00b18f04eec1f398cd088d5d77116a Mon Sep 17 00:00:00 2001 From: prixod Date: Fri, 24 Oct 2025 23:55:10 +0400 Subject: [PATCH] add configs --- .gitignore | 53 ++++- README.md | 237 +++++++++++++++++++++++ compose.yaml | 43 +++- k8s/README.md | 92 +++++++++ k8s/configmap.yaml | 37 ++++ k8s/gateway-deployment.yaml | 63 ++++++ k8s/namespace.yaml | 4 + k8s/worker-cpp-deployment.yaml | 82 ++++++++ src/LiquidCode.Tester.Common/Dockerfile | 21 -- src/LiquidCode.Tester.Gateway/Dockerfile | 25 ++- src/LiquidCode.Tester.Worker/Dockerfile | 36 +++- 11 files changed, 648 insertions(+), 45 deletions(-) create mode 100644 README.md create mode 100644 k8s/README.md create mode 100644 k8s/configmap.yaml create mode 100644 k8s/gateway-deployment.yaml create mode 100644 k8s/namespace.yaml create mode 100644 k8s/worker-cpp-deployment.yaml delete mode 100644 src/LiquidCode.Tester.Common/Dockerfile diff --git a/.gitignore b/.gitignore index add57be..72ff8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,50 @@ -bin/ -obj/ -/packages/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio cache/options directory +.vs/ +.vscode/ + +# Rider +.idea/ +*.sln.iml riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file +/_ReSharper.Caches/ + +# NuGet Packages +*.nupkg +**/packages/* +!**/packages/build/ + +# Docker +docker-compose.override.yml + +# Temporary files +*.tmp +*.log +*.swp +*~ + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e7d686 --- /dev/null +++ b/README.md @@ -0,0 +1,237 @@ +# LiquidCode Tester + +Система автоматической проверки решений задач по программированию (Online Judge) для платформы LiquidCode. + +## Архитектура + +Проект состоит из трёх основных компонентов: + +### 1. Gateway (Шлюз) +- **Технология**: ASP.NET Core Web API +- **Функции**: + - Приём запросов на тестирование через REST API + - Скачивание пакетов задач из Polygon + - Маршрутизация запросов к соответствующим Worker'ам по языку программирования + +### 2. Worker (Тестировщик) +- **Технология**: ASP.NET Core Web API + компиляторы языков +- **Функции**: + - Компиляция пользовательского кода + - Запуск в изолированной среде + - Тестирование на наборе тестов + - Контроль ограничений по времени и памяти + - Отправка статусов на callback URL + +### 3. Common (Общая библиотека) +- **Технология**: .NET Class Library +- **Функции**: Общие модели данных для Gateway и Worker + +## Поддерживаемые языки + +- **C++** (текущая реализация) +- Java (планируется) +- Kotlin (планируется) +- C# (планируется) + +## Модель данных + +### SubmitForTesterModel (запрос на тестирование) +```csharp +public record SubmitForTesterModel( + long Id, + long MissionId, + string Language, + string LanguageVersion, + string SourceCode, + string PackageUrl, + string CallbackUrl +); +``` + +### TesterResponseModel (результат тестирования) +```csharp +public record TesterResponseModel( + long SubmitId, + State State, // Waiting, Compiling, Testing, Done + ErrorCode ErrorCode, // None, CompileError, RuntimeError, etc. + string Message, + int CurrentTest, + int AmountOfTests +); +``` + +## Быстрый старт + +### Локальная разработка (Docker Compose) + +```bash +# Сборка и запуск всех сервисов +docker-compose up --build + +# Gateway доступен на http://localhost:8080 +# Worker доступен на http://localhost:8081 +``` + +### Разработка без Docker + +```bash +# Сборка всего решения +dotnet build + +# Запуск Gateway +cd src/LiquidCode.Tester.Gateway +dotnet run + +# Запуск Worker (в другом терминале) +cd src/LiquidCode.Tester.Worker +dotnet run +``` + +## API Endpoints + +### Gateway + +**POST /api/tester/submit** +```json +{ + "id": 123, + "missionId": 456, + "language": "C++", + "languageVersion": "17", + "sourceCode": "#include \nint main() { ... }", + "packageUrl": "https://example.com/package.zip", + "callbackUrl": "https://example.com/api/submit/callback" +} +``` + +**GET /api/tester/health** +- Проверка состояния Gateway + +### Worker + +**POST /api/test** +- Принимает multipart/form-data с метаданными и ZIP файлом пакета + +**GET /api/test/health** +- Проверка состояния Worker + +## Процесс тестирования + +1. **Приём запроса**: Gateway получает запрос с кодом и URL пакета +2. **Скачивание пакета**: Gateway скачивает Polygon пакет +3. **Маршрутизация**: Gateway отправляет запрос в Worker для нужного языка +4. **Парсинг**: Worker распаковывает и парсит пакет (тесты, лимиты) +5. **Компиляция**: Worker компилирует код (g++ для C++) +6. **Тестирование**: Worker последовательно запускает все тесты +7. **Callback**: Worker отправляет статусы на callback URL на каждом этапе +8. **Cleanup**: Worker удаляет временные файлы + +## Изоляция и безопасность + +### Текущая реализация +- Процессы запускаются с ограничением по времени +- Контроль памяти через PeakWorkingSet64 +- Процессы убиваются при превышении лимитов + +### Production рекомендации +- Linux namespaces (PID, network, mount) +- cgroups для контроля CPU и памяти +- seccomp для ограничения системных вызовов +- AppArmor/SELinux профили +- chroot окружение +- Отключение доступа к сети для тестируемого кода + +## Деплой в Kubernetes + +См. [k8s/README.md](k8s/README.md) для подробной инструкции. + +```bash +# Быстрый деплой +kubectl apply -f k8s/namespace.yaml +kubectl apply -f k8s/configmap.yaml +kubectl apply -f k8s/worker-cpp-deployment.yaml +kubectl apply -f k8s/gateway-deployment.yaml +``` + +## Конфигурация + +### Gateway (appsettings.json) +```json +{ + "PackageDownloadDirectory": "/tmp/packages", + "Workers": { + "Cpp": "http://liquidcode-tester-worker-cpp:8080" + } +} +``` + +### Worker (appsettings.json) +```json +{ + "Cpp": { + "Compiler": "g++", + "CompilerFlags": "-O2 -std=c++17 -Wall" + } +} +``` + +## Структура проекта + +``` +LiquidCode.Tester/ +├── src/ +│ ├── LiquidCode.Tester.Common/ # Общие модели +│ │ └── Models/ +│ ├── LiquidCode.Tester.Gateway/ # API Gateway +│ │ ├── Controllers/ +│ │ ├── Services/ +│ │ └── Dockerfile +│ └── LiquidCode.Tester.Worker/ # C++ Worker +│ ├── Controllers/ +│ ├── Services/ +│ └── Dockerfile +├── k8s/ # Kubernetes манифесты +├── compose.yaml # Docker Compose +└── README.md +``` + +## Требования + +- .NET 9.0 SDK +- Docker & Docker Compose (для контейнеризации) +- g++ (для C++ Worker) +- Kubernetes cluster (для production) + +## Расширение на другие языки + +Для добавления поддержки нового языка: + +1. Создайте новый Worker проект (или расширьте существующий) +2. Реализуйте `ICompilationService` для языка +3. Реализуйте `IExecutionService` для языка +4. Обновите конфигурацию Gateway +5. Создайте Dockerfile с нужным компилятором/runtime +6. Добавьте Kubernetes манифесты + +## Разработка + +### Структура кода +- **Controllers**: REST API endpoints +- **Services**: Бизнес-логика (компиляция, тестирование, callback) +- **Models**: Модели данных + +### Логирование +Используется встроенный ILogger ASP.NET Core. + +### Тестирование +```bash +dotnet test +``` + +## Лицензия + +[Укажите лицензию] + +## Авторы + +LiquidCode Team diff --git a/compose.yaml b/compose.yaml index 2fe0def..5eedec5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,19 +1,42 @@ services: - liquidcode.tester.gateway: - image: liquidcode.tester.gateway + gateway: + image: liquidcode-tester-gateway:latest + container_name: liquidcode-tester-gateway build: context: . - dockerfile: LiquidCode.Tester.Gateway/Dockerfile + dockerfile: src/LiquidCode.Tester.Gateway/Dockerfile + ports: + - "8080:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Development + - Workers__Cpp=http://worker-cpp:8080 + networks: + - liquidcode-network + depends_on: + - worker-cpp - liquidcode.tester.worker: - image: liquidcode.tester.worker + worker-cpp: + image: liquidcode-tester-worker-cpp:latest + container_name: liquidcode-tester-worker-cpp build: context: . dockerfile: src/LiquidCode.Tester.Worker/Dockerfile + ports: + - "8081:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Development + networks: + - liquidcode-network + # For better isolation in production, consider: + # security_opt: + # - no-new-privileges:true + # cap_drop: + # - ALL + # cap_add: + # - SETUID + # - SETGID - liquidcode.tester.common: - image: liquidcode.tester.common - build: - context: . - dockerfile: src/LiquidCode.Tester.Common/Dockerfile +networks: + liquidcode-network: + driver: bridge diff --git a/k8s/README.md b/k8s/README.md new file mode 100644 index 0000000..dd0bffc --- /dev/null +++ b/k8s/README.md @@ -0,0 +1,92 @@ +# Kubernetes Deployment + +## Prerequisites + +- Kubernetes cluster (minikube, kind, or cloud provider) +- kubectl configured +- Docker images built and available + +## Building Docker Images + +```bash +# Build Gateway image +docker build -t liquidcode-tester-gateway:latest -f src/LiquidCode.Tester.Gateway/Dockerfile . + +# Build C++ Worker image +docker build -t liquidcode-tester-worker-cpp:latest -f src/LiquidCode.Tester.Worker/Dockerfile . +``` + +## Deploying to Kubernetes + +```bash +# Create namespace +kubectl apply -f k8s/namespace.yaml + +# Apply ConfigMap +kubectl apply -f k8s/configmap.yaml + +# Deploy Worker (must be deployed first) +kubectl apply -f k8s/worker-cpp-deployment.yaml + +# Deploy Gateway +kubectl apply -f k8s/gateway-deployment.yaml +``` + +## Checking Status + +```bash +# Check all resources +kubectl get all -n liquidcode-tester + +# Check pods +kubectl get pods -n liquidcode-tester + +# Check services +kubectl get services -n liquidcode-tester + +# View logs +kubectl logs -n liquidcode-tester -l app=gateway +kubectl logs -n liquidcode-tester -l app=worker-cpp +``` + +## Access the Gateway + +```bash +# Get the external IP (for LoadBalancer) +kubectl get service liquidcode-tester-gateway -n liquidcode-tester + +# For minikube +minikube service liquidcode-tester-gateway -n liquidcode-tester + +# Port forward (alternative) +kubectl port-forward -n liquidcode-tester service/liquidcode-tester-gateway 8080:80 +``` + +## Scaling Workers + +```bash +# Scale C++ workers +kubectl scale deployment liquidcode-tester-worker-cpp -n liquidcode-tester --replicas=5 +``` + +## Cleanup + +```bash +# Delete all resources +kubectl delete namespace liquidcode-tester +``` + +## Production Considerations + +1. **Image Registry**: Push images to a container registry (Docker Hub, GCR, ECR, etc.) +2. **Resource Limits**: Adjust CPU/Memory limits based on workload +3. **Persistent Storage**: Add PersistentVolumes for package storage if needed +4. **Monitoring**: Add Prometheus/Grafana for metrics +5. **Logging**: Configure centralized logging (ELK, Loki, etc.) +6. **Security**: + - Use NetworkPolicies to restrict traffic + - Enable Pod Security Standards + - Use secrets for sensitive data + - Consider using a service mesh (Istio, Linkerd) +7. **Autoscaling**: Configure HorizontalPodAutoscaler for workers +8. **Ingress**: Use Ingress controller instead of LoadBalancer for production diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml new file mode 100644 index 0000000..3c76dc7 --- /dev/null +++ b/k8s/configmap.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: liquidcode-tester-config + namespace: liquidcode-tester +data: + gateway.appsettings.json: | + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "PackageDownloadDirectory": "/tmp/packages", + "Workers": { + "Cpp": "http://liquidcode-tester-worker-cpp:8080", + "Java": "http://liquidcode-tester-worker-java:8080", + "Kotlin": "http://liquidcode-tester-worker-kotlin:8080", + "CSharp": "http://liquidcode-tester-worker-csharp:8080" + } + } + worker.appsettings.json: | + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Cpp": { + "Compiler": "g++", + "CompilerFlags": "-O2 -std=c++17 -Wall" + } + } diff --git a/k8s/gateway-deployment.yaml b/k8s/gateway-deployment.yaml new file mode 100644 index 0000000..817c08d --- /dev/null +++ b/k8s/gateway-deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: liquidcode-tester-gateway + namespace: liquidcode-tester + labels: + app: gateway +spec: + replicas: 2 + selector: + matchLabels: + app: gateway + template: + metadata: + labels: + app: gateway + spec: + containers: + - name: gateway + image: liquidcode-tester-gateway:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: Workers__Cpp + value: "http://liquidcode-tester-worker-cpp:8080" + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /api/tester/health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /api/tester/health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: liquidcode-tester-gateway + namespace: liquidcode-tester +spec: + type: LoadBalancer + selector: + app: gateway + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..662aaf4 --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: liquidcode-tester diff --git a/k8s/worker-cpp-deployment.yaml b/k8s/worker-cpp-deployment.yaml new file mode 100644 index 0000000..0de837e --- /dev/null +++ b/k8s/worker-cpp-deployment.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: liquidcode-tester-worker-cpp + namespace: liquidcode-tester + labels: + app: worker-cpp + language: cpp +spec: + replicas: 3 + selector: + matchLabels: + app: worker-cpp + template: + metadata: + labels: + app: worker-cpp + language: cpp + spec: + containers: + - name: worker-cpp + image: liquidcode-tester-worker-cpp:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: Cpp__Compiler + value: "g++" + - name: Cpp__CompilerFlags + value: "-O2 -std=c++17 -Wall" + resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "2Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: /api/test/health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /api/test/health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + # Security context for isolation + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: liquidcode-tester-worker-cpp + namespace: liquidcode-tester +spec: + type: ClusterIP + selector: + app: worker-cpp + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http diff --git a/src/LiquidCode.Tester.Common/Dockerfile b/src/LiquidCode.Tester.Common/Dockerfile deleted file mode 100644 index 1aaa545..0000000 --- a/src/LiquidCode.Tester.Common/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base -USER $APP_UID -WORKDIR /app - -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj", "src/LiquidCode.Tester.Common/"] -RUN dotnet restore "src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj" -COPY . . -WORKDIR "/src/src/LiquidCode.Tester.Common" -RUN dotnet build "./LiquidCode.Tester.Common.csproj" -c $BUILD_CONFIGURATION -o /app/build - -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./LiquidCode.Tester.Common.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "LiquidCode.Tester.Common.dll"] diff --git a/src/LiquidCode.Tester.Gateway/Dockerfile b/src/LiquidCode.Tester.Gateway/Dockerfile index 8035a77..cfde14f 100644 --- a/src/LiquidCode.Tester.Gateway/Dockerfile +++ b/src/LiquidCode.Tester.Gateway/Dockerfile @@ -1,16 +1,25 @@ FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base -USER $APP_UID WORKDIR /app EXPOSE 8080 -EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj", "LiquidCode.Tester.Gateway/"] -RUN dotnet restore "LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj" + +# Copy Common project +COPY ["src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj", "src/LiquidCode.Tester.Common/"] + +# Copy Gateway project +COPY ["src/LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj", "src/LiquidCode.Tester.Gateway/"] + +# Restore dependencies +RUN dotnet restore "src/LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj" + +# Copy all source files COPY . . -WORKDIR "/src/LiquidCode.Tester.Gateway" + +# Build +WORKDIR "/src/src/LiquidCode.Tester.Gateway" RUN dotnet build "./LiquidCode.Tester.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish @@ -20,4 +29,10 @@ RUN dotnet publish "./LiquidCode.Tester.Gateway.csproj" -c $BUILD_CONFIGURATION FROM base AS final WORKDIR /app COPY --from=publish /app/publish . + +# Create directory for package downloads +RUN mkdir -p /tmp/packages + +ENV ASPNETCORE_URLS=http://+:8080 + ENTRYPOINT ["dotnet", "LiquidCode.Tester.Gateway.dll"] diff --git a/src/LiquidCode.Tester.Worker/Dockerfile b/src/LiquidCode.Tester.Worker/Dockerfile index 7e236a8..c9392e1 100644 --- a/src/LiquidCode.Tester.Worker/Dockerfile +++ b/src/LiquidCode.Tester.Worker/Dockerfile @@ -1,21 +1,47 @@ -FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base -USER $APP_UID -WORKDIR /app - +# Build stage FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src + +# Copy Common project +COPY ["src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj", "src/LiquidCode.Tester.Common/"] + +# Copy Worker project COPY ["src/LiquidCode.Tester.Worker/LiquidCode.Tester.Worker.csproj", "src/LiquidCode.Tester.Worker/"] + +# Restore dependencies RUN dotnet restore "src/LiquidCode.Tester.Worker/LiquidCode.Tester.Worker.csproj" + +# Copy all source files COPY . . + +# Build WORKDIR "/src/src/LiquidCode.Tester.Worker" RUN dotnet build "./LiquidCode.Tester.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/build +# Publish stage FROM build AS publish ARG BUILD_CONFIGURATION=Release RUN dotnet publish "./LiquidCode.Tester.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false -FROM base AS final +# Final stage - use aspnet runtime with C++ compiler +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final WORKDIR /app + +# Install C++ compiler and build tools +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + g++ \ + gcc \ + make \ + && rm -rf /var/lib/apt/lists/* + +# Copy published app COPY --from=publish /app/publish . + +# Create temp directory for compilation and testing +RUN mkdir -p /tmp/testing + +ENV ASPNETCORE_URLS=http://+:8080 + ENTRYPOINT ["dotnet", "LiquidCode.Tester.Worker.dll"]