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

io.fabric8.jube.local.NodeHelper Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/**
 *  Copyright 2005-2014 Red Hat, Inc.
 *
 *  Red Hat licenses this file to you 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 io.fabric8.jube.local;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ImmutableSet;
import io.fabric8.jube.KubernetesModel;
import io.fabric8.jube.Statuses;
import io.fabric8.jube.apimaster.ApiMasterService;
import io.fabric8.jube.process.InstallOptions;
import io.fabric8.jube.process.Installation;
import io.fabric8.jube.process.ProcessController;
import io.fabric8.jube.process.ProcessManager;
import io.fabric8.jube.util.ImageMavenCoords;
import io.fabric8.jube.util.InstallHelper;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.ContainerManifest;
import io.fabric8.kubernetes.api.model.ContainerState;
import io.fabric8.kubernetes.api.model.ContainerStateRunning;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.ReplicationControllerState;
import io.fabric8.kubernetes.api.model.PodState;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerStatus;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodTemplate;
import io.fabric8.kubernetes.api.model.Port;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.api.model.ReplicationControllerState;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.utils.Objects;
import io.fabric8.utils.Strings;
import io.hawt.aether.OpenMavenURL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A set of helper functions for implementing the local node
 */
public final class NodeHelper {
    public static final String KIND_POD = "Pod";
    public static final String KIND_REPLICATION_CONTROLLER = "ReplicationController";
    public static final String KIND_SERVICE = "SERVICE";

    // use 60 second stop timeout by default
    private static final int STOP_TIMEOUT = 60;

    private static final transient Logger LOG = LoggerFactory.getLogger(NodeHelper.class);

    private NodeHelper() {
        // utility class
    }

    /**
     * Returns the desired state; lazily creating one if required
     */
    public static PodState getOrCreateDesiredState(Pod pod) {
        Objects.notNull(pod, "pod");
        PodState desiredState = pod.getDesiredState();
        if (desiredState == null) {
            desiredState = new PodState();
            pod.setDesiredState(desiredState);
        }
        return desiredState;
    }

    /**
     * Returns the current state of the given pod; lazily creating one if required
     */
    public static PodState getOrCreateCurrentState(Pod pod) {
        Objects.notNull(pod, "pod");
        PodState currentState = pod.getCurrentState();
        if (currentState == null) {
            currentState = new PodState();
            pod.setCurrentState(currentState);
        }
        return currentState;
    }

    public static PodState getPodTemplateDesiredState(ReplicationController replicationController) {
        if (replicationController != null) {
            return getPodTemplateDesiredState(replicationController.getDesiredState());
        }
        return null;
    }

    public static PodState getPodTemplateDesiredState(ReplicationControllerState desiredState) {
        PodTemplate podTemplate;
        PodState podTemplatePodState = null;
        if (desiredState != null) {
            podTemplate = desiredState.getPodTemplate();
            if (podTemplate != null) {
                podTemplatePodState = podTemplate.getDesiredState();
            }
        }
        return podTemplatePodState;
    }

    /**
     * Returns the current container map for the current pod state; lazily creating if required
     */
    public static Map getOrCreateCurrentContainerInfo(Pod pod) {
        PodState currentState = getOrCreateCurrentState(pod);
        Map info = currentState.getInfo();
        if (info == null) {
            info = new HashMap<>();
            currentState.setInfo(info);
        }
        return info;
    }

    /**
     * Returns the containers state, lazily creating any objects if required.
     */
    public static ContainerState getOrCreateContainerState(Pod pod, String containerName) {
        ContainerStatus containerInfo = getOrCreateContainerInfo(pod, containerName);
        ContainerState state = containerInfo.getState();
        if (state == null) {
            state = new ContainerState();
            containerInfo.setState(state);
        }
        return state;
    }

    /**
     * Returns the container information for the given pod and container name, lazily creating as required
     */
    public static ContainerStatus getOrCreateContainerInfo(Pod pod, String containerName) {
        Map map = getOrCreateCurrentContainerInfo(pod);
        ContainerStatus containerInfo = map.get(containerName);
        if (containerInfo == null) {
            containerInfo = new ContainerStatus();
            map.put(containerName, containerInfo);
        }
        return containerInfo;
    }

    /**
     * Creates any missing containers; updating the currentState with the new values.
     */
    public static String createMissingContainers(final ProcessManager processManager, final KubernetesModel model, final Pod pod,
                                                 final PodState currentState, List containers) throws Exception {
        Map currentContainers = KubernetesHelper.getCurrentContainers(currentState);

        for (final Container container : containers) {
            // lets update the pod model if we update the ports
            podTransaction(model, pod, new Callable() {
                @Override
                public Void call() throws Exception {
                    createContainer(processManager, model, container, pod, currentState);
                    return null;
                }
            });
        }

        return null;
    }

    /**
     * Converts a possibly null list of Env objects into a Map of environment variables
     */
    public static Map createEnvironmentVariableMap(List envList) {
        Map answer = new HashMap<>();
        if (envList != null) {
            for (EnvVar env : envList) {
                String name = env.getName();
                String value = env.getValue();
                if (Strings.isNotBlank(name)) {
                    answer.put(name, value);
                }
            }
        }
        return answer;
    }


    public static List createEnvironmentVariables(Map environment) {
        List answer = new ArrayList<>();
        Set> entries = environment.entrySet();
        for (Map.Entry entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (Strings.isNotBlank(key)) {
                EnvVar env = new EnvVar();
                env.setName(key);
                env.setValue(value);
                answer.add(env);
            }
        }
        return answer;
    }

    protected static void createContainer(ProcessManager processManager, KubernetesModel model, Container container, Pod pod, PodState currentState) throws Exception {
        String containerName = container.getName();
        String image = container.getImage();
        Strings.notEmpty(image);
        OpenMavenURL mavenUrl = ImageMavenCoords.dockerImageToMavenURL(image);
        Objects.notNull(mavenUrl, "mavenUrl");

        LOG.info("Creating new container: {} from url: {}", containerName, mavenUrl);

        Map envVarMap = createEnvironmentVariableMap(container.getEnv());
        // now lets copy in the service env vars...
        appendServiceEnvironmentVariables(envVarMap, model);
        LOG.info("Env variables are: {}", envVarMap);
        InstallOptions.InstallOptionsBuilder builder = new InstallOptions.InstallOptionsBuilder().
                url(mavenUrl).environment(envVarMap);
        if (Strings.isNotBlank(containerName)) {
            builder = builder.name(containerName).id(containerName);
        }
        InstallOptions installOptions = builder.build();

        try {
            Installation installation;
            try {
                installation = processManager.install(installOptions, null);
            } catch (IOException ioe) {
                LOG.debug("Cannot find image at {} - trying with default prefix", mavenUrl);
                mavenUrl = ImageMavenCoords.dockerImageToMavenURL(image, true);
                Objects.notNull(mavenUrl, "mavenUrl");
                builder = new InstallOptions.InstallOptionsBuilder().
                        url(mavenUrl).environment(envVarMap);
                if (Strings.isNotBlank(containerName)) {
                    builder = builder.name(containerName).id(containerName);
                }
                installOptions = builder.build();
                installation = processManager.install(installOptions, null);
            }
            File installDir = installation.getInstallDir();

            ContainerStatus containerInfo = NodeHelper.getOrCreateContainerInfo(pod, containerName);

            LOG.info("Installed new process in directory: {}", installDir);
            ProcessController controller = installation.getController();
            Map environment = controller.getConfig().getEnvironment();
            container.setEnv(createEnvironmentVariables(environment));
            createInstallationPorts(environment, installation, container);
            model.updatePod(pod.getId(), pod);

            // TODO add a container to the current state
            LOG.info("Staring container: {}", containerName);
            controller.start();
            Long pid = controller.getPid();

            boolean alive = pid != null && pid > 0;
            LOG.info("Container: {} has pid: {} and is alive: {}", containerName, pid != null ? pid : "", alive);

            containerAlive(pod, containerName, alive);
        } catch (Exception e) {
            currentState.setStatus("Terminated: " + e);
            System.out.println("ERROR: Failed to create pod: " + pod.getId() + ". " + e);
            e.printStackTrace();
            LOG.error("Failed to create pod: " + pod.getId() + ". " + e.getMessage(), e);
        }
    }

