@@ -9,6 +9,7 @@ import { promisify } from 'node:util';
99import * as fs from 'node:fs' ;
1010import * as os from 'node:os' ;
1111import * as path from 'node:path' ;
12+ import assert from 'node:assert' ;
1213
1314const execAsync = promisify ( execFileCallback ) ;
1415
@@ -116,6 +117,69 @@ options: RedisServerDockerOptions, serverArguments: Array<string>): Promise<Redi
116117}
117118const RUNNING_SERVERS = new Map < Array < string > , ReturnType < typeof spawnRedisServerDocker > > ( ) ;
118119
120+ export interface ProxiedRedisServerDocker {
121+ ports : number [ ] ,
122+ dockerId : string
123+ }
124+
125+ export interface ProxiedRedisServerConfig {
126+ nOfProxies : number ,
127+ defaultInterceptors : ( 'cluster' | 'hitless' | 'logger' ) [ ]
128+ }
129+
130+ const RUNNING_PROXIED_SERVERS = new Map < string , Promise < ProxiedRedisServerDocker > > ( ) ;
131+
132+ export async function spawnProxiedRedisServer ( config : ProxiedRedisServerConfig ) : Promise < ProxiedRedisServerDocker > {
133+ const key = JSON . stringify ( config ) ;
134+ const runningServer = RUNNING_PROXIED_SERVERS . get ( key ) ;
135+ if ( runningServer ) {
136+ return runningServer ;
137+ }
138+
139+ const server = spawnProxiedRedisServerDocker ( config ) ;
140+ RUNNING_PROXIED_SERVERS . set ( key , server ) ;
141+ return server ;
142+ }
143+
144+ export async function spawnProxiedRedisServerDocker (
145+ config : ProxiedRedisServerConfig ,
146+ ) : Promise < ProxiedRedisServerDocker > {
147+
148+ assert ( config . nOfProxies > 0 , 'At least one proxy should be started' ) ;
149+ const ports : number [ ] = [ ] ;
150+ for ( let i = 0 ; i < config . nOfProxies ; i ++ ) {
151+ ports . push ( ( await portIterator . next ( ) ) . value ) ;
152+ }
153+
154+ const dockerArgs = [
155+ "run" ,
156+ "-d" ,
157+ "--network" , "host" ,
158+ "-e" , `LISTEN_PORT=${ ports . join ( ',' ) } ` ,
159+ "-e" , "TIEOUT=0" ,
160+ "-e" , `DEFAULT_INTERCEPTORS=${ config . defaultInterceptors . join ( ',' ) } ` ,
161+ "-e" , "ENABLE_LOGGING=true" ,
162+ "cae-resp-proxy-standalone"
163+ ]
164+
165+ console . log ( `[Docker] Spawning Proxy container` , dockerArgs . join ( ' ' ) ) ;
166+
167+ const { stdout, stderr } = await execAsync ( "docker" , dockerArgs ) ;
168+
169+ if ( ! stdout ) {
170+ throw new Error ( `docker run error - ${ stderr } ` ) ;
171+ }
172+
173+ while ( await isPortAvailable ( ports [ 0 ] ) ) {
174+ await setTimeout ( 50 ) ;
175+ }
176+
177+ return {
178+ ports,
179+ dockerId : stdout . trim ( ) ,
180+ } ;
181+ }
182+
119183export function spawnRedisServer ( dockerConfig : RedisServerDockerOptions , serverArguments : Array < string > ) : Promise < RedisServerDocker > {
120184 const runningServer = RUNNING_SERVERS . get ( serverArguments ) ;
121185 if ( runningServer ) {
@@ -319,7 +383,7 @@ export async function spawnRedisSentinel(
319383 if ( runningNodes ) {
320384 return runningNodes ;
321385 }
322-
386+
323387 const passIndex = serverArguments . indexOf ( '--requirepass' ) + 1 ;
324388 let password : string | undefined = undefined ;
325389 if ( passIndex != 0 ) {
@@ -329,7 +393,7 @@ export async function spawnRedisSentinel(
329393 const master = await spawnRedisServerDocker ( dockerConfigs , serverArguments ) ;
330394 const redisNodes : Array < RedisServerDocker > = [ master ] ;
331395 const replicaPromises : Array < Promise < RedisServerDocker > > = [ ] ;
332-
396+
333397 const replicasCount = 2 ;
334398 for ( let i = 0 ; i < replicasCount ; i ++ ) {
335399 replicaPromises . push ( ( async ( ) => {
@@ -344,37 +408,37 @@ export async function spawnRedisSentinel(
344408 await client . connect ( ) ;
345409 await client . replicaOf ( "127.0.0.1" , master . port ) ;
346410 await client . close ( ) ;
347-
411+
348412 return replica ;
349413 } ) ( ) ) ;
350414 }
351-
415+
352416 const replicas = await Promise . all ( replicaPromises ) ;
353417 redisNodes . push ( ...replicas ) ;
354418 RUNNING_NODES . set ( serverArguments , redisNodes ) ;
355419
356420 const sentinelPromises : Array < Promise < RedisServerDocker > > = [ ] ;
357421 const sentinelCount = 3 ;
358-
422+
359423 const appPrefix = 'sentinel-config-dir' ;
360424 const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , appPrefix ) ) ;
361425
362426 for ( let i = 0 ; i < sentinelCount ; i ++ ) {
363427 sentinelPromises . push (
364428 spawnSentinelNode (
365- dockerConfigs ,
366- serverArguments ,
429+ dockerConfigs ,
430+ serverArguments ,
367431 master . port ,
368432 "mymaster" ,
369433 path . join ( tmpDir , i . toString ( ) ) ,
370434 password ,
371435 ) ,
372436 )
373437 }
374-
438+
375439 const sentinelNodes = await Promise . all ( sentinelPromises ) ;
376440 RUNNING_SENTINELS . set ( serverArguments , sentinelNodes ) ;
377-
441+
378442 if ( tmpDir ) {
379443 fs . rmSync ( tmpDir , { recursive : true } ) ;
380444 }
@@ -396,14 +460,17 @@ after(() => {
396460 ...Array . from ( RUNNING_SENTINELS . values ( ) ) . map ( dockersPromise =>
397461 Promise . all ( dockersPromise . map ( ( { dockerId } ) => dockerRemove ( dockerId ) ) )
398462 ) ,
463+ ...Array . from ( RUNNING_PROXIED_SERVERS . values ( ) ) . map ( async dockerPromise =>
464+ dockerRemove ( ( await dockerPromise ) . dockerId )
465+ )
399466 ] ) ;
400467} ) ;
401468
402469
403470export async function spawnSentinelNode (
404471 dockerConfigs : RedisServerDockerOptions ,
405472 serverArguments : Array < string > ,
406- masterPort : number ,
473+ masterPort : number ,
407474 sentinelName : string ,
408475 tmpDir : string ,
409476 password ?: string ,
@@ -429,12 +496,12 @@ sentinel failover-timeout ${sentinelName} 1000
429496
430497 return await spawnRedisServerDocker (
431498 {
432- image : dockerConfigs . image ,
433- version : dockerConfigs . version ,
499+ image : dockerConfigs . image ,
500+ version : dockerConfigs . version ,
434501 mode : "sentinel" ,
435- mounts : [ `${ dir } /redis.conf:/redis/config/node-sentinel-1/redis.conf` ] ,
502+ mounts : [ `${ dir } /redis.conf:/redis/config/node-sentinel-1/redis.conf` ] ,
436503 port : port ,
437- } ,
504+ } ,
438505 serverArguments ,
439506 ) ;
440- }
507+ }
0 commit comments