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

feat: override external URL for additional registration #3935

Merged
merged 16 commits into from
Jan 14, 2025
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
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ jobs:
env:
APIML_SERVICE_APIMLID: central-apiml
APIML_SERVICE_HOSTNAME: gateway-service
APIML_SERVICE_EXTERNALURL: https://gateway-service:10010
APIML_GATEWAY_REGISTRY_ENABLED: true
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
mock-services:
Expand All @@ -313,6 +314,7 @@ jobs:
env:
APIML_SERVICE_APIMLID: domain-apiml
APIML_SERVICE_HOSTNAME: gateway-service-2
APIML_SERVICE_EXTERNALURL: https://gateway-service-2:10010
APIML_GATEWAY_REGISTRY_ENABLED: false
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
Expand Down
1 change: 1 addition & 0 deletions containers/gateway-service/prepare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ buildPackage $gateway_package "packageApiGateway"
buildApimlCommonPackage

preparePackage $gateway_package
preparePackage $apiml_common_package "apiml-common-lib"
prepareBasicFiles

copyToBuildContext $linux_distro $cpu_arch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@

package org.zowe.apiml.gateway.config;

import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.*;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
Expand All @@ -22,6 +19,8 @@
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.resolver.DefaultAddressResolverGroup;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
Expand Down Expand Up @@ -56,6 +55,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.pattern.PathPatternParser;
import org.zowe.apiml.config.AdditionalRegistration;
import org.zowe.apiml.config.AdditionalRegistrationCondition;
Expand All @@ -74,6 +74,8 @@

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -147,6 +149,9 @@ public ConnectionsConfig(ApplicationContext context) {
this.context = context;
}

@Value("${apiml.service.externalUrl:}")
private String externalUrl;

