7
7
import java .net .URL ;
8
8
import java .util .Arrays ;
9
9
import java .util .Collections ;
10
+ import java .util .HashMap ;
10
11
import java .util .List ;
11
12
import java .util .Map ;
12
13
import java .util .concurrent .CompletableFuture ;
13
14
import java .util .concurrent .ExecutionException ;
14
- import java .util .concurrent .Future ;
15
15
import java .util .concurrent .TimeoutException ;
16
+ import java .util .function .Supplier ;
16
17
17
18
import static org .testcontainers .containers .PathUtils .normalizePath ;
18
19
@@ -102,8 +103,11 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
102
103
private static final String ENV_TARANTOOL_RUNDIR = "TARANTOOL_RUNDIR" ;
103
104
private static final String ENV_TARANTOOL_DATADIR = "TARANTOOL_DATADIR" ;
104
105
private static final String ENV_TARANTOOL_INSTANCES_FILE = "TARANTOOL_INSTANCES_FILE" ;
106
+
105
107
private final CartridgeConfigParser instanceFileParser ;
106
108
private final TarantoolContainerClientHelper clientHelper ;
109
+ private final String TARANTOOL_RUN_DIR ;
110
+
107
111
private boolean useFixedPorts = false ;
108
112
private String routerHost = ROUTER_HOST ;
109
113
private int routerPort = ROUTER_PORT ;
@@ -113,7 +117,6 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
113
117
private String directoryResourcePath = SCRIPT_RESOURCE_DIRECTORY ;
114
118
private String instanceDir = INSTANCE_DIR ;
115
119
private String topologyConfigurationFile ;
116
- private String replicasetsFileName ;
117
120
118
121
/**
119
122
* Create a container with default image and specified instances file from the classpath resources. Assumes that
@@ -181,35 +184,39 @@ public TarantoolCartridgeContainer(String dockerFile, String buildImageName,
181
184
*/
182
185
public TarantoolCartridgeContainer (String dockerFile , String buildImageName , String instancesFile ,
183
186
String topologyConfigurationFile , final Map <String , String > buildArgs ) {
184
- this (withArguments (buildImage (dockerFile , buildImageName ), instancesFile , buildArgs ),
185
- instancesFile , topologyConfigurationFile );
187
+ this (buildImage (dockerFile , buildImageName ), instancesFile , topologyConfigurationFile , buildArgs );
186
188
}
187
189
190
+ private TarantoolCartridgeContainer (ImageFromDockerfile image , String instancesFile , String topologyConfigurationFile ,
191
+ Map <String , String > buildArgs ) {
192
+ super (withBuildArgs (image , buildArgs ));
193
+
194
+ TARANTOOL_RUN_DIR = mergeBuildArguments (buildArgs ).getOrDefault (ENV_TARANTOOL_RUNDIR , "/tmp/run" );
188
195
189
- private TarantoolCartridgeContainer (Future <String > image , String instancesFile , String topologyConfigurationFile ) {
190
- super (image );
191
196
if (instancesFile == null || instancesFile .isEmpty ()) {
192
197
throw new IllegalArgumentException ("Instance file name must not be null or empty" );
193
198
}
194
199
if (topologyConfigurationFile == null || topologyConfigurationFile .isEmpty ()) {
195
200
throw new IllegalArgumentException ("Topology configuration file must not be null or empty" );
196
201
}
197
- String fileType = topologyConfigurationFile .substring (topologyConfigurationFile .lastIndexOf ('.' ) + 1 );
198
- if (fileType .equals ("lua" )) {
199
- this .topologyConfigurationFile = topologyConfigurationFile ;
200
- }else {
201
- this .replicasetsFileName = topologyConfigurationFile .substring (topologyConfigurationFile .lastIndexOf ('/' )+1 );
202
- }
202
+ this .topologyConfigurationFile = topologyConfigurationFile ;
203
203
this .instanceFileParser = new CartridgeConfigParser (instancesFile );
204
204
this .clientHelper = new TarantoolContainerClientHelper (this );
205
205
}
206
206
207
- private static Future <String > withArguments (ImageFromDockerfile image , String instancesFile ,
208
- final Map <String , String > buildArgs ) {
209
- if (!buildArgs .isEmpty ()) {
210
- image .withBuildArgs (buildArgs );
207
+ private static ImageFromDockerfile withBuildArgs (ImageFromDockerfile image , Map <String , String > buildArgs ) {
208
+ Map <String , String > args = mergeBuildArguments (buildArgs );
209
+
210
+ if (!args .isEmpty ()) {
211
+ image .withBuildArgs (args );
211
212
}
212
213
214
+ return image ;
215
+ }
216
+
217
+ private static Map <String , String > mergeBuildArguments (Map <String , String > buildArgs ) {
218
+ Map <String , String > args = new HashMap <>(buildArgs );
219
+
213
220
for (String envVariable : Arrays .asList (
214
221
ENV_TARANTOOL_VERSION ,
215
222
ENV_TARANTOOL_SERVER_USER ,
@@ -222,11 +229,11 @@ private static Future<String> withArguments(ImageFromDockerfile image, String in
222
229
ENV_TARANTOOL_INSTANCES_FILE
223
230
)) {
224
231
String variableValue = System .getenv (envVariable );
225
- if (variableValue != null ) {
226
- image . withBuildArg (envVariable , variableValue );
232
+ if (variableValue != null && ! args . containsKey ( envVariable ) ) {
233
+ args . put (envVariable , variableValue );
227
234
}
228
235
}
229
- return image ;
236
+ return args ;
230
237
}
231
238
232
239
private static ImageFromDockerfile buildImage (String dockerFile , String buildImageName ) {
@@ -458,38 +465,25 @@ protected void containerIsStarting(InspectContainerResponse containerInfo) {
458
465
}
459
466
460
467
private boolean setupTopology () {
461
- if (topologyConfigurationFile == null ) {
462
- String runDirPath = null ;
463
- try {
464
- Container .ExecResult envVariablesContainer = this .execInContainer ("env" );
465
- String stdout = envVariablesContainer .getStdout ();
466
- int exitCode = envVariablesContainer .getExitCode ();
467
- if (exitCode != 0 ) {
468
- logger ().error ("Failed to bootstrap replica sets topology: {}" , stdout );
469
- }
470
- int startInd = stdout .lastIndexOf (ENV_TARANTOOL_RUNDIR + "=" );
471
- try {
472
- runDirPath = stdout .substring (startInd ,
473
- stdout .indexOf ('\n' , startInd )).split ("=" )[1 ];
474
- } catch (Exception e ) {
475
- logger ().error ("Missing dir-run environment variable: {}" , e .getMessage ());
476
- }
468
+ String fileType = topologyConfigurationFile .substring (topologyConfigurationFile .lastIndexOf ('.' ) + 1 );
477
469
478
- } catch ( Exception e ) {
479
- logger (). error ( "Failed to get environment variables: {}" , e . getMessage ());
480
- }
470
+ if ( fileType . equals ( "yml" ) ) {
471
+ String replicasetsFileName = topologyConfigurationFile
472
+ . substring ( topologyConfigurationFile . lastIndexOf ( '/' ) + 1 );
481
473
482
474
try {
483
- Container .ExecResult lsResult = this .execInContainer ("cartridge" , "replicasets" , "--run-dir=" + runDirPath ,
484
- "--file=" + this .replicasetsFileName , "setup" , "--bootstrap-vshard" );
485
- String stdout = lsResult .getStdout ();
486
- int exitCode = lsResult .getExitCode ();
487
- if (exitCode != 0 ) {
488
- logger ().error ("Failed to bootstrap replica sets topology: {}" , stdout );
475
+ Container .ExecResult result = execInContainer ("cartridge" ,
476
+ "replicasets" ,
477
+ "--run-dir=" + TARANTOOL_RUN_DIR ,
478
+ "--file=" + replicasetsFileName , "setup" , "--bootstrap-vshard" );
479
+ if (result .getExitCode () != 0 ) {
480
+ throw new RuntimeException ("Failed to change the app topology via cartridge CLI: "
481
+ + result .getStdout ());
489
482
}
490
483
} catch (Exception e ) {
491
- logger (). error ( "Failed to bootstrap replica sets topology: {}" , e .getMessage ());
484
+ throw new RuntimeException ( "Failed to change the app topology: " + e .getMessage ());
492
485
}
486
+
493
487
} else {
494
488
try {
495
489
executeScript (topologyConfigurationFile ).get ();
@@ -539,14 +533,65 @@ private void bootstrapVshard() {
539
533
protected void containerIsStarted (InspectContainerResponse containerInfo , boolean reused ) {
540
534
super .containerIsStarted (containerInfo , reused );
541
535
536
+ waitUntilRouterIsUp (60 );
542
537
retryingSetupTopology ();
538
+ // wait until Roles are configured
539
+ waitUntilCartridgeIsHealthy (10 );
543
540
bootstrapVshard ();
544
541
545
542
logger ().info ("Tarantool Cartridge cluster is started" );
546
543
logger ().info ("Tarantool Cartridge router is listening at {}:{}" , getRouterHost (), getRouterPort ());
547
544
logger ().info ("Tarantool Cartridge HTTP API is available at {}:{}" , getAPIHost (), getAPIPort ());
548
545
}
549
546
547
+ private void waitUntilRouterIsUp (int secondsToWait ) {
548
+ waitUntilTrue (secondsToWait , this ::routerIsUp );
549
+ }
550
+
551
+ private void waitUntilCartridgeIsHealthy (int secondsToWait ) {
552
+ waitUntilTrue (secondsToWait , this ::isCartridgeHealthy );
553
+ }
554
+
555
+ private void waitUntilTrue (int secondsToWait , Supplier <Boolean > waitFunc ) {
556
+ int secondsPassed = 0 ;
557
+ boolean result = waitFunc .get ();
558
+ while (!result && secondsPassed < secondsToWait ) {
559
+ result = waitFunc .get ();
560
+ try {
561
+ Thread .sleep (1000 );
562
+ } catch (InterruptedException e ) {
563
+ break ;
564
+ }
565
+ }
566
+ if (!result ) {
567
+ throw new RuntimeException ("Failed to change the app topology after retry" );
568
+ }
569
+ }
570
+
571
+ private boolean routerIsUp () {
572
+ String healthyCmd = " local cartridge = package.loaded['cartridge']" +
573
+ " return assert(cartridge ~= nil)" ;
574
+ try {
575
+ List <?> result = executeCommand (healthyCmd ).get ();
576
+ return (Boolean ) result .get (0 );
577
+ } catch (Exception e ) {
578
+ logger ().warn ("Error while waiting for router instance to be up: " + e .getMessage ());
579
+ return false ;
580
+ }
581
+ }
582
+
583
+ private boolean isCartridgeHealthy () {
584
+ String healthyCmd = " local cartridge = package.loaded['cartridge']" +
585
+ " return assert(cartridge) and assert(cartridge.is_healthy())" ;
586
+ try {
587
+ List <?> result = executeCommand (healthyCmd ).get ();
588
+ return (Boolean ) result .get (0 );
589
+ } catch (Exception e ) {
590
+ logger ().warn ("Error while waiting for cartridge healthy state: " + e .getMessage ());
591
+ return false ;
592
+ }
593
+ }
594
+
550
595
@ Override
551
596
public CompletableFuture <List <?>> executeScript (String scriptResourcePath ) throws Exception {
552
597
return clientHelper .executeScript (scriptResourcePath );
0 commit comments