    protected static List createInstallationPorts(Map environment, Installation installation, Container container) {
        try {
            List answer = container.getPorts();
            if (answer == null) {
                answer = new ArrayList<>();
                container.setPorts(answer);
            }
            File installDir = installation.getInstallDir();
            Map portMap = InstallHelper.readPortsFromDirectory(installDir);
            Set> entries = portMap.entrySet();
            for (Map.Entry entry : entries) {
                String key = entry.getKey();
                String value = entry.getValue();
                String containerPort = value;
                String envVarName = InstallHelper.portNameToHostEnvVarName(key);
                String hostPort = environment.get(envVarName);
                if (Strings.isNullOrBlank(hostPort)) {
                    LOG.warn("Cannot find env var value for $" + envVarName + " so cannot define the host port");
                } else {
                    int containerPortNumber = toPortNumber(value, key, "container");
                    int hostPortNumber = toPortNumber(hostPort, key, "host");
                    if (containerPortNumber > 0) {
                        // lets find a port object for the given number
                        Port port = findPortWithContainerPort(answer, containerPortNumber);
                        if (port == null) {
                            port = new Port();
                            port.setContainerPort(containerPortNumber);
                            answer.add(port);
                        }
                        port.setName(key);
                        if (hostPortNumber > 0) {
                            port.setHostPort(hostPortNumber);
                        }
                    }
                }
            }
            return answer;
        } catch (IOException e) {
            LOG.warn("Failed to load ports for installation " + installation + ". " + e.getMessage(), e);
            return null;
        }
    }

    public static Port findPortWithContainerPort(List ports, int containerPortNumber) {
        for (Port port : ports) {
            Integer containerPort = port.getContainerPort();
            if (containerPort != null && containerPort == containerPortNumber) {
                return port;
            }
        }
        return null;
    }

    protected static int toPortNumber(String text, String key, String description) {
        try {
            return Integer.parseInt(text);
        } catch (NumberFormatException e) {
            LOG.warn("Failed to parse port text '" + text + "' for port " + key + " " + description + ". " + e, e);
            return -1;
        }
    }

    public static int findHostPortForService(Pod pod, int serviceContainerPort) {
        List containers = KubernetesHelper.getContainers(pod);
        for (Container container : containers) {
            List ports = container.getPorts();
            if (ports != null) {
                for (Port port : ports) {
                    Integer containerPort = port.getContainerPort();
                    Integer hostPort = port.getHostPort();
                    if (containerPort != null && containerPort == serviceContainerPort) {
                        if (hostPort != null) {
                            return hostPort;
                        }
                    }
                }
            }
        }
        LOG.warn("Could not find host port for service port: {} pod: {}", serviceContainerPort, pod);
        return serviceContainerPort;
    }

    public static void appendServiceEnvironmentVariables(Map map, KubernetesModel model) {
        ImmutableSet> entries = model.getServiceMap().entrySet();
        for (Map.Entry entry : entries) {
            String id = entry.getKey().toUpperCase().replaceAll("-", "_");
            String envVarPrefix = id + "_SERVICE_";
            Service service = entry.getValue();

            // TODO should we allow different service ports?
            String host = ApiMasterService.getHostName();
            Integer port = service.getPort();
            if (port != null) {
                String hostEnvVar = envVarPrefix + "HOST";
                String portEnvVar = envVarPrefix + "PORT";

                map.put(hostEnvVar, host);
                map.put(portEnvVar, "" + port);
            }
        }
    }

    public static void deleteContainers(ProcessManager processManager, KubernetesModel model, Pod pod, PodState currentState, List desiredContainers) throws Exception {
        for (Container container : desiredContainers) {
            deleteContainer(processManager, model, container, pod, currentState);
        }
    }

