Skip to content

Commit

Permalink
Merge pull request #33789 from Sgitario/32882
Browse files Browse the repository at this point in the history
Print messages about ports that can't change at runtime for K8s
  • Loading branch information
iocanel authored Jul 26, 2023
2 parents 9d1ef52 + de116aa commit c00bbdb
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,7 @@ KubernetesPortBuildItem registerGrpcServiceInKubernetes(List<BindableServiceBuil
.orElse(true);
if (useSeparateServer) {
// Only expose the named port "grpc" if the gRPC server is exposed using a separate server.
int port = ConfigProvider.getConfig().getOptionalValue("quarkus.grpc.server.port", Integer.class)
.orElse(9000);
return new KubernetesPortBuildItem(port, "grpc");
return KubernetesPortBuildItem.fromRuntimeConfiguration("grpc", "quarkus.grpc.server.port", 9000, true);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
package io.quarkus.kubernetes.spi;

import java.util.Optional;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.Feature;

public final class KubernetesPortBuildItem extends MultiBuildItem {

private final int port;
private final String name;
/**
* Indicates when the port is enabled vs simply configured.
* For example the presence `quarkus.http.ssl-port` also is not enought to tell us if enabled.
* Still, we need to communicate its value and let `quarkus-kubernetes` extension decide.
**/
private final boolean enabled;
private final Optional<Property<Integer>> source;

public KubernetesPortBuildItem(int port, Feature feature) {
this(port, feature.getName());
this(port, feature.getName(), true, Optional.empty());
}

public KubernetesPortBuildItem(int port, String name) {
this(port, name, true, Optional.empty());
}

public KubernetesPortBuildItem(int port, String name, boolean enabled, Optional<Property<Integer>> source) {
this.port = port;
this.name = name;
this.source = source;
this.enabled = enabled;
}

public static KubernetesPortBuildItem fromRuntimeConfiguration(String name, String propertyName, Integer defaultValue,
boolean enabled) {
Property<Integer> origin = Property.fromRuntimeConfiguration(propertyName, Integer.class, defaultValue);
Integer port = origin.getValue().orElse(defaultValue);
return new KubernetesPortBuildItem(port, name, enabled, Optional.of(origin));
}

public static KubernetesPortBuildItem fromRuntimeConfiguration(String name, String propertyName, Integer defaultValue) {
Property<Integer> origin = Property.fromRuntimeConfiguration(propertyName, Integer.class, defaultValue);
Integer port = origin.getValue().orElse(defaultValue);
return new KubernetesPortBuildItem(port, name, origin.getValue().isPresent(), Optional.of(origin));
}

public int getPort() {
Expand All @@ -24,4 +52,12 @@ public int getPort() {
public String getName() {
return name;
}

public boolean isEnabled() {
return enabled;
}

public Optional<Property<Integer>> getSource() {
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.quarkus.kubernetes.spi;

import java.util.Optional;

import org.eclipse.microprofile.config.ConfigProvider;

public class Property<T> {

private final String name;
private final Class<T> type;
private final Optional<T> value;
private final T defaultValue;
private final boolean runtime;

public Property(String name, Class<T> type, Optional<T> value, T defaultValue, boolean runtime) {
this.name = name;
this.type = type;
this.value = value;
this.defaultValue = defaultValue;
this.runtime = runtime;
}

public static <T> Property<T> fromRuntimeConfiguration(String name, Class<T> type, T defaultValue) {
return new Property<T>(name, type, ConfigProvider.getConfig().getOptionalValue(name, type), defaultValue, true);
}

public String getName() {
return name;
}

public Class<T> getType() {
return type;
}

public Optional<T> getValue() {
return value;
}

public T getDefaultValue() {
return defaultValue;
}

public boolean isRuntime() {
return runtime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.microprofile.config.ConfigProvider;

import io.dekorate.kubernetes.config.Annotation;
import io.dekorate.kubernetes.config.ConfigMapVolumeBuilder;
import io.dekorate.kubernetes.config.EnvBuilder;
Expand Down Expand Up @@ -99,11 +99,11 @@
import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem;
import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem;
import io.quarkus.kubernetes.spi.KubernetesServiceAccountBuildItem;
import io.quarkus.kubernetes.spi.Property;
import io.quarkus.kubernetes.spi.RoleRef;
import io.quarkus.kubernetes.spi.Subject;

public class KubernetesCommonHelper {

private static final String ANY = null;
private static final String OUTPUT_ARTIFACT_FORMAT = "%s%s.jar";
private static final String[] PROMETHEUS_ANNOTATION_TARGETS = { "Service",
Expand Down Expand Up @@ -164,9 +164,16 @@ public static Optional<Port> getPort(List<KubernetesPortBuildItem> ports, Platfo
public static Map<String, Port> combinePorts(List<KubernetesPortBuildItem> ports,
PlatformConfiguration config) {
Map<String, Port> allPorts = new HashMap<>();
allPorts.putAll(verifyPorts(ports).entrySet().stream()
Map<String, Port> activePorts = new HashMap<>();

allPorts.putAll(ports.stream()
.map(p -> new PortBuilder().withName(p.getName()).withContainerPort(p.getPort()).build())
.collect(Collectors.toMap(Port::getName, Function.identity(), (first, second) -> first))); //prevent dublicate keys

activePorts.putAll(verifyPorts(ports)
.entrySet().stream()
.map(e -> new PortBuilder().withName(e.getKey()).withContainerPort(e.getValue()).build())
.collect(Collectors.toMap(Port::getName, p -> p)));
.collect(Collectors.toMap(Port::getName, Function.identity(), (first, second) -> first))); //prevent dublicate keys

config.getPorts().entrySet().forEach(e -> {
String name = e.getKey();
Expand All @@ -186,19 +193,30 @@ public static Map<String, Port> combinePorts(List<KubernetesPortBuildItem> ports
.withPath(Strings.isNotNullOrEmpty(configuredPort.getPath()) ? configuredPort.getPath()
: buildItemPort.getPath())
.build();
activePorts.put(name, combinedPort);
});
return activePorts;
}

// Special handling for ports with name "https". We look up the container port from the Quarkus configuration.
if ("https".equals(name) && combinedPort.getContainerPort() == null) {
int containerPort = ConfigProvider.getConfig()
.getOptionalValue("quarkus.http.ssl-port", Integer.class)
.orElse(8443);

combinedPort = new PortBuilder(combinedPort).withContainerPort(containerPort).build();
/**
* Creates the configurator build items.
*/
public static void printMessageAboutPortsThatCantChange(String target, List<KubernetesPortBuildItem> ports,
PlatformConfiguration configuration) {
ports.stream().forEach(port -> {
boolean enabled = port.isEnabled() || configuration.getPorts().containsKey(port.getName());
if (enabled) {
String name = "quarkus." + target + ".ports." + port.getName() + ".container-port";
Optional<Integer> value = Optional.ofNullable(configuration.getPorts().get(port.getName()))
.map(p -> p.containerPort)
.filter(OptionalInt::isPresent)
.map(OptionalInt::getAsInt);
Property<Integer> kubernetesPortProperty = new Property(name, Integer.class, value, null, false);
PropertyUtil.printMessages(String.format("The container port %s", port.getName()), target,
kubernetesPortProperty,
port.getSource());
}

allPorts.put(name, combinedPort);
});
return allPorts;
}

/**
Expand Down Expand Up @@ -1069,15 +1087,18 @@ private static Map<String, Integer> verifyPorts(List<KubernetesPortBuildItem> ku
final Map<String, Integer> result = new HashMap<>();
final Set<Integer> usedPorts = new HashSet<>();
for (KubernetesPortBuildItem entry : kubernetesPortBuildItems) {
if (!entry.isEnabled()) {
continue;
}
final String name = entry.getName();
if (result.containsKey(name)) {
throw new IllegalArgumentException(
"All Kubernetes ports must have unique names - " + name + "has been used multiple times");
"All Kubernetes ports must have unique names - " + name + " has been used multiple times");
}
final Integer port = entry.getPort();
if (usedPorts.contains(port)) {
throw new IllegalArgumentException(
"All Kubernetes ports must be unique - " + port + "has been used multiple times");
"All Kubernetes ports must be unique - " + port + " has been used multiple times");
}
result.put(name, port);
usedPorts.add(port);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static io.quarkus.kubernetes.deployment.Constants.READINESS_PROBE;
import static io.quarkus.kubernetes.deployment.Constants.ROUTE;
import static io.quarkus.kubernetes.deployment.Constants.STARTUP_PROBE;
import static io.quarkus.kubernetes.deployment.KubernetesCommonHelper.printMessageAboutPortsThatCantChange;
import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.MANAGEMENT_PORT_NAME;
import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.managementPortIsEnabled;
import static io.quarkus.kubernetes.deployment.OpenshiftConfig.OpenshiftFlavor.v3;
Expand Down Expand Up @@ -378,6 +379,8 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic
|| !config.route.targetPort.equals(MANAGEMENT_PORT_NAME))) {
result.add(new DecoratorBuildItem(OPENSHIFT, new RemovePortFromServiceDecorator(name, MANAGEMENT_PORT_NAME)));
}

printMessageAboutPortsThatCantChange(OPENSHIFT, ports, config);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.kubernetes.deployment;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import org.jboss.logging.Logger;

import io.quarkus.kubernetes.spi.Property;

public class PropertyUtil {

private static final Set<String> VISITED_EXTENSION_PROPERTIES = new HashSet<>();
private static final Logger LOG = Logger.getLogger(PropertyUtil.class);

public static <T> void printMessages(String usage, String platform, Property<T> kubernetesProperty,
Optional<Property<T>> extensionProperty) {
extensionProperty.ifPresent(p -> {
printMessages(usage, platform, kubernetesProperty, p);
});
}

public static <T> void printMessages(String usage, String platform, Property<T> kubernetesProperty,
Property<T> extensionProperty) {
if (!VISITED_EXTENSION_PROPERTIES.add(extensionProperty.getName())) {
return;
}

String platformCapitalized = platform.substring(0, 1).toUpperCase() + platform.substring(1);
T kubernetesValue = kubernetesProperty.getValue().orElse(null);
if (kubernetesValue == null) {
// If no kubernetes property is provided, this will be used instead.
String defaultOrProvided = extensionProperty.getValue().isPresent() ? "provided" : "default";
String stringValue = String.valueOf(extensionProperty.getValue().orElse(extensionProperty.getDefaultValue()));
LOG.infof("%s manifests are generated with '%s' having %s value '%s'. "
+ "The app and manifests will get out of sync if the property '%s' is changed at runtime.",
platformCapitalized, usage, defaultOrProvided, stringValue, extensionProperty.getName());

} else if (extensionProperty.getValue().filter(v -> !v.equals(kubernetesValue)).isPresent()) {
// We have conflicting properties that need to be aligned. Maybe warn?
String runtimeOrBuildTime = extensionProperty.isRuntime() ? "runtime" : "buildtime";
LOG.debugf(
"%s property '%s' has been set with value '%s' while %s property '%s' is set with '%s'. %s will be set using the former.",
platformCapitalized, kubernetesProperty.getName(), kubernetesProperty.getValue().get(), runtimeOrBuildTime,
extensionProperty.getName(), extensionProperty.getValue().get(), usage);
} else {
// Both proeprties are present and aligned.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static io.quarkus.kubernetes.deployment.Constants.LIVENESS_PROBE;
import static io.quarkus.kubernetes.deployment.Constants.READINESS_PROBE;
import static io.quarkus.kubernetes.deployment.Constants.STARTUP_PROBE;
import static io.quarkus.kubernetes.deployment.KubernetesCommonHelper.printMessageAboutPortsThatCantChange;
import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.MANAGEMENT_PORT_NAME;
import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.managementPortIsEnabled;
import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.VANILLA_KUBERNETES_PRIORITY;
Expand Down Expand Up @@ -298,7 +299,7 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic
.withMaxUnavailable(config.rollingUpdate.maxUnavailable)
.build())));
}

printMessageAboutPortsThatCantChange(KUBERNETES, ports, config);
return result;
}

Expand All @@ -320,4 +321,4 @@ void externalizeInitTasks(
jobs, initContainers, env, roles, roleBindings, decorators);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,26 +174,16 @@ void filterMultipleVertxInstancesWarning(LaunchModeBuildItem launchModeBuildItem

@BuildStep
public void kubernetes(BuildProducer<KubernetesPortBuildItem> kubernetesPorts) {
if (isSslConfigured()) {
// ssl is not disabled
int sslPort = ConfigProvider.getConfig()
.getOptionalValue("quarkus.http.ssl-port", Integer.class)
.orElse(8443);
kubernetesPorts.produce(new KubernetesPortBuildItem(sslPort, "https"));
}

int port = ConfigProvider.getConfig().getOptionalValue("quarkus.http.port", Integer.class).orElse(8080);
kubernetesPorts.produce(new KubernetesPortBuildItem(port, "http"));
kubernetesPorts.produce(KubernetesPortBuildItem.fromRuntimeConfiguration("http", "quarkus.http.port", 8080, true));
kubernetesPorts.produce(
KubernetesPortBuildItem.fromRuntimeConfiguration("https", "quarkus.http.ssl-port", 8443, isSslConfigured()));
}

@BuildStep
public KubernetesPortBuildItem kubernetesForManagement(
ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig) {
if (managementInterfaceBuildTimeConfig.enabled) {
int port = ConfigProvider.getConfig().getOptionalValue("quarkus.management.port", Integer.class).orElse(9000);
return new KubernetesPortBuildItem(port, "management");
}
return null;
return KubernetesPortBuildItem.fromRuntimeConfiguration("management", "quarkus.management.port", 9000,
managementInterfaceBuildTimeConfig.enabled);
}

@BuildStep
Expand Down

0 comments on commit c00bbdb

Please sign in to comment.