@PostConstruct
public void updateConfigParameters() {
ServerProperties serverProperties = context.getBean(ServerProperties.class);
Expand Down Expand Up @@ -328,13 +333,13 @@ private CloudEurekaClient registerInTheApimlInstance(EurekaClientConfig config,
configBean.setServiceUrl(urls);

EurekaInstanceConfig eurekaInstanceConfig = appManager.getEurekaInstanceConfig();
InstanceInfo newInfo = eurekaFactory.createInstanceInfo(eurekaInstanceConfig);
InstanceInfo newInfo = create(eurekaInstanceConfig);

updateMetadata(newInfo, apimlRegistration);

RestTemplateDiscoveryClientOptionalArgs args1 = defaultArgs(getDefaultEurekaClientHttpRequestFactorySupplier());
RestTemplateTransportClientFactories factories = new RestTemplateTransportClientFactories(args1);
return eurekaFactory.createCloudEurekaClient(eurekaInstanceConfig, newInfo, configBean, context, factories, args1);
return eurekaFactory.createCloudEurekaClient(new AdditionalEurekaConfiguration(eurekaInstanceConfig, newInfo), newInfo, configBean, context, factories, args1);
}

private boolean isRouteKey(String key) {
Expand Down Expand Up @@ -416,4 +421,126 @@ public CorsUtils corsUtils() {
return new CorsUtils(corsEnabled, null);
}

public InstanceInfo create(EurekaInstanceConfig config) {
LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
.setDurationInSecs(config.getLeaseExpirationDurationInSeconds());

// Builder the instance information to be registered with eureka
// server
pj892031 marked this conversation as resolved.
Show resolved Hide resolved
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();

String namespace = config.getNamespace();
if (!namespace.endsWith(".")) {
namespace = namespace + ".";
}
URL url;
try {
url = new URL(externalUrl);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}

builder
.setNamespace(namespace)
.setAppName(config.getAppname())
.setInstanceId(config.getInstanceId())
.setAppGroupName(config.getAppGroupName())
.setDataCenterInfo(config.getDataCenterInfo())
.setIPAddr(config.getIpAddress())
.setHostName(url.getHost())
.setPort(url.getPort())
.enablePort(InstanceInfo.PortType.UNSECURE, config.isNonSecurePortEnabled())
.setSecurePort(url.getPort())
.enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
.setVIPAddress(config.getVirtualHostName())
.setSecureVIPAddress(config.getSecureVirtualHostName())
.setHomePageUrl(null, UriComponentsBuilder.fromUriString(externalUrl).path(config.getHomePageUrlPath()).toUriString())
.setStatusPageUrl(null, UriComponentsBuilder.fromUriString(externalUrl).path(config.getStatusPageUrlPath()).toUriString())
.setHealthCheckUrls(config.getHealthCheckUrlPath(), null,null)
.setASGName(config.getASGName());

// Start off with the STARTING state to avoid traffic
if (!config.isInstanceEnabledOnit()) {
InstanceInfo.InstanceStatus initialStatus = InstanceInfo.InstanceStatus.STARTING;
if (log.isInfoEnabled()) {
log.info("Setting initial instance status as: " + initialStatus);
}
builder.setStatus(initialStatus);
}
else {
if (log.isInfoEnabled()) {
log.info("Setting initial instance status as: " + InstanceInfo.InstanceStatus.UP
+ ". This may be too early for the instance to advertise itself as available. "
+ "You would instead want to control this via a healthcheck handler.");
}
}

// Add any user-specific metadata information
var fromUrl = UriComponentsBuilder.fromUriString(config.getHomePageUrl()).path("/").toUriString();
var toUrl = UriComponentsBuilder.fromUriString(externalUrl).path("/").toUriString();
for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {
String key = mapEntry.getKey();
String value = mapEntry.getValue();
// only add the metadata if the value is present
if (value != null && !value.isEmpty()) {
value = value.replace(fromUrl, toUrl);
builder.add(key, value);
}
}

InstanceInfo instanceInfo = builder.build();
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
return instanceInfo;
}

@RequiredArgsConstructor
static class AdditionalEurekaConfiguration implements EurekaInstanceConfig {

@Delegate(excludes = NonDelegated.class)
private final EurekaInstanceConfig eurekaInstanceConfig;

private final InstanceInfo instanceInfo;

@Override
public String getHostName(boolean refresh) {
eurekaInstanceConfig.getHostName(refresh);
return instanceInfo.getHostName();
}

@Override
public String getHealthCheckUrl() {
if (instanceInfo.isPortEnabled(InstanceInfo.PortType.UNSECURE)) {
return instanceInfo.getHealthCheckUrl();
}
return instanceInfo.getSecureHealthCheckUrl();
}

@Override
public String getSecureHealthCheckUrl() {
return instanceInfo.getSecureHealthCheckUrl();
}

@Override
public String getHomePageUrl() {
return instanceInfo.getHomePageUrl();
}

@Override
public String getStatusPageUrl() {
return instanceInfo.getStatusPageUrl();
}

interface NonDelegated {

String getHostName(boolean refresh);
String getHealthCheckUrl();
String getSecureHealthCheckUrl();
String getHomePageUrl();
String getStatusPageUrl();

}

}

}
4 changes: 4 additions & 0 deletions gateway-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ eureka:
#ports are computed in code
homePageUrl: ${apiml.service.scheme}://${apiml.service.hostname}:${apiml.service.port}/
healthCheckUrl: ${apiml.service.scheme}://${apiml.service.hostname}:${apiml.service.port}/application/health
healthCheckUrlPath: /application/health
port: ${apiml.service.port}
securePort: ${apiml.service.port}
nonSecurePortEnabled: ${apiml.service.nonSecurePortEnabled}
securePortEnabled: ${apiml.service.securePortEnabled}
statusPageUrl: ${apiml.service.scheme}://${apiml.service.hostname}:${apiml.service.port}/application/info
statusPageUrlPath: /application/info
metadata-map:
apiml:
registrationType: primary
Expand Down Expand Up @@ -103,6 +106,7 @@ apiml:
preferIpAddress: false
nonSecurePortEnabled: false
securePortEnabled: true
externalUrl: ${apiml.service.scheme}://${apiml.service.hostname}:${apiml.service.port}
security:
headersToBeCleared: X-Certificate-Public,X-Certificate-DistinguishedName,X-Certificate-CommonName
ssl:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void setUp() throws Exception {
metadata.put("apiml.routes.0.gatewayUrl", "/api/v1");
metadata.put("apiml.routes.0.serviceUrl", "/service/api/v1");
instanceInfo = InstanceInfo.Builder.newBuilder().setAppName("service1").setMetadata(metadata).build();
lenient().when(eurekaFactory.createInstanceInfo(any())).thenReturn(instanceInfo);
lenient().doReturn(instanceInfo).when(configSpy).create(any());
}

@Test
Expand Down
Loading
Loading