package eu.esdihumboldt.hale.common.test.docker.config; import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.spotify.docker.client.DefaultDockerClient; import com.spotify.docker.client.DockerClient; import com.spotify.docker.client.DockerException; import com.spotify.docker.client.ImageNotFoundException; import com.spotify.docker.client.messages.ContainerConfig; import com.spotify.docker.client.messages.ContainerCreation; import com.spotify.docker.client.messages.ContainerInfo; import com.spotify.docker.client.messages.HostConfig; import com.spotify.docker.client.messages.PortBinding; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; /** * A Hale docker client * * @author Sameer Sheikh * */ public class HaleDockerClient implements DockerContainer { private static final ALogger LOGGER = ALoggerFactory.getLogger(HaleDockerClient.class); private DockerClient dc; private ContainerConfig containerConf; private Map<String, List<PortBinding>> portMapper; private String containerId; private URI uri; private final ContainerParameters dbc; private ContainerCreation creation; private String containerIp; /** * A parameterized constructor * * @param dbc parameters related to a general docker client */ public HaleDockerClient(ContainerParameters dbc) { this.dbc = dbc; } /** * Checks the availability of the Docker server. * * @return <code>true</code> if the Docker server is available, * <code>false</code> otherwise */ public boolean isServerAvailable() { if (dc == null) { throw new IllegalStateException( "Docker client not created yet: call createContainer() first"); } int numAttempts = 3; for (int i = 0; i < numAttempts; i++) { try { String result = dc.ping(); if ("OK".equals(result)) { return true; } } catch (Exception e) { int attemptsLeft = numAttempts - (i + 1); LOGGER.debug("Exception occurred connection to docker server: " + attemptsLeft + " attempts left", e); try { Thread.sleep(1000); } catch (InterruptedException e1) { // ignore } } } return false; } /** * creates a container using the parameters. * */ public void createContainer() { Set<String> exposedPorts = new HashSet<String>(dbc.getExposedPortList()); uri = URI.create(dbc.getDockerHost()); dc = new DefaultDockerClient(uri); containerConf = ContainerConfig.builder().image(dbc.getImageName()).cmd(dbc.getCommands()) .exposedPorts(exposedPorts).build(); } /** * Gets the host name from the url configured in the docker configuration * * @return host name configured */ @Override public String getHostName() { return uri.getHost(); } /** * @return the container IP address */ public String getContainerIp() { return containerIp; } /** * start a container. Container can be started in the privileged mode if the * 'isPrivileged' key in the configuration is set as true. * * @throws DockerException docker exception * @throws InterruptedException interrupted exception */ public void startContainer() throws DockerException, InterruptedException { try { dc.inspectImage(containerConf.image()); } catch (ImageNotFoundException e) { // pull image if it is not present LOGGER.info( MessageFormat.format("Docker image not found, attempting to pull image {0}...", containerConf.image())); dc.pull(containerConf.image()); } // TODO also add a setting to pull the image always? LOGGER.info(MessageFormat.format("Preparing container for image {0}...", containerConf.image())); creation = dc.createContainer(containerConf); containerId = creation.id(); LOGGER.info(MessageFormat.format("Created container with ID {0}, now starting...", containerId)); final HostConfig hostConfig; if (getHostName() == null) { // don't publish ports (probably unix socket connection) hostConfig = HostConfig.builder().publishAllPorts(false).privileged(dbc.isPrivileged()) .build(); } else { // XXX publishing all ports can be very bad if the host is // accessible externally hostConfig = HostConfig.builder().publishAllPorts(dbc.isExposeAllPorts()) .privileged(dbc.isPrivileged()).build(); } dc.startContainer(containerId, hostConfig); final ContainerInfo info = dc.inspectContainer(containerId); portMapper = info.networkSettings().ports(); containerIp = info.networkSettings().ipAddress(); } /** * gets the binded docker host port * * @param port the configured port number * @return the binded docker host port number for the given port */ @Override public int getHostPort(int port) { ArrayList<PortBinding> bindings = (ArrayList<PortBinding>) portMapper.get(port + "/tcp"); if (bindings != null && bindings.size() > 0) { return Integer.parseInt(bindings.get(0).hostPort()); } else { throw new IllegalArgumentException("Port not available"); } } /** * kill and remove the container * * @throws Exception if fails to kill the container or remove it */ public void killAndRemoveContainer() throws Exception { if (containerId == null) { return; } try { LOGGER.info(MessageFormat.format("Killing container {0}...", containerId)); dc.killContainer(containerId); } finally { LOGGER.info(MessageFormat.format("Removing container {0}...", containerId)); dc.removeContainer(containerId); } } /** * @return the container ID or <code>null</code> if no container was created * yet */ public String getContainerId() { if (creation != null) { return creation.id(); } else { return null; } } }