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

io.kroxylicious.proxy.internal.PortConflictDetector Maven / Gradle / Ivy

/*
 * Copyright Kroxylicious Authors.
 *
 * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */

package io.kroxylicious.proxy.internal;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import io.kroxylicious.proxy.model.VirtualCluster;
import io.kroxylicious.proxy.service.HostPort;

/**
 * Detects potential for port conflicts arising between virtual cluster configurations.
 */
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class PortConflictDetector {

    private static final Optional ANY_INTERFACE = Optional.empty();
    private static final String ANY_STRING = "";

    private enum BindingScope {
        SHARED,
        EXCLUSIVE
    }

    /**
     * Validates the configuration throwing an exception if a conflict is detected.
     *
     * @param virtualClusterMap map of virtual clusters.
     * @param otherExclusivePort an optional exclusive port that should conflict with virtual cluster ports
     */
    public void validate(Collection virtualClusterMap, Optional otherExclusivePort) {

        Set seenVirtualClusters = new HashSet<>();

        Map, Set> inUseExclusivePorts = new HashMap<>();
        Map, Map> inUseSharedPorts = new HashMap<>();

        virtualClusterMap.stream()
                .sorted(Comparator.comparing(VirtualCluster::getClusterName))
                .forEach(virtualCluster -> {
                    var name = virtualCluster.getClusterName();
                    var proposedSharedPorts = virtualCluster.getSharedPorts();
                    var proposedExclusivePorts = virtualCluster.getExclusivePorts();

                    if (otherExclusivePort.isPresent()) {
                        Optional otherInterface = otherExclusivePort.map(hostPort -> hostPort.host().equals("0.0.0.0") ? null : hostPort.host());
                        var checkPorts = virtualCluster.getBindAddress().isEmpty() || otherInterface.isEmpty() || virtualCluster.getBindAddress().equals(otherInterface);
                        if (checkPorts) {
                            checkForConflictsWithOtherExclusivePort(otherExclusivePort.get(), name, virtualCluster, proposedExclusivePorts, BindingScope.EXCLUSIVE);
                            checkForConflictsWithOtherExclusivePort(otherExclusivePort.get(), name, virtualCluster, proposedSharedPorts, BindingScope.SHARED);
                        }
                    }

                    // if this virtual cluster is binding to , we need to check for conflicts on the  interface and *all* specific interfaces,
                    // otherwise we just check for conflicts on  and the specified specific interface
                    var exclusiveCheckSet = virtualCluster.getBindAddress().isEmpty() ? inUseExclusivePorts.keySet()
                            : Set.of(ANY_INTERFACE, virtualCluster.getBindAddress());

                    // check the proposed *exclusive ports* for conflicts with the *exclusive ports* seen so far.
                    assertExclusivePortsAreMutuallyExclusive(seenVirtualClusters, inUseExclusivePorts, virtualCluster, name, proposedExclusivePorts, exclusiveCheckSet);

                    // check the proposed *shared ports* for conflicts with the *exclusive ports* seen so far.
                    assertSharedPortsDoNotOverlapWithExlusivePorts(seenVirtualClusters, inUseExclusivePorts, virtualCluster, name, proposedSharedPorts,
                            exclusiveCheckSet);

                    // check the proposed *exclusive ports* for conflicts with the *shared ports* seen so far.
                    assertExclusivePortsDoNotOverlapWithExclusivePorts(seenVirtualClusters, inUseSharedPorts, virtualCluster, name, proposedExclusivePorts,
                            exclusiveCheckSet);

                    // check the proposed *shared ports* for conflicts with the shared ports seen so far on *different* interfaces.
                    assertSharedPortsAreExclusiveAcrossInterfaces(seenVirtualClusters, inUseSharedPorts, virtualCluster, name, proposedSharedPorts, exclusiveCheckSet);

                    // check for proposed shared ports for differing TLS configuration
                    assertSharedPortsHaveMatchingTlsConfiguration(seenVirtualClusters, inUseSharedPorts, virtualCluster, name, proposedSharedPorts);

                    seenVirtualClusters.add(name);
                    inUseExclusivePorts.computeIfAbsent(virtualCluster.getBindAddress(), k -> new HashSet<>()).addAll(proposedExclusivePorts);
                    var sharedMap = inUseSharedPorts.computeIfAbsent(virtualCluster.getBindAddress(), k -> new HashMap<>());
                    proposedSharedPorts.forEach(p -> sharedMap.put(p, virtualCluster.isUseTls()));

                });
    }

    private void assertSharedPortsHaveMatchingTlsConfiguration(Set seenVirtualClusters, Map, Map> inUseSharedPorts,
                                                               VirtualCluster virtualCluster, String name,
                                                               Set proposedSharedPorts) {
        proposedSharedPorts.forEach(p -> {
            var tls = inUseSharedPorts.getOrDefault(virtualCluster.getBindAddress(), Map.of()).get(p);
            if (tls != null && !tls.equals(virtualCluster.isUseTls())) {
                throw buildOverviewException(name, seenVirtualClusters, buildTlsConflictException(p, virtualCluster.getBindAddress()));
            }
        });
    }

    private void assertSharedPortsAreExclusiveAcrossInterfaces(Set seenVirtualClusters, Map, Map> inUseSharedPorts,
                                                               VirtualCluster virtualCluster, String name,
                                                               Set proposedSharedPorts, Set> exclusiveCheckSet) {
        var sharedCheckSet = new HashSet<>(exclusiveCheckSet);
        sharedCheckSet.remove(virtualCluster.getBindAddress());

        inUseSharedPorts.entrySet().stream()
                .filter(interfacePortsEntry -> sharedCheckSet.contains(interfacePortsEntry.getKey()))
                .forEach(entry -> {
                    var bindingInterface = entry.getKey();
                    var ports = entry.getValue().keySet();
                    var conflicts = getSortedPortConflicts(ports, proposedSharedPorts);
                    if (!conflicts.isEmpty()) {
                        throw buildPortConflictException(name, seenVirtualClusters, conflicts,
                                virtualCluster.getBindAddress(), BindingScope.SHARED,
                                bindingInterface, BindingScope.SHARED);
                    }
                });
    }

    private void assertExclusivePortsDoNotOverlapWithExclusivePorts(Set seenVirtualClusters, Map, Map> inUseSharedPorts,
                                                                    VirtualCluster virtualCluster, String name,
                                                                    Set proposedExclusivePorts, Set> exclusiveCheckSet) {
        inUseSharedPorts.entrySet().stream()
                .filter(interfacePortsEntry -> exclusiveCheckSet.contains(interfacePortsEntry.getKey()))
                .forEach(entry -> {
                    var bindingInterface = entry.getKey();
                    var ports = entry.getValue().keySet();
                    var conflicts = getSortedPortConflicts(ports, proposedExclusivePorts);
                    if (!conflicts.isEmpty()) {
                        throw buildPortConflictException(name, seenVirtualClusters, conflicts,
                                virtualCluster.getBindAddress(), BindingScope.EXCLUSIVE,
                                bindingInterface, BindingScope.SHARED);
                    }
                });
    }

    private void assertSharedPortsDoNotOverlapWithExlusivePorts(Set seenVirtualClusters, Map, Set> inUseExclusivePorts,
                                                                VirtualCluster virtualCluster, String name,
                                                                Set proposedSharedPorts, Set> exclusiveCheckSet) {
        inUseExclusivePorts.entrySet().stream()
                .filter(interfacePortsEntry -> exclusiveCheckSet.contains(interfacePortsEntry.getKey()))
                .forEach(entry -> {
                    var bindingInterface = entry.getKey();
                    var ports = entry.getValue();
                    var conflicts = getSortedPortConflicts(ports, proposedSharedPorts);
                    if (!conflicts.isEmpty()) {
                        throw buildPortConflictException(name, seenVirtualClusters, conflicts,
                                virtualCluster.getBindAddress(), BindingScope.SHARED,
                                bindingInterface, BindingScope.EXCLUSIVE);
                    }
                });
    }

    private void assertExclusivePortsAreMutuallyExclusive(Set seenVirtualClusters, Map, Set> inUseExclusivePorts,
                                                          VirtualCluster virtualCluster, String name,
                                                          Set proposedExclusivePorts, Set> exclusiveCheckSet) {
        inUseExclusivePorts.entrySet().stream()
                .filter(interfacePortsEntry -> exclusiveCheckSet.contains(interfacePortsEntry.getKey()))
                .forEach(entry -> {
                    var bindingInterface = entry.getKey();
                    var ports = entry.getValue();
                    var conflicts = getSortedPortConflicts(ports, proposedExclusivePorts);
                    if (!conflicts.isEmpty()) {
                        throw buildPortConflictException(name, seenVirtualClusters, conflicts,
                                virtualCluster.getBindAddress(), BindingScope.EXCLUSIVE,
                                bindingInterface, BindingScope.EXCLUSIVE);
                    }
                });
    }

    private void checkForConflictsWithOtherExclusivePort(HostPort otherHostPort, String name, VirtualCluster cluster, Set proposedExclusivePorts,
                                                         BindingScope scope) {
        var ports = Set.of(otherHostPort.port());
        var conflicts = getSortedPortConflicts(ports, proposedExclusivePorts);
        if (!conflicts.isEmpty()) {
            Optional proposedBindingInterface = cluster.getBindAddress();
            var portConflicts = conflicts.stream().map(String::valueOf).collect(Collectors.joining(","));

            throw new IllegalStateException("The %s bind of port(s) %s for virtual cluster '%s' to %s would conflict with another (non-cluster) port binding".formatted(
                    scope.name().toLowerCase(Locale.ROOT),
                    portConflicts,
                    name,
                    proposedBindingInterface.orElse(ANY_STRING)));
        }
    }

    private List getSortedPortConflicts(Set ports, Set candidates) {
        return ports.stream().filter(candidates::contains).sorted().toList();
    }

    private IllegalStateException buildPortConflictException(String virtualClusterName, Set seenVirtualClusters, List conflicts,
                                                             Optional proposedBindingInterface, BindingScope proposedScope,
                                                             Optional existingBindingInterface, BindingScope existingBindingScope) {
        var portConflicts = conflicts.stream().map(String::valueOf).collect(Collectors.joining(","));

        var underlying = new IllegalStateException("The %s bind of port(s) %s to %s would conflict with existing %s port bindings on %s.".formatted(
                proposedScope.name().toLowerCase(Locale.ROOT),
                portConflicts,
                proposedBindingInterface.orElse(ANY_STRING),
                existingBindingScope.name().toLowerCase(Locale.ROOT),
                existingBindingInterface.orElse(ANY_STRING)));
        return buildOverviewException(virtualClusterName, seenVirtualClusters, underlying);
    }

    private IllegalStateException buildTlsConflictException(Integer port, Optional bindingAddress) {
        return new IllegalStateException(
                "The shared bind of port %d to %s has conflicting TLS settings with existing port on the same interface.".formatted(port,
                        bindingAddress.orElse(ANY_STRING)));
    }

    private IllegalStateException buildOverviewException(String virtualClusterName, Set seenVirtualClusters, IllegalStateException underlying) {
        var seenVirtualClustersString = seenVirtualClusters.stream().sorted().map(s -> "'" + s + "'").collect(Collectors.joining(","));
        return new IllegalStateException("Configuration for virtual cluster '%s' conflicts with configuration for virtual cluster%s: %s.".formatted(virtualClusterName,
                seenVirtualClusters.size() > 1 ? "s" : "", seenVirtualClustersString), underlying);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy