Skip to content

Commit

Permalink
Merge branch 'main' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
aureamunoz authored Oct 28, 2024
2 parents 4283b88 + 7b5726a commit 8e10087
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 5 deletions.
7 changes: 7 additions & 0 deletions api/src/main/java/io/smallrye/stork/api/ServiceRegistrar.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
public interface ServiceRegistrar<MetadataKeyType extends Enum<MetadataKeyType> & MetadataKey> {

default Uni<Void> registerServiceInstance(String serviceName, String ipAddress, int defaultPort) {
checkAddressNotNull(ipAddress);
return registerServiceInstance(serviceName, Metadata.empty(), ipAddress, defaultPort);
}

default void checkAddressNotNull(String ipAddress) {
if (ipAddress == null || ipAddress.isEmpty() || ipAddress.isBlank()) {
throw new IllegalArgumentException("Parameter ipAddress should be provided.");
}
}

Uni<Void> registerServiceInstance(String serviceName, Metadata<MetadataKeyType> metadata, String ipAddress,
int defaultPort);

Expand Down
16 changes: 16 additions & 0 deletions docs/docs/load-balancer/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### Load Balancer / Service Selection in SmallRye Stork

Once services are registered and discovered, the next critical step is selecting which service instance will handle a given request.
SmallRye Stork provides flexible load balancing strategies to efficiently distribute requests across multiple instances of a service.
This ensures optimal resource usage, improved performance, and high availability.

#### Key Features:
- **Multiple Load Balancing Strategies**: SmallRye Stork supports several built-in strategies for selecting service instances.
Check them out in the following dedicated sections.
- **Customizable Strategies**: You can define custom service selection strategies based on your unique use case or performance requirements, ensuring that the load balancer can adapt to specific needs.

#### How it Works:
Once a service has been registered and discovered, the load balancer comes into play when a client makes a request to that service.
Stork applies the configured load balancing strategy to select an instance from the available pool of discovered services.

This feature ensures that your services remain responsive, scalable, and resilient, providing a smooth experience for both users and developers.
12 changes: 12 additions & 0 deletions docs/docs/service-discovery/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Service Discovery in SmallRye Stork

As already introduced, service discovery is a crucial part of modern microservices architectures.
It allows services to dynamically discover the location of other services at runtime, which is particularly useful in distributed systems where services may scale up or down,
or change their network addresses.

SmallRye Stork provides a flexible and extensible mechanism for service discovery.
It supports out of the box some service discovery such as Kubernetes or Consul but the main strength of it is customization so you can easily create your own implementation related on your business for example.
Stork allows services to communicate with each other without requiring hardcoded addresses, making it an ideal solution for microservices deployments.
SmallRye Stork brings this capability to clients for Quarkus applications but it's vendor agnostic so you easily use it with other solutions and even in standalone mode.

You can explore the different implementations and learn how to create your own in the following sections.
19 changes: 19 additions & 0 deletions docs/docs/service-registration/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Service Registration in SmallRye Stork

Service registration is the process by which services announce their availability to a central registry, allowing other services to discover and communicate with them.
In SmallRye Stork, service registration is automated and integrated with supported registries like **Consul**.
This ensures that services can dynamically join and leave the network.

#### Key Features:
- **Automatic Registration**: For Quarkus applications, SmallRye Stork automatically registers it with the configured service registry (e.g., Consul).

**IMPORTANT** Public IP address needs to be provided. Smallrye Stork will fail if the service IP address is not provided during registration.

#### Supported Registries:
Currently, Smallrye Stork provides seamless integration with **Consul**, Eureka and a Static registry.
This integration simplifies the management of dynamic environments where services are frequently added or removed.

#### Custom Registration:
In addition to the default mechanisms, SmallRye Stork allows you to implement custom service registration strategies, providing flexibility for different infrastructures or custom service discovery needs.

In the following sections you can have more details about each specific implementation.
3 changes: 3 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ nav:
- Javadoc: 'https://javadoc.io/doc/io.smallrye.stork/smallrye-stork-api/latest/index.html'
- Using Stork with Quarkus: './quarkus.md'
- Service Discovery:
- Overview: 'service-discovery/overview.md'
- Consul: 'service-discovery/consul.md'
- DNS: 'service-discovery/dns.md'
- Kubernetes: 'service-discovery/kubernetes.md'
Expand All @@ -23,6 +24,7 @@ nav:
- Static List: 'service-discovery/static-list.md'
- Custom Service Discovery: 'service-discovery/custom-service-discovery.md'
- Load-Balancing:
- Overview: 'load-balancer/overview.md'
- Round Robin: 'load-balancer/round-robin.md'
- Random: 'load-balancer/random.md'
- Least Requests: 'load-balancer/least-requests.md'
Expand All @@ -31,6 +33,7 @@ nav:
- Sticky: 'load-balancer/sticky.md'
- Custom Load Balancer: 'load-balancer/custom-load-balancer.md'
- Service Registration:
- Overview: 'service-registration/overview.md'
- Consul: 'service-registration/consul.md'
- Eureka: 'service-registration/eureka.md'
- Static List: 'service-registration/static-list.md'
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<vertx.version>4.5.10</vertx.version>
<version.vertx-mutiny-bindings>3.15.1</version.vertx-mutiny-bindings>
<!-- override the testcontainers version with the newest one -->
<version.testcontainers>1.20.2</version.testcontainers>
<version.testcontainers>1.20.3</version.testcontainers>

<kubernetes-client.version>6.13.4</kubernetes-client.version>
<!-- we cannot update to 2.x as jboss-logging does not support SLF4J 2 for now -->
Expand All @@ -58,7 +58,7 @@
<revapi-reporter-text.version>0.15.0</revapi-reporter-text.version>
<revapi.skip>true</revapi.skip>

<jandex-maven-plugin.version>3.2.2</jandex-maven-plugin.version>
<jandex-maven-plugin.version>3.2.3</jandex-maven-plugin.version>

<plugin.version.failsafe>3.5.1</plugin.version.failsafe>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public ConsulServiceRegistrar(ConsulRegistrarConfiguration config, String servic
@Override
public Uni<Void> registerServiceInstance(String serviceName, Metadata<ConsulMetadataKey> metadata, String ipAddress,
int defaultPort) {
checkAddressNotNull(ipAddress);

String consulId = metadata.getMetadata().get(ConsulMetadataKey.META_CONSUL_SERVICE_ID).toString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.time.Duration;
import java.util.Map;
Expand Down Expand Up @@ -86,4 +88,26 @@ void shouldRegisterServiceInstancesInConsul() throws InterruptedException {
assertThat(subscriber.awaitItem().getItem()).isNotNull();

}

@Test
void shouldFailIfNoIpAddressProvided() throws InterruptedException {
String serviceName = "my-service";
TestConfigProvider.addServiceConfig(serviceName, null, null, "consul",
null, Map.of("consul-host", "localhost", "consul-port", String.valueOf(consulPort), "refresh-period", "5"),
Map.of("consul-host", "localhost", "consul-port", String.valueOf(consulPort)));
Stork stork = StorkTestUtils.getNewStorkInstance();

ServiceRegistrar<ConsulMetadataKey> consulRegistrar = stork.getService(serviceName).getServiceRegistrar();

Exception exception = assertThrows(IllegalArgumentException.class, () -> {
consulRegistrar.registerServiceInstance(serviceName, Metadata.of(ConsulMetadataKey.class)
.with(ConsulMetadataKey.META_CONSUL_SERVICE_ID, serviceName), null, 8406);
});

String expectedMessage = "Parameter ipAddress should be provided.";
String actualMessage = exception.getMessage();

assertTrue(actualMessage.contains(expectedMessage));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ public EurekaServiceRegistrar(EurekaRegistrarConfiguration config, String servic
public Uni<Void> registerServiceInstance(String serviceName, Metadata<EurekaMetadataKey> metadata, String ipAddress,
int defaultPort) {

checkAddressNotNull(ipAddress);

return registerApplicationInstance(client, serviceName,
metadata.getMetadata().get(EurekaMetadataKey.META_EUREKA_SERVICE_ID).toString(), "192.5.10.236", "acme.com",
metadata.getMetadata().get(EurekaMetadataKey.META_EUREKA_SERVICE_ID).toString(), ipAddress, null,
defaultPort, null, -1, "UP", "");

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.smallrye.stork.impl.EurekaMetadataKey;
import io.smallrye.stork.test.StorkTestUtils;
import io.smallrye.stork.test.TestConfigProviderBean;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.core.buffer.Buffer;
Expand Down Expand Up @@ -93,7 +94,7 @@ public void testRegistrationServiceInstances(TestInfo info) {

CountDownLatch registrationLatch = new CountDownLatch(1);
eurekaServiceRegistrar.registerServiceInstance(serviceName, Metadata.of(EurekaMetadataKey.class)
.with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), "acme.com", 8406).subscribe()
.with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), "localhost", 8406).subscribe()
.with(success -> registrationLatch.countDown(), failure -> fail(""));

await().atMost(Duration.ofSeconds(10))
Expand All @@ -109,6 +110,14 @@ public void testRegistrationServiceInstances(TestInfo info) {
assertThat(httpResponse).isNotNull();
assertThat(httpResponse.statusCode()).isEqualTo(200);

JsonObject jsonResponse = httpResponse.bodyAsJsonObject();
JsonObject application = jsonResponse.getJsonObject("application");
JsonObject jsonServiceInstance = application.getJsonArray("instance").getJsonObject(0);

assertThat(jsonServiceInstance.getString("instanceId")).isEqualTo("my-service");
assertThat(jsonServiceInstance.getString("ipAddr")).isEqualTo("localhost");
assertThat(jsonServiceInstance.getJsonObject("port").getInteger("$")).isEqualTo(8406);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.time.Duration;
import java.util.Map;
Expand All @@ -27,6 +29,7 @@
import io.smallrye.stork.impl.EurekaMetadataKey;
import io.smallrye.stork.test.StorkTestUtils;
import io.smallrye.stork.test.TestConfigProvider;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.core.buffer.Buffer;
Expand Down Expand Up @@ -77,7 +80,7 @@ public void testRegistrationServiceInstances(TestInfo info) {
CountDownLatch registrationLatch = new CountDownLatch(1);

eurekaServiceRegistrar.registerServiceInstance(serviceName, Metadata.of(EurekaMetadataKey.class)
.with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), "acme.com", 8406).subscribe()
.with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), "localhost", 8406).subscribe()
.with(success -> registrationLatch.countDown(), failure -> fail(""));

await().atMost(Duration.ofSeconds(10))
Expand All @@ -93,6 +96,36 @@ public void testRegistrationServiceInstances(TestInfo info) {
assertThat(httpResponse).isNotNull();
assertThat(httpResponse.statusCode()).isEqualTo(200);

JsonObject jsonResponse = httpResponse.bodyAsJsonObject();
JsonObject application = jsonResponse.getJsonObject("application");
JsonObject jsonServiceInstance = application.getJsonArray("instance").getJsonObject(0);

assertThat(jsonServiceInstance.getString("instanceId")).isEqualTo("my-service");
assertThat(jsonServiceInstance.getString("ipAddr")).isEqualTo("localhost");
assertThat(jsonServiceInstance.getJsonObject("port").getInteger("$")).isEqualTo(8406);

}

@Test
void shouldFailIfNoIpAddressProvided() throws InterruptedException {
String serviceName = "my-service";
TestConfigProvider.addServiceConfig(serviceName, null, null, "eureka", null, null,
Map.of("eureka-host", eureka.getHost(), "eureka-port", String.valueOf(port)));

Stork stork = StorkTestUtils.getNewStorkInstance();

ServiceRegistrar<EurekaMetadataKey> eurekaServiceRegistrar = stork.getService(serviceName).getServiceRegistrar();

Exception exception = assertThrows(IllegalArgumentException.class, () -> {
eurekaServiceRegistrar.registerServiceInstance(serviceName, Metadata.of(EurekaMetadataKey.class)
.with(EurekaMetadataKey.META_EUREKA_SERVICE_ID, serviceName), null, 8406);
});

String expectedMessage = "Parameter ipAddress should be provided.";
String actualMessage = exception.getMessage();

assertTrue(actualMessage.contains(expectedMessage));

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public StaticListServiceRegistrar(StaticRegistrarConfiguration config, String se
public Uni<Void> registerServiceInstance(String serviceName, Metadata<Metadata.DefaultMetadataKey> metadata,
String ipAddress,
int defaultPort) {
checkAddressNotNull(ipAddress);
HostAndPort hostAndPortToAdd = StorkAddressUtils.parseToHostAndPort(ipAddress, defaultPort,
"service '" + serviceName + "'");
String hostAndPortToAddString = StorkAddressUtils.parseToString(hostAndPortToAdd);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.smallrye.stork.serviceregistration.staticlist;

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

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -89,4 +91,28 @@ void shouldRegisterServiceInstancesWithSchemeAndPath() {
assertThat(addresses).hasSize(1);
assertThat(addresses.get(0)).isEqualTo("localhost:8081/hello");
}

@Test
void shouldFailIfAddresseNull() {
TestConfigProvider.addServiceConfig("first-service", null, null, "static",
null, null, null);

stork = StorkTestUtils.getNewStorkInstance();

String serviceName = "first-service";

ServiceRegistrar<Metadata.DefaultMetadataKey> staticRegistrar = stork.getService(serviceName).getServiceRegistrar();

staticRegistrar.registerServiceInstance(serviceName, "http://localhost:8081/hello", 8080);

Exception exception = assertThrows(IllegalArgumentException.class, () -> {
staticRegistrar.registerServiceInstance(serviceName, null, 8080);
});

String expectedMessage = "Parameter ipAddress should be provided.";
String actualMessage = exception.getMessage();

assertTrue(actualMessage.contains(expectedMessage));

}
}

0 comments on commit 8e10087

Please sign in to comment.