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

dev.galasa.docker.internal.DockerEnvironment Maven / Gradle / Ivy

The newest version!
/*
 * Copyright contributors to the Galasa project
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package dev.galasa.docker.internal;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import dev.galasa.docker.DockerContainer;
import dev.galasa.docker.DockerManagerException;
import dev.galasa.docker.DockerProvisionException;
import dev.galasa.docker.internal.properties.DockerSlots;
import dev.galasa.framework.spi.DssAdd;
import dev.galasa.framework.spi.DssDelete;
import dev.galasa.framework.spi.DynamicStatusStoreException;
import dev.galasa.framework.spi.IDynamicResource;
import dev.galasa.framework.spi.IDynamicStatusStoreService;
import dev.galasa.framework.spi.IFramework;

/**
 * Docker Environment. Manages the flow of both docker containers and slots to a
 * specified docker engine
 * 
 *   
 */
public class DockerEnvironment implements IDockerEnvironment {
    private IFramework framework;
    private DockerManagerImpl dockerManager;
    private IDynamicStatusStoreService dss;
    private IDynamicResource dynamicResource;
    private Map containersByTag = new HashMap<>();
    private Map enginesByTag = new HashMap<>();
    private boolean dockerEnginesChecked;
    private List volumes = new ArrayList<>();

    private final static Log logger = LogFactory.getLog(DockerEnvironment.class);

    /**
     * Sets the environment up to use the dss in the docker namespace.
     * 
     * @param framework
     * @param dockerManager
     * @throws DockerManagerException
     */
    public DockerEnvironment(IFramework framework, DockerManagerImpl dockerManager) throws DockerManagerException {
        this.framework = framework;
        this.dockerManager = dockerManager;

        try {
            this.dss = framework.getDynamicStatusStoreService(dockerManager.NAMESPACE);
        } catch (DynamicStatusStoreException e) {
            throw new DockerManagerException("Failed to create Docker environment", e);
        }

    }

    /**
     * Provisions the specified docker containers and stored the tag.
     * 
     * @param tag
     * @param imageName
     * @param start     (boolean)
     * @return DockerContainerImpl
     * @throws DockerProvisionException
     */
    @Override
    public DockerContainerImpl provisionDockerContainer(String tag, String imageName, boolean start,
            String dockerEngineTag) throws DockerProvisionException {
        DockerContainerImpl container = containersByTag.get(tag);
        DockerEngineImpl engine = enginesByTag.get(dockerEngineTag);
        if (container != null) {
            logger.info("Container already provisioned: " + tag);
            return container;
        }

        if (engine == null) {
            engine = buildDockerEngine(dockerEngineTag);
            enginesByTag.put(dockerEngineTag, engine);
        }

        if (dockerEnginesChecked != true) {
            engine.checkEngine();
            dockerEnginesChecked = true;
        }

        try {
            DockerSlotImpl slot = provisionDockerSlot(engine);

            DockerImageImpl image = new DockerImageImpl(framework, dockerManager, engine, imageName);

            container = new DockerContainerImpl(framework, dockerManager, tag, engine, image, start, slot);
            containersByTag.put(tag, container);

            logger.debug("Docker Container '" + tag + "' was provisioned as slot '"
                    + container.getDockerSlot().getSlotName());

            return container;
        } catch (DockerManagerException e) {
            throw new DockerProvisionException("Failed to gather resources.", e);
        }
    }

    /**
     * Builds all the recorded docker containers
     * 
     * @param testClasses
     * @throws DockerProvisionException
     */
    @Override
    public void build(List> testClasses) throws DockerProvisionException {
        if (!enginesByTag.isEmpty()) {
            checkDockerEngines();
            dockerEnginesChecked = true;
        }
        // if (this.dockerEngine != null) {
        // this.dockerEngine.checkEngine();
        // dockerEnginesChecked = true;
        // }

        for (DockerContainerImpl container : getContainers()) {
            container.checkContainer();
        }
    }

