Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Service Registration feature in a standalone way #888

Merged
merged 1 commit into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions api/src/main/java/io/smallrye/stork/api/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public Service(String serviceName,
this.instanceSelectionLock = requiresStrictRecording ? new Semaphore(1) : null;
}

private Service(Builder builder) {
this.serviceName = builder.serviceName;
this.serviceSelectionType = builder.serviceSelectionType;
this.serviceDiscoveryType = builder.serviceDiscoveryType;
this.observations = builder.observations;
this.loadBalancer = builder.loadBalancer;
this.serviceDiscovery = builder.serviceDiscovery;
this.serviceRegistrar = builder.serviceRegistrar;
this.instanceSelectionLock = builder.requiresStrictRecording ? new Semaphore(1) : null;
}

/**
* Selects a service instance.
* <p>
Expand Down Expand Up @@ -212,4 +223,59 @@ public ObservationCollector getObservations() {
public String getServiceName() {
return serviceName;
}

public static class Builder {
private String serviceName;
private final ObservationCollector observations;
private ServiceDiscovery serviceDiscovery;
private String serviceSelectionType = "round-robin";
private String serviceDiscoveryType;
private LoadBalancer loadBalancer;
private ServiceRegistrar<?> serviceRegistrar;
private boolean requiresStrictRecording = false;

public Builder serviceName(String serviceName) {
this.serviceName = serviceName;
return this;
}

public Builder(ObservationCollector observations) {
this.observations = observations;
}

public Builder serviceDiscovery(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
return this;
}

public Builder serviceSelectionType(String serviceSelectionType) {
this.serviceSelectionType = serviceSelectionType;
return this;
}

public Builder serviceDiscoveryType(String serviceDiscoveryType) {
this.serviceDiscoveryType = serviceDiscoveryType;
return this;
}

public Builder loadBalancer(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
return this;
}

public Builder serviceRegistrar(ServiceRegistrar<?> serviceRegistrar) {
this.serviceRegistrar = serviceRegistrar;
return this;
}

public Builder requiresStrictRecording(boolean requiresStrictRecording) {
this.requiresStrictRecording = requiresStrictRecording;
return this;
}

public Service build() {
return new Service(this);
}
}

}
10 changes: 10 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@
<artifactId>stork-spring-boot-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.smallrye.stork</groupId>
<artifactId>stork-service-registration-consul</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.smallrye.stork</groupId>
<artifactId>stork-service-registration-static-list</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.smallrye.stork</groupId>
<artifactId>stork-test-utils</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@
"io.smallrye.stork.api.config.LoadBalancerAttributes",
"io.smallrye.stork.api.config.ServiceDiscoveryType",
"io.smallrye.stork.api.config.ServiceDiscoveryAttribute",
"io.smallrye.stork.api.config.ServiceDiscoveryAttributes"
"io.smallrye.stork.api.config.ServiceDiscoveryAttributes",
"io.smallrye.stork.api.config.ServiceRegistrarType",
"io.smallrye.stork.api.config.ServiceRegistrarAttribute",
"io.smallrye.stork.api.config.ServiceRegistrarAttributes"
})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class ConfigurationGenerator extends AbstractProcessor {

Expand Down
84 changes: 47 additions & 37 deletions core/src/main/java/io/smallrye/stork/Stork.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ public Stork(StorkInfrastructure storkInfrastructure) {
services.put(serviceConfig.serviceName(), service);
}
for (Service service : services.values()) {
service.getServiceDiscovery().initialize(this);
if (service.getServiceDiscovery() != null) {
service.getServiceDiscovery().initialize(this);
}
}
}

Expand Down Expand Up @@ -178,54 +180,64 @@ private Service createService(Map<String, LoadBalancerLoader> loadBalancerLoader
Map<String, ServiceDiscoveryLoader> serviceDiscoveryProviders,
Map<String, ServiceRegistrarLoader> serviceRegistrarLoaders, ServiceConfig serviceConfig) {
var serviceDiscoveryConfig = serviceConfig.serviceDiscovery();
if (serviceDiscoveryConfig == null) {
throw new IllegalArgumentException(
"No service discovery defined for service " + serviceConfig.serviceName());
}
String serviceDiscoveryType = serviceDiscoveryConfig.type();
if (serviceDiscoveryType == null) {
var serviceBuilder = new Service.Builder(infrastructure.getObservationCollector());
if (serviceDiscoveryConfig != null && serviceDiscoveryConfig.type() == null) {
throw new IllegalArgumentException(
"Service discovery type not defined for service " + serviceConfig.serviceName());
}

final var serviceDiscoveryProvider = serviceDiscoveryProviders.get(serviceDiscoveryType);
if (serviceDiscoveryProvider == null) {
throw new IllegalArgumentException("ServiceDiscoveryProvider not found for type " + serviceDiscoveryType);
if (serviceDiscoveryConfig != null) {
if (serviceDiscoveryProviders.get(serviceDiscoveryConfig.type()) == null) {
throw new IllegalArgumentException(
"ServiceDiscoveryProvider not found for type " + serviceDiscoveryConfig.type());
}
}

if (serviceConfig.secure()) {
// Backward compatibility
LOGGER.warn("The 'secure' attribute is deprecated, use the 'secure' service discovery attribute instead");
// We do not know if we can add to the parameters, such create a new SimpleConfigWithType
Map<String, String> newConfig = new HashMap<>(serviceDiscoveryConfig.parameters());
newConfig.put("secure", "true");
serviceDiscoveryConfig = new SimpleServiceConfig.SimpleServiceDiscoveryConfig(serviceDiscoveryType, newConfig);
if (serviceDiscoveryConfig != null) {
Map<String, String> newConfig = new HashMap<>(serviceDiscoveryConfig.parameters());
newConfig.put("secure", "true");
serviceDiscoveryConfig = new SimpleServiceConfig.SimpleServiceDiscoveryConfig(serviceDiscoveryConfig.type(),
newConfig);
}
}

final var serviceDiscovery = serviceDiscoveryProvider.createServiceDiscovery(serviceDiscoveryConfig,
serviceConfig.serviceName(), serviceConfig, infrastructure);

final var loadBalancerConfig = serviceConfig.loadBalancer();
final LoadBalancer loadBalancer;
String loadBalancerType;
if (loadBalancerConfig == null) {
// no load balancer, use round-robin
LOGGER.debug("No load balancer configured for type {}, using {}", serviceDiscoveryType,
RoundRobinLoadBalancerProvider.ROUND_ROBIN_TYPE);
loadBalancerType = RoundRobinLoadBalancerProvider.ROUND_ROBIN_TYPE;
loadBalancer = new RoundRobinLoadBalancer();
} else {
loadBalancerType = loadBalancerConfig.type();
final var loadBalancerProvider = loadBalancerLoaders.get(loadBalancerType);
if (loadBalancerProvider == null) {
throw new IllegalArgumentException("No LoadBalancerProvider for type " + loadBalancerType);
if (serviceDiscoveryConfig != null) {
final var serviceDiscoveryProvider = serviceDiscoveryProviders.get(serviceDiscoveryConfig.type());
final var serviceDiscovery = serviceDiscoveryProvider.createServiceDiscovery(serviceDiscoveryConfig,
serviceConfig.serviceName(), serviceConfig, infrastructure);

final var loadBalancerConfig = serviceConfig.loadBalancer();
final LoadBalancer loadBalancer;
String loadBalancerType;
if (loadBalancerConfig == null) {
// no load balancer, use round-robin
LOGGER.debug("No load balancer configured for type {}, using {}", serviceDiscoveryConfig.type(),
RoundRobinLoadBalancerProvider.ROUND_ROBIN_TYPE);
loadBalancerType = RoundRobinLoadBalancerProvider.ROUND_ROBIN_TYPE;
loadBalancer = new RoundRobinLoadBalancer();
} else {
loadBalancerType = loadBalancerConfig.type();
final var loadBalancerProvider = loadBalancerLoaders.get(loadBalancerType);
if (loadBalancerProvider == null) {
throw new IllegalArgumentException("No LoadBalancerProvider for type " + loadBalancerType);
}

loadBalancer = loadBalancerProvider.createLoadBalancer(loadBalancerConfig, serviceDiscovery);
}

loadBalancer = loadBalancerProvider.createLoadBalancer(loadBalancerConfig, serviceDiscovery);
serviceBuilder = serviceBuilder.serviceName(serviceConfig.serviceName())
.serviceDiscovery(serviceDiscovery)
.serviceSelectionType(loadBalancerType)
.serviceDiscoveryType(serviceDiscoveryConfig.type())
.loadBalancer(loadBalancer)
.requiresStrictRecording(loadBalancer.requiresStrictRecording());
}

final var serviceRegistrarConfig = serviceConfig.serviceRegistrar();
ServiceRegistrar<?> serviceRegistrar = null;
final ServiceRegistrar<?> serviceRegistrar;
if (serviceRegistrarConfig == null) {
LOGGER.debug("No service registrar configured for service {}", serviceConfig.serviceName());
} else {
Expand All @@ -237,12 +249,10 @@ private Service createService(Map<String, LoadBalancerLoader> loadBalancerLoader

serviceRegistrar = serviceRegistrarLoader.createServiceRegistrar(serviceRegistrarConfig,
serviceConfig.serviceName(), infrastructure);
serviceBuilder.serviceName(serviceConfig.serviceName()).serviceRegistrar(serviceRegistrar);
}

return new Service(serviceConfig.serviceName(),
loadBalancerType, serviceDiscoveryType, infrastructure.getObservationCollector(),
loadBalancer, serviceDiscovery, serviceRegistrar,
loadBalancer.requiresStrictRecording());
return serviceBuilder.build();
}

private <T extends ElementWithType> Map<String, T> loadFromServiceLoader(Class<T> loaderClass) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.stork.servicediscovery.consul;
package io.smallrye.stork.impl;

import io.smallrye.stork.api.MetadataKey;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.stork.servicediscovery.eureka;
package io.smallrye.stork.impl;

import io.smallrye.stork.api.MetadataKey;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.smallrye.stork.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InMemoryAddressesBackend {

private static Map<String, List<String>> backend = new HashMap<>();

public static List<String> getAddresses(String serviceName) {
return backend.get(serviceName);
}

public static void add(String serviceName, String address) {
if (serviceName == null || serviceName.length() == 0) {
throw new IllegalArgumentException("No service name provided for address " + address);
}
if (backend.get(serviceName) != null) {
if (!backend.get(serviceName).contains(address)) {
backend.get(serviceName).add(address);
}
} else {
List<String> addresses = new ArrayList<>();
cescoffier marked this conversation as resolved.
Show resolved Hide resolved
addresses.add(address);
backend.put(serviceName, addresses);
}
}

public static void clear(String serviceName) {
if (backend != null) {
backend.remove(serviceName);
}
}

public static void clearAll() {
backend.clear();
}
}
44 changes: 44 additions & 0 deletions core/src/test/java/io/smallrye/stork/StorkTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ public Map<String, String> parameters() {
}
};

private static final ConfigWithType SERVICE_REGISTRAR_CONFIG = new ConfigWithType() {

@Override
public String type() {
return "test";
}

@Override
public Map<String, String> parameters() {
return Collections.emptyMap();
}
};

@BeforeEach
public void init() {
TestEnv.SPI_ROOT.mkdirs();
Expand Down Expand Up @@ -277,6 +290,23 @@ public Map<String, String> parameters() {
Assertions.assertTrue(stork.getService("a").getLoadBalancer() instanceof RoundRobinLoadBalancer);
}

@Test
public void initWithOnlyServiceRegistrarConfiguration() {
TestEnv.configurations.add(new FakeServiceConfig("test", null, null, SERVICE_REGISTRAR_CONFIG));
TestEnv.install(ConfigProvider.class, RegistrarConfigProvider.class);
Assertions.assertDoesNotThrow(() -> Stork.initialize());
}

@Test
void initWithoutServiceDiscovery() {
TestEnv.configurations.add(new FakeServiceConfig("a", null, null, null));
TestEnv.install(ConfigProvider.class, TestEnv.AnchoredConfigProvider.class);
Assertions.assertDoesNotThrow(() -> Stork.initialize());
Stork stork = Stork.getInstance();
Assertions.assertTrue(stork.getServiceOptional("missing").isEmpty());
Assertions.assertThrows(NoSuchServiceDefinitionException.class, () -> stork.getService("missing"));
}

public static class ServiceAConfigProvider implements ConfigProvider {

@Override
Expand Down Expand Up @@ -305,6 +335,20 @@ public int priority() {
}
}

public static class RegistrarConfigProvider implements ConfigProvider {

@Override
public List<ServiceConfig> getConfigs() {
ServiceConfig service = new FakeServiceConfig("test", null, null, SERVICE_REGISTRAR_CONFIG);
return List.of(service);
}

@Override
public int priority() {
return 100;
}
}

private static class FakeSecureServiceConfig extends FakeServiceConfig {

private FakeSecureServiceConfig(String name, ConfigWithType sd, ConfigWithType lb, ConfigWithType sr) {
Expand Down
36 changes: 36 additions & 0 deletions core/src/test/java/io/smallrye/stork/TestMetadataKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.smallrye.stork;

import io.smallrye.stork.api.MetadataKey;

public enum TestMetadataKey implements MetadataKey {

/**
* The key for the consul service id.
*/
META_CONSUL_SERVICE_ID("consul-service-id"),
/**
* The key for the consul service node.
*/
META_CONSUL_SERVICE_NODE("consul-service-node"),
/**
* The key for the consul service node address.
*/
META_CONSUL_SERVICE_NODE_ADDRESS("consul-service-node-address");

private final String name;

/**
* Creates a new ConsulMetadataKey
*
* @param name the name
*/
TestMetadataKey(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

}
Loading