Compare commits

...

21 Commits

Author SHA1 Message Date
Janis d2cd3254b3 feat(benchmarks): add MoveBenchmark for performance testing of game rules
Build & Test (NowChessSystems) TeamCity build finished
2026-05-06 09:19:20 +02:00
Janis 763a2b8c39 feat(benchmarks): add JMH benchmarks for performance testing and load scripts
Build & Test (NowChessSystems) TeamCity build failed
2026-05-06 08:41:49 +02:00
Janis 0eb752d493 fix(redis): enhance GameRedisSubscriberManager to use ReactiveRedisDataSource and improve subscription handling
Build & Test (NowChessSystems) TeamCity build finished
2026-05-06 08:41:30 +02:00
Janis e279c39246 fix(auth): add InternalClientHeadersFactory for custom client headers management
Build & Test (NowChessSystems) TeamCity build failed
2026-05-06 08:07:58 +02:00
TeamCity a101866bcf ci: bump version with Build-67 2026-05-05 18:20:39 +00:00
Janis 5baf6a7cdb fix(redis): update Redis configuration with max pool size and waiting parameters
Build & Test (NowChessSystems) TeamCity build finished
2026-05-05 20:01:32 +02:00
TeamCity a10958b0d1 ci: bump version with Build-66 2026-05-05 07:07:00 +00:00
Janis dc224abe26 fix: Lints
Build & Test (NowChessSystems) TeamCity build finished
2026-05-05 08:44:25 +02:00
Janis 1813ea1d2d fix(redis): simplify refreshRedisHeartbeat logic and ensure proper error handling
Build & Test (NowChessSystems) TeamCity build failed
2026-05-05 08:24:10 +02:00
Janis 6e0fd9523e fix(auth): update InternalAuthFilter to use @ApplicationScoped and add index-dependency configuration
Build & Test (NowChessSystems) TeamCity build failed
2026-05-05 06:49:45 +02:00
Janis 847b13202c fix(redis): prevent concurrent Redis heartbeat refreshes using AtomicBoolean
Build & Test (NowChessSystems) TeamCity build failed
2026-05-04 22:45:45 +02:00
Janis c08d5303eb fix(auth): change InternalAuthFilter to use @Singleton and add HTTP tests for secret validation
Build & Test (NowChessSystems) TeamCity build failed
2026-05-03 17:27:30 +02:00
Janis 33e5017f51 fix(redis): add max pool wait time and switch to ReactiveRedisDataSource for heartbeat updates
Build & Test (NowChessSystems) TeamCity build failed
2026-05-03 16:47:54 +02:00
TeamCity de391113dc ci: bump version with Build-65 2026-05-03 11:30:40 +00:00
Janis 85b187293f fix(auth): correct internal secret validation logic in InternalAuthFilter
Build & Test (NowChessSystems) TeamCity build finished
2026-05-03 13:12:57 +02:00
TeamCity 4a145cb538 ci: bump version with Build-64 2026-05-03 10:34:44 +00:00
Janis 327c23a6aa fix(ci): update image existence check to use GitHub token for authentication
Build & Test (NowChessSystems) TeamCity build finished
2026-05-03 12:16:31 +02:00
Janis d522f7f6ed fix(coordinator): refine type casting in rolloutSpec method (#45)
Build & Test (NowChessSystems) TeamCity build failed
Reviewed-on: #45
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-05-03 12:12:39 +02:00
Janis 82d0b754be fix(coordinator): use genericKubernetesResources API for Argo Rollout scaling (#44)
Build & Test (NowChessSystems) TeamCity build failed
Reviewed-on: #44
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-05-02 22:27:18 +02:00
TeamCity 4694f516fa ci: bump version with Build-63 2026-05-02 19:39:06 +00:00
Janis fa3c6b2886 fix(coordinator): use genericKubernetesResources API for Argo Rollout scaling (#43)
Build & Test (NowChessSystems) TeamCity build finished
fabric8 disallows client.resources(classOf[GenericKubernetesResource]) — throws
KubernetesClientException at runtime. Switch to genericKubernetesResources(apiVersion, kind)
which is the correct API for CRDs.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

Reviewed-on: #43
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-05-02 21:22:53 +02:00
35 changed files with 962 additions and 66 deletions
+5 -2
View File
@@ -351,7 +351,10 @@
- function unsubscribe - function unsubscribe
- _...1 more_ - _...1 more_
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage - `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent - `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala`
- class GameRedisPublisher
- class GameRedisPublisher
- function onGameEvent
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala` - `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
- class GameRedisSubscriberManager - class GameRedisSubscriberManager
- function subscribeGame - function subscribeGame
@@ -657,7 +660,7 @@
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files - `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files - `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files - `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files - `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **8** files
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files - `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files - `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
+1 -1
View File
@@ -19,7 +19,7 @@
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files - `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files - `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files - `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files - `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **8** files
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files - `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files - `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
+4 -1
View File
@@ -295,7 +295,10 @@
- function unsubscribe - function unsubscribe
- _...1 more_ - _...1 more_
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage - `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent - `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala`
- class GameRedisPublisher
- class GameRedisPublisher
- function onGameEvent
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala` - `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
- class GameRedisSubscriberManager - class GameRedisSubscriberManager
- function subscribeGame - function subscribeGame
+20 -4
View File
@@ -19,12 +19,20 @@ jobs:
- id: check - id: check
run: | run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "Triggered manually — allowing build"
echo "allowed=true" >> "$GITHUB_OUTPUT" echo "allowed=true" >> "$GITHUB_OUTPUT"
else else
COMMIT_AUTHOR=$(git log -1 --format='%an') COMMIT_AUTHOR=$(git log -1 --format='%an')
COMMIT_SHA=$(git log -1 --format='%H')
COMMIT_MSG=$(git log -1 --format='%s')
echo "Commit: ${COMMIT_SHA}"
echo "Author: ${COMMIT_AUTHOR}"
echo "Message: ${COMMIT_MSG}"
if [[ "$COMMIT_AUTHOR" == "TeamCity" ]]; then if [[ "$COMMIT_AUTHOR" == "TeamCity" ]]; then
echo "Author is TeamCity — allowing build"
echo "allowed=true" >> "$GITHUB_OUTPUT" echo "allowed=true" >> "$GITHUB_OUTPUT"
else else
echo "Author is not TeamCity — skipping build"
echo "allowed=false" >> "$GITHUB_OUTPUT" echo "allowed=false" >> "$GITHUB_OUTPUT"
fi fi
fi fi
@@ -59,20 +67,28 @@ jobs:
if [ -f "modules/${{ matrix.module }}/versions.env" ]; then if [ -f "modules/${{ matrix.module }}/versions.env" ]; then
source modules/${{ matrix.module }}/versions.env source modules/${{ matrix.module }}/versions.env
VERSION="${MAJOR}.${MINOR}.${PATCH}" VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "[${{ matrix.module }}] Version: ${VERSION}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
else else
echo "[${{ matrix.module }}] No versions.env found — using 'latest'"
echo "version=latest" >> "$GITHUB_OUTPUT" echo "version=latest" >> "$GITHUB_OUTPUT"
fi fi
- name: Check if image exists in GHCR - name: Check if image exists in GHCR
id: image-check id: image-check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ PACKAGE="now-chess-systems%2F${{ matrix.module }}"
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ VERSION="${{ steps.version.outputs.version }}"
"https://ghcr.io/v2/now-chess/now-chess-systems/${{ matrix.module }}/manifests/${{ steps.version.outputs.version }}") EXISTING_TAGS=$(gh api "orgs/now-chess/packages/container/${PACKAGE}/versions" \
if [[ "$STATUS" == "200" ]]; then --jq '.[].metadata.container.tags[]' 2>/dev/null || echo "")
echo "[${{ matrix.module }}] Existing tags: $(echo "${EXISTING_TAGS}" | tr '\n' ' ' | xargs)"
if echo "${EXISTING_TAGS}" | grep -qx "${VERSION}"; then
echo "[${{ matrix.module }}] Image ${VERSION} already exists — skipping build"
echo "exists=true" >> "$GITHUB_OUTPUT" echo "exists=true" >> "$GITHUB_OUTPUT"
else else
echo "[${{ matrix.module }}] Image ${VERSION} not found — will build"
echo "exists=false" >> "$GITHUB_OUTPUT" echo "exists=false" >> "$GITHUB_OUTPUT"
fi fi
+3 -1
View File
@@ -3,6 +3,7 @@ plugins {
id("org.scoverage") version "8.1" apply false id("org.scoverage") version "8.1" apply false
id("com.diffplug.spotless") version "8.4.0" apply false id("com.diffplug.spotless") version "8.4.0" apply false
id("io.github.cosmicsilence.scalafix") version "0.2.6" apply false id("io.github.cosmicsilence.scalafix") version "0.2.6" apply false
id("me.champeau.jmh") version "0.7.2" apply false
} }
group = "de.nowchess" group = "de.nowchess"
@@ -99,7 +100,8 @@ val versions = mapOf(
"SCALA_PARSER_COMBINATORS" to "2.4.0", "SCALA_PARSER_COMBINATORS" to "2.4.0",
"FASTPARSE" to "3.0.2", "FASTPARSE" to "3.0.2",
"JACKSON" to "2.17.2", "JACKSON" to "2.17.2",
"JACKSON_SCALA" to "2.17.2" "JACKSON_SCALA" to "2.17.2",
"JMH" to "1.37"
) )
extra["VERSIONS"] = versions extra["VERSIONS"] = versions
+100
View File
@@ -0,0 +1,100 @@
# NowChess Load Testing with k6
Performance testing suite for NowChess services using k6.
## Installation
```bash
# Install k6 (macOS)
brew install k6
# Or download from https://k6.io/docs/getting-started/installation/
```
## Test Scenarios
### 1. Ramp-Up Test
Gradually increases load from 10 to 100 concurrent users.
```bash
k6 run ramp-up.js
```
Target: Identify system behavior under gradual load increase.
### 2. Stress Test
Incremental load increase up to 500 concurrent users to find breaking point.
```bash
k6 run stress-test.js
```
Target: Determine system capacity and failure point.
### 3. Spike Test
Sudden traffic surge (baseline 50 → 500 users instantly).
```bash
k6 run spike-test.js
```
Target: Test recovery and resilience to sudden spikes.
### 4. Constant Load Test
Maintains 50 VUs for 10 minutes.
```bash
k6 run constant-load.js
```
Target: Check stability under sustained load.
## Environment Variables
```bash
# Override service endpoints
export BASE_URL=http://localhost:8080
export ACCOUNT_HOST=http://localhost:8083
export STORE_HOST=http://localhost:8085
k6 run ramp-up.js
```
## Prerequisites
Ensure services are running:
- Core: `localhost:8080`
- Account: `localhost:8083`
- Store: `localhost:8085`
- Redis: `localhost:6379` (with increased pool size)
## Metrics Interpretation
- `http_req_duration`: Response time (p95, p99 percentiles matter most)
- `http_req_failed`: Failed requests (connection errors, errors, non-2xx responses)
- `vus`: Virtual Users (concurrent connections)
- `iterations`: Completed test cycles per VU
## Results
k6 generates HTML report output. Use with:
```bash
k6 run --out=csv=results.csv ramp-up.js
```
## Troubleshooting
If you see:
- **"max pool wait timeout"** → Redis pool still too small or maxWaitTime too short
- **"connection refused"** → Service not running
- **"high p99 latency"** → System approaching capacity
Increase Redis pool settings in `modules/*/src/main/resources/application.yml`:
```yaml
quarkus:
redis:
pool:
max-size: 128 # Increase if still hitting limits
max-waiting: 256
```
+25
View File
@@ -0,0 +1,25 @@
export const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
export const ACCOUNT_HOST = __ENV.ACCOUNT_HOST || 'http://localhost:8083';
export const STORE_HOST = __ENV.STORE_HOST || 'http://localhost:8085';
export const CORE_HOST = BASE_URL;
export const ENDPOINTS = {
// Account endpoints
accountCreateUser: `${ACCOUNT_HOST}/api/account`,
accountLogin: `${ACCOUNT_HOST}/api/account/login`,
accountProfile: `${ACCOUNT_HOST}/api/account/me`,
accountPublicProfile: (username) => `${ACCOUNT_HOST}/api/account/${username}`,
// Store endpoints
storeGame: `${STORE_HOST}/api/games`,
storeGameById: (gameId) => `${STORE_HOST}/api/games/${gameId}`,
// Core endpoints (game operations)
gameWebSocket: (gameId) => `ws://localhost:8080/api/games/${gameId}/ws`,
};
export const TEST_USERS = [
{ username: 'load-test-user-1', email: 'load1@example.com' },
{ username: 'load-test-user-2', email: 'load2@example.com' },
{ username: 'load-test-user-3', email: 'load3@example.com' },
];
+40
View File
@@ -0,0 +1,40 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { ENDPOINTS } from './config.js';
export const options = {
vus: 50,
duration: '10m',
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.1'],
},
};
export default function () {
// Simulate consistent user traffic
// 1. Get account profile
const profileRes = http.get(ENDPOINTS.accountProfile);
check(profileRes, {
'profile status is 200 or 401': (r) => r.status === 200 || r.status === 401,
});
sleep(0.5);
// 2. Get public profile
const publicRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
check(publicRes, {
'public profile status ok': (r) => r.status > 0,
});
sleep(0.5);
// 3. List store games
const gamesRes = http.get(ENDPOINTS.storeGame);
check(gamesRes, {
'store games status ok': (r) => r.status > 0,
});
sleep(1);
}
+35
View File
@@ -0,0 +1,35 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { ENDPOINTS, ACCOUNT_HOST } from './config.js';
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 50 },
{ duration: '5m', target: 100 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.1'],
},
};
export default function () {
// Test account endpoints
const accountRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
check(accountRes, {
'account endpoint status is 200': (r) => r.status === 200,
});
sleep(1);
// Test store endpoints
const storeRes = http.get(ENDPOINTS.storeGame);
check(storeRes, {
'store endpoint status is 200': (r) => r.status === 200 || r.status === 401,
});
sleep(1);
}
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
TEST_TYPE="${1:-ramp-up}"
BASE_URL="${BASE_URL:-http://localhost:8080}"
OUTPUT_FILE="${2:-results-$(date +%s).csv}"
echo -e "${BLUE}NowChess Load Test Runner${NC}"
echo "Test Type: $TEST_TYPE"
echo "Base URL: $BASE_URL"
echo ""
# Check if k6 is installed
if ! command -v k6 &> /dev/null; then
echo -e "${RED}Error: k6 not found. Install from https://k6.io/docs/getting-started/installation/${NC}"
exit 1
fi
case "$TEST_TYPE" in
ramp-up)
echo -e "${GREEN}Running Ramp-Up Test (10->100 VUs over 13 minutes)${NC}"
k6 run --out=csv=$OUTPUT_FILE ramp-up.js
;;
stress)
echo -e "${GREEN}Running Stress Test (up to 500 VUs)${NC}"
k6 run --out=csv=$OUTPUT_FILE stress-test.js
;;
spike)
echo -e "${GREEN}Running Spike Test (sudden 50->500 spike)${NC}"
k6 run --out=csv=$OUTPUT_FILE spike-test.js
;;
constant)
echo -e "${GREEN}Running Constant Load Test (50 VUs for 10m)${NC}"
k6 run --out=csv=$OUTPUT_FILE constant-load.js
;;
all)
echo -e "${GREEN}Running All Tests${NC}"
for test in ramp-up stress spike constant; do
echo ""
echo -e "${BLUE}Starting $test test...${NC}"
$0 $test
sleep 5
done
exit 0
;;
*)
echo -e "${RED}Usage: $0 {ramp-up|stress|spike|constant|all} [output-file]${NC}"
echo ""
echo "Examples:"
echo " $0 ramp-up"
echo " $0 stress results.csv"
echo " $0 all"
exit 1
;;
esac
echo -e "${GREEN}Test complete. Results saved to $OUTPUT_FILE${NC}"
+36
View File
@@ -0,0 +1,36 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { ENDPOINTS } from './config.js';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '30s', target: 500, ramp: 'fast' },
{ duration: '2m', target: 500 },
{ duration: '30s', target: 50, ramp: 'fast' },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<2000', 'p(99)<5000'],
http_req_failed: ['rate<0.3'],
},
};
export default function () {
// Rapid account checks
const urls = [
ENDPOINTS.accountPublicProfile('testuser'),
ENDPOINTS.storeGame,
ENDPOINTS.accountProfile,
];
urls.forEach((url) => {
const res = http.get(url);
check(res, {
'response received': (r) => r.status > 0,
'response time under 3s': (r) => r.timings.duration < 3000,
});
});
sleep(0.1);
}
+38
View File
@@ -0,0 +1,38 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { ENDPOINTS } from './config.js';
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 200 },
{ duration: '5m', target: 300 },
{ duration: '5m', target: 400 },
{ duration: '5m', target: 500 },
{ duration: '5m', target: 400 },
{ duration: '5m', target: 200 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<1000', 'p(99)<2000'],
http_req_failed: ['rate<0.2'],
},
};
export default function () {
const userIndex = Math.floor(Math.random() * 10);
// Burst account endpoint
const accountRes = http.get(ENDPOINTS.accountPublicProfile(`user-${userIndex}`));
check(accountRes, {
'account endpoint responds': (r) => r.status > 0,
});
// Burst store endpoint
const storeRes = http.get(ENDPOINTS.storeGame);
check(storeRes, {
'store endpoint responds': (r) => r.status > 0,
});
sleep(Math.random() * 0.5);
}
@@ -1,9 +1,9 @@
package de.nowchess.account.client package de.nowchess.account.client
import de.nowchess.security.InternalSecretClientFilter import de.nowchess.security.{InternalClientHeadersFactory, InternalSecretClientFilter}
import jakarta.ws.rs.* import jakarta.ws.rs.*
import jakarta.ws.rs.core.MediaType import jakarta.ws.rs.core.MediaType
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider import org.eclipse.microprofile.rest.client.annotation.{RegisterClientHeaders, RegisterProvider}
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
case class CorePlayerInfo(id: String, displayName: String) case class CorePlayerInfo(id: String, displayName: String)
@@ -19,6 +19,7 @@ case class CoreGameResponse(gameId: String)
@Path("/api/board/game") @Path("/api/board/game")
@RegisterRestClient(configKey = "core-service") @RegisterRestClient(configKey = "core-service")
@RegisterProvider(classOf[InternalSecretClientFilter]) @RegisterProvider(classOf[InternalSecretClientFilter])
@RegisterClientHeaders(classOf[InternalClientHeadersFactory])
trait CoreGameClient: trait CoreGameClient:
@POST @POST
+36
View File
@@ -0,0 +1,36 @@
# Gradle
.gradle/
build/
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
+77
View File
@@ -0,0 +1,77 @@
# JMH Benchmarks
Java Microbenchmark Harness (JMH) benchmarks for performance-critical components.
## Building
```bash
./gradlew modules:benchmarks:jmhJar
```
Produces `modules/benchmarks/build/libs/benchmarks-1.0-SNAPSHOT-jmh.jar`.
## Running Benchmarks
Since these benchmarks are written in Scala, JMH auto-discovery via jar packaging doesn't work. Instead, run benchmarks via classpath:
```bash
./gradlew modules:benchmarks:run --args='de.nowchess.benchmarks.MoveBenchmark'
```
Or compile and run with explicit classpath:
```bash
./gradlew modules:benchmarks:compileScala
java -cp "modules/benchmarks/build/classes/scala/main:modules/benchmarks/build/resources/main:$(./gradlew -q printClasspath)" \
org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark
```
Adjust JMH options:
- `-f 1` — 1 fork (faster iteration, less reliable)
- `-i 3 -w 2` — 3 measurement iterations, 2 warmup iterations
- `-bm avgt` — average time mode
- `-tu us` — time unit (microseconds)
Example:
```bash
java -cp "..." org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark -f 1 -i 3 -w 2 -bm avgt
```
## Writing Benchmarks
Create Scala class with `@Benchmark` methods:
```scala
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Fork(2)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
class SomeBenchmark {
@Setup(Level.Trial)
def setup(): Unit = ???
@org.openjdk.jmh.annotations.Benchmark
def benchmarkSomething(bh: Blackhole): Unit = {
val result = expensiveOperation()
bh.consume(result) // Prevent dead-code elimination
}
}
```
## Benchmark Modes
- `AverageTime` — average execution time per operation
- `Throughput` — operations per unit time
- `SampleTime` — latency samples (min, max, percentiles)
- `SingleShotTime` — total time to execute N ops once
- `All` — run all modes
## Limitations
JMH's Gradle plugin doesn't fully support Scala annotation processing, so benchmarks must be run explicitly by class name rather than auto-discovered. This is a known limitation of JMH + Scala; consider Java-based benchmarks for full auto-discovery support.
## References
- [OpenJDK JMH](https://github.com/openjdk/jmh)
- [JMH Samples](https://hg.openjdk.org/code-tools/jmh/file/tip/jmh-samples)
+61
View File
@@ -0,0 +1,61 @@
plugins {
id("scala")
id("me.champeau.jmh")
}
group = "de.nowchess"
version = "1.0-SNAPSHOT"
@Suppress("UNCHECKED_CAST")
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
repositories {
mavenCentral()
}
scala {
scalaVersion = versions["SCALA3"]!!
}
dependencies {
implementation("org.scala-lang:scala3-library_3") {
version {
strictly(versions["SCALA3"]!!)
}
}
compileOnly("org.scala-lang:scala3-compiler_3") {
version {
strictly(versions["SCALA3"]!!)
}
}
// Core modules for benchmarking
implementation(project(":modules:api"))
implementation(project(":modules:core"))
implementation(project(":modules:rule"))
implementation(project(":modules:bot-platform"))
// JMH (jmh configuration is for annotation processor + runtime)
implementation("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
jmh("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
jmh("org.openjdk.jmh:jmh-generator-annprocess:${versions["JMH"]!!}")
}
configurations.matching { !it.name.startsWith("jmh") }.configureEach {
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
}
jmh {
jmhVersion.set(versions["JMH"]!!)
includeTests.set(false)
duplicateClassesStrategy.set(DuplicatesStrategy.WARN)
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
tasks.withType<org.gradle.api.tasks.scala.ScalaCompile> {
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
}
@@ -0,0 +1,52 @@
package de.nowchess.benchmarks
import org.openjdk.jmh.annotations.{BenchmarkMode, Fork, Measurement, OutputTimeUnit, Setup, State, Warmup}
import org.openjdk.jmh.annotations.Mode
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Level
import org.openjdk.jmh.infra.Blackhole
import de.nowchess.api.board.{File, Rank, Square}
import de.nowchess.api.game.GameContext
import de.nowchess.rules.sets.DefaultRules
import java.util.concurrent.TimeUnit
// scalafix:off DisableSyntax.var
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Fork(value = 2, warmups = 1)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
class MoveBenchmark {
private var gameContext: GameContext = scala.compiletime.uninitialized
@Setup(Level.Trial)
def setup(): Unit =
gameContext = GameContext.initial
@org.openjdk.jmh.annotations.Benchmark
def benchmarkAllLegalMoves(bh: Blackhole): Unit = {
val moves = DefaultRules.allLegalMoves(gameContext)
bh.consume(moves)
}
@org.openjdk.jmh.annotations.Benchmark
def benchmarkIsCheck(bh: Blackhole): Unit = {
val inCheck = DefaultRules.isCheck(gameContext)
bh.consume(inCheck)
}
@org.openjdk.jmh.annotations.Benchmark
def benchmarkLegalMovesFirstSquare(bh: Blackhole): Unit = {
val firstSquare = Square(File.A, Rank.R2)
val moves = DefaultRules.legalMoves(gameContext)(firstSquare)
bh.consume(moves)
}
@org.openjdk.jmh.annotations.Benchmark
def benchmarkIsCheckmate(bh: Blackhole): Unit = {
val result = DefaultRules.isCheckmate(gameContext)
bh.consume(result)
}
}
+56
View File
@@ -82,3 +82,59 @@
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0)) * **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc)) * **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
## (2026-05-02)
### Features
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
### Bug Fixes
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#43](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/43)) ([fa3c6b2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fa3c6b2886dc59c14c5dad834acc9b41e42023bb))
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
## (2026-05-03)
### Features
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
### Bug Fixes
* **coordinator:** refine type casting in rolloutSpec method ([#45](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/45)) ([d522f7f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/d522f7f6edf9c985f03dd16816439d4184f1a589))
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#43](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/43)) ([fa3c6b2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fa3c6b2886dc59c14c5dad834acc9b41e42023bb))
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#44](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/44)) ([82d0b75](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/82d0b754be1075084944b466858672d944f9f7d8))
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
## (2026-05-05)
### Features
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
### Bug Fixes
* **coordinator:** refine type casting in rolloutSpec method ([#45](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/45)) ([d522f7f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/d522f7f6edf9c985f03dd16816439d4184f1a589))
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#43](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/43)) ([fa3c6b2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fa3c6b2886dc59c14c5dad834acc9b41e42023bb))
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#44](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/44)) ([82d0b75](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/82d0b754be1075084944b466858672d944f9f7d8))
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
@@ -5,6 +5,8 @@ quarkus:
port: 8086 port: 8086
redis: redis:
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379} hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
max-pool-size: 64
max-pool-waiting: 128
grpc: grpc:
server: server:
port: 9086 port: 9086
@@ -30,14 +30,15 @@ class AutoScaler:
if kubeClientInstance.isUnsatisfied then None if kubeClientInstance.isUnsatisfied then None
else Some(kubeClientInstance.get()) else Some(kubeClientInstance.get())
private val argoApiVersion = "argoproj.io/v1alpha1"
private val argoKind = "Rollout"
// scalafix:off DisableSyntax.asInstanceOf // scalafix:off DisableSyntax.asInstanceOf
// scalafix:off DisableSyntax.isInstanceOf
private def rolloutSpec(rollout: GenericKubernetesResource): Option[java.util.Map[String, AnyRef]] = private def rolloutSpec(rollout: GenericKubernetesResource): Option[java.util.Map[String, AnyRef]] =
Option(rollout.get("spec")).collect { Option(rollout.get[AnyRef]("spec")).collect { case m: java.util.Map[?, ?] =>
case m if m.isInstanceOf[java.util.Map[?, ?]] => m.asInstanceOf[java.util.Map[String, AnyRef]] m.asInstanceOf[java.util.Map[String, AnyRef]]
} }
// scalafix:on DisableSyntax.asInstanceOf // scalafix:on DisableSyntax.asInstanceOf
// scalafix:on DisableSyntax.isInstanceOf
def checkAndScale: Unit = def checkAndScale: Unit =
if config.autoScaleEnabled then if config.autoScaleEnabled then
@@ -61,7 +62,7 @@ class AutoScaler:
try try
Option( Option(
kube kube
.resources(classOf[GenericKubernetesResource]) .genericKubernetesResources(argoApiVersion, argoKind)
.inNamespace(config.k8sNamespace) .inNamespace(config.k8sNamespace)
.withName(config.k8sRolloutName) .withName(config.k8sRolloutName)
.get(), .get(),
@@ -102,7 +103,7 @@ class AutoScaler:
try try
Option( Option(
kube kube
.resources(classOf[GenericKubernetesResource]) .genericKubernetesResources(argoApiVersion, argoKind)
.inNamespace(config.k8sNamespace) .inNamespace(config.k8sNamespace)
.withName(config.k8sRolloutName) .withName(config.k8sRolloutName)
.get(), .get(),
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0 MAJOR=0
MINOR=7 MINOR=10
PATCH=0 PATCH=0
+87
View File
@@ -710,3 +710,90 @@
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289)) * IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289))
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189)) * update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762)) * update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
## (2026-05-05)
### Features
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([f088c4e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f088c4e9ffcc498d3d1b6f01e8f50042d5830d55))
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([33e785d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e785d22af87724839b62ae91dfe74a05b398c3))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([8744bee](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8744bee2dd20966dae90a09c21a43d5b06f59e00))
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([b5a2966](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b5a2966adafa9650f0f7d601bdeb8fdd13710327))
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
* **rule:** Rules as a microservice ([#39](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/39)) ([093134d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/093134d36c6844ba02a36a28d5d044f09291cd1d))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
### Bug Fixes
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
* **auth:** change InternalAuthFilter to use @Singleton and add HTTP tests for secret validation ([c08d530](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c08d5303eb9e70d36c8eebf6a061ccb71e118fe5))
* **auth:** update InternalAuthFilter to use @ApplicationScoped and add index-dependency configuration ([6e0fd95](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6e0fd9523e001756ce7109e639ebb54be4fcdabf))
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
* **heartbeat:** inject ObjectMapper into InstanceHeartbeatService ([#42](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/42)) ([0c98151](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0c981517da1f94cd10ae396e47bde2b35d0b3ba0))
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289))
* Lints ([dc224ab](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dc224abe26acf5361c56956006e1cc51b75b0b7e))
* **redis:** add max pool wait time and switch to ReactiveRedisDataSource for heartbeat updates ([33e5017](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e5017f51a998327b180f778f73964cc10c05d3))
* **redis:** prevent concurrent Redis heartbeat refreshes using AtomicBoolean ([847b132](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/847b13202cb909d18ca3304c27ebe17ce2312b8e))
* **redis:** simplify refreshRedisHeartbeat logic and ensure proper error handling ([1813ea1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1813ea1d2d5d093f7925f87371b5e29820bf1136))
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
## (2026-05-05)
### Features
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([f088c4e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f088c4e9ffcc498d3d1b6f01e8f50042d5830d55))
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([33e785d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e785d22af87724839b62ae91dfe74a05b398c3))
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([8744bee](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8744bee2dd20966dae90a09c21a43d5b06f59e00))
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([b5a2966](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b5a2966adafa9650f0f7d601bdeb8fdd13710327))
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
* **rule:** Rules as a microservice ([#39](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/39)) ([093134d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/093134d36c6844ba02a36a28d5d044f09291cd1d))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
### Bug Fixes
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
* **auth:** change InternalAuthFilter to use @Singleton and add HTTP tests for secret validation ([c08d530](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c08d5303eb9e70d36c8eebf6a061ccb71e118fe5))
* **auth:** update InternalAuthFilter to use @ApplicationScoped and add index-dependency configuration ([6e0fd95](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6e0fd9523e001756ce7109e639ebb54be4fcdabf))
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
* **heartbeat:** inject ObjectMapper into InstanceHeartbeatService ([#42](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/42)) ([0c98151](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0c981517da1f94cd10ae396e47bde2b35d0b3ba0))
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289))
* Lints ([dc224ab](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dc224abe26acf5361c56956006e1cc51b75b0b7e))
* **redis:** add max pool wait time and switch to ReactiveRedisDataSource for heartbeat updates ([33e5017](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e5017f51a998327b180f778f73964cc10c05d3))
* **redis:** prevent concurrent Redis heartbeat refreshes using AtomicBoolean ([847b132](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/847b13202cb909d18ca3304c27ebe17ce2312b8e))
* **redis:** simplify refreshRedisHeartbeat logic and ensure proper error handling ([1813ea1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1813ea1d2d5d093f7925f87371b5e29820bf1136))
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
@@ -3,8 +3,15 @@ quarkus:
port: 8080 port: 8080
application: application:
name: nowchess-core name: nowchess-core
index-dependency:
security:
group-id: de.nowchess
artifact-id: security
redis: redis:
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379} hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
max-pool-wait-time: 30s
max-pool-size: 64
max-pool-waiting: 128
grpc: grpc:
clients: clients:
rule-grpc: rule-grpc:
@@ -10,8 +10,9 @@ import de.nowchess.chess.observer.Observer
import de.nowchess.chess.registry.GameRegistry import de.nowchess.chess.registry.GameRegistry
import de.nowchess.chess.resource.GameDtoMapper import de.nowchess.chess.resource.GameDtoMapper
import de.nowchess.chess.service.InstanceHeartbeatService import de.nowchess.chess.service.InstanceHeartbeatService
import io.quarkus.redis.datasource.ReactiveRedisDataSource
import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.RedisDataSource
import io.quarkus.redis.datasource.pubsub.PubSubCommands import io.quarkus.redis.datasource.pubsub.ReactivePubSubCommands
import jakarta.annotation.PreDestroy import jakarta.annotation.PreDestroy
import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.inject.Instance import jakarta.enterprise.inject.Instance
@@ -29,6 +30,7 @@ class GameRedisSubscriberManager:
// scalafix:off DisableSyntax.var // scalafix:off DisableSyntax.var
@Inject var redis: RedisDataSource = uninitialized @Inject var redis: RedisDataSource = uninitialized
@Inject var reactiveRedis: ReactiveRedisDataSource = uninitialized
@Inject var registry: GameRegistry = uninitialized @Inject var registry: GameRegistry = uninitialized
@Inject var objectMapper: ObjectMapper = uninitialized @Inject var objectMapper: ObjectMapper = uninitialized
@Inject var redisConfig: RedisConfig = uninitialized @Inject var redisConfig: RedisConfig = uninitialized
@@ -40,7 +42,7 @@ class GameRedisSubscriberManager:
if heartbeatServiceInstance.isUnsatisfied then None if heartbeatServiceInstance.isUnsatisfied then None
else Some(heartbeatServiceInstance.get()) else Some(heartbeatServiceInstance.get())
private val c2sListeners = new ConcurrentHashMap[String, PubSubCommands.RedisSubscriber]() private val c2sListeners = new ConcurrentHashMap[String, ReactivePubSubCommands.ReactiveRedisSubscriber]()
private val s2cObservers = new ConcurrentHashMap[String, Observer]() private val s2cObservers = new ConcurrentHashMap[String, Observer]()
private def c2sTopic(gameId: String): String = private def c2sTopic(gameId: String): String =
@@ -50,35 +52,37 @@ class GameRedisSubscriberManager:
s"${redisConfig.prefix}:game:$gameId:s2c" s"${redisConfig.prefix}:game:$gameId:s2c"
def subscribeGame(gameId: String): Unit = def subscribeGame(gameId: String): Unit =
try val writebackFn: String => Unit = json => redis.pubsub(classOf[String]).publish("game-writeback", json)
val handler: Consumer[String] = msg => handleC2sMessage(gameId, msg) val obs = new GameRedisPublisher(
val subscriber = redis.pubsub(classOf[String]).subscribe(c2sTopic(gameId), handler) gameId,
c2sListeners.put(gameId, subscriber) registry,
redis,
objectMapper,
s2cTopicName(gameId),
writebackFn,
ioClient,
unsubscribeGame,
)
s2cObservers.put(gameId, obs)
registry.get(gameId).foreach(_.engine.subscribe(obs))
heartbeatServiceOpt.foreach(_.addGameSubscription(gameId))
val writebackFn: String => Unit = json => redis.pubsub(classOf[String]).publish("game-writeback", json) val handler: Consumer[String] = msg => handleC2sMessage(gameId, msg)
val obs = new GameRedisPublisher( reactiveRedis
gameId, .pubsub(classOf[String])
registry, .subscribe(c2sTopic(gameId), handler)
redis, .subscribe()
objectMapper, .`with`(
s2cTopicName(gameId), subscriber => {
writebackFn, c2sListeners.put(gameId, subscriber)
ioClient, log.debugf("Subscribed to game %s", gameId)
unsubscribeGame, },
failure => log.warnf(failure, "Redis subscription failed for game %s", gameId),
) )
s2cObservers.put(gameId, obs)
registry.get(gameId).foreach(_.engine.subscribe(obs))
log.debugf("Subscribed to game %s", gameId)
heartbeatServiceOpt.foreach(_.addGameSubscription(gameId))
catch
case e: Exception =>
log.warnf(e, "Redis subscription failed for game %s", gameId)
()
def unsubscribeGame(gameId: String): Unit = def unsubscribeGame(gameId: String): Unit =
Option(c2sListeners.remove(gameId)).foreach { subscriber => Option(c2sListeners.remove(gameId)).foreach { subscriber =>
subscriber.unsubscribe(c2sTopic(gameId)) subscriber.unsubscribe(c2sTopic(gameId)).subscribe().`with`(_ => (), _ => ())
} }
Option(s2cObservers.remove(gameId)).foreach { obs => Option(s2cObservers.remove(gameId)).foreach { obs =>
registry.get(gameId).foreach(_.engine.unsubscribe(obs)) registry.get(gameId).foreach(_.engine.unsubscribe(obs))
@@ -154,5 +158,5 @@ class GameRedisSubscriberManager:
@PreDestroy @PreDestroy
def cleanup(): Unit = def cleanup(): Unit =
c2sListeners.forEach((gameId, subscriber) => subscriber.unsubscribe(c2sTopic(gameId))) c2sListeners.forEach((gameId, subscriber) => subscriber.unsubscribe(c2sTopic(gameId)).await().indefinitely())
s2cObservers.forEach((gameId, obs) => registry.get(gameId).foreach(_.engine.unsubscribe(obs))) s2cObservers.forEach((gameId, obs) => registry.get(gameId).foreach(_.engine.unsubscribe(obs)))
@@ -8,8 +8,10 @@ import io.quarkus.runtime.ShutdownEvent
import io.quarkus.grpc.GrpcClient import io.quarkus.grpc.GrpcClient
import org.eclipse.microprofile.config.inject.ConfigProperty import org.eclipse.microprofile.config.inject.ConfigProperty
import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.RedisDataSource
import io.quarkus.redis.datasource.ReactiveRedisDataSource
import scala.compiletime.uninitialized import scala.compiletime.uninitialized
import java.util.concurrent.{Executors, TimeUnit} import java.util.concurrent.{Executors, TimeUnit}
import java.util.concurrent.atomic.AtomicBoolean
import java.net.InetAddress import java.net.InetAddress
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.jboss.logging.Logger import org.jboss.logging.Logger
@@ -24,6 +26,9 @@ class InstanceHeartbeatService:
@Inject @Inject
private var redis: RedisDataSource = uninitialized private var redis: RedisDataSource = uninitialized
@Inject
private var reactiveRedis: ReactiveRedisDataSource = uninitialized
@Inject @Inject
private var mapper: ObjectMapper = uninitialized private var mapper: ObjectMapper = uninitialized
@@ -51,6 +56,7 @@ class InstanceHeartbeatService:
private var serviceActive = false private var serviceActive = false
private var shuttingDown = false private var shuttingDown = false
// scalafix:on DisableSyntax.var // scalafix:on DisableSyntax.var
private val redisHeartbeatPending = new AtomicBoolean(false)
def onStart(@Observes event: StartupEvent): Unit = def onStart(@Observes event: StartupEvent): Unit =
if coordinatorEnabled then if coordinatorEnabled then
@@ -167,25 +173,36 @@ class InstanceHeartbeatService:
} }
private def refreshRedisHeartbeat(): Unit = private def refreshRedisHeartbeat(): Unit =
try if redisHeartbeatPending.compareAndSet(false, true) then
val key = s"$redisPrefix:instances:$instanceId" try
val key = s"$redisPrefix:instances:$instanceId"
val metadata = Map( val metadata = Map(
"instanceId" -> instanceId, "instanceId" -> instanceId,
"hostname" -> getHostname, "hostname" -> getHostname,
"httpPort" -> httpPort, "httpPort" -> httpPort,
"grpcPort" -> grpcPort, "grpcPort" -> grpcPort,
"subscriptionCount" -> subscriptionCount, "subscriptionCount" -> subscriptionCount,
"localCacheSize" -> localCacheSize, "localCacheSize" -> localCacheSize,
"lastHeartbeat" -> java.time.Instant.now().toString, "lastHeartbeat" -> java.time.Instant.now().toString,
"state" -> "HEALTHY", "state" -> "HEALTHY",
) )
val json = mapper.writeValueAsString(metadata) val json = mapper.writeValueAsString(metadata)
redis.value(classOf[String]).setex(key, 5L, json) reactiveRedis
catch .value(classOf[String])
case ex: Exception => .setex(key, 5L, json)
log.warnf(ex, "Failed to refresh Redis heartbeat") .subscribe()
.`with`(
_ => redisHeartbeatPending.set(false),
(ex: Throwable) =>
redisHeartbeatPending.set(false)
log.warnf(ex, "Failed to refresh Redis heartbeat"),
)
catch
case ex: Exception =>
redisHeartbeatPending.set(false)
log.warnf(ex, "Failed to serialize Redis heartbeat metadata")
private def getHostname: String = private def getHostname: String =
try InetAddress.getLocalHost.getHostName try InetAddress.getLocalHost.getHostName
@@ -0,0 +1,72 @@
package de.nowchess.chess.resource
import de.nowchess.chess.grpc.{IoGrpcClientWrapper, RuleSetGrpcAdapter}
import de.nowchess.chess.redis.GameRedisSubscriberManager
import io.quarkus.test.InjectMock
import io.quarkus.test.junit.{QuarkusTest, TestProfile}
import io.quarkus.test.junit.QuarkusTestProfile
import io.restassured.RestAssured
import jakarta.ws.rs.core.MediaType
import org.junit.jupiter.api.{DisplayName, Test}
import java.util.Map as JMap
// scalafix:off
class InternalAuthEnabledProfile extends QuarkusTestProfile:
override def getConfigOverrides(): JMap[String, String] =
JMap.of(
"nowchess.internal.auth.enabled",
"true",
"nowchess.internal.secret",
"test-secret-123",
)
@QuarkusTest
@TestProfile(classOf[InternalAuthEnabledProfile])
@DisplayName("InternalAuthFilter HTTP")
class InternalAuthFilterHttpTest:
@InjectMock
var ioWrapper: IoGrpcClientWrapper = scala.compiletime.uninitialized
@InjectMock
var ruleAdapter: RuleSetGrpcAdapter = scala.compiletime.uninitialized
@InjectMock
var subscriberManager: GameRedisSubscriberManager = scala.compiletime.uninitialized
@Test
@DisplayName("POST /api/board/game without secret returns 401")
def rejectNoSecret(): Unit =
RestAssured
.`given`()
.contentType(MediaType.APPLICATION_JSON)
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(401)
@Test
@DisplayName("POST /api/board/game with wrong secret returns 401")
def rejectWrongSecret(): Unit =
RestAssured
.`given`()
.contentType(MediaType.APPLICATION_JSON)
.header("X-Internal-Secret", "wrong-secret")
.body("{}")
.when()
.post("/api/board/game")
.`then`()
.statusCode(401)
@Test
@DisplayName("GET /api/board/game/{id} without secret returns 404 not 401")
def nonInternalEndpointNotBlocked(): Unit =
RestAssured
.`given`()
.when()
.get("/api/board/game/nonexistent")
.`then`()
.statusCode(404)
// scalafix:on
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0 MAJOR=0
MINOR=27 MINOR=29
PATCH=0 PATCH=0
+9
View File
@@ -28,3 +28,12 @@
### Features ### Features
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299)) * true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
## (2026-05-03)
### Features
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
### Bug Fixes
* **auth:** correct internal secret validation logic in InternalAuthFilter ([85b1872](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/85b187293f12f149494986872d6b06789945ea18))
@@ -23,5 +23,5 @@ class InternalAuthFilter extends ContainerRequestFilter:
override def filter(ctx: ContainerRequestContext): Unit = override def filter(ctx: ContainerRequestContext): Unit =
if authEnabled then if authEnabled then
val header = Option(ctx.getHeaderString("X-Internal-Secret")) val header = Option(ctx.getHeaderString("X-Internal-Secret"))
if header.isEmpty || header.get.equals(secret) then if header.isEmpty || (!header.get.equals(secret)) then
ctx.abortWith(Response.status(Response.Status.UNAUTHORIZED).build()) ctx.abortWith(Response.status(Response.Status.UNAUTHORIZED).build())
@@ -0,0 +1,30 @@
package de.nowchess.security
import jakarta.enterprise.context.ApplicationScoped
import jakarta.ws.rs.core.MultivaluedMap
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl
import scala.compiletime.uninitialized
@ApplicationScoped
class InternalClientHeadersFactory extends DefaultClientHeadersFactoryImpl {
@ConfigProperty(name = "nowchess.internal.secret", defaultValue = "")
// scalafix:off DisableSyntax.var
var secret: String = uninitialized
@ConfigProperty(name = "nowchess.internal.auth.enabled", defaultValue = "true")
var authEnabled: Boolean = uninitialized
// scalafix:on DisableSyntax.var
override def update(
incomingHeaders: MultivaluedMap[String, String],
clientOutgoingHeaders: MultivaluedMap[String, String],
): MultivaluedMap[String, String] = {
val default = super.update(incomingHeaders, clientOutgoingHeaders)
default.putSingle("X-Internal-Secret", secret)
default
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0 MAJOR=0
MINOR=6 MINOR=7
PATCH=0 PATCH=0
+15
View File
@@ -61,3 +61,18 @@
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed)) * **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e)) * **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299)) * true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
## (2026-05-05)
### Features
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
* **config:** update application.yml to nest HTTP port configuration ([3efebd5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3efebd5ed0493159c51f7246d18d59bac58cf875))
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
### Bug Fixes
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
@@ -8,6 +8,8 @@ quarkus:
enabled: true enabled: true
redis: redis:
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379} hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
max-pool-size: 64
max-pool-waiting: 128
datasource: datasource:
db-kind: postgresql db-kind: postgresql
username: ${DB_USER:nowchess} username: ${DB_USER:nowchess}
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0 MAJOR=0
MINOR=6 MINOR=7
PATCH=0 PATCH=0
+1
View File
@@ -26,4 +26,5 @@ include(
"modules:ws", "modules:ws",
"modules:store", "modules:store",
"modules:coordinator", "modules:coordinator",
"modules:benchmarks",
) )