All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.xdev.tci.safestart.SafeNamedContainerStarter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2024 XDEV Software (https://xdev.software)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package software.xdev.tci.safestart;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;


/**
 * It's not possible to start a container with e.g. the same name, port bindings, etc. with startupAttempts >
 * 1, which is the default in a few containers (e.g. MariaDB or Selenium-Browsers).
 * 

* {@link SafeNamedContainerStarter} addresses these problems by *

    *
  • ensuring that after every started container has a unique name (addresses the name problem)
  • *
  • removing previously failed started containers (addresses the port and other problems)
  • *
*/ public class SafeNamedContainerStarter> implements Runnable { protected static final Logger LOG = LoggerFactory.getLogger(SafeNamedContainerStarter.class); protected final String baseContainerName; protected final List containerNames = Collections.synchronizedList(new ArrayList<>()); protected final C container; protected final Consumer starter; protected boolean attachRandomUUID = true; public SafeNamedContainerStarter( final String baseContainerName, final C container) { this(baseContainerName, container, C::start); } public SafeNamedContainerStarter( final String baseContainerName, final C container, final Consumer starter) { this.baseContainerName = Objects.requireNonNull(baseContainerName); this.container = Objects.requireNonNull(container); this.starter = Objects.requireNonNull(starter); this.prepareContainer(); } void prepareContainer() { final AtomicInteger startAttemptCounter = new AtomicInteger(1); this.container.withCreateContainerCmdModifier(cmd -> { // We are here again -> Prev start (if any) must have failed! // Clear now, otherwise e.g. fixed port bindings will fail again and forever this.tryCleanupContainerAfterStartFail(this.containerNames); this.containerNames.clear(); // Generate new name on each start attempt, so that we can clearly identify the container String generatedName = this.baseContainerName + "-" + startAttemptCounter.getAndIncrement(); if(this.attachRandomUUID) { generatedName += "-" + UUID.randomUUID(); } // Add first so that we can skip the stream later (there is no skipLast Method) this.containerNames.add(0, generatedName); cmd.withName(generatedName); }); } public SafeNamedContainerStarter withAttachRandomUUID(final boolean attachRandomUUID) { this.attachRandomUUID = attachRandomUUID; return this; } @Override public void run() { this.start(); } public void start() { try { this.starter.accept(this.container); this.tryCleanupContainerAfterStartFail(this.containerNames.stream() .skip(1) // First one is successfully started one .toList()); } catch(final RuntimeException rex) { this.tryCleanupContainerAfterStartFail(this.containerNames); throw rex; } } @SuppressWarnings("resource") protected void tryCleanupContainerAfterStartFail(final List containerNames) { for(final String containerName : containerNames) { LOG.info("Start of container[name='{}'] failed; Trying to remove container...", containerName); try { DockerClientFactory.lazyClient() .removeContainerCmd(containerName) .withForce(true) .exec(); LOG.info("Removed failed container[name='{}']", containerName); } catch(final Exception ex) { LOG.debug("Unable to cleanup container[name='{}']", containerName, ex); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy