Skip to content

Commit

Permalink
Allow specifying per-port nodePort
Browse files Browse the repository at this point in the history
Resolves: #17582
  • Loading branch information
geoand committed Jun 4, 2021
1 parent 4f8cf8b commit 164116f
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.dekorate.kubernetes.annotation.ServiceType;
Expand All @@ -42,6 +43,7 @@
import io.quarkus.kubernetes.deployment.EnvConverter;
import io.quarkus.kubernetes.deployment.KubernetesCommonHelper;
import io.quarkus.kubernetes.deployment.KubernetesConfig;
import io.quarkus.kubernetes.deployment.PortConfig;
import io.quarkus.kubernetes.deployment.ResourceNameUtil;
import io.quarkus.kubernetes.spi.ConfiguratorBuildItem;
import io.quarkus.kubernetes.spi.DecoratorBuildItem;
Expand Down Expand Up @@ -139,8 +141,17 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic

//Service handling
result.add(new DecoratorBuildItem(MINIKUBE, new ApplyServiceTypeDecorator(name, ServiceType.NodePort.name())));
result.add(new DecoratorBuildItem(MINIKUBE, new AddNodePortDecorator(name, config.getNodePort()
.orElseGet(() -> getStablePortNumberInRange(name, MIN_NODE_PORT_VALUE, MAX_NODE_PORT_VALUE)))));
List<PortConfig> nodeConfigPorts = config.getPorts().values().stream().filter(pc -> pc.nodePort.isPresent())
.collect(Collectors.toList());
if (!nodeConfigPorts.isEmpty()) {
for (PortConfig portConfig : nodeConfigPorts) {
result.add(new DecoratorBuildItem(KUBERNETES,
new AddNodePortDecorator(name, portConfig.nodePort.getAsInt(), portConfig.containerPort)));
}
} else {
result.add(new DecoratorBuildItem(MINIKUBE, new AddNodePortDecorator(name, config.getNodePort()
.orElseGet(() -> getStablePortNumberInRange(name, MIN_NODE_PORT_VALUE, MAX_NODE_PORT_VALUE)))));
}

//Probe port handling
Integer port = ports.stream().filter(p -> HTTP_PORT.equals(p.getName())).map(KubernetesPortBuildItem::getPort)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,55 @@

import static io.quarkus.kubernetes.deployment.Constants.*;

import java.util.Optional;
import java.util.function.Predicate;

import org.jboss.logging.Logger;

import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.NamedResourceDecorator;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.ServiceSpecFluent;