    private void checkDockerEngines() throws DockerProvisionException {
        for (String id : enginesByTag.keySet()) {
            enginesByTag.get(id).checkEngine();
        }
    }

    /**
     * Discrads the entire docker environment, stopping and deleting all docker
     * containers in instance and volumes.
     * 
     * @throws DockerManagerException
     */
    @Override
    public void discard() throws DockerManagerException {
        for (DockerContainerImpl container : containersByTag.values()) {
            container.discard();
        }
        for (DockerVolumeImpl volume : volumes) {
            removeDockerVolume(volume);
        }
    }

    /**
     * Returns the docker engine.
     */
    @Override
    public DockerEngineImpl getDockerEngineImpl(String dockerEngineTag) throws DockerManagerException {
        if (enginesByTag.containsKey(dockerEngineTag)) {
            return enginesByTag.get(dockerEngineTag);
        }
        throw new DockerManagerException("Unable to find Docker engine with the tag: " + dockerEngineTag);
    }

    /**
     * Returns the container implementation for a given tag
     * 
     * @param dockerContainerTag
     * @return DockerContainerImpl
     * @throws DockerManagerException
     */
    @Override
    public DockerContainerImpl getDockerContainerImpl(String dockerContainerTag) throws DockerManagerException {
        if (containersByTag.containsKey(dockerContainerTag)) {
            return containersByTag.get(dockerContainerTag);
        }
        throw new DockerManagerException("Unable to find Docker container with the tag: " + dockerContainerTag);
    }

    /**
     * Returns the collection of docker container tags
     * 
     * @return Collections
     */
    @Override
    public Collection getContainers() {
        return containersByTag.values();
    }

    /**
     * Creates the docker engine.
     * 
     * @return DockerEngineImpl
     * @throws DockerProvisionException
     */
    private DockerEngineImpl buildDockerEngine(String dockerEngineTag) throws DockerProvisionException {
        if (enginesByTag.containsKey(dockerEngineTag)) {
            logger.info("DockerEngine already built, returning that.");
            return enginesByTag.get(dockerEngineTag);
        }
        DockerEngineImpl dockerEngine = new DockerEngineImpl(framework, dockerManager, dockerEngineTag, dss);

        enginesByTag.put(dockerEngineTag, dockerEngine);

        return dockerEngine;
    }

    /**
     * Provisions the docker containers from the annotations in the test classs
     * 
     * @param annotation
     * @return DockerContainerImpl
     * @throws DockerProvisionException
     */
    private DockerContainerImpl provisionDockerContainer(DockerContainer annotation) throws DockerProvisionException {
        return provisionDockerContainer("GALASA_" + annotation.dockerContainerTag().trim().toUpperCase(),
                annotation.image(), annotation.start(), annotation.dockerEngineTag());
    }

    /**
     * Provisions a docker slot for a docker container.
     * @return
     * @throws DockerProvisionException
     * @throws DockerManagerException
     */
    private DockerSlotImpl provisionDockerSlot(DockerEngineImpl engine)
            throws DockerProvisionException, DockerManagerException {
        String runName = framework.getTestRunName();
        String dockerEngineId = engine.getEngineId();

        this.dynamicResource = this.dss.getDynamicResource("engine." + dockerEngineId);

        return allocateAndCreateDssSlot(dockerEngineId, runName, engine);
    }

    /**
     * Used during the provisioning of a docker slot to allocate a slot within the
     * DSS to lock the resource.
     * 
     * @param dockerHost
     * @return boolean (failed/passed)
     * @throws DockerProvisionException
     */
    private DockerSlotImpl allocateAndCreateDssSlot(String dockerEngineId, String runName, DockerEngineImpl engine)
            throws DockerProvisionException {
        String slotKey = "engine." + dockerEngineId + ".current.slots";
        String slotNamePrefix = "SLOT_" + runName + "_";
        String allocatedSlotName;
        String slotPropertyKey;
        String allocatedTime = Instant.now().toString();

        HashMap slotProps = new HashMap<>();

        try {
            int maxSlots = Integer.parseInt(DockerSlots.get(engine));
            int usedSlots = 0;
            String currentSlots = dss.get(slotKey);

            if (currentSlots != null) {
                usedSlots = Integer.parseInt(currentSlots);
            }
            if (usedSlots >= maxSlots) {
                throw new DockerProvisionException("Not enough available slots");
            }
            usedSlots++;
            String slotIncrease = Integer.toString(usedSlots);

            for (int i = 0;; i++) {
                slotProps.clear();
                allocatedSlotName = slotNamePrefix + i;
                slotPropertyKey = "engine." + dockerEngineId + ".slot." + allocatedSlotName;
                slotProps.put("slot." + dockerEngineId + ".run." + runName + "." + allocatedSlotName, "active");
                if (dss.putSwap(slotPropertyKey, null, runName)) {
                    break;
                }
            }

            if (dss.putSwap(slotKey, currentSlots, slotIncrease, slotProps)) {
                String resourcePropertyPrefix = "slot." + allocatedSlotName;

                HashMap resProps = new HashMap<>();
                resProps.put(resourcePropertyPrefix, runName);
                resProps.put(resourcePropertyPrefix + ".allocated", allocatedTime);

                dynamicResource.put(resProps);

                return new DockerSlotImpl(dockerManager, engine, allocatedSlotName, resProps);
            } else {
                // Retry as another slot was allocated during the allocation of this slot
                dss.delete(slotPropertyKey);
                return allocateAndCreateDssSlot(dockerEngineId, runName, engine);
            }
        } catch (DockerManagerException e) {
            logger.error("Could not find number of Docker slots in CPS");
        } catch (DynamicStatusStoreException e) {
            logger.warn("Could not perform putswap on dss");
        }
        throw new DockerProvisionException("Failed to provision Docker slot");
    }

    /**
     * Free a specifed docker slot in terms of the container and environment
     * 
     * @param dockerSlot
     * @throws DockerProvisionException
     */
    @Override
    public void freeDockerSlot(DockerSlotImpl dockerSlot) throws DockerProvisionException {
        DockerEngineImpl dockerEngine = dockerSlot.getDockerEngine();
        String dockerEngineId = dockerEngine.getEngineId();

        try {
            String currentSlot = dss.get("engine." + dockerEngineId + ".current.slots");
            if (currentSlot == null) {
                return;
            }

            int usedSlots = Integer.parseInt(currentSlot);
            usedSlots--;
            if (usedSlots < 0) {
                usedSlots = 0;
            }
            dynamicResource.delete(dockerSlot.getResourcePropertyKeys());

            String prefix = "engine." + dockerEngineId + ".slot." + dockerSlot.getSlotName();
            String slotKey = "slot." + dockerEngineId + ".run." + framework.getTestRunName() + "."
                    + dockerSlot.getSlotName();
            HashMap otherProps = new HashMap<>();
            otherProps.put(slotKey, "free");
            if (!dss.putSwap("engine." + dockerEngineId + ".current.slots", currentSlot, Integer.toString(usedSlots),
                    otherProps)) {
                Thread.sleep(200);
                freeDockerSlot(dockerSlot);
                return;
            }

            HashSet delProps = new HashSet<>();
            delProps.add(prefix);
            delProps.add(prefix + ".allocated");
            delProps.add(slotKey);
            dss.delete(delProps);
            logger.info("Discarding slot: " + dockerSlot.getSlotName() + ". on the Docker engine: " + dockerEngineId);
        } catch (Exception e) {
            logger.warn("Failed to free slot on engine " + dockerEngineId + ", slot " + dockerSlot.getSlotName()
                    + ", leaving for manager clean up routines", e);
        }
    }

