77import java .net .URL ;
88import java .util .Arrays ;
99import java .util .Collections ;
10+ import java .util .HashMap ;
1011import java .util .List ;
1112import java .util .Map ;
1213import java .util .concurrent .CompletableFuture ;
1314import java .util .concurrent .ExecutionException ;
14- import java .util .concurrent .Future ;
1515import java .util .concurrent .TimeoutException ;
16+ import java .util .function .Supplier ;
1617
1718import static org .testcontainers .containers .PathUtils .normalizePath ;
1819
@@ -102,8 +103,11 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
102103 private static final String ENV_TARANTOOL_RUNDIR = "TARANTOOL_RUNDIR" ;
103104 private static final String ENV_TARANTOOL_DATADIR = "TARANTOOL_DATADIR" ;
104105 private static final String ENV_TARANTOOL_INSTANCES_FILE = "TARANTOOL_INSTANCES_FILE" ;
106+
105107 private final CartridgeConfigParser instanceFileParser ;
106108 private final TarantoolContainerClientHelper clientHelper ;
109+ private final String TARANTOOL_RUN_DIR ;
110+
107111 private boolean useFixedPorts = false ;
108112 private String routerHost = ROUTER_HOST ;
109113 private int routerPort = ROUTER_PORT ;
@@ -113,7 +117,6 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
113117 private String directoryResourcePath = SCRIPT_RESOURCE_DIRECTORY ;
114118 private String instanceDir = INSTANCE_DIR ;
115119 private String topologyConfigurationFile ;
116- private String replicasetsFileName ;
117120
118121 /**
119122 * 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,
181184 */
182185 public TarantoolCartridgeContainer (String dockerFile , String buildImageName , String instancesFile ,
183186 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 );
186188 }
187189
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" );
188195
189- private TarantoolCartridgeContainer (Future <String > image , String instancesFile , String topologyConfigurationFile ) {
190- super (image );
191196 if (instancesFile == null || instancesFile .isEmpty ()) {
192197 throw new IllegalArgumentException ("Instance file name must not be null or empty" );
193198 }
194199 if (topologyConfigurationFile == null || topologyConfigurationFile .isEmpty ()) {
195200 throw new IllegalArgumentException ("Topology configuration file must not be null or empty" );
196201 }
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 ;
203203 this .instanceFileParser = new CartridgeConfigParser (instancesFile );
204204 this .clientHelper = new TarantoolContainerClientHelper (this );
205205 }
206206
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 );
211212 }
212213
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+
213220 for (String envVariable : Arrays .asList (
214221 ENV_TARANTOOL_VERSION ,
215222 ENV_TARANTOOL_SERVER_USER ,
@@ -222,11 +229,11 @@ private static Future<String> withArguments(ImageFromDockerfile image, String in
222229 ENV_TARANTOOL_INSTANCES_FILE
223230 )) {
224231 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 );
227234 }
228235 }
229- return image ;
236+ return args ;
230237 }
231238
232239 private static ImageFromDockerfile buildImage (String dockerFile , String buildImageName ) {
@@ -458,38 +465,25 @@ protected void containerIsStarting(InspectContainerResponse containerInfo) {
458465 }
459466
460467 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 );
477469
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 );
481473
482474 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 ());
489482 }
490483 } 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 ());
492485 }
486+
493487 } else {
494488 try {
495489 executeScript (topologyConfigurationFile ).get ();
@@ -539,14 +533,65 @@ private void bootstrapVshard() {
539533 protected void containerIsStarted (InspectContainerResponse containerInfo , boolean reused ) {
540534 super .containerIsStarted (containerInfo , reused );
541535
536+ waitUntilRouterIsUp (60 );
542537 retryingSetupTopology ();
538+ // wait until Roles are configured
539+ waitUntilCartridgeIsHealthy (10 );
543540 bootstrapVshard ();
544541
545542 logger ().info ("Tarantool Cartridge cluster is started" );
546543 logger ().info ("Tarantool Cartridge router is listening at {}:{}" , getRouterHost (), getRouterPort ());
547544 logger ().info ("Tarantool Cartridge HTTP API is available at {}:{}" , getAPIHost (), getAPIPort ());
548545 }
549546
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+
550595 @ Override
551596 public CompletableFuture <List <?>> executeScript (String scriptResourcePath ) throws Exception {
552597 return clientHelper .executeScript (scriptResourcePath );
0 commit comments