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

com.alexecollins.docker.orchestration.DockerOrchestrator Maven / Gradle / Ivy

package com.alexecollins.docker.orchestration;


import com.alexecollins.docker.orchestration.model.*;
import com.alexecollins.docker.orchestration.util.Pinger;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.DockerException;
import com.github.dockerjava.api.NotFoundException;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copyLarge;

/**
 * Orchestrates multiple Docker containers based on
 */
public class DockerOrchestrator {
	public static final String DEFAULT_HOST = "http://127.0.0.1:2375";
	public static final FileFilter DEFAULT_FILTER = new FileFilter() {
		@Override
		public boolean accept(File pathname) {
			return false;
		}
	};
	public static final Properties DEFAULT_PROPERTIES = new Properties();

	private static final Logger LOGGER = LoggerFactory.getLogger(DockerOrchestrator.class);
	private static final int snooze = 0;

	private final DockerClient docker;
	private final Repo repo;

    private final FileOrchestrator fileOrchestrator;
	private final Set buildFlags;

    public DockerOrchestrator(DockerClient docker, File src, File workDir, File rootDir, String prefix, FileFilter filter, Properties properties) {
        this(docker, new Repo(docker, prefix, src, properties), new FileOrchestrator(workDir, rootDir, filter, properties), EnumSet.noneOf(BuildFlag.class));
    }

	public DockerOrchestrator(DockerClient docker, File src, File workDir, File rootDir, String prefix, FileFilter filter, Properties properties, Set buildFlags) {
        this(docker, new Repo(docker, prefix, src, properties), new FileOrchestrator(workDir, rootDir, filter, properties), buildFlags);
	}

	public DockerOrchestrator(DockerClient docker, Repo repo, FileOrchestrator fileOrchestrator) {
		this(docker,repo, fileOrchestrator, EnumSet.noneOf(BuildFlag.class));
	}

    public DockerOrchestrator(DockerClient docker, Repo repo, FileOrchestrator fileOrchestrator, Set buildFlags) {
	    if (docker == null) {
            throw new IllegalArgumentException("docker is null");
        }
        if (repo == null) {
            throw new IllegalArgumentException("repo is null");
        }
	    if (buildFlags == null) {throw new IllegalArgumentException("buildFlags is null");}


        this.docker = docker;
        this.repo = repo;
        this.fileOrchestrator = fileOrchestrator;

	    this.buildFlags = buildFlags;
    }

	public void clean() {
		for (Id id : repo.ids(true)) {
			stop(id);
			clean(id);
		}
	}

	void clean(final Id id) {
		if (id == null) {
			throw new IllegalArgumentException("id is null");
		}
		stop(id);
		LOGGER.info("Clean " + id);
		for (Container container : repo.findContainers(id, true)) {
			LOGGER.info("Removing container " + container.getId());
			try {
				docker.removeContainerCmd(container.getId()).withForce().exec();
			} catch (DockerException e) {
				throw new OrchestrationException(e);
			}
		}
		String imageId = null;
		try {
			imageId = repo.getImageId(id);
		} catch (NotFoundException e) {
			LOGGER.warn("Image " + id + " not found");
		} catch (DockerException e) {
			throw new OrchestrationException(e);
		}
		if (imageId != null) {
            LOGGER.info("Removing image " + imageId);
            try {
				docker.removeImageCmd(imageId).exec();
			} catch (DockerException e) {
				LOGGER.warn(e.getMessage());
			}
		}
		snooze();
	}

	void build(final Id id) {
		if (id == null) {
			throw new IllegalArgumentException("id is null");
		}
		LOGGER.info("Package " + id);
		try {
			build(prepare(id), id);
		} catch (IOException e) {
			throw new OrchestrationException(e);
		}

		snooze();
	}

	private void snooze() {
        if (snooze == 0) {
            return;
        }
        LOGGER.info("Snoozing for " + snooze + "ms");
		try {
			Thread.sleep(snooze);
		} catch (InterruptedException e) {
			throw new OrchestrationException(e);
		}
	}

    private File prepare(Id id) throws IOException {
        if (id == null) {
			throw new IllegalArgumentException("id is null");
		}
        return fileOrchestrator.prepare(id, repo.src(id), repo.conf(id));
    }




	@SuppressWarnings(("DM_DEFAULT_ENCODING"))
	private void build(File dockerFolder, Id id) {

		InputStream in;
		try {
            BuildImageCmd build = docker.buildImageCmd(dockerFolder);
            for(BuildFlag f : buildFlags){
                switch (f){
                    case NO_CACHE: build.withNoCache();break;
                    case REMOVE_INTERMEDIATE_IMAGES: build.withRemove(true);break;
                }
            }
            build.withTag(repo.imageName(id));
            in = build.exec();
		} catch (DockerException e) {
			throw new OrchestrationException(e);
		}

		final StringWriter out = new StringWriter();
		try {
			copyLarge(new InputStreamReader(in, Charset.defaultCharset()), out);
		} catch (IOException e) {
			throw new OrchestrationException(e);
		} finally {
			closeQuietly(in);
		}

		String log = out.toString();
		if (!log.contains("Successfully built")) {
			throw new IllegalStateException("failed to build, log missing lines in" + log);
		}

		snooze();
	}


    private void start(final Id id) {
        if (id == null) {
            throw new IllegalArgumentException("id is null");
        }

        try {
            Container existingContainer = repo.findContainer(id);

            if (existingContainer == null) {
                LOGGER.info("No existing container so creating and starting new one");
                String containerId = createNewContainer(id);
                startContainer(containerId, id);

            } else if (!isImageIdFromContainerMatchingProvidedImageId(existingContainer.getId(), id)) {
                LOGGER.info("Image IDs do not match, removing container and creating new one from image");
                docker.removeContainerCmd(existingContainer.getId()).exec();
                startContainer(createNewContainer(id), id);

            } else if(isRunning(id)) {
                LOGGER.info("Container " + id + " already running");

            } else {
                LOGGER.info("Starting existing container " + existingContainer.getId());
                startContainer(existingContainer.getId(), id);
            }

        } catch (DockerException e) {
            throw new OrchestrationException(e);
        }
	    snooze();
	    healthCheck(id);
    }

    private boolean isImageIdFromContainerMatchingProvidedImageId(String containerId, final Id id) {
        try {
            String containerImageId = lookupImageIdFromContainer(containerId);
            String imageId = repo.getImageId(id);
            return containerImageId.equals(imageId);
        } catch (DockerException e) {
            LOGGER.error("Unable to find image with id " + id, e);
            throw new OrchestrationException(e);
        }

    }

    private String lookupImageIdFromContainer(String containerId) {
        try {
            InspectContainerResponse containerInspectResponse = docker.inspectContainerCmd(containerId).exec();
            return containerInspectResponse.getImageId();
        } catch (DockerException e) {
            LOGGER.error("Unable to inspect container " + containerId, e);
            throw new OrchestrationException(e);
        }
    }

    private void startContainer(String idOfContainerToStart, final Id id) {
        try {
            LOGGER.info("Starting " + id);
            StartContainerCmd start = docker.startContainerCmd(idOfContainerToStart);

            newHostConfig(id,start);
            start.exec();
        } catch (DockerException e) {
            LOGGER.error("Unable to start container " + idOfContainerToStart, e);
            throw new OrchestrationException(e);
        }
    }


    private String createNewContainer(Id id) throws DockerException {
        LOGGER.info("Creating " + id);
        Conf conf = repo.conf(id);
        CreateContainerCmd createCmd = docker.createContainerCmd(repo.getImageId(id));
        createCmd.withName(repo.containerName(id));
        LOGGER.info(" - env " + conf.getEnv());
        createCmd.withEnv(asEnvList(conf.getEnv()));
        CreateContainerResponse response = createCmd.exec();
		snooze();
        return response.getId();
	}

