Skip to content
Merged
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
11 changes: 11 additions & 0 deletions mysql-dual-conn/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-dual-conn
environment:
MYSQL_ROOT_PASSWORD: rootpass
ports:
- "3306:3306"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
10 changes: 10 additions & 0 deletions mysql-dual-conn/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE DATABASE IF NOT EXISTS myntra_oms;
CREATE DATABASE IF NOT EXISTS camunda;

CREATE USER IF NOT EXISTS 'omsAppUser'@'%' IDENTIFIED BY 'omsPassword';
GRANT ALL PRIVILEGES ON myntra_oms.* TO 'omsAppUser'@'%';

CREATE USER IF NOT EXISTS 'stagebuster'@'%' IDENTIFIED BY 'camundaPassword';
GRANT ALL PRIVILEGES ON camunda.* TO 'stagebuster'@'%';

FLUSH PRIVILEGES;
48 changes: 48 additions & 0 deletions mysql-dual-conn/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>mysql-dual-conn</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mysql-dual-conn</name>
<description>E2E test for Keploy MySQL multi-connection handshake matching</description>
Comment on lines +14 to +18
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

This new sample module doesn't include a README.md with run instructions (docker compose up, how to run the app, and which endpoints to hit). Other sample apps in this repo include per-module READMEs, and having one here will make the E2E scenario reproducible for contributors and CI debugging.

Copilot uses AI. Check for mistakes.

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.example.mysqlreplicate;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
* Replicates the multi-datasource setup that triggers the Keploy
* "no mysql mocks matched the HandshakeResponse41" error.
*
* Two HikariCP pools connect to the SAME MySQL server but with
* DIFFERENT usernames and databases. During Keploy test replay,
* each new TCP connection triggers simulateInitialHandshake which
* must match the client's HandshakeResponse41 against recorded
* config mocks. The mismatch occurs because:
*
* 1. mocks[0] (always omsAppUser/myntra_oms) is used to send the
* server greeting to ALL incoming connections.
* 2. When the camunda pool connects, its HandshakeResponse41 has
* username=stagebuster, database=camunda, and different
* capability_flags (423535119 vs 20881935).
* 3. The matcher loops all config mocks looking for a match on
* username + database + capability_flags + charset + filler.
* If no recorded config mock matches those exact fields, the
* error fires.
*/
@Configuration
public class DataSourceConfig {

// ---- OMS pool (matches omsAppUser / myntra_oms mocks) ----

@Value("${datasource.oms.jdbc-url}")
private String omsJdbcUrl;

@Value("${datasource.oms.username}")
private String omsUsername;

@Value("${datasource.oms.password}")
private String omsPassword;

@Value("${datasource.oms.driver-class-name}")
private String omsDriverClass;

// ---- Camunda pool (matches stagebuster / camunda mocks) ----

@Value("${datasource.camunda.jdbc-url}")
private String camundaJdbcUrl;

@Value("${datasource.camunda.username}")
private String camundaUsername;

@Value("${datasource.camunda.password}")
private String camundaPassword;

@Value("${datasource.camunda.driver-class-name}")
private String camundaDriverClass;

@Bean(name = "omsDataSource", destroyMethod = "close")
@Primary
public HikariDataSource omsDataSource() {
HikariConfig config = new HikariConfig();
config.setPoolName("oms-dataSource");
config.setUsername(omsUsername);
config.setPassword(omsPassword);
return buildDataSource(config, 5, omsJdbcUrl, omsDriverClass);
}

@Bean(name = "camundaDataSource", destroyMethod = "close")
public HikariDataSource camundaDataSource() {
HikariConfig config = new HikariConfig();
config.setPoolName("camunda-dataSource");
config.setUsername(camundaUsername);
config.setPassword(camundaPassword);
return buildDataSource(config, 5, camundaJdbcUrl, camundaDriverClass);
}

@Bean(name = "omsJdbcTemplate")
@Primary
public JdbcTemplate omsJdbcTemplate() {
return new JdbcTemplate(omsDataSource());
}

@Bean(name = "camundaJdbcTemplate")
public JdbcTemplate camundaJdbcTemplate() {
return new JdbcTemplate(camundaDataSource());
}

private HikariDataSource buildDataSource(HikariConfig config, int maxConns,
String jdbcUrl, String driverClass) {
config.setMaximumPoolSize(maxConns);
config.setMinimumIdle(2);
config.setKeepaliveTime(5000);
config.setIdleTimeout(10000);
config.setConnectionTimeout(5000);
config.setValidationTimeout(2000);
config.setMaxLifetime(7200000);
config.setLeakDetectionThreshold(2000);
Comment on lines +96 to +103
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The HikariCP pool is configured with very aggressive timing/leak-detection settings (e.g., keepalive/connection/validation timeouts and a 2s leak detection threshold). For CI/E2E usage this can make startup and runs flaky on slower hosts and may generate noisy pool activity/logs unrelated to the handshake scenario being tested. Consider relying on Hikari defaults here, or at least relaxing these values and making them configurable via properties.

Copilot uses AI. Check for mistakes.
config.setDriverClassName(driverClass);
config.setJdbcUrl(jdbcUrl);
return new HikariDataSource(config);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.mysqlreplicate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MysqlReplicateApplication {
public static void main(String[] args) {
SpringApplication.run(MysqlReplicateApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example.mysqlreplicate;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Simple REST controller that queries both datasources.
* Each endpoint triggers a DB query that forces a connection
* from the respective pool — reproducing the multi-handshake
* scenario during Keploy replay.
*/
@RestController
public class QueryController {

private final JdbcTemplate omsJdbc;
private final JdbcTemplate camundaJdbc;

public QueryController(@Qualifier("omsJdbcTemplate") JdbcTemplate omsJdbc,
@Qualifier("camundaJdbcTemplate") JdbcTemplate camundaJdbc) {
this.omsJdbc = omsJdbc;
this.camundaJdbc = camundaJdbc;
}

/**
* Queries both databases, triggering connections from both pools.
* During Keploy test mode this forces two distinct HandshakeResponse41
* packets with different username/database/capability_flags values.
*/
@GetMapping("/api/query-both")
public Map<String, Object> queryBoth() {
Map<String, Object> result = new HashMap<>();

// OMS query — user=omsAppUser, db=myntra_oms
List<Map<String, Object>> omsResult = omsJdbc.queryForList("SELECT 1 AS oms_check");
result.put("oms", omsResult);
Comment on lines +36 to +41
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

queryBoth() builds the response with a HashMap, which doesn't guarantee key iteration order. Since these sample apps are used for record/replay style E2E tests, a nondeterministic JSON object key order can introduce flaky response matching. Prefer a deterministic map implementation (e.g., insertion-ordered) for the response payload.

Copilot uses AI. Check for mistakes.

// Camunda query — user=stagebuster, db=camunda
List<Map<String, Object>> camundaResult = camundaJdbc.queryForList("SELECT 1 AS camunda_check");
result.put("camunda", camundaResult);

return result;
}

@GetMapping("/api/oms")
public List<Map<String, Object>> queryOms() {
return omsJdbc.queryForList("SELECT 1 AS oms_check");
}

@GetMapping("/api/camunda")
public List<Map<String, Object>> queryCamunda() {
return camundaJdbc.queryForList("SELECT 1 AS camunda_check");
}
}
15 changes: 15 additions & 0 deletions mysql-dual-conn/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server.port=8080

# --- OMS DataSource (primary) ---
datasource.oms.jdbc-url=jdbc:mysql://localhost:3306/myntra_oms?useSSL=false
datasource.oms.username=omsAppUser
datasource.oms.password=omsPassword
datasource.oms.driver-class-name=com.mysql.cj.jdbc.Driver

# --- Camunda DataSource (secondary, different user & capability flags) ---
# allowPublicKeyRetrieval=true causes different MySQL capability flags,
# which is the key condition that triggers the multi-handshake matching bug.
datasource.camunda.jdbc-url=jdbc:mysql://localhost:3306/camunda?useSSL=false&allowPublicKeyRetrieval=true
datasource.camunda.username=stagebuster
datasource.camunda.password=camundaPassword
datasource.camunda.driver-class-name=com.mysql.cj.jdbc.Driver
Loading