    protected static void deleteContainer(ProcessManager processManager, KubernetesModel model, Container container, Pod pod, PodState currentState) throws Exception {
        String containerName = container.getName();
        Installation installation = processManager.getInstallation(containerName);
        if (installation == null) {
            LOG.info("Cannot delete non existing container: {}", containerName);
            return;
        }

        ProcessController controller = installation.getController();

        boolean kill = false;

        // try graceful to stop first, then kill afterwards
        // as the controller may issue a command that stops asynchronously, we need to check if the pid is alive
        // until its graceful shutdown, before we go harder and try to kill it
        try {
            LOG.info("Stopping container: {}", containerName);
            controller.stop();
        } catch (Exception e) {
            kill = true;
            LOG.warn("Error during stopping container: " + containerName + ". This exception is ignored", e);
        }

        // if the process was alive then kill is not needed
        boolean stopped = false;
        for (int i = 0; i < STOP_TIMEOUT; i++) {
            if (i > 0) {
                LOG.info("Waiting for {} seconds to graceful stop container: {}", i, containerName);
            }
            // check if the process has been stopped graceful
            if (!safeCheckIsAlive(installation)) {
                stopped = true;
                break;
            } else {
                // wait 1 sec
                Thread.sleep(1000);
            }
        }
        if (!stopped) {
            LOG.warn("Cannot graceful stop container: {} after {} seconds. Will now forcibly shutdown the container.", containerName, STOP_TIMEOUT);
        }

        // if we did not stop graceful then do a kill
        if (kill || !stopped) {
            try {
                LOG.info("Killing container: {}", containerName);
                controller.kill();
            } catch (Exception e) {
                LOG.warn("Error during killing container: " + containerName + ". This exception is ignored", e);
            }
        }

        try {
            LOG.info("Uninstalling container: {}", containerName);
            controller.uninstall();
        } catch (Exception e) {
            LOG.warn("Error during uninstalling container: " + containerName + ". This exception is ignored", e);
        }

        try {
            LOG.info("Deleting pod: {}", pod.getId());
            model.deletePod(pod.getId(), pod.getNamespace());
        } catch (Exception e) {
            // ignore
            LOG.warn("Error during deleting pod: " + pod.getId() + ". This exception is ignored", e);
        }

        LOG.info("Deleted container: {} done", containerName);
    }

    private static boolean safeCheckIsAlive(Installation installation) {
        Long pid = 0L;
        try {
            pid = installation.getActivePid();
        } catch (Exception e) {
            // ignore, but force a pid value so we run for the timeout duration
        }
        return pid != null && pid.longValue() > 0;
    }


    public static void containerAlive(Pod pod, String id, boolean alive) {
        PodState currentState = getOrCreateCurrentState(pod);
        String status = currentState.getStatus();
        if (alive) {
            currentState.setStatus(Statuses.RUNNING);
        } else {
            if (Strings.isNullOrBlank(status)) {
                currentState.setStatus(Statuses.WAITING);
            } else if (!Objects.equal(Statuses.WAITING, status)) {
                currentState.setStatus(Statuses.TERMINATED);
            }
        }
        setContainerRunningState(pod, id, alive);
    }

    public static void setPodStatus(Pod pod, String status) {
        PodState currentState = getOrCreateCurrentState(pod);
        currentState.setStatus(status);
    }

    public static void setContainerRunningState(Pod pod, String id, boolean alive) {
        ContainerState state = getOrCreateContainerState(pod, id);
        if (alive) {
            ContainerStateRunning running = new ContainerStateRunning();
            state.setRunning(running);
        } else {
            state.setRunning(null);
        }
    }

    public static Container addOrUpdateDesiredContainer(Pod pod, String containerName, Container container) {
        List containers = getOrCreatePodDesiredContainers(pod);
        Container oldContainer = findContainer(containers, containerName);
        if (oldContainer != null) {
            // lets update it just in case something changed...
            containers.remove(oldContainer);
        }
        Container newContainer = new Container();

        // TODO we should use bean utils or something to copy properties in case we miss one!
        newContainer.setCommand(container.getCommand());
        newContainer.setEnv(container.getEnv());
        newContainer.setImage(container.getImage());
        newContainer.setPorts(container.getPorts());
        newContainer.setVolumeMounts(container.getVolumeMounts());
        newContainer.setWorkingDir(container.getWorkingDir());
        newContainer.getAdditionalProperties().putAll(container.getAdditionalProperties());
        newContainer.setName(containerName);
        LOG.info("Added new container: {}", containerName);

        containers.add(newContainer);
        return newContainer;
    }