    /**
     * Converts String to String map to list of
     * key=value strings.
     * @param env
     * @return
     */
    private String[] asEnvList(Map env) {
        ArrayList list = new ArrayList();
        for(Map.Entry entry : env.entrySet()){
            list.add(entry.getKey()+"="+entry.getValue());
        }
        return list.toArray(new String[list.size()]);
    }


    private boolean isRunning(Id id) {
		if (id == null) {throw new IllegalArgumentException("id is null");}
		boolean running = false;
        final Container candidate = repo.findContainer(id);
		for (Container container : docker.listContainersCmd().withShowAll(false).exec()) {
			running |= candidate != null && candidate.getId().equals(container.getId());
		}
		return running;
	}

	private void healthCheck(Id id) {
		final HealthChecks healthChecks = repo.conf(id).getHealthChecks();
		for (Ping ping : healthChecks.getPings()) {
			LOGGER.info("Pinging " + ping.getUrl());
			if (!Pinger.ping(ping.getUrl(), ping.getTimeout())) {
				throw new OrchestrationException("timeout waiting for " + ping.getUrl() + " for " + ping.getTimeout());
			}
		}
	}

	private void newHostConfig(Id id, StartContainerCmd config) {
		config.withPublishAllPorts(true);

        Link[] links = links(id);
        LOGGER.info(" - links " + repo.conf(id).getLinks());
        config.withLinks(links);

		final Ports portBindings = new Ports();
		for (String e : repo.conf(id).getPorts()) {

			final String[] split = e.split(" ");

			assert split.length == 1 || split.length == 2;

			final int a = Integer.parseInt(split[0]);
			final int b = split.length == 2 ? Integer.parseInt(split[1]) : a;

			LOGGER.info(" - port " + e);
			portBindings.bind(new ExposedPort("tcp", a), new Ports.Binding(b));
		}
        config.withPortBindings(portBindings);

        LOGGER.info(" - volumes " + repo.conf(id).getVolumes());

        final List binds = new ArrayList();
        for (Map.Entry entry : repo.conf(id).getVolumes().entrySet()) {
            String volumePath = entry.getKey();
            String hostPath = entry.getValue();
            File file = new File(hostPath);
            String path = file.getAbsolutePath();
            LOGGER.info(" - volumes " + volumePath +" <- "+ path);
            binds.add(new Bind(path, new Volume(volumePath)));
        }

		config.withBinds(binds.toArray(new Bind[binds.size()]));
	}

	private Link[] links(Id id) {
		final List links = repo.conf(id).getLinks();
		final Link[] out = new Link[links.size()];
		for (int i = 0; i < links.size(); i++) {
			final String name = repo.findContainer(links.get(i)).getNames()[0];
			out[i] = new Link(name,name);
		}
		return out;
	}

	private void stop(final Id id) {
		if (id == null) {
			throw new IllegalArgumentException("id is null");
		}
		for (Container container : repo.findContainers(id, false)) {
			LOGGER.info("Stopping " + Arrays.toString(container.getNames()));
			try {
				docker.stopContainerCmd(container.getId()).withTimeout(1).exec();
			} catch (DockerException e) {
				throw new OrchestrationException(e);
			}
			snooze();
		}
	}

	public void build() {
		for (Id id : ids()) {
			build(id);
		}
	}

	public void start() {
		for (Id id : ids()) {
			try {
				if (!repo.imageExists(id)) {
					build(id);
				}
			} catch (DockerException e) {
				throw new OrchestrationException(e);
			}
			start(id);
		}
	}

	public void stop() {
		for (Id id : repo.ids(true)) {
			stop(id);
		}
	}

	public List ids() {
		return repo.ids(false);
	}

	public void push() {
		for (Id id : ids()) {
			push(id);
		}
	}

	private void push(Id id) {
		try {
            docker.pushImageCmd(repo.imageName(id)).withAuthConfig(docker.authConfig()).exec();
        } catch (DockerException e) {
			throw new OrchestrationException(e);
		}
		snooze();
	}

	public boolean isRunning() {
		for (Id id : ids()) {
			if (!isRunning(id)) {
				return false;
			}
		}
		return true;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy