udphole

Basic UDP wormhole proxy
git clone git://git.finwo.net/app/udphole
Log | Files | Refs | README | LICENSE

cluster.js (9537B)


      1 const path = require('path');
      2 const {
      3   spawnDaemon,
      4   killDaemon,
      5   killAllDaemons,
      6   connectApi,
      7   apiCommand,
      8   findFreePort,
      9   sleep,
     10   createUdpEchoServer,
     11   sendUdp,
     12   TIMEOUT
     13 } = require('./helpers');
     14 
     15 const CLUSTER_CONFIG_PATH = path.join(__dirname, 'config-cluster.ini');
     16 const NODE1_CONFIG_PATH = path.join(__dirname, 'config-node1.ini');
     17 const NODE2_CONFIG_PATH = path.join(__dirname, 'config-node2.ini');
     18 const CLUSTER_API_PORT = 19121;
     19 const NODE1_API_PORT = 19122;
     20 const NODE2_API_PORT = 19123;
     21 
     22 async function runTest() {
     23   let cluster = null;
     24   let node1 = null;
     25   let node2 = null;
     26   let clusterApi = null;
     27   let node1Api = null;
     28   let node2Api = null;
     29   let returnCode = 0;
     30   let resp;
     31 
     32   console.log('=== Cluster Test ===\n');
     33 
     34   await killAllDaemons();
     35 
     36   try {
     37     console.log('1. Starting backing daemon node1...');
     38     node1 = await spawnDaemon(NODE1_CONFIG_PATH, 'daemon');
     39     console.log(`   Node1 started (PID: ${node1.pid})`);
     40 
     41     console.log('2. Starting backing daemon node2...');
     42     node2 = await spawnDaemon(NODE2_CONFIG_PATH, 'daemon');
     43     console.log(`   Node2 started (PID: ${node2.pid})`);
     44 
     45     console.log('3. Starting cluster...');
     46     cluster = await spawnDaemon(CLUSTER_CONFIG_PATH, 'cluster');
     47     console.log(`   Cluster started (PID: ${cluster.pid})`);
     48 
     49     await sleep(6000); // Wait for healthcheck to run
     50 
     51     console.log('4. Connecting to cluster API...');
     52     clusterApi = await connectApi(CLUSTER_API_PORT);
     53     console.log('   Connected to cluster');
     54 
     55     console.log('5. Connecting to node1 API...');
     56     node1Api = await connectApi(NODE1_API_PORT);
     57     console.log('   Connected to node1');
     58     resp = await apiCommand(node1Api, 'auth', 'finwo', 'testsecret');
     59     console.log(`   Auth response: ${resp}`);
     60 
     61     console.log('6. Connecting to node2 API...');
     62     node2Api = await connectApi(NODE2_API_PORT);
     63     console.log('   Connected to node2');
     64     resp = await apiCommand(node2Api, 'auth', 'finwo', 'testsecret');
     65     console.log(`   Auth response: ${resp}`);
     66 
     67     console.log('7. Authenticating with cluster...');
     68     resp = await apiCommand(clusterApi, 'auth', 'test', 'testsecret');
     69     console.log(`   Auth response: ${resp}`);
     70     if (resp !== 'OK') throw new Error('Cluster authentication failed');
     71 
     72     console.log('8. Testing session.count on cluster (no sessions)...');
     73     resp = await apiCommand(clusterApi, 'session.count');
     74     console.log(`   cluster session.count: ${resp}`);
     75     if (typeof resp !== 'number' || resp !== 0) {
     76       throw new Error(`Expected 0 sessions, got ${resp}`);
     77     }
     78 
     79     console.log('9. Creating session on cluster...');
     80     resp = await apiCommand(clusterApi, 'session.create', 'test-session-1', '60');
     81     console.log(`   session.create: ${resp}`);
     82     if (resp !== 'OK') throw new Error('Failed to create session via cluster');
     83 
     84     console.log('10. Verifying session created on one of the nodes...');
     85     let node1Count = await apiCommand(node1Api, 'session.count');
     86     let node2Count = await apiCommand(node2Api, 'session.count');
     87     console.log(`    Node1 session.count: ${node1Count}, Node2 session.count: ${node2Count}`);
     88     let totalCount = node1Count + node2Count;
     89     if (totalCount !== 1) {
     90       throw new Error(`Expected 1 total session across nodes, got ${totalCount}`);
     91     }
     92 
     93     console.log('11. Testing session.list aggregation...');
     94     resp = await apiCommand(clusterApi, 'session.list');
     95     console.log(`    cluster session.list: ${JSON.stringify(resp)}`);
     96     if (!Array.isArray(resp) || resp.length !== 1) {
     97       throw new Error(`Expected 1 session in list, got ${resp.length}`);
     98     }
     99 
    100     console.log('12. Testing session.count aggregation...');
    101     resp = await apiCommand(clusterApi, 'session.count');
    102     console.log(`    cluster session.count: ${resp}`);
    103     if (resp !== totalCount) {
    104       throw new Error(`Expected ${totalCount} sessions, got ${resp}`);
    105     }
    106 
    107     console.log('13. Creating another session (should go to different node)...');
    108     resp = await apiCommand(clusterApi, 'session.create', 'test-session-2', '60');
    109     console.log(`    session.create: ${resp}`);
    110     if (resp !== 'OK') throw new Error('Failed to create second session');
    111 
    112     console.log('14. Verifying sessions distributed...');
    113     node1Count = await apiCommand(node1Api, 'session.count');
    114     node2Count = await apiCommand(node2Api, 'session.count');
    115     console.log(`    Node1: ${node1Count}, Node2: ${node2Count}`);
    116     if (node1Count + node2Count !== 2) {
    117       throw new Error('Expected 2 total sessions');
    118     }
    119 
    120     console.log('15. Testing session.info routing...');
    121     resp = await apiCommand(clusterApi, 'session.info', 'test-session-1');
    122     console.log(`    session.info: ${JSON.stringify(resp).substring(0, 80)}...`);
    123     if (!Array.isArray(resp)) {
    124       throw new Error('Expected array response for session.info');
    125     }
    126 
    127     console.log('16. Testing session.destroy...');
    128     resp = await apiCommand(clusterApi, 'session.destroy', 'test-session-1');
    129     console.log(`    session.destroy: ${resp}`);
    130     if (resp !== 'OK') throw new Error('Failed to destroy session');
    131 
    132     console.log('17. Verifying session destroyed...');
    133     resp = await apiCommand(clusterApi, 'session.count');
    134     console.log(`    cluster session.count after destroy: ${resp}`);
    135     if (resp !== 1) {
    136       throw new Error(`Expected 1 session after destroy, got ${resp}`);
    137     }
    138 
    139     console.log('18. Testing socket creation on existing session...');
    140     resp = await apiCommand(clusterApi, 'session.socket.create.listen', 'test-session-2', 'socket1');
    141     console.log(`    socket.create.listen: ${JSON.stringify(resp)}`);
    142     if (!Array.isArray(resp) || resp.length < 1) {
    143       throw new Error('Expected port in response');
    144     }
    145 
    146     console.log('18b. Testing advertise address and UDP forwarding...');
    147     const listenPort = resp[0];
    148     const advertiseAddr = resp[1];
    149     console.log(`    Listen port: ${listenPort}, advertise addr: ${advertiseAddr}`);
    150     if (advertiseAddr !== '127.0.0.2' && advertiseAddr !== '127.0.0.3') {
    151       throw new Error(`Expected advertise address 127.0.0.2 or 127.0.0.3, got ${advertiseAddr}`);
    152     }
    153 
    154     console.log('    Starting echo server...');
    155     const echoServer = await createUdpEchoServer();
    156     console.log(`    Echo server on port: ${echoServer.port}`);
    157 
    158     console.log('    Creating connect socket to echo server...');
    159     resp = await apiCommand(clusterApi, 'session.socket.create.connect', 'test-session-2', 'relay', '127.0.0.1', echoServer.port);
    160     console.log(`    socket.create.connect: ${JSON.stringify(resp)}`);
    161 
    162     console.log('    Creating forward: socket1 -> relay...');
    163     resp = await apiCommand(clusterApi, 'session.forward.create', 'test-session-2', 'socket1', 'relay');
    164     console.log(`    forward.create: ${resp}`);
    165 
    166     console.log('    Sending UDP packet to listen socket...');
    167     await sendUdp(listenPort, advertiseAddr, 'hello');
    168     console.log('    Sent "hello"');
    169 
    170     console.log('    Waiting for echo response...');
    171     const messages = echoServer.getMessages();
    172     const start = Date.now();
    173     while (messages.length === 0 && Date.now() - start < TIMEOUT) {
    174       await new Promise(r => setTimeout(r, 50));
    175     }
    176     if (messages.length === 0) {
    177       throw new Error('Timeout: no message received by echo server');
    178     }
    179     const msg = messages[0];
    180     console.log(`    Received: "${msg.data}" from ${msg.rinfo.address}:${msg.rinfo.port}`);
    181     if (msg.data !== 'hello') {
    182       throw new Error(`Expected "hello", got "${msg.data}"`);
    183     }
    184     echoServer.socket.close();
    185 
    186     console.log('19. Testing node failure handling...');
    187     console.log('    Killing node1...');
    188     await killDaemon(node1);
    189     node1Api.end();
    190     node1Api = null;
    191     await sleep(6000); // Wait for healthcheck to detect failure
    192 
    193     console.log('    Creating session while node1 is down...');
    194     resp = await apiCommand(clusterApi, 'session.create', 'test-session-3', '60');
    195     console.log(`    session.create: ${resp}`);
    196     if (resp !== 'OK') throw new Error('Failed to create session while node1 down');
    197 
    198     console.log('    Verifying session via cluster session.list...');
    199     resp = await apiCommand(clusterApi, 'session.list');
    200     console.log('    cluster session.list:', resp);
    201     if (!Array.isArray(resp) || resp.length < 1) {
    202       throw new Error('Expected at least 1 session in cluster');
    203     }
    204 
    205     console.log('    Restarting node1...');
    206     node1 = await spawnDaemon(NODE1_CONFIG_PATH, 'daemon');
    207     console.log(`    Node1 restarted (PID: ${node1.pid})`);
    208     await sleep(1000);
    209     node1Api = await connectApi(NODE1_API_PORT);
    210     await apiCommand(node1Api, 'auth', 'finwo', 'testsecret');
    211     await sleep(6000); // Wait for healthcheck to detect recovery
    212 
    213     console.log('    Creating another session after node1 recovery...');
    214     resp = await apiCommand(clusterApi, 'session.create', 'test-session-4', '60');
    215     console.log(`    session.create: ${resp}`);
    216     if (resp !== 'OK') throw new Error('Failed to create session after node1 recovery');
    217 
    218     console.log('\n✓ PASS: All cluster tests passed');
    219 
    220   } catch (err) {
    221     console.error(`\n✗ FAIL: ${err.message}`);
    222     console.error(err.stack);
    223     returnCode = 1;
    224   } finally {
    225     if (clusterApi) clusterApi.end();
    226     if (node1Api) node1Api.end();
    227     if (node2Api) node2Api.end();
    228     if (cluster) await killDaemon(cluster);
    229     if (node1) await killDaemon(node1);
    230     if (node2) await killDaemon(node2);
    231     await killAllDaemons();
    232     process.exit(returnCode);
    233   }
    234 }
    235 
    236 runTest();