public class AddNodePortDecorator extends NamedResourceDecorator<ServiceSpecFluent> {

private static final Logger log = Logger.getLogger(AddNodePortDecorator.class);

private final int nodePort;
private final Optional<String> matchingPortName;

public AddNodePortDecorator(String name, int nodePort) {
this(name, nodePort, Optional.empty());
}

public AddNodePortDecorator(String name, int nodePort, Optional<String> matchingPortName) {
super(name);
if (nodePort < MIN_NODE_PORT_VALUE || nodePort > MAX_NODE_PORT_VALUE) {
log.info("Using a port outside of the " + MIN_NODE_PORT_VALUE + "-" + MAX_NODE_PORT_VALUE
+ " range might not work, see https://kubernetes.io/docs/concepts/services-networking/service/#nodeport");
}
this.nodePort = nodePort;
this.matchingPortName = matchingPortName;
}

@SuppressWarnings("unchecked")
@Override
public void andThenVisit(ServiceSpecFluent service, ObjectMeta resourceMeta) {
ServiceSpecFluent.PortsNested<?> editFirstPort = service.editFirstPort();
editFirstPort.withNodePort(nodePort);
editFirstPort.endPort();
ServiceSpecFluent.PortsNested<?> editPort;
if (matchingPortName.isPresent()) {
editPort = service.editMatchingPort(new Predicate<ServicePortBuilder>() {
@Override
public boolean test(ServicePortBuilder servicePortBuilder) {
return (servicePortBuilder.hasName())
&& (servicePortBuilder.getName().equals(matchingPortName.get()));
}
});
} else {
editPort = service.editFirstPort();
}
editPort.withNodePort(nodePort);
editPort.endPort();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,32 @@ public class PortConfig {
* The port number. Refers to the container port.
*/
@ConfigItem
OptionalInt containerPort;
public OptionalInt containerPort;

/**
* The host port.
*/
@ConfigItem
OptionalInt hostPort;
public OptionalInt hostPort;

/**
* The application path (refers to web application path).
*
* @return The path, defaults to /.
*/
@ConfigItem(defaultValue = "/")
Optional<String> path;
public Optional<String> path;

/**
* The protocol.
*/
@ConfigItem(defaultValue = "TCP")
Protocol protocol;
public Protocol protocol;

/**
* The nodePort to which this port should be mapped to.
* This only takes affect when the serviceType is set to node-port.
*/
public OptionalInt nodePort;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.dekorate.kubernetes.annotation.ServiceType;
Expand Down Expand Up @@ -133,8 +135,18 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic

// Service handling
result.add(new DecoratorBuildItem(KUBERNETES, new ApplyServiceTypeDecorator(name, config.getServiceType().name())));
if ((config.getServiceType() == ServiceType.NodePort) && config.nodePort.isPresent()) {
result.add(new DecoratorBuildItem(KUBERNETES, new AddNodePortDecorator(name, config.nodePort.getAsInt())));
if ((config.getServiceType() == ServiceType.NodePort)) {
List<Map.Entry<String, PortConfig>> nodeConfigPorts = config.ports.entrySet().stream()
.filter(e -> e.getValue().nodePort.isPresent())
.collect(Collectors.toList());
if (!nodeConfigPorts.isEmpty()) {
for (Map.Entry<String, PortConfig> entry : nodeConfigPorts) {
result.add(new DecoratorBuildItem(KUBERNETES,
new AddNodePortDecorator(name, entry.getValue().nodePort.getAsInt(), Optional.of(entry.getKey()))));
}
} else if (config.nodePort.isPresent()) {
result.add(new DecoratorBuildItem(KUBERNETES, new AddNodePortDecorator(name, config.nodePort.getAsInt())));
}
}

// Probe port handling
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.quarkus.it.kubernetes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.quarkus.test.ProdBuildResults;
import io.quarkus.test.ProdModeTestResults;
import io.quarkus.test.QuarkusProdModeTest;

public class KubernetesWithMultiplePortsTest {

@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class))
.setApplicationName("kubernetes-with-multiple-ports")
.setApplicationVersion("0.1-SNAPSHOT")
.withConfigurationResource("kubernetes-with-multiple-ports.properties");

@ProdBuildResults
private ProdModeTestResults prodModeTestResults;

@Test
public void assertGeneratedResources() throws IOException {
Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");
assertThat(kubernetesDir)
.isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json"))
.isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml"));
List<HasMetadata> kubernetesList = DeserializationUtil
.deserializeAsList(kubernetesDir.resolve("kubernetes.yml"));

assertThat(kubernetesList).hasSize(2);

assertThat(kubernetesList).filteredOn(i -> "Deployment".equals(i.getKind())).singleElement().satisfies(i -> {
assertThat(i).isInstanceOfSatisfying(Deployment.class, d -> {
assertThat(d.getMetadata()).satisfies(m -> {
assertThat(m.getName()).isEqualTo("kubernetes-with-multiple-ports");
});

assertThat(d.getSpec()).satisfies(deploymentSpec -> {
assertThat(deploymentSpec.getTemplate()).satisfies(t -> {
assertThat(t.getSpec()).satisfies(podSpec -> {
assertThat(podSpec.getContainers()).singleElement().satisfies(container -> {
assertThat(container.getPorts()).hasSize(2);
assertThat(container.getPorts()).filteredOn(cp -> cp.getContainerPort() == 8080).singleElement()
.satisfies(c -> assertThat(c.getName()).isEqualTo("http"));
assertThat(container.getPorts()).filteredOn(cp -> cp.getContainerPort() == 5005).singleElement()
.satisfies(c -> assertThat(c.getName()).isEqualTo("remote"));
});
});
});
});
});
});

assertThat(kubernetesList).filteredOn(i -> "Service".equals(i.getKind())).singleElement().satisfies(i -> {
assertThat(i).isInstanceOfSatisfying(Service.class, s -> {
assertThat(s.getSpec()).satisfies(spec -> {
assertEquals("NodePort", spec.getType());
assertThat(spec.getPorts()).hasSize(2);
assertThat(spec.getPorts()).filteredOn(sp -> sp.getPort() == 8080).singleElement().satisfies(p -> {
assertThat(p.getNodePort()).isEqualTo(30000);
});
assertThat(spec.getPorts()).filteredOn(sp -> sp.getPort() == 5005).singleElement().satisfies(p -> {
assertThat(p.getNodePort()).isEqualTo(31000);
});
});
});
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
quarkus.kubernetes.ports.http.name=http
quarkus.kubernetes.ports.http.host-port=8080
quarkus.kubernetes.ports.http.container-port=8080
quarkus.kubernetes.ports.http.node-port=30000
quarkus.kubernetes.ports.remote.name=http
quarkus.kubernetes.ports.remote.host-port=5005
quarkus.kubernetes.ports.remote.container-port=5005
quarkus.kubernetes.ports.remote.node-port=31000
quarkus.kubernetes.service-type=NodePort

0 comments on commit 164116f

Please sign in to comment.