    /**
     * Used by resource management to clean up stale properties
     * 
     * @param runName
     * @param dockerEngineId
     * @param slotName
     * @param dss
     */
    public static void deleteStaleDssSlot(String runName, String dockerEngineId, String slotName,
            IDynamicStatusStoreService dss) {
        try {
            String numberOfSlotKey = "engine." + dockerEngineId + ".current.slots";
            String currentSlot = dss.get(numberOfSlotKey);
            if (currentSlot == null) {
                return;
            }

            int usedSlots = Integer.parseInt(currentSlot);
            usedSlots--;
            if (usedSlots < 0) {
                usedSlots = 0;
            }

            IDynamicResource dynamicResource = dss.getDynamicResource("engine." + dockerEngineId);
            String resPrefix = "slot." + slotName;

            HashSet resProps = new HashSet<>();
            resProps.add(resPrefix);
            resProps.add(resPrefix + ".allocated");
            dynamicResource.delete(resProps);

            String slotStatusKey = "slot." + dockerEngineId + ".run." + runName + "." + slotName;
            String runIdKey = "engine." + dockerEngineId + ".slot." + slotName;

            HashMap dockerDssProps = new HashMap<>();
            dockerDssProps.put(slotStatusKey, "free");

            if ("active".equals(dss.get(slotStatusKey))) {
                if (!dss.putSwap(numberOfSlotKey, currentSlot, Integer.toString(usedSlots), dockerDssProps)) {
                    Thread.sleep(200);
                    deleteStaleDssSlot(runName, dockerEngineId, slotName, dss);
                    return;
                }
                HashSet props = new HashSet<>();
                props.add(slotStatusKey);
                props.add(runIdKey);
                dss.delete(props);
            }

        } catch (Exception e) {
            logger.error("Failed to discard slot " + slotName + " on Docker engine " + dockerEngineId, e);
        }
    }

    @Override
    public DockerVolumeImpl allocateDockerVolume(String volumeName, String tag, String mountPath, String dockerEngineTag, boolean readOnly) throws DockerProvisionException {

        DockerEngineImpl engine = enginesByTag.get(dockerEngineTag);
        if (engine == null) {
            engine = buildDockerEngine(dockerEngineTag);
            enginesByTag.put(dockerEngineTag, engine);
        }
        
        // String enginePropertyPrefix = "engine." + dockerEngineTag + ".volume."; 
        String volumePropertyPrefix = "volume.";
        String preProvisionVolumeName = "GALASA_VOLUME_" + framework.getTestRunName() + "_";
        int volumeNumber = 1;
        String fullVolumeName;
        boolean provision = true;

        try {
            if (!"".equals(volumeName)) {
                fullVolumeName = volumeName;
                provision = false;
            } else {
                while (framework.getTestRunName().equals(dss.get(volumePropertyPrefix + preProvisionVolumeName + volumeNumber+ ".run"))){
                        volumeNumber++;
                }
                fullVolumeName = preProvisionVolumeName + volumeNumber;
                dss.performActions(new DssAdd(volumePropertyPrefix + fullVolumeName + ".engine", dockerEngineTag),
                                    new DssAdd(volumePropertyPrefix + fullVolumeName + ".run", framework.getTestRunName()));
            }
            
        } catch (DynamicStatusStoreException e) {
            throw new DockerProvisionException("Failed to form a volume name", e);
        }


        try {
            DockerVolumeImpl volume = new DockerVolumeImpl(dockerManager, fullVolumeName, tag, mountPath, engine, readOnly, provision);
            if (provision) {
                volumes.add(volume);
            }

            
            return volume;
        } catch (DockerManagerException e) {
            throw new DockerProvisionException("Failed to allocate Docker volume.", e);
        }
    }

    @Override
    public void removeDockerVolume(DockerVolumeImpl volume) throws DockerManagerException {
        String volumeRun = "volume." + volume.getVolumeName() + ".run";
        String volumeEngine = "volume." + volume.getVolumeName() + ".engine";

        try{
            dss.performActions(
                new DssDelete(volumeRun, framework.getTestRunName()),
                new DssDelete(volumeEngine, volume.getEngineTag())
            );
        } catch (DynamicStatusStoreException e) {
            throw new DockerManagerException("Failed to clean dss Volume properties for: " + volume.getVolumeName() ,e);
        }

        volume.discard();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy