Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docker-compose/multiwar/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SHOULD_INIT_GEO_DATA=true
SHOULD_INIT_LINES_CATALOG=true

COMPOSE_PROJECT_NAME=gridstudy

TOMCAT_PORT=12345
58 changes: 58 additions & 0 deletions docker-compose/multiwar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# WAR Deploy Tool

Generated with little supervision by opus4.6.

Builds GridSuite microservices as WAR files for docker-compose deployment, **without modifying any original source files**.

## How it works

Instead of patching server sources, this tool generates **wrapper Maven projects** that:
1. Depend on the original server JAR (classes artifact)
2. Add a generated `SpringBootServletInitializer` class that delegates to the original Application class
3. Package everything as a WAR with a clean context-path name

```
Original server (unmodified) Wrapper project (generated)
┌─────────────────────────┐ ┌─────────────────────────┐
│ report-server/ │ │ war-wrappers/report-server/
│ pom.xml (jar) │◄─────────│ pom.xml (war) │
│ ReportApplication.java│ │ WarInitializer.java │
└─────────────────────────┘ └─────────────────────────┘
│ │
▼ ▼
gridsuite-report-server.jar report-server.war
```

## Quick Start

```bash
# Build all WARs and prepare for docker-compose
./wars.sh

# Start the stack
docker compose up
```

## Enabling/disabling servers

Edit the `MANIFEST` array in `war-deploy.sh`. Comment lines with `#` to disable:

```bash
"actions-server|actions-server||..." # enabled
#"timeseries-server|timeseries-server||..." # disabled
```

## Prerequisites

- Java 21+
- Maven 3.8+
- Server submodules checked out and buildable in the layout of Gridsuite Aggregator

## Excluded (Boot WebFlux — incompatible(??) with WAR)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check WebFlux dependencies in excluded servers to verify incompatibility claims

echo "Checking for WebFlux dependencies in excluded servers..."
for server in config-server config-notification-server directory-notification-server \
              merge-notification-server study-notification-server gateway; do
  if [ -d "../../../${server}" ]; then
    echo "=== ${server} ==="
    fd -e xml -e gradle pom.xml build.gradle --exec rg -l "spring-boot-starter-webflux|spring-webflux"
  fi
done

Repository: gridsuite/deployment

Length of output: 121


🏁 Script executed:

# Explore the repository structure
git ls-files | head -30

Repository: gridsuite/deployment

Length of output: 1210


🏁 Script executed:

# Check the main directory structure
ls -la

Repository: gridsuite/deployment

Length of output: 713


🏁 Script executed:

# Look for build files that would indicate where services are defined
fd -e "pom.xml" -e "build.gradle" -o -e "docker-compose.yml" | head -20

Repository: gridsuite/deployment

Length of output: 181


🏁 Script executed:

# Read the multiwar README to understand context
cat docker-compose/multiwar/README.md

Repository: gridsuite/deployment

Length of output: 1830


🏁 Script executed:

# Check if any service source code or configs are in this repo that might reference WebFlux
rg -i "webflux|spring-boot-starter-web" --type markdown --type yml

Repository: gridsuite/deployment

Length of output: 92


🌐 Web query:

Spring Boot WebFlux WAR packaging compatibility servlet container

💡 Result:

Spring Boot WebFlux applications are not compatible with WAR packaging for deployment to servlet containers. Official Spring Boot documentation explicitly states: "Because Spring WebFlux does not strictly depend on the Servlet API and applications are deployed by default on an embedded Reactor Netty server, War deployment is not supported for WebFlux applications."[1][2][3][4]. This position is consistent across versions, including recent 3.3.x documentation[2][4], and Spring Boot team members have confirmed no plans to support it due to technical challenges like requiring Servlet 3.1+ with async I/O and risks of mixing blocking/non-blocking code[5][6]. Spring Boot recommends packaging WebFlux apps as executable JARs with embedded Netty[5][6]. While plain Spring Framework WebFlux supports WAR deployment via AbstractReactiveWebInitializer on Servlet 3.1+ containers[7][8][9], this is not integrated with Spring Boot's auto-configuration and leads to errors or unsupported behavior when attempted[5][10][11][12]. No changes noted as of 2026[13][14].

Citations:


🌐 Web query:

Spring Cloud Config Server WebFlux Spring Cloud Gateway architecture

💡 Result:

