blob: 13d35373cd4a981a438cbf02c4db50bddb36bf84 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SessionPool } from '../../src/client/SessionPool';
describe('Multi-Node E2E Tests', () => {
const IOTDB_HOST = process.env.IOTDB_HOST || 'localhost';
const IOTDB_PORT_1 = parseInt(process.env.IOTDB_PORT || '6667', 10);
const IOTDB_PORT_2 = parseInt(process.env.IOTDB_PORT_2 || '6668', 10);
const IOTDB_PORT_3 = parseInt(process.env.IOTDB_PORT_3 || '6669', 10);
const IOTDB_USER = process.env.IOTDB_USER || 'root';
const IOTDB_PASSWORD = process.env.IOTDB_PASSWORD || 'root';
const IS_MULTI_NODE = process.env.MULTI_NODE === 'true';
let pool1: SessionPool;
let pool2: SessionPool;
let pool3: SessionPool;
let isConnected = false;
beforeAll(async () => {
// Skip multi-node tests if not in multi-node environment
if (!IS_MULTI_NODE) {
console.log('Skipping Multi-Node tests - not in multi-node environment (MULTI_NODE env var not set)');
return;
}
// For 3C3D setup, connect to all three DataNode ports
// This enables true multi-node load distribution and testing
console.log(`Connecting to IoTDB cluster:`);
console.log(` DataNode 1: ${IOTDB_HOST}:${IOTDB_PORT_1}`);
console.log(` DataNode 2: ${IOTDB_HOST}:${IOTDB_PORT_2}`);
console.log(` DataNode 3: ${IOTDB_HOST}:${IOTDB_PORT_3}`);
try {
// Create three pools, each connected to a different DataNode
pool1 = new SessionPool(IOTDB_HOST, IOTDB_PORT_1, {
username: IOTDB_USER,
password: IOTDB_PASSWORD,
maxPoolSize: 5,
minPoolSize: 2,
});
pool2 = new SessionPool(IOTDB_HOST, IOTDB_PORT_2, {
username: IOTDB_USER,
password: IOTDB_PASSWORD,
maxPoolSize: 5,
minPoolSize: 2,
});
pool3 = new SessionPool(IOTDB_HOST, IOTDB_PORT_3, {
username: IOTDB_USER,
password: IOTDB_PASSWORD,
maxPoolSize: 5,
minPoolSize: 2,
});
await pool1.init();
await pool2.init();
await pool3.init();
isConnected = true;
console.log(`Successfully connected to IoTDB cluster on ports ${IOTDB_PORT_1}, ${IOTDB_PORT_2}, ${IOTDB_PORT_3}`);
} catch (error) {
console.warn('Could not connect to IoTDB. Multi-node E2E tests will be skipped.');
console.warn('Error:', error);
}
}, 60000);
afterAll(async () => {
if (!IS_MULTI_NODE) {
return;
}
if (isConnected) {
try {
await pool1.executeNonQueryStatement('DROP DATABASE root.multinode_test');
} catch (error) {
// Ignore cleanup errors
}
await pool1.close();
await pool2.close();
await pool3.close();
}
}, 60000);
test('Should initialize pools with connections to all three DataNodes', async () => {
if (!IS_MULTI_NODE || !isConnected) {
console.log('Skipping test - not in multi-node environment or no IoTDB connection');
return;
}
expect(pool1.getPoolSize()).toBeGreaterThanOrEqual(2);
expect(pool2.getPoolSize()).toBeGreaterThanOrEqual(2);
expect(pool3.getPoolSize()).toBeGreaterThanOrEqual(2);
console.log(`Pools initialized: DataNode1=${pool1.getPoolSize()}, DataNode2=${pool2.getPoolSize()}, DataNode3=${pool3.getPoolSize()} connections`);
});
test('Should create database and timeseries on first node', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
try {
await pool1.executeNonQueryStatement('CREATE DATABASE root.multinode_test');
} catch (error: any) {
if (!error.message.includes('already exists')) {
throw error;
}
}
await pool1.executeNonQueryStatement(
'CREATE TIMESERIES root.multinode_test.device1.temperature WITH DATATYPE=FLOAT'
);
await pool1.executeNonQueryStatement(
'CREATE TIMESERIES root.multinode_test.device1.humidity WITH DATATYPE=FLOAT'
);
});
test('Should handle concurrent load distributed across all three DataNodes', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
const operationsPerNode = 7;
const promises: Promise<any>[] = [];
// Distribute operations across all three DataNode pools
for (let i = 0; i < operationsPerNode; i++) {
// Insert to DataNode 1
promises.push(
pool1.insertTablet({
deviceId: 'root.multinode_test.device1',
measurements: ['temperature', 'humidity'],
dataTypes: [3, 3],
timestamps: [Date.now() + i * 1000],
values: [[20 + i * 0.1, 60 + i * 0.2]],
})
);
// Query from DataNode 2
promises.push(
pool2.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 10')
);
// Insert to DataNode 3
promises.push(
pool3.insertTablet({
deviceId: 'root.multinode_test.device1',
measurements: ['temperature', 'humidity'],
dataTypes: [3, 3],
timestamps: [Date.now() + i * 1000 + 500],
values: [[21 + i * 0.1, 61 + i * 0.2]],
})
);
}
const results = await Promise.all(promises);
expect(results).toHaveLength(operationsPerNode * 3);
console.log(`Completed ${operationsPerNode * 3} concurrent operations across 3 DataNodes`);
console.log(`Pool stats: DN1=${pool1.getPoolSize()}/${pool1.getAvailableSize()}, DN2=${pool2.getPoolSize()}/${pool2.getAvailableSize()}, DN3=${pool3.getPoolSize()}/${pool3.getAvailableSize()}`);
});
test('Should verify data replication across all DataNodes', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
// Insert data through DataNode 1
await pool1.insertTablet({
deviceId: 'root.multinode_test.device1',
measurements: ['temperature', 'humidity'],
dataTypes: [3, 3],
timestamps: [Date.now()],
values: [[99.9, 99.9]],
});
// Wait a bit for replication
await new Promise(resolve => setTimeout(resolve, 2000));
// Query from all three DataNodes - should see the same data
const result1 = await pool1.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1');
const result2 = await pool2.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1');
const result3 = await pool3.executeQueryStatement('SELECT COUNT(*) FROM root.multinode_test.device1');
expect(result1.rows.length).toBeGreaterThan(0);
expect(result2.rows.length).toBeGreaterThan(0);
expect(result3.rows.length).toBeGreaterThan(0);
console.log(`Data replicated across all DataNodes - verified queries from ports 6667, 6668, 6669`);
});
test('Should handle large batch inserts across multiple DataNodes', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
const batchSize = 100;
const timestamps: number[] = [];
const values: number[][] = [];
const baseTime = Date.now();
for (let i = 0; i < batchSize; i++) {
timestamps.push(baseTime + i * 1000);
values.push([25 + Math.random() * 5, 65 + Math.random() * 10]);
}
// Insert through different DataNodes
await pool1.insertTablet({
deviceId: 'root.multinode_test.device1',
measurements: ['temperature', 'humidity'],
dataTypes: [3, 3],
timestamps,
values,
});
// Verify data from different DataNode
const result = await pool2.executeQueryStatement(
'SELECT COUNT(*) FROM root.multinode_test.device1'
);
expect(result.rows.length).toBeGreaterThan(0);
console.log(`Inserted ${batchSize} records via DataNode1, queried via DataNode2`);
});
test('Should maintain all pool healths under stress', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
const initialSize1 = pool1.getPoolSize();
const initialSize2 = pool2.getPoolSize();
const initialSize3 = pool3.getPoolSize();
// Execute many operations across all DataNodes
const promises: Promise<any>[] = [];
for (let i = 0; i < 30; i++) {
promises.push(pool1.executeQueryStatement('SHOW DATABASES'));
promises.push(pool2.executeQueryStatement('SHOW DATABASES'));
promises.push(pool3.executeQueryStatement('SHOW DATABASES'));
}
await Promise.all(promises);
// Pools should maintain their sizes
expect(pool1.getPoolSize()).toBeGreaterThanOrEqual(initialSize1);
expect(pool2.getPoolSize()).toBeGreaterThanOrEqual(initialSize2);
expect(pool3.getPoolSize()).toBeGreaterThanOrEqual(initialSize3);
console.log('All three pool healths maintained after stress test');
});
test('Should handle queries across all DataNodes simultaneously', async () => {
if (!isConnected) {
console.log('Skipping test - no IoTDB connection');
return;
}
// Execute queries simultaneously on all three DataNodes
const [result1, result2, result3] = await Promise.all([
pool1.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'),
pool2.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'),
pool3.executeQueryStatement('SELECT * FROM root.multinode_test.device1 LIMIT 5'),
]);
expect(result1.rows.length).toBeGreaterThan(0);
expect(result2.rows.length).toBeGreaterThan(0);
expect(result3.rows.length).toBeGreaterThan(0);
console.log(`Simultaneous queries across 3 DataNodes: DN1=${result1.rows.length}, DN2=${result2.rows.length}, DN3=${result3.rows.length} rows`);
});
});