diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 14d6672469..18c0532b06 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -63,7 +63,7 @@ jobs:
APIML_SECURITY_AUTH_JWT_CUSTOMAUTHHEADER: customJwtHeader
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMUSERHEADER: customUserHeader
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMAUTHHEADER: customPassticketHeader
- APIML_SERVICE_CENTRALREGISTRYURLS: https://discovery-service-2:10011/eureka
+ ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS: https://discovery-service-2:10011/eureka
mock-services:
image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }}
metrics-service:
@@ -254,10 +254,6 @@ jobs:
APIML_SERVICE_APIMLID: central-apiml
APIML_CLOUDGATEWAY_REGISTRY_ENABLED: true
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
- discoverable-client:
- image: ghcr.io/balhar-jakub/discoverable-client:${{ github.run_id }}-${{ github.run_number }}
- mock-services:
- image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }}
# Second group of services represents domain apiml instance which registers it's gateway in central's discovery service
discovery-service-2:
@@ -274,7 +270,9 @@ jobs:
APIML_SERVICE_HOSTNAME: gateway-service-2
APIML_SERVICE_PORT: 10037
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
- APIML_SERVICE_CENTRALREGISTRYURLS: https://discovery-service:10011/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
diff --git a/cloud-gateway-service/src/main/resources/application.yml b/cloud-gateway-service/src/main/resources/application.yml
index 9a1d353e2d..63a8db7790 100644
--- a/cloud-gateway-service/src/main/resources/application.yml
+++ b/cloud-gateway-service/src/main/resources/application.yml
@@ -24,7 +24,7 @@ apiml:
nonStrictVerifySslCertificatesOfServices: true
cloudGateway:
registry:
- enabled: false
+ enabled: true
metadata-key-allow-list: zos.sysname,zos.system,zos.sysplex,zos.cpcName,zos.zosName,zos.lpar
server:
@@ -62,10 +62,19 @@ logging:
management:
endpoint:
gateway:
- enabled: true
+ enabled: false
endpoints:
web:
base-path: /application
exposure:
include: health,gateway
+---
+spring:
+ config:
+ activate:
+ on-profile: debug
+
+logging:
+ level:
+ org.zowe.apiml: DEBUG
diff --git a/cloud-gateway-service/src/main/resources/logback.xml b/cloud-gateway-service/src/main/resources/logback.xml
index 999077ccc1..e7f7740af5 100644
--- a/cloud-gateway-service/src/main/resources/logback.xml
+++ b/cloud-gateway-service/src/main/resources/logback.xml
@@ -48,4 +48,4 @@
-
\ No newline at end of file
+
diff --git a/config/local/gateway-service.yml b/config/local/gateway-service.yml
index e2c4348c08..43dab9a4e4 100644
--- a/config/local/gateway-service.yml
+++ b/config/local/gateway-service.yml
@@ -5,7 +5,7 @@ apiml:
hostname: localhost
ipAddress: 127.0.0.1
port: 10010
- centralRegistryUrls: https://localhost:10021/eureka/,https://localhost:10031/eureka/
+ additionalRegistration: # List of additional Apiml Discovery Services metadata to register with
discoveryServiceUrls: https://localhost:10011/eureka/
security:
diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh
index 9e6cf90389..d6951c8a72 100755
--- a/gateway-package/src/main/resources/bin/start.sh
+++ b/gateway-package/src/main/resources/bin/start.sh
@@ -54,7 +54,6 @@
# - ZWE_configs_apiml_security_oidc_identityMapperUrl
# - ZWE_configs_apiml_security_oidc_identityMapperUser
# - ZWE_configs_apiml_service_allowEncodedSlashes - Allows encoded slashes on on URLs through gateway
-# - ZWE_configs_apiml_service_centralRegistryUrls - List of additional Discovery Services URLs to register with
# - ZWE_configs_apiml_service_corsEnabled
# - ZWE_configs_certificate_keystore_alias - The alias of the key within the keystore
# - ZWE_configs_certificate_keystore_file - The keystore to use for SSL certificates
@@ -210,9 +209,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} java \
-Dapiml.service.hostname=${ZWE_haInstance_hostname:-localhost} \
-Dapiml.service.port=${ZWE_configs_port:-7554} \
-Dapiml.service.discoveryServiceUrls=${ZWE_DISCOVERY_SERVICES_LIST:-"https://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_discovery_port:-7553}/eureka/"} \
- -Dapiml.service.centralRegistryUrls=${ZWE_configs_apiml_service_centralRegistryUrls:-} \
-Dapiml.service.allowEncodedSlashes=${ZWE_configs_apiml_service_allowEncodedSlashes:-true} \
- -Dapiml.service.centralRegistryUrls=${ZWE_configs_apiml_service_centralRegistryUrls:-} \
-Dapiml.service.corsEnabled=${ZWE_configs_apiml_service_corsEnabled:-false} \
-Dapiml.service.externalUrl="${httpProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \
-Dapiml.service.apimlId=${ZWE_configs_apimlId:-} \
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistration.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistration.java
new file mode 100644
index 0000000000..7e87b46f9c
--- /dev/null
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistration.java
@@ -0,0 +1,36 @@
+/*
+ * 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.gateway.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AdditionalRegistration {
+
+ private String discoveryServiceUrls;
+ private List routes;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class Route {
+ private String gatewayUrl;
+ private String serviceUrl;
+ }
+}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationCondition.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationCondition.java
new file mode 100644
index 0000000000..99acf46e23
--- /dev/null
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationCondition.java
@@ -0,0 +1,37 @@
+/*
+ * 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.gateway.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.web.context.support.StandardServletEnvironment;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RequiredArgsConstructor
+public class AdditionalRegistrationCondition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String dcUrls = context.getEnvironment().getProperty("apiml.service.additionalRegistration[0].discoveryServiceUrls");
+ List additionalKeys = ((StandardServletEnvironment) context.getEnvironment()).getSystemEnvironment()
+ .entrySet().stream().map(e -> e.getKey().toUpperCase()).filter(key -> key.startsWith(AdditionalRegistrationConfig.COMMON_PREFIX))
+ .collect(Collectors.toList());
+ boolean isAdditionalRegistrationsDetected = dcUrls != null || !additionalKeys.isEmpty();
+ log.debug("isAdditionalRegistrationsDetected: {}", isAdditionalRegistrationsDetected);
+ return isAdditionalRegistrationsDetected;
+ }
+}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfig.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfig.java
new file mode 100644
index 0000000000..69446fe81c
--- /dev/null
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfig.java
@@ -0,0 +1,136 @@
+/*
+ * 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.gateway.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.util.CollectionUtils;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@Slf4j
+@Configuration
+@RequiredArgsConstructor
+public class AdditionalRegistrationConfig {
+
+ public static final String DISCOVERY_SERVICE_URLS_KEY = "DISCOVERYSERVICEURLS";
+ public static final String GATEWAY_URL_KEY = "GATEWAYURL";
+ public static final String COMMON_PREFIX = "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_";
+ public static final String SERVICE_URL_KEY = "SERVICEURL";
+ private static final int EXPECTED_ROUTE_PART_INDEX = 2;
+
+ @Bean
+ public List additionalRegistration(StandardEnvironment environment) {
+ List additionalRegistrations = extractAdditionalRegistrations(System.getenv());
+ log.debug("Parsed {} additional regs, \t first: {}", additionalRegistrations.size(), additionalRegistrations.stream().findFirst().orElse(null));
+ return additionalRegistrations;
+ }
+
+ static List extractAdditionalRegistrations(Map allProperties) {
+
+ if (CollectionUtils.isEmpty(allProperties)) {
+ return Collections.emptyList();
+ }
+
+ Map additionalProperties = allProperties.entrySet().stream().filter(entry -> entry.getKey().startsWith(COMMON_PREFIX))
+ .filter(entry -> StringUtils.isNotBlank(entry.getValue()))
+ .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey().replace(COMMON_PREFIX, ""), entry.getValue()))
+ .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, (previous, current) -> current, TreeMap::new));
+
+ for (Map.Entry prp : additionalProperties.entrySet()) {
+ log.debug("Additional property: {}={}", prp.getKey(), prp.getValue());
+ }
+
+ Integer listSize = additionalProperties.keySet().stream().map(key -> parsePropertyName(key).getKey())
+ .max(Integer::compareTo).map(maxIndex -> maxIndex + 1).orElse(0);
+
+ List additionalRegistrations = IntStream.range(0, listSize)
+ .mapToObj(index -> AdditionalRegistration.builder().routes(new ArrayList<>()).build())
+ .collect(Collectors.toList());
+
+ mapProperties(additionalRegistrations, additionalProperties);
+ for (AdditionalRegistration registration : additionalRegistrations) {
+ List definedRoutes = registration.getRoutes().stream().filter(route -> !AdditionalRegistrationConfig.isRouteDefined(route)).collect(Collectors.toList());
+ registration.setRoutes(definedRoutes);
+ }
+
+ return additionalRegistrations.stream()
+ .filter(registration -> StringUtils.isNotBlank(registration.getDiscoveryServiceUrls()))
+ .collect(Collectors.toList());
+ }
+
+ private static void mapProperties(List additionalRegistrations, Map properties) {
+ for (Map.Entry entry : properties.entrySet()) {
+ String propertyKey = entry.getKey();
+ Pair property = parsePropertyName(propertyKey);
+ if (property.getKey() == -1) {
+ continue;
+ }
+ final String propertyValue = entry.getValue();
+ switch (StringUtils.upperCase(property.getValue())) {
+ case DISCOVERY_SERVICE_URLS_KEY:
+ additionalRegistrations.get(property.getKey()).setDiscoveryServiceUrls(propertyValue);
+ break;
+ case GATEWAY_URL_KEY:
+ setRouteProperty(additionalRegistrations.get(property.getKey()), parseRouteIndex(propertyKey), (AdditionalRegistration.Route route) -> route.setGatewayUrl(propertyValue));
+ break;
+ case SERVICE_URL_KEY:
+ setRouteProperty(additionalRegistrations.get(property.getKey()), parseRouteIndex(propertyKey), (AdditionalRegistration.Route route) -> route.setServiceUrl(propertyValue));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private static void setRouteProperty(AdditionalRegistration registration, int routeIndex, Consumer setter) {
+ if (routeIndex > -1) {
+ if (registration.getRoutes().size() <= routeIndex) {
+ registration.getRoutes().add(new AdditionalRegistration.Route());
+ setRouteProperty(registration, routeIndex, setter);
+ }
+ setter.accept(registration.getRoutes().get(routeIndex));
+ }
+ }
+
+ private static int parseRouteIndex(String propertyName) {
+ String[] parts = StringUtils.split(propertyName, "_.");
+ if (parts.length > EXPECTED_ROUTE_PART_INDEX && StringUtils.isNumeric(parts[EXPECTED_ROUTE_PART_INDEX])) {
+ return Integer.parseInt(parts[EXPECTED_ROUTE_PART_INDEX]);
+ }
+ return -1;
+ }
+
+ private static Pair parsePropertyName(String fullPropertyName) {
+ String[] parts = StringUtils.split(fullPropertyName, "_");
+ if (StringUtils.isNumeric(parts[0])) {
+ return Pair.of(Integer.parseInt(parts[0]), parts[parts.length - 1]);
+ }
+ return Pair.of(-1, null);
+ }
+
+ private static boolean isRouteDefined(AdditionalRegistration.Route route) {
+ return route == null || StringUtils.isBlank(route.getGatewayUrl()) && StringUtils.isBlank(route.getServiceUrl());
+ }
+}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/DiscoveryClientConfig.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/DiscoveryClientConfig.java
index c845e68824..c8cdaba922 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/DiscoveryClientConfig.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/DiscoveryClientConfig.java
@@ -12,27 +12,36 @@
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.HealthCheckHandler;
+import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.config.annotation.RefreshScope;
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.util.CollectionUtils;
import org.zowe.apiml.gateway.discovery.ApimlDiscoveryClient;
+import org.zowe.apiml.gateway.discovery.ApimlDiscoveryClientFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.springframework.cloud.netflix.eureka.EurekaClientConfigBean.DEFAULT_ZONE;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.ROUTES;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.ROUTES_GATEWAY_URL;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.ROUTES_SERVICE_URL;
/**
* This configuration override bean EurekaClient with custom ApimlDiscoveryClient. This bean offer additional method
@@ -42,16 +51,15 @@
* Configuration also add listeners to call other beans waiting for fetch new registry. It speeds up distribution of
* changes in whole gateway.
*/
+@Slf4j
@Configuration
@RequiredArgsConstructor
public class DiscoveryClientConfig {
- private final ApplicationContext context;
private final AbstractDiscoveryClientOptionalArgs> optionalArgs;
+ private final ApimlDiscoveryClientFactory apimlDiscoveryClientFactory;
+ private final ApplicationContext context;
private final EurekaJerseyClientImpl.EurekaJerseyClientBuilder eurekaJerseyClientBuilder;
- @Value("${apiml.service.centralRegistryUrls:-}")
- private String[] centralRegistryUrls;
-
@Bean(destroyMethod = "shutdown")
@RefreshScope
public ApimlDiscoveryClient primaryApimlEurekaClient(ApplicationInfoManager manager,
@@ -67,33 +75,60 @@ public ApimlDiscoveryClient primaryApimlEurekaClient(ApplicationInfoManager mana
}
@Bean(destroyMethod = "shutdown")
- @ConditionalOnProperty(name = "apiml.service.centralRegistryUrls")
+ @Conditional({AdditionalRegistrationCondition.class})
@RefreshScope
public DiscoveryClientWrapper additionalDiscoveryClientWrapper(ApplicationInfoManager manager,
EurekaClientConfig config,
- @Autowired(required = false) HealthCheckHandler healthCheckHandler
+ @Autowired(required = false) HealthCheckHandler healthCheckHandler,
+ List additionalRegistrations
) {
- ApplicationInfoManager appManager = ProxyUtils.getTargetObject(manager);
- List discoveryClientsList = new ArrayList<>();
- for (String url : centralRegistryUrls) {
+ List discoveryClientsList = new ArrayList<>(additionalRegistrations.size());
+ for (AdditionalRegistration apimlRegistration : additionalRegistrations) {
+ ApimlDiscoveryClient additionalApimlRegistration = registerInTheApimlInstance(config, healthCheckHandler, apimlRegistration, manager);
+ discoveryClientsList.add(additionalApimlRegistration);
+ }
- EurekaClientConfigBean configBean = new EurekaClientConfigBean();
- BeanUtils.copyProperties(config, configBean);
+ return new DiscoveryClientWrapper(discoveryClientsList);
+ }
- Map urls = new HashMap<>();
- urls.put("defaultZone", url);
+ private ApimlDiscoveryClient registerInTheApimlInstance(EurekaClientConfig config, HealthCheckHandler healthCheckHandler, AdditionalRegistration apimlRegistration, ApplicationInfoManager appManager) {
- configBean.setServiceUrl(urls);
+ EurekaClientConfigBean configBean = new EurekaClientConfigBean();
+ BeanUtils.copyProperties(config, configBean);
- MutableDiscoveryClientOptionalArgs args = new MutableDiscoveryClientOptionalArgs();
- args.setEurekaJerseyClient(eurekaJerseyClientBuilder.build());
+ Map urls = new HashMap<>();
+ log.debug("additional registration: {}", apimlRegistration.getDiscoveryServiceUrls());
+ urls.put(DEFAULT_ZONE, apimlRegistration.getDiscoveryServiceUrls());
- final ApimlDiscoveryClient discoveryClientClient = new ApimlDiscoveryClient(appManager, configBean, args, this.context);
- discoveryClientClient.registerHealthCheck(healthCheckHandler);
- discoveryClientsList.add(discoveryClientClient);
- }
+ configBean.setServiceUrl(urls);
- return new DiscoveryClientWrapper(discoveryClientsList);
+ MutableDiscoveryClientOptionalArgs args = new MutableDiscoveryClientOptionalArgs();
+ args.setEurekaJerseyClient(eurekaJerseyClientBuilder.build());
+
+ ApplicationInfoManager bareManager = ProxyUtils.getTargetObject(appManager);
+ InstanceInfo ii = getInstanceInfo(apimlRegistration, bareManager);
+
+ ApplicationInfoManager perClientAppManager = new ApplicationInfoManager(bareManager.getEurekaInstanceConfig(), ii, null);
+ final ApimlDiscoveryClient discoveryClientClient = apimlDiscoveryClientFactory.buildApimlDiscoveryClient(perClientAppManager, configBean, args, context);
+ discoveryClientClient.registerHealthCheck(healthCheckHandler);
+
+ return discoveryClientClient;
+ }
+
+ private static InstanceInfo getInstanceInfo(AdditionalRegistration apimlRegistration, ApplicationInfoManager bareManager) {
+ if (!CollectionUtils.isEmpty(apimlRegistration.getRoutes())) {
+ Map metadataWithRoutes = bareManager.getInfo().getMetadata().entrySet().stream().filter(entry -> !entry.getKey().startsWith(ROUTES)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ int index = 0;
+ for (AdditionalRegistration.Route route : apimlRegistration.getRoutes()) {
+ metadataWithRoutes.put(ROUTES + "." + index + "." + ROUTES_GATEWAY_URL, route.getGatewayUrl());
+ metadataWithRoutes.put(ROUTES + "." + index + "." + ROUTES_SERVICE_URL, route.getServiceUrl());
+ index++;
+ }
+ InstanceInfo.Builder builder = new InstanceInfo.Builder(bareManager.getInfo());
+ builder.setMetadata(metadataWithRoutes);
+ return builder.build();
+ }
+ return bareManager.getInfo();
}
}
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/discovery/ApimlDiscoveryClientFactory.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/discovery/ApimlDiscoveryClientFactory.java
new file mode 100644
index 0000000000..c854eb3aed
--- /dev/null
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/discovery/ApimlDiscoveryClientFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.gateway.discovery;
+
+import com.netflix.appinfo.ApplicationInfoManager;
+import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
+import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component
+public class ApimlDiscoveryClientFactory {
+
+ public ApimlDiscoveryClient buildApimlDiscoveryClient(ApplicationInfoManager perClientAppManager, EurekaClientConfigBean configBean, MutableDiscoveryClientOptionalArgs args, ApplicationContext context) {
+ return new ApimlDiscoveryClient(perClientAppManager, configBean, args, context);
+ }
+}
diff --git a/gateway-service/src/main/resources/application.yml b/gateway-service/src/main/resources/application.yml
index eb1b64dea3..87dd701be1 100644
--- a/gateway-service/src/main/resources/application.yml
+++ b/gateway-service/src/main/resources/application.yml
@@ -43,7 +43,9 @@ apiml:
scheme: https # "https" or "http"
preferIpAddress: false
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
- centralRegistryUrls: # List of additional Discovery Services URLs to register with
+
+
+ additionalRegistration: # List of additional Apiml Discovery Services metadata to register with
httpclient:
conn-pool:
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConditionTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConditionTest.java
new file mode 100644
index 0000000000..2b7361682f
--- /dev/null
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConditionTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.gateway.config;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.web.context.support.StandardServletEnvironment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class AdditionalRegistrationConditionTest {
+
+ @Test
+ void givenEnvironmentVariables_thenReturnTrue() {
+ AdditionalRegistrationCondition crc = new AdditionalRegistrationCondition();
+ ConditionContext context = mock(ConditionContext.class);
+ StandardServletEnvironment env = mock(StandardServletEnvironment.class);
+ Map systemEnv = new HashMap<>();
+ systemEnv.put("ZWE_configs_apiml_service_additionalRegistration_0_DISCOVERYSERVICEURLS", "https://localhost:123,https://localhostr:345");
+ systemEnv.put("ZWE_configs_apiml_service_additionalRegistration_1_DISCOVERYSERVICEURLS", "https://localhost:555,https://localhostr:666");
+ when(env.getSystemEnvironment()).thenReturn(systemEnv);
+ when(context.getEnvironment()).thenReturn(env);
+ assertTrue(crc.matches(context, null));
+ }
+
+ @Test
+ void givenPropertyVariables_thenReturnTrue() {
+ AdditionalRegistrationCondition crc = new AdditionalRegistrationCondition();
+ ConditionContext context = mock(ConditionContext.class);
+ StandardServletEnvironment env = mock(StandardServletEnvironment.class);
+ when(env.getProperty("apiml.service.additionalRegistration[0].discoveryServiceUrls")).thenReturn("https://localhost:123,https://localhostr:345");
+ when(context.getEnvironment()).thenReturn(env);
+ assertTrue(crc.matches(context, null));
+ }
+
+ @Test
+ void givenMissingConfiguration_thenReturnFalse() {
+ AdditionalRegistrationCondition crc = new AdditionalRegistrationCondition();
+ ConditionContext context = mock(ConditionContext.class);
+ StandardServletEnvironment env = mock(StandardServletEnvironment.class);
+ when(context.getEnvironment()).thenReturn(env);
+ assertFalse(crc.matches(context, null));
+ }
+
+}
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfigTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfigTest.java
new file mode 100644
index 0000000000..fe1df3ca08
--- /dev/null
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/AdditionalRegistrationConfigTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.gateway.config;
+
+import org.apache.groovy.util.Maps;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+class AdditionalRegistrationConfigTest {
+ @Nested
+ class GivenInvalidEnvironmentPropertiesAreProvided {
+ @Test
+ void shouldParseEmptyListFromNullMap() {
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(null);
+ assertThat(registrations).isEmpty();
+ }
+
+ @Test
+ void shouldParseEmptyListFromIrrelevantMap() {
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(Maps.of("someKey", "someValue"));
+ assertThat(registrations).isEmpty();
+ }
+
+ @Test
+ void shouldParseEmptyListFromEmptyValues() {
+ Map allProperties = Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS", "",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_SERVICEURL", "",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_GATEWAYURL", "");
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(allProperties);
+ assertThat(registrations).isEmpty();
+ }
+
+ @Test
+ void shouldParseBadRoutesIndexToEmptyRoutes() {
+ Map allProperties = Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS", "https://eureka",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_A_SERVICEURL", "/",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_A_GATEWAYURL", "/");
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(allProperties);
+
+ assertThat(registrations).containsExactly(AdditionalRegistration.builder().discoveryServiceUrls("https://eureka").routes(emptyList()).build());
+ }
+
+ @Test
+ void shouldParseBadIndexToEmptyRegistrations() {
+ Map allProperties = Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION__DISCOVERYSERVICEURLS", "https://eureka");
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(allProperties);
+
+ assertThat(registrations).isEmpty();
+ }
+ }
+
+ @Nested
+ class GivenValidEnvironmentPropertiesAreProvided {
+
+ private final Map envProperties = new TreeMap<>();
+
+ @BeforeEach
+ void setUp() {
+ envProperties.putAll(
+ Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS", "https://eureka",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_SERVICEURL", "/",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_GATEWAYURL", "/")
+ );
+ }
+
+ @Test
+ void shouldParseFirstAdditionalRegistration() {
+
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(envProperties);
+
+ AdditionalRegistration expectedRegistration = new AdditionalRegistration("https://eureka", singletonList(new AdditionalRegistration.Route("/", "/")));
+
+ assertThat(registrations).hasSize(1);
+ assertThat(registrations.get(0)).isEqualTo(expectedRegistration);
+ }
+
+ @Test
+ void shouldParseAdditionalRegistrationWithoutRoutes() {
+
+ envProperties.putAll(
+ Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_DISCOVERYSERVICEURLS", "https://eureka-2",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_0_SERVICEURL", "",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_0_GATEWAYURL", null)
+ );
+
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(envProperties);
+
+ AdditionalRegistration expectedSecondRegistration = new AdditionalRegistration("https://eureka-2", Collections.emptyList());
+
+ assertThat(registrations).hasSize(2);
+ assertThat(registrations.get(1)).isEqualTo(expectedSecondRegistration);
+ }
+
+ @Test
+ void shouldParseAdditionalRegistrationWithPartiallyDefinedRoutes() {
+
+ envProperties.putAll(
+ Maps.of(
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_DISCOVERYSERVICEURLS", "https://eureka-2",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_0_SERVICEURL", "",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_0_GATEWAYURL", null,
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_1_SERVICEURL", "/serviceUrl",
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_1_GATEWAYURL", null,
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_2_SERVICEURL", null,
+ "ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_1_ROUTES_2_GATEWAYURL", "/gatewayUrl")
+ );
+
+ List registrations = AdditionalRegistrationConfig.extractAdditionalRegistrations(envProperties);
+
+ AdditionalRegistration expectedSecondRegistration = new AdditionalRegistration("https://eureka-2", Arrays.asList(new AdditionalRegistration.Route(null, "/serviceUrl"), new AdditionalRegistration.Route("/gatewayUrl", null)));
+
+ assertThat(registrations).hasSize(2);
+ assertThat(registrations.get(1)).isEqualTo(expectedSecondRegistration);
+ }
+ }
+}
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientBeanTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientBeanTest.java
index cae8fc56a0..ea21772d7e 100644
--- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientBeanTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientBeanTest.java
@@ -10,17 +10,25 @@
package org.zowe.apiml.gateway.config;
-import com.netflix.appinfo.*;
+import com.netflix.appinfo.ApplicationInfoManager;
+import com.netflix.appinfo.DataCenterInfo;
+import com.netflix.appinfo.InstanceInfo;
+import com.netflix.appinfo.LeaseInfo;
+import com.netflix.appinfo.MyDataCenterInfo;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.context.ApplicationContext;
-import org.springframework.test.util.ReflectionTestUtils;
+import org.zowe.apiml.gateway.discovery.ApimlDiscoveryClientFactory;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.zowe.apiml.product.constants.CoreService.GATEWAY;
class DiscoveryClientBeanTest {
DiscoveryClientConfig dcConfig;
@@ -29,23 +37,31 @@ class DiscoveryClientBeanTest {
void setup() {
ApplicationContext context = mock(ApplicationContext.class);
EurekaJerseyClientImpl.EurekaJerseyClientBuilder builder = mock(EurekaJerseyClientImpl.EurekaJerseyClientBuilder.class);
- dcConfig = new DiscoveryClientConfig(context, null, builder);
+ dcConfig = new DiscoveryClientConfig(null, new ApimlDiscoveryClientFactory(), context, builder);
}
@Test
- void givenListOfCentralRegistryURLs_thenCreateNewDiscoveryClientForEach() {
- String[] centralRegistryUrls = {"https://host:10021/eureka", "https://host:10011/eureka"};
- ReflectionTestUtils.setField(dcConfig, "centralRegistryUrls", centralRegistryUrls);
+ void givenListOfAdditionalRegistrations_thenCreateNewDiscoveryClientForEach() {
+ List additionalRegistrations = Arrays.asList(
+ AdditionalRegistration.builder().discoveryServiceUrls("https://host:10021/eureka").build(),
+ AdditionalRegistration.builder().discoveryServiceUrls("https://host:10011/eureka").build());
+
+
ApplicationInfoManager manager = mock(ApplicationInfoManager.class);
- InstanceInfo info = mock(InstanceInfo.class);
+ InstanceInfo info = new InstanceInfo.Builder(mock(InstanceInfo.class)).setAppName(GATEWAY.name()).build();
+
when(manager.getInfo()).thenReturn(info);
when(info.getIPAddr()).thenReturn("127.0.0.1");
+
when(info.getDataCenterInfo()).thenReturn(new MyDataCenterInfo(DataCenterInfo.Name.MyOwn));
LeaseInfo leaseInfo = mock(LeaseInfo.class);
+
when(info.getLeaseInfo()).thenReturn(leaseInfo);
+
EurekaClientConfigBean bean = new EurekaClientConfigBean();
- DiscoveryClientWrapper wrapper = dcConfig.additionalDiscoveryClientWrapper(manager, bean, null);
+ DiscoveryClientWrapper wrapper = dcConfig.additionalDiscoveryClientWrapper(manager, bean, null, additionalRegistrations);
wrapper.shutdown();
- assertEquals(2, wrapper.getDiscoveryClients().size());
+
+ assertThat(wrapper.getDiscoveryClients()).hasSize(2);
}
}
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientConfigTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientConfigTest.java
new file mode 100644
index 0000000000..5d606fb2d0
--- /dev/null
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/DiscoveryClientConfigTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.gateway.config;
+
+import com.netflix.appinfo.ApplicationInfoManager;
+import com.netflix.appinfo.HealthCheckHandler;
+import com.netflix.appinfo.InstanceInfo;
+import com.netflix.discovery.EurekaClientConfig;
+import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.zowe.apiml.gateway.discovery.ApimlDiscoveryClient;
+import org.zowe.apiml.gateway.discovery.ApimlDiscoveryClientFactory;
+
+import java.util.ArrayList;
+import java.util.TreeMap;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.zowe.apiml.product.constants.CoreService.GATEWAY;
+
+@ExtendWith(MockitoExtension.class)
+class DiscoveryClientConfigTest {
+ @Nested
+ class WhenProcessingAdditionalRegistrations {
+ @Mock
+ private EurekaJerseyClientImpl.EurekaJerseyClientBuilder eurekaJerseyClientBuilder;
+ @Mock
+ private ApplicationInfoManager appManager;
+ @Mock
+ private ApimlDiscoveryClient discoveryClientClient;
+ @Mock
+ private ApimlDiscoveryClientFactory apimlDiscoveryClientFactory;
+ @Captor
+ private ArgumentCaptor appInfoManagerCaptor;
+ @Mock
+ private EurekaClientConfig eurekaClientConfig;
+ @Mock
+ private HealthCheckHandler healthCheckHandler;
+
+ private AdditionalRegistration registration;
+ private InstanceInfo instanceInfo;
+ @Mock
+ private InstanceInfo baseInstanceInfo;
+ @InjectMocks
+ private DiscoveryClientConfig discoveryClientConfig;
+
+ @BeforeEach
+ public void setUp() {
+ registration = AdditionalRegistration.builder()
+ .discoveryServiceUrls("https://host:10011/eureka").routes(new ArrayList<>()).build();
+
+ instanceInfo = new InstanceInfo.Builder(baseInstanceInfo).setAppName(GATEWAY.name()).setMetadata(new TreeMap<>()).build();
+ when(appManager.getInfo()).thenReturn(instanceInfo);
+ when(apimlDiscoveryClientFactory.buildApimlDiscoveryClient(any(), any(), any(), any())).thenReturn(discoveryClientClient);
+
+ }
+
+ @Test
+ void shouldRegisterHealthCheckHandler() {
+ DiscoveryClientWrapper discoveryClientWrapper = discoveryClientConfig.additionalDiscoveryClientWrapper(appManager, eurekaClientConfig, healthCheckHandler, singletonList(registration));
+
+ assertThat(discoveryClientWrapper.getDiscoveryClients()).hasSize(1);
+ verify(discoveryClientClient).registerHealthCheck(healthCheckHandler);
+ }
+
+ @Test
+ void shouldAddAdditionalRoutesToMetadata() {
+
+ registration.getRoutes().add(new AdditionalRegistration.Route("/gatewayUrl", "/serviceUrl"));
+
+ discoveryClientConfig.additionalDiscoveryClientWrapper(appManager, eurekaClientConfig, healthCheckHandler, singletonList(registration));
+
+ verify(apimlDiscoveryClientFactory).buildApimlDiscoveryClient(appInfoManagerCaptor.capture(), any(), any(), any());
+
+ ApplicationInfoManager createdInfoManager = appInfoManagerCaptor.getValue();
+ InstanceInfo info = createdInfoManager.getInfo();
+ when(info.getMetadata()).thenCallRealMethod();
+ assertThat(info.getMetadata()).containsEntry("apiml.routes.0.gatewayUrl", "/gatewayUrl");
+ assertThat(info.getMetadata()).containsEntry("apiml.routes.0.serviceUrl", "/serviceUrl");
+ }
+
+ @Test
+ void shouldNotAddAdditionalRoutesToMetadata() {
+
+ discoveryClientConfig.additionalDiscoveryClientWrapper(appManager, eurekaClientConfig, healthCheckHandler, singletonList(registration));
+
+ verify(apimlDiscoveryClientFactory).buildApimlDiscoveryClient(appInfoManagerCaptor.capture(), any(), any(), any());
+
+ ApplicationInfoManager createdInfoManager = appInfoManagerCaptor.getValue();
+ InstanceInfo info = createdInfoManager.getInfo();
+ when(info.getMetadata()).thenCallRealMethod();
+ assertThat(info.getMetadata()).isEmpty();
+ }
+
+ }
+
+}
diff --git a/gateway-service/src/test/resources/application.yml b/gateway-service/src/test/resources/application.yml
index dda5fb2ff8..78914d73ad 100644
--- a/gateway-service/src/test/resources/application.yml
+++ b/gateway-service/src/test/resources/application.yml
@@ -16,7 +16,7 @@ apiml:
allowEncodedSlashes: true
discoveryServiceUrls: https://localhost:10011/eureka/
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
- centralRegistryUrls: # List of additional Discovery Services URLs to register with
+ additionalRegistration: # List of additional Apiml Discovery Services metadata to register with
loadBalancer:
distribute: false
gateway:
diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/cloudgateway/CentralRegistryTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/cloudgateway/CentralRegistryTest.java
index e62c2a1398..5aad09e781 100644
--- a/integration-tests/src/test/java/org/zowe/apiml/functional/cloudgateway/CentralRegistryTest.java
+++ b/integration-tests/src/test/java/org/zowe/apiml/functional/cloudgateway/CentralRegistryTest.java
@@ -53,7 +53,7 @@ class CentralRegistryTest implements TestWithStartedInstances {
@SneakyThrows
static void setupAll() {
//In order to avoid config customization
- ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().setInstances(2);
+ ConfigReader.environmentConfiguration().getGatewayServiceConfiguration().setInstances(1);
TlsConfiguration tlsCfg = ConfigReader.environmentConfiguration().getTlsConfiguration();
SslContextConfigurer sslContextConfigurer = new SslContextConfigurer(tlsCfg.getKeyStorePassword(), tlsCfg.getClientKeystore(), tlsCfg.getKeyStore());
@@ -87,7 +87,7 @@ void shouldFindBothApimlIds() {
List apimlIds = listCentralRegistry(null, null, null)
.extract().jsonPath().getList("apimlId");
- assertThat(apimlIds, Matchers.hasItems(Matchers.equalTo("central-apiml"), Matchers.equalTo("domain-apiml")));
+ assertThat(apimlIds, Matchers.containsInAnyOrder("central-apiml", "domain-apiml"));
}
@Test
diff --git a/schemas/gateway-schema.json b/schemas/gateway-schema.json
index 8a9f6a65ce..9b6eee3e44 100644
--- a/schemas/gateway-schema.json
+++ b/schemas/gateway-schema.json
@@ -175,11 +175,23 @@
"description": "Allow URLs on gateway to contain encoded slashes.",
"default": true
},
- "centralRegistryUrls": {
+ "additionalRegistration": {
"type": "array",
- "description": "List of additional Discovery Services URLs to register with.",
+ "description": "List of additional Discovery Services URLs to register with and the routing patterns.",
"minItems": 1,
- "uniqueItems": true
+ "items": {
+ "type": "object",
+ "properties": {
+ "discoveryServiceUrls": {
+ "type": "string",
+ "description": "List of Discovery Services URLs in one security domain. You can separate multiple urls by comma or semicolon."
+ },
+ "routes": {
+ "$ref": "#/$defs/routes"
+ }
+ },
+ "required": ["discoveryServiceUrls","routes"]
+ }
},
"corsEnabled": {
"type": "boolean",
@@ -286,6 +298,24 @@
"description": "TCP network port",
"minimum": 1024,
"maximum": 65535
+ },
+ "routes": {
+ "type": "array",
+ "description": "Routing parameters",
+ "items": {
+ "type": "object",
+ "properties": {
+ "gatewayUrl": {
+ "type": "string",
+ "description": "The portion of the gateway URL which is replaced by the serviceUrl path."
+ },
+ "serviceUrl": {
+ "type": "string",
+ "description": "The portion of the service instance URL path which replaces the gatewayUrl part."
+ }
+ },
+ "required": ["gatewayUrl","serviceUrl"]
+ }
}
}
}