Spring Cloud Config Server WebFlux + Spring Cloud Gateway: how the architecture typically fits together (and where WebFlux can be tricky). 1) What each component is (and what protocol it speaks) - Spring Cloud Config Server exposes an HTTP resource-based API for external configuration (name/value pairs or YAML) and is embeddable in a Spring Boot app using @EnableConfigServer. It runs as a Spring Boot application (default port 8080; commonly 8888). [1] - Spring Cloud Gateway Server WebFlux is a reactive (Spring WebFlux/Project Reactor) gateway; its configuration (routes, predicates, filters) is driven by RouteDefinitionLocator/RouteDefinition and routes are matched via Spring WebFlux HandlerMapping infrastructure. [2][3][4] 2) Canonical “centralized config + reactive gateway” architecture A typical setup is: - Config Server (Spring Boot app): backs centralized config and serves it over HTTP. - Gateway (Spring Cloud Gateway on WebFlux): routes incoming client requests to downstream services (often discovered via Eureka, though that’s a separate concern). Gateway is itself reactive and expects WebFlux runtime semantics. [2][4][3] - Each downstream microservice (including Gateway itself if desired) is a Config Client: it fetches its own configuration from Config Server at startup using the Config Client support (connects to Config Server via spring.cloud.config.uri or spring.config.import=optional:configserver:...). [5][6] Key idea: Config Server is not “wired into” Gateway request routing at the WebFlux layer; instead, it provides configuration to the apps that need it (Gateway and downstream services). Config Server is an HTTP backend for configuration, not a routing component for Gateway. [1][5] 3) Where “Config Server + WebFlux” can be confusing - If you add Spring WebFlux starter(s) to Config Server, you may run into servlet-vs-reactive controller differences. A Stack Overflow report notes that the Config Server ResourceController can rely on ServletWebRequest, and therefore may not work properly with a reactive stack out of the box. [7] So for production-grade architectures, treat Config Server as a server that provides an HTTP API for config, and ensure its own web stack/controller compatibility matches the Spring Cloud Config version you’re using (don’t assume that adding spring-boot-starter-webflux will automatically make every endpoint reactive). [7][1] 4) How Gateway configuration relates to Config Server - Spring Cloud Gateway’s route configuration is driven by route definitions (RouteDefinitionLocator) and can be provided via properties (default PropertiesRouteDefinitionLocator loads from Spring Boot @ConfigurationProperties). [3] - Therefore, the most common approach is: a) Put Gateway route settings (e.g., spring.cloud.gateway.routes.* or equivalent RouteLocator configuration) in the config repository. b) Have the Gateway application fetch those settings from Config Server as part of its normal Config Client startup. This keeps Gateway reactive (it still runs on WebFlux) while config is supplied via the Config Server’s HTTP API. [3][1][5] 5) A concrete “wiring” pattern (conceptual) - Config Server: serves configuration artifacts for each application name/profile over HTTP. [1] - Gateway app: starts, connects to Config Server, loads properties (including Gateway route/predicate/filter config), then starts routing requests reactively. [5][3][4] If you tell me your exact versions (Spring Boot + Spring Cloud release train + Spring Cloud Config/Gateway versions) and whether you want Gateway routes to come from Config Server Git files or from discovery/database, I can draft an example application.yml/bootstrapping layout for both Config Server and Gateway.

Citations:


Remove the uncertainty markers from the WebFlux incompatibility statement.

The exclusion is correct and well-documented: Spring Boot officially does not support WAR packaging for WebFlux applications because WebFlux depends on an embedded Reactor Netty server and does not integrate with the Servlet API. Replace the "(??) " with a definitive statement since this is a documented limitation, not an uncertain one.

