Skip to content

Commit

Permalink
feat: Cloud Gateway - create additional registrations (#3181)
Browse files Browse the repository at this point in the history
Signed-off-by: alexandr cumarav <[email protected]>
  • Loading branch information
cumarav authored Nov 6, 2023
1 parent de63ffa commit c6cc561
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 31 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,17 @@ jobs:
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS: https://discovery-service:10011/eureka
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_GATEWAYURL: /
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_SERVICEURL: /
cloud-gateway-service-2:
image: ghcr.io/balhar-jakub/cloud-gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SERVICE_APIMLID: domain-apiml
APIML_SERVICE_HOSTNAME: cloud-gateway-service-2
APIML_CLOUDGATEWAY_REGISTRY_ENABLED: false
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS: https://discovery-service:10011/eureka
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_GATEWAYURL: /
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_SERVICEURL: /

steps:
- uses: actions/checkout@v3
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.cloudgatewayservice.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;

import java.util.List;

/**
* Purpose of this holder is to keep additional {@link CloudEurekaClient} instances in the custom bean so that it does not interfere with standard `primary` eureka autoconfiguration.
* <p>
* Wrapper exposes proxy `shutdown` call of the client instances
*/
@Getter
@RequiredArgsConstructor
public class AdditionalEurekaClientsHolder {
private final List<CloudEurekaClient> discoveryClients;

public void shutdown() {
if (discoveryClients != null) {
discoveryClients.forEach(CloudEurekaClient::shutdown);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
package org.zowe.apiml.cloudgatewayservice.config;

import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
Expand All @@ -22,6 +24,7 @@
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -34,16 +37,22 @@
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
import org.springframework.cloud.util.ProxyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.pattern.PathPatternParser;
import org.zowe.apiml.config.AdditionalRegistration;
import org.zowe.apiml.config.AdditionalRegistrationCondition;
import org.zowe.apiml.config.AdditionalRegistrationParser;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
Expand All @@ -59,6 +68,12 @@
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.cloud.netflix.eureka.EurekaClientConfigBean.DEFAULT_ZONE;


@Configuration
Expand Down Expand Up @@ -195,12 +210,56 @@ public CloudEurekaClient primaryEurekaClient(ApplicationInfoManager manager, Eur
AbstractDiscoveryClientOptionalArgs<?> args = new MutableDiscoveryClientOptionalArgs();
args.setEurekaJerseyClient(eurekaJerseyClient);

CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, args,
this.context);
final CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, args, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}

@Bean
public List<AdditionalRegistration> additionalRegistration(StandardEnvironment environment) {
List<AdditionalRegistration> additionalRegistrations = new AdditionalRegistrationParser().extractAdditionalRegistrations(System.getenv());
log.debug("Parsed {} additional registration: {}", additionalRegistrations.size(), additionalRegistrations);
return additionalRegistrations;
}

@Bean(destroyMethod = "shutdown")
@Conditional(AdditionalRegistrationCondition.class)
@RefreshScope
public AdditionalEurekaClientsHolder additionalEurekaClientsHolder(ApplicationInfoManager manager,
EurekaClientConfig config,
List<AdditionalRegistration> additionalRegistrations,
EurekaFactory eurekaFactory,
@Autowired(required = false) HealthCheckHandler healthCheckHandler
) {
List<CloudEurekaClient> additionalClients = new ArrayList<>(additionalRegistrations.size());
for (AdditionalRegistration apimlRegistration : additionalRegistrations) {
CloudEurekaClient cloudEurekaClient = registerInTheApimlInstance(config, apimlRegistration, manager, eurekaFactory);
additionalClients.add(cloudEurekaClient);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
}
return new AdditionalEurekaClientsHolder(additionalClients);
}

private CloudEurekaClient registerInTheApimlInstance(EurekaClientConfig config, AdditionalRegistration apimlRegistration, ApplicationInfoManager appManager, EurekaFactory eurekaFactory) {

log.debug("additional registration: {}", apimlRegistration.getDiscoveryServiceUrls());
Map<String, String> urls = new HashMap<>();
urls.put(DEFAULT_ZONE, apimlRegistration.getDiscoveryServiceUrls());

EurekaClientConfigBean configBean = new EurekaClientConfigBean();
BeanUtils.copyProperties(config, configBean);
configBean.setServiceUrl(urls);

EurekaJerseyClient jerseyClient = factory().createEurekaJerseyClientBuilder(eurekaServerUrl, serviceId).build();
MutableDiscoveryClientOptionalArgs args = new MutableDiscoveryClientOptionalArgs();
args.setEurekaJerseyClient(jerseyClient);

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

return eurekaFactory.createCloudEurekaClient(eurekaInstanceConfig, newInfo, configBean, args, context);
}

@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.cloudgatewayservice.config;

import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.InstanceInfoFactory;
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

/**
* Eureka dependencies injection helper
*/
@Component
public class EurekaFactory {

/**
* Create new copy of instance info
*
* @param instanceConfig eureka instance config to copy from
*/
InstanceInfo createInstanceInfo(EurekaInstanceConfig instanceConfig) {
return new InstanceInfoFactory().create(instanceConfig);
}

public CloudEurekaClient createCloudEurekaClient(EurekaInstanceConfig eurekaInstanceConfig, InstanceInfo newInfo, EurekaClientConfigBean configBean, MutableDiscoveryClientOptionalArgs args, ApplicationContext context) {
ApplicationInfoManager perClientAppManager = new ApplicationInfoManager(eurekaInstanceConfig, newInfo, null);
return new CloudEurekaClient(perClientAppManager, configBean, args, context);
}
}
22 changes: 17 additions & 5 deletions cloud-gateway-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
eureka:
instance:
instanceId: ${apiml.service.hostname}:${apiml.service.id}:${apiml.service.port}
#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
metadata-map:
apiml:
service.apimlId : ${apiml.service.apimlId}
client:
fetchRegistry: true
registerWithEureka: true
region: default
serviceUrl:
defaultZone: ${apiml.service.discoveryServiceUrls}
healthcheck:
enabled: true

spring:
application:
Expand All @@ -13,14 +25,14 @@ apiml:
timeout: 60
service:
apimlId: apiml1
corsEnabled: true
discoveryServiceUrls: https://localhost:10011/eureka/
forwardClientCertEnabled: false
hostname: localhost
id: ${spring.application.name}
ignoredHeadersWhenCorsEnabled: Access-Control-Request-Method,Access-Control-Request-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,Origin
port: 10023
hostname: localhost
discoveryServiceUrls: https://localhost:10011/eureka/
scheme: https # "https" or "http"
corsEnabled: true
ignoredHeadersWhenCorsEnabled: Access-Control-Request-Method,Access-Control-Request-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,Origin
forwardClientCertEnabled: false
security:
ssl:
nonStrictVerifySslCertificatesOfServices: true
Expand Down
8 changes: 8 additions & 0 deletions cloud-gateway-service/src/main/resources/banner.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
_____ _ _ _____ _
/ ____|| | | | / ____| | |
| | | | ___ _ _ __| | | | __ __ _ | |_ ___ __ __ __ _ _ _
| | | | / _ \ | | | | / _` | | | |_ | / _` || __|/ _ \\ \ /\ / // _` || | | |
| |____ | || (_) || |_| || (_| | | |__| || (_| || |_| __/ \ V V /| (_| || |_| |
\_____||_| \___/ \__,_| \__,_| \_____| \__,_| \__|\___| \_/\_/ \__,_| \__, |
__/ |
|___/
Loading

0 comments on commit c6cc561

Please sign in to comment.