org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner.java

Source

/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;

import static java.util.stream.Collectors.toList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import io.fabric8.kubernetes.api.model.Pod;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.installer.server.model.impl.InstallerImpl;
import org.eclipse.che.api.installer.server.model.impl.InstallerServerConfigImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;

/**
 * Fixes installers servers ports if conflicts are present.
 *
 * <p>Installers fail to start if they provide servers which should be running on the same port.
 * Installers in different machines can conflict too since containers in a pod use shared ports. To
 * resolve such conflicts Infrastructure provides a free port for installer server by injection
 * environment variable.
 *
 * <p>The environment variable name has the following format
 * `CHE_SERVER_{NORMALISED_SERVER_NAME}_PORT`. Where `NORMALISED_SERVER_NAME` is a server name in
 * the upper case where all characters which are not Latin letters or digits replaced with `_`
 * symbol. So installers must respect the corresponding environment variables with the recommended
 * value for server port.
 *
 * @author Sergii Leshchenko
 */
public class InstallerServersPortProvisioner implements ConfigurationProvisioner {

    public static final String SERVER_PORT_ENV_FMT = "CHE_SERVER_%s_PORT";

    private final int minPort;
    private final int maxPort;

    @Inject
    public InstallerServersPortProvisioner(@Named("che.infra.kubernetes.installer_server_min_port") int minPort,
            @Named("che.infra.kubernetes.installer_server_max_port") int maxPort) {
        this.minPort = minPort;
        this.maxPort = maxPort;
    }

    @Override
    public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException {

        for (Pod pod : k8sEnv.getPods().values()) {
            // it is needed to detect conflicts between all containers in a pod
            // because they use the same ports
            List<InternalMachineConfig> podMachines = pod.getSpec().getContainers().stream()
                    .map(container -> k8sEnv.getMachines().get(Names.machineName(pod, container)))
                    .collect(toList());

            fixInstallersPortsConflicts(podMachines);
        }
    }

    @VisibleForTesting
    void fixInstallersPortsConflicts(List<InternalMachineConfig> machinesConfigs) throws InfrastructureException {
        ServersPorts ports = new ServersPorts();

        for (InternalMachineConfig machineConfig : machinesConfigs) {
            for (InstallerImpl installer : machineConfig.getInstallers()) {
                Map<Integer, Collection<String>> port2ServerRefs = getServersRefsGroupedByPorts(installer);

                for (Entry<Integer, Collection<String>> portServersEntry : port2ServerRefs.entrySet()) {
                    Integer port = portServersEntry.getKey();

                    if (!ports.occupy(port)) {
                        int newPort = ports.findFreePort();
                        assignNewPort(portServersEntry.getValue(), newPort, machineConfig, installer);
                    }
                }
            }
        }
    }

    @VisibleForTesting
    void assignNewPort(Collection<String> serversRefs, int newPort, InternalMachineConfig machineConfig,
            InstallerImpl installer) {

        for (String serverName : serversRefs) {
            ServerConfig serverConfig = installer.getServers().get(serverName);
            String protocol = getPortProtocol(serverConfig.getPort()).second;

            InstallerServerConfigImpl newServerConfig = new InstallerServerConfigImpl(serverConfig);

            newServerConfig.setPort(newPort + "/" + protocol);

            installer.getServers().put(serverName, newServerConfig);

            machineConfig.getServers().put(serverName, newServerConfig);

            // put environment variable for installer
            machineConfig.getEnv().put(getEnvName(serverName), Integer.toString(newPort));
        }
    }

    @VisibleForTesting
    Map<Integer, Collection<String>> getServersRefsGroupedByPorts(InstallerImpl installer) {
        Multimap<Integer, String> portToServerNames = ArrayListMultimap.create();

        for (Entry<String, ? extends ServerConfig> serverEntry : installer.getServers().entrySet()) {
            String serverName = serverEntry.getKey();
            ServerConfig serverConfig = serverEntry.getValue();

            Pair<Integer, String> portProtocol = getPortProtocol(serverConfig.getPort());

            portToServerNames.put(portProtocol.first, serverName);
        }

        return portToServerNames.asMap();
    }

    @VisibleForTesting
    String getEnvName(String serverName) {
        String serverNameEnv = serverName.replaceAll("\\W", "_");
        return String.format(SERVER_PORT_ENV_FMT, serverNameEnv.toUpperCase());
    }

    private Pair<Integer, String> getPortProtocol(String port) {
        String[] dividedPort = port.split("/");
        Integer portValue = Integer.parseInt(dividedPort[0]);
        String protocol = dividedPort[1];
        return Pair.of(portValue, protocol);
    }

    class ServersPorts {
        private Set<Integer> occupiedPorts;
        private int freePort;

        private ServersPorts() {
            this.occupiedPorts = new HashSet<>();
            this.freePort = minPort;
        }

        @VisibleForTesting
        ServersPorts(int initPortValue, Set<Integer> occupiedPorts) {
            this.occupiedPorts = occupiedPorts;
            this.freePort = initPortValue;
        }

        /**
         * Marks the specified port as occupied.
         *
         * <p>If the specified port is not already occupied <tt>true</tt> will be returned,
         * <tt>false</tt> otherwise.
         *
         * @return <tt>true</tt> if this set did not already contain the specified element
         */
        @VisibleForTesting
        boolean occupy(int port) {
            return occupiedPorts.add(port);
        }

        @VisibleForTesting
        int findFreePort() throws InternalInfrastructureException {
            int newPort;
            do {
                newPort = freePort++;
                if (newPort > maxPort) {
                    throw new InternalInfrastructureException(String.format(
                            "There is no available port in configured ports range [%s, %s].", minPort, maxPort));
                }
            } while (!occupiedPorts.add(newPort));

            return newPort;
        }

        @VisibleForTesting
        Set<Integer> getOccupiedPorts() {
            return occupiedPorts;
        }
    }
}