Consider updating to: "Excluded (Spring Boot WebFlux — not compatible with WAR packaging)"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose/multiwar/README.md` at line 51, Update the section header "##
Excluded (Boot WebFlux — incompatible(??) with WAR)" by removing the uncertainty
markers and making the incompatibility definitive; replace it with a clear
statement such as "## Excluded (Spring Boot WebFlux — not compatible with WAR
packaging)" so the README unambiguously reflects the documented limitation of
WebFlux and WAR packaging.


- config-server
- config-notification-server
- directory-notification-server
- merge-notification-server
- study-notification-server
- gateway
143 changes: 143 additions & 0 deletions docker-compose/multiwar/SHARED_CLASSLOADING_ISSUES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
Shared Classloading Issues — WAR Deployment on Single Tomcat
=============================================================

All 21 Spring Boot microservices deployed as WARs on one Tomcat instance,
with all dependency JARs in Tomcat's shared lib/ (common classloader).
Each WAR contains only its own server classes JAR.

ISSUE 1: Bean definition conflicts
───────────────────────────────────
Symptom: BeanDefinitionOverrideException — duplicate bean names from
@Configuration classes in gridsuite-computation, gridsuite-network-modification
JARs now visible to ALL webapps via common classloader.
Fix: -Dspring.main.allow-bean-definition-overriding=true in CATALINA_OPTS.

ISSUE 2: DataSource auto-configuration on non-DB servers
────────────────────────────────────────────────────────
Symptom: "Could not resolve placeholder 'powsybl-ws.database.name'" on
explore-server, network-conversion-server, network-map-server.
Cause: powsybl-ws-commons JAR in shared lib has application.yaml referencing
DB properties. With HikariCP/spring-jdbc on common classpath,
DataSourceAutoConfiguration activates for all webapps.
Fix: Auto-detect non-DB servers (no JPA/JDBC/Liquibase deps) and add
spring.autoconfigure.exclude entries to their externalized configs:
DataSourceAutoConfiguration, HibernateJpaAutoConfiguration,
LiquibaseAutoConfiguration, DataSourceTransactionManagerAutoConfiguration.

ISSUE 3: Spring Security activating on all servers
──────────────────────────────────────────────────
Symptom: All servers return "Please sign in" login page.
Cause: Only explore-server depends on spring-security, but security JARs
in shared lib activate SecurityAutoConfiguration for all webapps.
Fix: Auto-detect non-security servers (no spring-boot-starter-security or
spring-security-config in deps) and add exclusions:
SecurityAutoConfiguration, UserDetailsServiceAutoConfiguration,
ManagementWebSecurityAutoConfiguration.

ISSUE 4: Logback ConcurrentModificationException during parallel deployment
───────────────────────────────────────────────────────────────────────────
Symptom: ConcurrentModificationException in ContextBase.getCopyOfPropertyMap
during startup.
Cause: startStopThreads="10" in server.xml → 10 WARs deploy simultaneously,
all sharing the same Logback LoggerContext from common classloader.
Race condition in property map access.
Fix: Set startStopThreads="1" in server.xml for sequential deployment.
Shared lib preloads all classes, so sequential is fast enough.

ISSUE 5: ServiceLoader "not a subtype" errors
──────────────────────────────────────────────
Symptom: ServiceConfigurationError: ActivePowerControlLoader not a subtype
of ExtensionLoader.
Cause: Version mismatch between shared lib (e.g. 1.40.0) and per-WAR
override JARs (1.41.0). Different classloaders load the same
interface → different type identity → ServiceLoader rejects impl.
Fix: Removed per-WAR version overrides entirely. All servers use the
highest version from shared lib (sort -k5Vr). No override JARs in
WEB-INF/lib/ — only the server's own classes JAR.

ISSUE 6: antlr4 tool JAR conflicts with antlr4-runtime
───────────────────────────────────────────────────────
Symptom: ATN version mismatch errors from Hibernate HQL parser.
Cause: org.antlr:antlr4:4.5.1 (parser generator tool) bundles old runtime
classes that conflict with antlr4-runtime:4.13.0 used by Hibernate.
Fix: Exclude org.antlr:antlr4: (tool JAR) from shared lib via grep filter.
antlr4-runtime (the actual dependency) is kept.

ISSUE 7: ResourceBundle not found for reports.properties
────────────────────────────────────────────────────────
Symptom: MissingResourceException: Can't find bundle for base name
com.powsybl.network.conversion.server.reports.
Cause: MultiBundleMessageTemplateProvider (in powsybl-commons, common CL)
calls ResourceBundle.getBundle() without explicit classloader →
uses caller's CL (common CL) → can't see .properties files inside
each WAR's server JAR in WEB-INF/lib/.
Fix: Extract reports*.properties from all 9 server source trees, package
into a single resource-bundles.jar in shared lib. All bundle paths
are unique (different packages), so no conflicts.

ISSUE 8: Elasticsearch thread pool explosion (441 threads)
──────────────────────────────────────────────────────────
Symptom: 706 threads total; 441 are elasticsearch-rest-client I/O threads.
21 ES client pools × 21 threads each (one per CPU core).
Cause: ElasticsearchRestClientAutoConfiguration activates for ALL 21 webapps
because ES client JARs are on the common classpath. Only 5 servers
actually use Elasticsearch (case, directory, network-conversion,
network-modification, study).
Fix: Auto-detect non-ES servers (no elasticsearch/elastic-clients in deps)
and add exclusions: ElasticsearchRestClientAutoConfiguration,
ElasticsearchClientAutoConfiguration,
ReactiveElasticsearchClientAutoConfiguration,
ElasticsearchDataAutoConfiguration,
ElasticsearchRepositoriesAutoConfiguration.
Result: 706 → 456 threads (−250).

ISSUE 9: OpenTelemetry tracing auto-configuration failure
─────────────────────────────────────────────────────────
Symptom: APPLICATION FAILED TO START — OpenTelemetryTracingAutoConfiguration
required a bean of type 'io.opentelemetry.api.OpenTelemetry'.
Cause: OTel JARs on common classpath activate tracing autoconfigs for all
webapps. Each webapp also creates BatchSpanProcessor and
BatchLogRecordProcessor threads (42 total) needlessly.
Fix: Exclude for all servers (none configure OTel tracing):
OpenTelemetryAutoConfiguration (×2 in different packages),
OpenTelemetryTracingAutoConfiguration,
OtlpAutoConfiguration, OtlpTracingAutoConfiguration,
OpenTelemetryLoggingAutoConfiguration.

MEMORY USAGE REPORT (21 webapps, shared classloading)
======================================================
Container: 2.5g limit, no swap | JVM: -Xmx1g, G1GC

Docker stats: 2.24 GiB / 2.5 GiB (89%) — 488 threads (PIDs)

NMT committed breakdown (1,619 MB total):
Java Heap 1,020 MB (Xmx 1g, ~300MB used at idle, ~1g under load)
Metaspace 193 MB (43K classes across 21 webapps)
GC (G1) 92 MB (card tables, remembered sets)
Code cache 85 MB (JIT compiled code)
Symbol table 71 MB (interned strings, 582K entries)
Threads 51 MB (488 threads × ~1MB stack)
Class metadata 34 MB (class pointers, vtables)
NMT overhead 14 MB
Other (CDS, etc.) 59 MB

RSS vs NMT gap (~600 MB):
NMT committed = 1,619 MB, docker reports 2,236 MB.
Difference is thread stack RSS, glibc malloc arenas, mmap'd JARs,
and kernel page table overhead not tracked by NMT.

Comparison vs 21 separate containers:
Each server standalone uses ~300-500 MB (avg ~400 MB).
21 × 400 MB = 8.4 GB total.
Shared classloading: 2.24 GB = ~73% memory reduction.

ISSUE 10: Maven build failures
─────────────────────────────
8a) Thread contention: -T2.0C (40 threads on 20 cores) caused Maven lock
contention. Fixed: -T4.
8b) Missing artifact com.bea.xml:jsr173-ri:1.0 (transitive of stax-utils).
Fixed: wildcard <exclusions> on all shared-deps POM entries since
_shared.txt is already the full transitive closure.
8c) Empty WARs: packagingExcludes=WEB-INF/lib/*.jar removed everything.
maven-war-plugin doesn't support ! negation in packagingExcludes.
Fixed: switched to packagingIncludes (whitelist approach).
Comment on lines +134 to +143
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

ISSUE numbering mismatch.

The header says "ISSUE 10" but the sub-items below are labeled 8a / 8b / 8c.

🔧 Proposed fix
 ISSUE 10: Maven build failures
 ─────────────────────────────
-8a) Thread contention: -T2.0C (40 threads on 20 cores) caused Maven lock
+10a) Thread contention: -T2.0C (40 threads on 20 cores) caused Maven lock
     contention. Fixed: -T4.
-8b) Missing artifact com.bea.xml:jsr173-ri:1.0 (transitive of stax-utils).
+10b) Missing artifact com.bea.xml:jsr173-ri:1.0 (transitive of stax-utils).
     Fixed: wildcard <exclusions> on all shared-deps POM entries since
     _shared.txt is already the full transitive closure.
-8c) Empty WARs: packagingExcludes=WEB-INF/lib/*.jar removed everything.
+10c) Empty WARs: packagingExcludes=WEB-INF/lib/*.jar removed everything.
     maven-war-plugin doesn't support ! negation in packagingExcludes.
     Fixed: switched to packagingIncludes (whitelist approach).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose/multiwar/SHARED_CLASSLOADING_ISSUES.txt` around lines 134 -
143, The ISSUE numbering is inconsistent: the header reads "ISSUE 10" while the
bullet items are labeled "8a/8b/8c"; update the identifiers to be consistent by
either changing the header to "ISSUE 8" or renumbering the sub-items to
"10a/10b/10c" (and update any cross-references or indexes that mention these
IDs), ensuring the header and each sub-item label match (e.g., header "ISSUE 10"
with sub-items "10a/10b/10c" or vice versa) so readers and tooling won't be
confused.

Loading