88import com .google .common .base .Splitter ;
99import com .google .common .collect .Maps ;
1010import com .google .common .util .concurrent .Uninterruptibles ;
11+ import org .apache .commons .lang .StringUtils ;
1112import org .junit .runner .Description ;
1213import org .rnorth .ducttape .ratelimits .RateLimiter ;
1314import org .rnorth .ducttape .ratelimits .RateLimiterBuilder ;
@@ -46,8 +47,9 @@ public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> e
4647 private final String identifier ;
4748 private final Map <String , AmbassadorContainer > ambassadorContainers = new HashMap <>();
4849 private final List <File > composeFiles ;
49- private Set <String > spawnedContainerIds = Collections .emptySet ();
50- private Map <String , Integer > scalingPreferences = new HashMap <>();
50+ private final Set <String > spawnedContainerIds = new HashSet <>();
51+ private final Set <String > spawnedNetworkIds = new HashSet <>();
52+ private final Map <String , Integer > scalingPreferences = new HashMap <>();
5153 private DockerClient dockerClient ;
5254 private boolean localCompose ;
5355 private boolean pull = true ;
@@ -116,15 +118,13 @@ public void starting(Description description) {
116118 }
117119
118120 private void pullImages () {
119- getDockerCompose ("pull" )
120- .start ();
121+ runWithCompose ("pull" );
121122 }
122123
123124
124125 private void createServices () {
125- // Start the docker-compose container, which starts up the services
126- getDockerCompose ("up -d" )
127- .start ();
126+ // Run the docker-compose container, which starts up the services
127+ runWithCompose ("up -d" );
128128 }
129129
130130 private void tailChildContainerLogs () {
@@ -137,16 +137,18 @@ private void tailChildContainerLogs() {
137137 );
138138 }
139139
140- private DockerCompose getDockerCompose (String cmd ) {
140+ private void runWithCompose (String cmd ) {
141141 final DockerCompose dockerCompose ;
142142 if (localCompose ) {
143143 dockerCompose = new LocalDockerCompose (composeFiles , identifier );
144144 } else {
145145 dockerCompose = new ContainerisedDockerCompose (composeFiles , identifier );
146146 }
147- return dockerCompose
147+
148+ dockerCompose
148149 .withCommand (cmd )
149- .withEnv (env );
150+ .withEnv (env )
151+ .invoke ();
150152 }
151153
152154 private void applyScaling () {
@@ -157,8 +159,7 @@ private void applyScaling() {
157159 sb .append (" " ).append (scale .getKey ()).append ("=" ).append (scale .getValue ());
158160 }
159161
160- getDockerCompose (sb .toString ())
161- .start ();
162+ runWithCompose (sb .toString ());
162163 }
163164 }
164165
@@ -171,19 +172,18 @@ private void registerContainersForShutdown() {
171172 containers .forEach (container ->
172173 ResourceReaper .instance ().registerContainerForCleanup (container .getId (), container .getNames ()[0 ]));
173174
174- // Ensure that the default network for this compose environment, if any, is also cleaned up
175- ResourceReaper .instance ().registerNetworkForCleanup (identifier + "_default" );
176175 // Compose can define their own networks as well; ensure these are cleaned up
177176 dockerClient .listNetworksCmd ().exec ().forEach (network -> {
178177 if (network .getName ().contains (identifier )) {
179- ResourceReaper .instance ().registerNetworkForCleanup (network .getId ());
178+ spawnedNetworkIds .add (network .getId ());
179+ ResourceReaper .instance ().registerNetworkIdForCleanup (network .getId ());
180180 }
181181 });
182182
183183 // remember the IDs to allow containers to be killed as soon as we reach stop()
184- spawnedContainerIds = containers .stream ()
184+ spawnedContainerIds . addAll ( containers .stream ()
185185 .map (Container ::getId )
186- .collect (Collectors .toSet ());
186+ .collect (Collectors .toSet ())) ;
187187
188188 } catch (DockerException e ) {
189189 logger ().debug ("Failed to stop a service container with exception" , e );
@@ -240,15 +240,25 @@ public void finished(Description description) {
240240 ambassadorContainers .forEach ((String address , AmbassadorContainer container ) -> container .stop ());
241241
242242 // Kill the services using docker-compose
243- getDockerCompose ("down -v" )
244- .start ();
243+ try {
244+ runWithCompose ("down -v" );
245+
246+ // If we reach here then docker-compose down has cleared networks and containers;
247+ // we can unregister from ResourceReaper
248+ spawnedContainerIds .forEach (ResourceReaper .instance ()::unregisterContainer );
249+ spawnedNetworkIds .forEach (ResourceReaper .instance ()::unregisterNetwork );
250+ } catch (Exception e ) {
251+ // docker-compose down failed; use ResourceReaper to ensure cleanup
245252
246- // remove the networks before removing the containers
247- ResourceReaper .instance ().removeNetworks (identifier );
253+ // kill the spawned service containers
254+ spawnedContainerIds .forEach (ResourceReaper .instance ()::stopAndRemoveContainer );
255+
256+ // remove the networks after removing the containers
257+ spawnedNetworkIds .forEach (ResourceReaper .instance ()::removeNetworkById );
258+ }
248259
249- // kill the spawned service containers
250- spawnedContainerIds .forEach (id -> ResourceReaper .instance ().stopAndRemoveContainer (id ));
251260 spawnedContainerIds .clear ();
261+ spawnedNetworkIds .clear ();
252262 }
253263 }
254264
@@ -372,7 +382,7 @@ interface DockerCompose {
372382
373383 DockerCompose withEnv (Map <String , String > env );
374384
375- void start ();
385+ void invoke ();
376386
377387 default void validateFileList (List <File > composeFiles ) {
378388 checkNotNull (composeFiles );
@@ -417,7 +427,7 @@ public ContainerisedDockerCompose(List<File> composeFiles, String identifier) {
417427 }
418428
419429 @ Override
420- public void start () {
430+ public void invoke () {
421431 super .start ();
422432
423433 this .followOutput (new Slf4jLogConsumer (logger ()));
@@ -431,6 +441,19 @@ public void start() {
431441 logger ().info ("Docker Compose has finished running" );
432442
433443 AuditLogger .doComposeLog (this .getCommandParts (), this .getEnv ());
444+
445+ final Integer exitCode = this .dockerClient .inspectContainerCmd (containerId )
446+ .exec ()
447+ .getState ()
448+ .getExitCode ();
449+
450+ if (exitCode == null || exitCode != 0 ) {
451+ throw new ContainerLaunchException (
452+ "Containerised Docker Compose exited abnormally with code " +
453+ exitCode +
454+ " whilst running command: " +
455+ StringUtils .join (this .getCommandParts (), ' ' ));
456+ }
434457 }
435458}
436459
@@ -468,7 +491,7 @@ public DockerCompose withEnv(Map<String, String> env) {
468491 }
469492
470493 @ Override
471- public void start () {
494+ public void invoke () {
472495 // bail out early
473496 if (!CommandLine .executableExists (COMPOSE_EXECUTABLE )) {
474497 throw new ContainerLaunchException ("Local Docker Compose not found. Is " + COMPOSE_EXECUTABLE + " on the PATH?" );
0 commit comments