    public static List getOrCreatePodDesiredContainers(Pod pod) {
        PodState podDesiredState = NodeHelper.getOrCreateDesiredState(pod);
        ContainerManifest manifest = podDesiredState.getManifest();
        if (manifest == null) {
            manifest = new ContainerManifest();
            podDesiredState.setManifest(manifest);
        }
        List containers = manifest.getContainers();
        if (containers == null) {
            containers = new ArrayList<>();
            manifest.setContainers(containers);
        }
        return containers;
    }

    public static Container findContainer(List containers, String name) {
        for (Container container : containers) {
            if (Objects.equal(container.getName(), name)) {
                return container;
            }
        }
        return null;
    }

    public static ReplicationControllerState getOrCreateCurrentState(ReplicationController replicationController) {
        ReplicationControllerState currentState = replicationController.getCurrentState();
        if (currentState == null) {
            currentState = new ReplicationControllerState();
            replicationController.setCurrentState(currentState);
        }
        return currentState;
    }

    public static void deletePod(ProcessManager processManager, KubernetesModel model, String podId, String namespace) throws Exception {
        Pod pod = model.deletePod(podId, namespace);
        if (pod != null) {
            List desiredContainers = NodeHelper.getOrCreatePodDesiredContainers(pod);
            NodeHelper.deleteContainers(processManager, model, pod, NodeHelper.getOrCreateCurrentState(pod), desiredContainers);
        }
    }

    /**
     * Performs a block of code and updates the pod model if its updated
     */
    public static void podTransaction(KubernetesModel model, Pod pod, Runnable task) {
        String oldJson = getPodJson(pod);
        task.run();
        String newJson = getPodJson(pod);

        // lets only update the model if we've really changed the pod
        if (!java.util.Objects.equals(oldJson, newJson)) {
            model.updatePod(pod.getId(), pod);
        }
    }

    /**
     * Performs a block of code and updates the pod model if its updated
     */
    public static  T podTransaction(KubernetesModel model, Pod pod, Callable task) throws Exception {
        String oldJson = getPodJson(pod);
        T answer = task.call();
        String newJson = getPodJson(pod);

        // lets only update the model if we've really changed the pod
        if (!java.util.Objects.equals(oldJson, newJson)) {
            model.updatePod(pod.getId(), pod);
        }
        return answer;
    }


    /**
     * Performs a block of code and updates the pod model if its updated
     */
    public static void excludeFromProcessMonitor(ProcessMonitor monitor, Pod pod, Runnable task) {
        String id = pod.getId();
        monitor.addExcludedPodId(id);
        try {
            task.run();
        } finally {
            monitor.removeExcludedPodId(id);
        }
    }

    /**
     * Performs a block of code and updates the pod model if its updated
     */
    public static  T excludeFromProcessMonitor(ProcessMonitor monitor, Pod pod,  Callable task) throws Exception {
        String id = pod.getId();
        monitor.addExcludedPodId(id);
        try {
            return task.call();
        } finally {
            monitor.removeExcludedPodId(id);
        }
    }


    protected static String getPodJson(Pod pod) {
        try {
            return KubernetesHelper.toJson(pod);
        } catch (JsonProcessingException e) {
            LOG.warn("Could not convert pod to json: " + e, e);
            return null;
        }
    }

    /**
     * Returns true if there has been a change in the JSON of the given entity
     */
    public static boolean podHasChanged(Pod currentEntity, Pod oldEntity) {
        if (currentEntity == null || oldEntity == null) {
            return true;
        }
        String oldJson = getPodJson(oldEntity);
        String newJson = getPodJson(currentEntity);
        return !java.util.Objects.equals(oldJson, newJson);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy