diff --git a/cloud-gateway-service/build.gradle b/cloud-gateway-service/build.gradle
index 32c7946499..40f87fef6e 100644
--- a/cloud-gateway-service/build.gradle
+++ b/cloud-gateway-service/build.gradle
@@ -60,6 +60,8 @@ configurations.all {
dependencies {
api project(':common-service-core')
+ api project(':apiml-utility')
+
implementation libs.eh.cache
implementation libs.spring.cloud.starter.gateway
@@ -80,6 +82,7 @@ dependencies {
implementation libs.spring.expression
implementation libs.bcpkix
implementation libs.nimbusJoseJwt
+ implementation libs.janino
compileOnly libs.lombok
annotationProcessor libs.lombok
@@ -89,8 +92,9 @@ dependencies {
testAnnotationProcessor libs.lombok
testImplementation libs.spring.boot.starter.test
testImplementation libs.rest.assured
-
+ testImplementation libs.reactorTest
testImplementation libs.mockito.inline
+
}
bootJar {
diff --git a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/ConnectionsConfig.java b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/ConnectionsConfig.java
index 3c88aff37c..07516793f3 100644
--- a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/ConnectionsConfig.java
+++ b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/ConnectionsConfig.java
@@ -45,8 +45,10 @@
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.pattern.PathPatternParser;
import org.zowe.apiml.message.core.MessageService;
+import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
import org.zowe.apiml.security.HttpsConfig;
+import org.zowe.apiml.security.HttpsConfigError;
import org.zowe.apiml.security.HttpsFactory;
import org.zowe.apiml.security.SecurityUtils;
import org.zowe.apiml.util.CorsUtils;
@@ -112,6 +114,7 @@ public class ConnectionsConfig {
@Value("${apiml.service.corsEnabled:false}")
private boolean corsEnabled;
private final ApplicationContext context;
+ private static final ApimlLogger apimlLog = ApimlLogger.of(ConnectionsConfig.class, YamlMessageServiceInstance.getInstance());
public ConnectionsConfig(ApplicationContext context) {
this.context = context;
@@ -171,9 +174,9 @@ SslContext sslContext() {
trustManagerFactory.init(trustStore);
return SslContextBuilder.forClient().keyManager(keyManagerFactory).trustManager(trustManagerFactory).build();
} catch (Exception e) {
- log.error("Exception while creating SSL context", e);
- System.exit(1);
- return null;
+ apimlLog.log("org.zowe.apiml.common.sslContextInitializationError", e.getMessage());
+ throw new HttpsConfigError("Error initializing SSL Context: " + e.getMessage(), e,
+ HttpsConfigError.ErrorCode.HTTP_CLIENT_INITIALIZATION_FAILED, factory().getConfig());
}
}
diff --git a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/RegistryConfig.java b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/RegistryConfig.java
new file mode 100644
index 0000000000..7f97b849ee
--- /dev/null
+++ b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/config/RegistryConfig.java
@@ -0,0 +1,31 @@
+/*
+ * 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.discovery.EurekaClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.zowe.apiml.services.BasicInfoService;
+import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
+
+@Configuration
+public class RegistryConfig {
+
+ @Bean
+ public EurekaMetadataParser eurekaMetadataParser() {
+ return new EurekaMetadataParser();
+ }
+
+ @Bean
+ public BasicInfoService basicInfoService(EurekaClient eurekaClient, EurekaMetadataParser eurekaMetadataParser) {
+ return new BasicInfoService(eurekaClient, eurekaMetadataParser);
+ }
+}
diff --git a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJob.java b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJob.java
new file mode 100644
index 0000000000..ec3bdf7ac7
--- /dev/null
+++ b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJob.java
@@ -0,0 +1,95 @@
+/*
+ * 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.scheduled;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.zowe.apiml.cloudgatewayservice.service.GatewayIndexService;
+import org.zowe.apiml.cloudgatewayservice.service.InstanceInfoService;
+import org.zowe.apiml.product.constants.CoreService;
+import org.zowe.apiml.services.BasicInfoService;
+import org.zowe.apiml.services.ServiceInfo;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.APIML_ID;
+
+/**
+ * Scheduled job to refresh registry of all registered gateways services.
+ * Behaviour of the job can be configured by the following settings:
+ *
+ * apiml:
+ * cloudGateway:
+ * cachePeriodSec: - default value 120 seconds
+ * maxSimultaneousRequests: - default value 20
+ * clientKeystore: - default value null
+ * clientKeystorePassword: - default value null
+ * clientKeystoreType: - default PKCS12
+ * serviceRegistryEnabled: - default value false
+ *
+ */
+@EnableScheduling
+@Slf4j
+@Component
+@ConditionalOnExpression("${apiml.cloudGateway.serviceRegistryEnabled:false}")
+@RequiredArgsConstructor
+public class GatewayScanJob {
+
+ private final BasicInfoService basicInfoService;
+ private final EurekaRegistration serviceRegistration;
+ private final GatewayIndexService gatewayIndexerService;
+ private final InstanceInfoService instanceInfoService;
+ @Value("${apiml.service.apimlId:#{null}}")
+ private String currentApimlId;
+ @Value("${apiml.cloudGateway.maxSimultaneousRequests:20}")
+ private int maxSimultaneousRequests;
+
+ @Scheduled(initialDelay = 5000, fixedDelayString = "${apiml.cloudGateway.refresh-interval-ms:30000}")
+ public void startScanExternalGatewayJob() {
+
+ log.debug("Scan gateways job start");
+ doScanExternalGateway()
+ .subscribe();
+ addLocalServices();
+ }
+
+ private void addLocalServices() {
+ String apimlIdKey = Optional.ofNullable(currentApimlId).orElse(serviceRegistration.getInstanceId());
+ List localServices = basicInfoService.getServicesInfo();
+ gatewayIndexerService.putApimlServices(apimlIdKey, localServices);
+ }
+
+ /**
+ * reactive entry point for the external gateways index refresh
+ */
+ protected Flux> doScanExternalGateway() {
+
+ Mono> registeredGateways = instanceInfoService.getServiceInstance(CoreService.GATEWAY.getServiceId())
+ .map(gateways -> gateways.stream().filter(info -> !StringUtils.equals(info.getMetadata().getOrDefault(APIML_ID, "N/A"), currentApimlId)).collect(Collectors.toList()));
+
+ Flux serviceInstanceFlux = registeredGateways.flatMapMany(Flux::fromIterable);
+
+ return serviceInstanceFlux
+ .flatMap(gatewayIndexerService::indexGatewayServices, maxSimultaneousRequests);
+ }
+}
diff --git a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexService.java b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexService.java
new file mode 100644
index 0000000000..0356dd5ac2
--- /dev/null
+++ b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexService.java
@@ -0,0 +1,170 @@
+/*
+ * 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.service;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableMap;
+import io.netty.handler.ssl.SslContext;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.zowe.apiml.message.log.ApimlLogger;
+import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
+import org.zowe.apiml.services.ServiceInfo;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.tcp.SslProvider;
+
+import javax.validation.constraints.NotNull;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.nonNull;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.springframework.util.CollectionUtils.isEmpty;
+import static org.zowe.apiml.cloudgatewayservice.service.WebClientHelper.load;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.APIML_ID;
+
+/**
+ * Maintain all registered gateways lookup data. Internal caches uses apimlId is a key.
+ * if apimlId key is not available then synthetic key is generated containing SUBSTITUTE_
prefix and instanceId
+ */
+@Slf4j
+@Service
+public class GatewayIndexService {
+ private final ApimlLogger apimlLog = ApimlLogger.of(GatewayIndexService.class, YamlMessageServiceInstance.getInstance());
+ private final Cache apimlGatewayLookup;
+ private final Cache> apimlServicesCache;
+ private final WebClient defaultWebClient;
+ private SslContext customClientSslContext = null;
+
+ public GatewayIndexService(WebClient defaultWebClient,
+ @Value("${apiml.cloudGateway.cachePeriodSec:120}") int cachePeriodSec,
+ @Value("${apiml.cloudGateway.clientKeystore:#{null}}") String clientKeystorePath,
+ @Value("${apiml.cloudGateway.clientKeystorePassword:#{null}}") char[] clientKeystorePassword,
+ @Value("${apiml.cloudGateway.clientKeystoreType:PKCS12}") String keystoreType
+ ) {
+ this.defaultWebClient = defaultWebClient;
+
+ apimlGatewayLookup = CacheBuilder.newBuilder().expireAfterWrite(cachePeriodSec, SECONDS).build();
+ apimlServicesCache = CacheBuilder.newBuilder().expireAfterWrite(cachePeriodSec, SECONDS).build();
+
+ if (isNotBlank(clientKeystorePath) && nonNull(clientKeystorePassword)) {
+ customClientSslContext = load(clientKeystorePath, clientKeystorePassword, keystoreType);
+ }
+ }
+
+ private WebClient buildWebClient(ServiceInstance registration) {
+ final String baseUrl = String.format("%s://%s:%d", registration.getScheme(), registration.getHost(), registration.getPort());
+ if (this.customClientSslContext != null) {
+ SslProvider sslProvider = SslProvider.builder().sslContext(customClientSslContext).build();
+ HttpClient httpClient = HttpClient.create()
+ .secure(sslProvider);
+
+ return WebClient.builder()
+ .baseUrl(baseUrl)
+ .clientConnector(new ReactorClientHttpConnector(httpClient))
+ .defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
+ .build();
+ }
+
+ return defaultWebClient.mutate()
+ .baseUrl(baseUrl)
+ .build();
+ }
+
+ public Mono> indexGatewayServices(ServiceInstance registration) {
+ String apimlIdKey = extractApimlId(registration).orElse(buildAlternativeApimlIdKey(registration));
+ log.debug("Fetching registered gateway instance services: {}", apimlIdKey);
+ apimlGatewayLookup.put(apimlIdKey, registration);
+ return fetchServices(apimlIdKey, registration)
+ .doOnError(ex -> apimlLog.log("org.zowe.apiml.gateway.servicesRequestFailed", apimlIdKey, ex.getMessage()))
+ .onErrorComplete()
+ .doFinally(signal -> log.debug("\t {} completed with {}", apimlIdKey, signal));
+ }
+
+ /**
+ * Store entry in the Services Registry. Should be used to store services info from the current apiml instance
+ *
+ * @param apimlId unique apimlId
+ * @param services List of the services
+ */
+ public void putApimlServices(@NotNull String apimlId, List services) {
+ apimlServicesCache.put(apimlId, services);
+ }
+
+ private Mono> fetchServices(String apimlId, ServiceInstance registration) {
+ WebClient webClient = buildWebClient(registration);
+ final ParameterizedTypeReference> serviceInfoType = new ParameterizedTypeReference>() {
+ };
+
+ return webClient.get().uri("/gateway/services")
+ .retrieve()
+ .bodyToMono(serviceInfoType)
+ .doOnNext(foreignServices -> apimlServicesCache.put(apimlId, foreignServices));
+ }
+
+ private String buildAlternativeApimlIdKey(ServiceInstance registration) {
+ return "SUBSTITUTE" + "_" + registration.getInstanceId();
+ }
+
+ private Optional extractApimlId(ServiceInstance registration) {
+ return Optional.ofNullable(registration.getMetadata()).map(m -> m.get(APIML_ID));
+ }
+
+ /**
+ * list currently cached apiml registry with option to filter by the apimlId
and apiId
+ *
+ * @param apimlId - filter for only services from the particular apiml instance, NULL - filter not applied
+ * @param apiId - filter for only services of particular type e.g. zowe.apiml.apicatalog
+ * @return full of filter immutable map of the registry
+ */
+ public Map> listRegistry(String apimlId, String apiId) {
+
+ Map> allServices = ImmutableMap.>builder()
+ .putAll(apimlServicesCache.asMap()).build();
+ return allServices.entrySet().stream()
+ .filter(entry -> apimlId == null || StringUtils.equals(apimlId, entry.getKey()))
+ .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), filterServicesByApiId(entry.getValue(), apiId)))
+ .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
+ }
+
+ List filterServicesByApiId(List apimlIdServices, String apiId) {
+ if (!CollectionUtils.isEmpty(apimlIdServices)) {
+ return apimlIdServices.stream()
+ .filter(Objects::nonNull)
+ .filter(serviceInfo -> apiId == null || hasSameApiId(serviceInfo, apiId))
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+ private boolean hasSameApiId(ServiceInfo serviceInfo, String apiId) {
+ if (serviceInfo.getApiml() != null && !isEmpty(serviceInfo.getApiml().getApiInfo())) {
+ return StringUtils.equals(apiId, serviceInfo.getApiml().getApiInfo().get(0).getApiId());
+ }
+ return false;
+ }
+}
diff --git a/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelper.java b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelper.java
new file mode 100644
index 0000000000..c3e0cd86cb
--- /dev/null
+++ b/cloud-gateway-service/src/main/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelper.java
@@ -0,0 +1,79 @@
+/*
+ * 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.service;
+
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.zowe.apiml.message.log.ApimlLogger;
+import org.zowe.apiml.message.yaml.YamlMessageServiceInstance;
+import org.zowe.apiml.security.HttpsConfigError;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Utility class for the custom Netty {@link SslContext} creation.
+ * Does not support keyring because client keystore override mainly used in development mode and not supposed be run on the Mainframe.
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class WebClientHelper {
+ private static final ApimlLogger apimlLog = ApimlLogger.of(WebClientHelper.class, YamlMessageServiceInstance.getInstance());
+
+ /**
+ * Load {@link SslContext} from the specified keystore
+ *
+ * @param keystorePath path to the keystore file
+ * @param password keystore password
+ * @param keystoreType keystore type
+ * @throws IllegalArgumentException if keystore file does not exist.
+ * @throws HttpsConfigError if any error occur during the context creation.
+ */
+ public static SslContext load(String keystorePath, char[] password, String keystoreType) {
+ File keyStoreFile = new File(keystorePath);
+ if (keyStoreFile.exists()) {
+ try (InputStream is = Files.newInputStream(Paths.get(keystorePath))) {
+ KeyStore keyStore = KeyStore.getInstance(keystoreType);
+ keyStore.load(is, password);
+ return initSslContext(keyStore, password);
+ } catch (Exception e) {
+ apimlLog.log("org.zowe.apiml.common.sslContextInitializationError", e.getMessage());
+ throw new HttpsConfigError("Error initializing SSL Context: " + e.getMessage(), e,
+ HttpsConfigError.ErrorCode.HTTP_CLIENT_INITIALIZATION_FAILED);
+ }
+ } else {
+ throw new IllegalArgumentException("Not existing file: " + keystorePath);
+ }
+ }
+
+ private static SslContext initSslContext(KeyStore keyStore, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, SSLException {
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
+ kmf.init(keyStore, password);
+
+ return SslContextBuilder.forClient()
+ .trustManager(InsecureTrustManagerFactory.INSTANCE)
+ .keyManager(kmf).build();
+ }
+
+
+}
diff --git a/cloud-gateway-service/src/main/resources/application.yml b/cloud-gateway-service/src/main/resources/application.yml
index f6b7314cb7..c09d47f0dc 100644
--- a/cloud-gateway-service/src/main/resources/application.yml
+++ b/cloud-gateway-service/src/main/resources/application.yml
@@ -3,19 +3,28 @@ eureka:
serviceUrl:
defaultZone: https://localhost:10011/eureka/
+spring:
+ application:
+ name: cloud-gateway
+
apiml:
gateway:
timeout: 60
service:
- id: cloud-gateway
+ apimlId: apiml1
+ id: ${spring.application.name}
port: 10023
hostname: localhost
+ 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
+ cloudGateway:
+ serviceRegistryEnabled: false
+
server:
port: ${apiml.service.port}
ssl:
@@ -29,7 +38,6 @@ server:
trustStorePassword: password
trustStoreType: PKCS12
-spring:
cloud:
gateway:
discovery:
@@ -41,10 +49,13 @@ spring:
main:
allow-circular-references: true
+logbackServiceName: ZWEACG1
+
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
+ reactor.netty.http.client.HttpClientConnect: OFF
management:
endpoint:
diff --git a/cloud-gateway-service/src/main/resources/cloud-gateway-log-messages.yml b/cloud-gateway-service/src/main/resources/cloud-gateway-log-messages.yml
index 2835245a8e..6eddb131fa 100644
--- a/cloud-gateway-service/src/main/resources/cloud-gateway-log-messages.yml
+++ b/cloud-gateway-service/src/main/resources/cloud-gateway-log-messages.yml
@@ -351,6 +351,16 @@ messages:
reason: "The JWT token or client certificate is not valid"
action: "Configure your client to provide valid authentication."
+ - key: org.zowe.apiml.common.sslContextInitializationError
+ number: ZWEAM400
+ type: ERROR
+ text: "Error initializing SSL Context: '%s'"
+ reason: "An error occurred while initializing the SSL Context."
+ action: "Refer to the specific message to identify the exact problem.\n
+ Possible causes include:\n
+ - Incorrect security algorithm\n
+ - The keystore is invalid or corrupted\n
+ - The certificate is invalid or corrupted"
# Revoke personal access token
- key: org.zowe.apiml.security.query.invalidRevokeRequestBody
@@ -359,3 +369,10 @@ messages:
text: "Body in the revoke request is not valid."
reason: "The request body is not valid"
action: "Use a valid body in the request. Format of a message: {userId: string, (optional)timestamp: long} or {serviceId: string, (optional)timestamp: long}."
+
+ - key: org.zowe.apiml.gateway.servicesRequestFailed
+ number: ZWESG100
+ type: WARNING
+ text: "Cannot receive information about services on API Gateway with apimlId '%s' because: %s"
+ reason: "Cannot connect to the Gateway service."
+ action: "Make sure that the external Gateway service is running and the truststore of the both Gateways contain the corresponding certificate."
diff --git a/cloud-gateway-service/src/main/resources/logback.xml b/cloud-gateway-service/src/main/resources/logback.xml
new file mode 100644
index 0000000000..999077ccc1
--- /dev/null
+++ b/cloud-gateway-service/src/main/resources/logback.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${apimlLogPattern}
+
+
+
+
+
+ ${STORAGE_LOCATION}/${logbackServiceName}.log
+
+
+ ${STORAGE_LOCATION}/${logbackServiceName}.%i.log
+ ${MIN_INDEX}
+ ${MAX_INDEX}
+
+
+
+ ${MAX_FILE_SIZE}
+
+
+
+
+ ${apimlLogPattern}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJobTest.java b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJobTest.java
new file mode 100644
index 0000000000..41525558ec
--- /dev/null
+++ b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/scheduled/GatewayScanJobTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.scheduled;
+
+
+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.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.zowe.apiml.cloudgatewayservice.service.GatewayIndexService;
+import org.zowe.apiml.cloudgatewayservice.service.InstanceInfoService;
+import org.zowe.apiml.product.constants.CoreService;
+import org.zowe.apiml.services.BasicInfoService;
+import org.zowe.apiml.services.ServiceInfo;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+
+@ExtendWith(MockitoExtension.class)
+class GatewayScanJobTest {
+
+ @Mock
+ private ServiceInstance instanceOne;
+ @Mock
+ private ServiceInstance instanceTwo;
+ @Mock
+ private List apimlServicesOne;
+ @Mock
+ private List apimlServicesTwo;
+ @Mock
+ private GatewayIndexService gatewayIndexerService;
+ @Mock
+ private InstanceInfoService instanceInfoService;
+ @Mock
+ private BasicInfoService basicInfoService;
+ @Mock
+ private EurekaRegistration serviceRegistration;
+ @InjectMocks
+ private GatewayScanJob gatewayScanJob;
+
+ @BeforeEach
+ public void setUp() {
+ ReflectionTestUtils.setField(gatewayScanJob, "maxSimultaneousRequests", 3);
+
+ lenient().when(instanceInfoService.getServiceInstance(CoreService.GATEWAY.getServiceId())).thenReturn(Mono.just(asList(instanceOne, instanceTwo)));
+
+ lenient().when(gatewayIndexerService.indexGatewayServices(instanceOne)).thenReturn(Mono.just(apimlServicesOne));
+ lenient().when(gatewayIndexerService.indexGatewayServices(instanceTwo)).thenReturn(Mono.just(apimlServicesTwo));
+ }
+
+ @Nested
+ class WhenScanningExternalGateway {
+ @Test
+ void shouldTriggerIndexingForRegisteredGateways() {
+ StepVerifier.create(gatewayScanJob.doScanExternalGateway())
+ .expectNext(apimlServicesOne)
+ .expectNext(apimlServicesTwo)
+ .verifyComplete();
+
+ verify(gatewayIndexerService).indexGatewayServices(instanceOne);
+ verify(gatewayIndexerService).indexGatewayServices(instanceTwo);
+ verifyNoMoreInteractions(gatewayIndexerService);
+ }
+ }
+
+ @Test
+ void scheduledCallShouldTriggerReactiveAction() {
+ GatewayScanJob spy = spy(gatewayScanJob);
+ when(spy.doScanExternalGateway()).thenReturn(Flux.empty());
+
+ spy.startScanExternalGatewayJob();
+
+ verify(spy).doScanExternalGateway();
+ }
+}
diff --git a/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/CertificateChainServiceTest.java b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/CertificateChainServiceTest.java
index 11249b3948..0f116d764b 100644
--- a/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/CertificateChainServiceTest.java
+++ b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/CertificateChainServiceTest.java
@@ -11,6 +11,7 @@
package org.zowe.apiml.cloudgatewayservice.service;
import lombok.NonNull;
+import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -97,11 +98,15 @@ void setup() throws CertificateException {
ReflectionTestUtils.setField(certificateChainService, "certificates", certificates, Certificate[].class);
}
+ private String normalizeText(String input) {
+ return StringUtils.replace(input, "\r\n", "\n");
+ }
+
@Test
void whenGetCertificates_thenPEMIsProduced() {
- String result = certificateChainService.getCertificatesInPEMFormat();
+ String result = normalizeText(certificateChainService.getCertificatesInPEMFormat());
assertNotNull(result);
- assertEquals(CERTIFICATE_1 + CERTIFICATE_2, result);
+ assertEquals(normalizeText(CERTIFICATE_1 + CERTIFICATE_2), result);
}
}
diff --git a/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexServiceTest.java b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexServiceTest.java
new file mode 100644
index 0000000000..30c36c5cde
--- /dev/null
+++ b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/GatewayIndexServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.service;
+
+
+import io.netty.handler.ssl.SslContext;
+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 org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.zowe.apiml.services.ServiceInfo;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.zowe.apiml.cloudgatewayservice.service.WebClientHelperTest.KEYSTORE_PATH;
+import static org.zowe.apiml.cloudgatewayservice.service.WebClientHelperTest.PASSWORD;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.APIML_ID;
+
+@ExtendWith(MockitoExtension.class)
+class GatewayIndexServiceTest {
+ private GatewayIndexService gatewayIndexService;
+ private final ParameterizedTypeReference> serviceInfoType = new ParameterizedTypeReference>() {
+ };
+ private ServiceInfo serviceInfoA, serviceInfoB;
+ private WebClient webClient;
+ private final String apiCatalogApiId = "zowe.apiml.apicatalog";
+ @Mock
+ private ClientResponse clientResponse;
+ @Mock
+ private ExchangeFunction exchangeFunction;
+ @Mock
+ private ServiceInstance eurekaInstance;
+
+ @BeforeEach
+ void setUp() {
+
+ lenient().when(eurekaInstance.getMetadata()).thenReturn(Maps.of(APIML_ID, "testApimlIdA"));
+ lenient().when(eurekaInstance.getInstanceId()).thenReturn("testInstanceIdA");
+
+ serviceInfoA = new ServiceInfo();
+ serviceInfoB = new ServiceInfo();
+
+ serviceInfoB.setApiml(new ServiceInfo.Apiml());
+ ServiceInfo.ApiInfoExtended sysviewApiInfo = new ServiceInfo.ApiInfoExtended();
+ sysviewApiInfo.setApiId(apiCatalogApiId);
+
+ serviceInfoB.getApiml().setApiInfo(Collections.singletonList(sysviewApiInfo));
+
+ webClient = spy(WebClient.builder().exchangeFunction(exchangeFunction).build());
+ gatewayIndexService = new GatewayIndexService(webClient, 60, null, null, null);
+ }
+
+ @Nested
+ class WhenIndexingGatewayService {
+
+ @BeforeEach
+ void setUp() {
+ lenient().when(exchangeFunction.exchange(any(ClientRequest.class)))
+ .thenReturn(Mono.just(clientResponse));
+
+ lenient().when(clientResponse.bodyToMono(serviceInfoType)).thenReturn(Mono.just(Arrays.asList(serviceInfoA, serviceInfoB)));
+ }
+
+ @Test
+ void shouldCacheListOfTheServices() {
+
+ StepVerifier.FirstStep> servicesVerifier = StepVerifier.create(gatewayIndexService.indexGatewayServices(eurekaInstance));
+
+ servicesVerifier
+ .expectNext(asList(serviceInfoA, serviceInfoB))
+ .verifyComplete();
+
+ verify(exchangeFunction).exchange(any());
+
+ Map> allServices = gatewayIndexService.listRegistry(null, null);
+
+ assertThat(allServices).containsOnlyKeys("testApimlIdA");
+ assertThat(allServices.get("testApimlIdA")).containsExactlyInAnyOrder(serviceInfoA, serviceInfoB);
+ verifyNoMoreInteractions(exchangeFunction);
+ }
+
+ @Test
+ void shouldGenerateSyntheticApimlIdCacheKey() {
+ when(eurekaInstance.getMetadata()).thenReturn(null);
+
+ StepVerifier.FirstStep> servicesVerifier = StepVerifier.create(gatewayIndexService.indexGatewayServices(eurekaInstance));
+
+ servicesVerifier
+ .expectNext(asList(serviceInfoA, serviceInfoB))
+ .verifyComplete();
+
+ Map> allServices = gatewayIndexService.listRegistry(null, null);
+
+ assertThat(allServices).containsOnlyKeys("SUBSTITUTE_testInstanceIdA");
+ }
+
+ @Test
+ void shouldFilterCachedServicesByApiId() {
+
+ StepVerifier.create(gatewayIndexService.indexGatewayServices(eurekaInstance))
+ .expectNext(asList(serviceInfoA, serviceInfoB))
+ .verifyComplete();
+
+ Map> allServices = gatewayIndexService.listRegistry(null, apiCatalogApiId);
+
+ assertThat(allServices).containsOnly(new AbstractMap.SimpleEntry<>("testApimlIdA", Collections.singletonList(serviceInfoB)));
+ }
+
+ @Test
+ void shouldReturnEmptyMapForNotExistingApimlId() {
+ assertThat(gatewayIndexService.listRegistry("unknownId", null)).isEmpty();
+ assertThat(gatewayIndexService.listRegistry(null, "unknownApiId")).isEmpty();
+ assertThat(gatewayIndexService.listRegistry("unknownId", "unknownApiId")).isEmpty();
+ }
+ }
+
+ @Nested
+ class WhenUsingCustomClientKey {
+
+ @Test
+ void shouldInitializeCustomSslContext() {
+
+ gatewayIndexService = new GatewayIndexService(webClient, 60, KEYSTORE_PATH, PASSWORD, "PKCS12");
+
+ SslContext customClientSslContext = (SslContext) ReflectionTestUtils.getField(gatewayIndexService, "customClientSslContext");
+
+ assertThat(customClientSslContext).isNotNull();
+ }
+
+ @Test
+ void shouldNotUseDefaultWebClientWhenCustomContextIdProvided() {
+ gatewayIndexService = new GatewayIndexService(webClient, 60, KEYSTORE_PATH, PASSWORD, "PKCS12");
+
+ StepVerifier.create(gatewayIndexService.indexGatewayServices(eurekaInstance))
+ .verifyComplete();
+
+ verifyNoInteractions(webClient);
+ }
+
+ @Test
+ void shouldSkipCustomSslContextCreationIfPasswordNotDefined() {
+
+ gatewayIndexService = new GatewayIndexService(webClient, 60, KEYSTORE_PATH, null, null);
+
+ SslContext customClientSslContext = (SslContext) ReflectionTestUtils.getField(gatewayIndexService, "customClientSslContext");
+
+ assertThat(customClientSslContext).isNull();
+ }
+
+ @Test
+ void shouldUseDefaultWebClientWhenCustomSslContextIsNotProvided() {
+ gatewayIndexService = new GatewayIndexService(webClient, 60, null, null, null);
+
+ StepVerifier.create(gatewayIndexService.indexGatewayServices(eurekaInstance))
+ .verifyComplete();
+
+ verify(webClient).mutate();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelperTest.java b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelperTest.java
new file mode 100644
index 0000000000..3e715b3506
--- /dev/null
+++ b/cloud-gateway-service/src/test/java/org/zowe/apiml/cloudgatewayservice/service/WebClientHelperTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.service;
+
+import io.netty.handler.ssl.SslContext;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.zowe.apiml.security.HttpsConfigError;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.AssertionsForClassTypes.catchThrowableOfType;
+import static org.zowe.apiml.security.HttpsConfigError.ErrorCode.HTTP_CLIENT_INITIALIZATION_FAILED;
+
+class WebClientHelperTest {
+ private static final char[] WRONG_PASSWORD = "wrong_password".toCharArray(); // NOSONAR
+ static final String KEYSTORE_PATH = "../keystore/localhost/localhost.keystore.p12";
+ static final char[] PASSWORD = "password".toCharArray(); // NOSONAR
+
+ @Nested
+ class WhenLoading {
+ @Test
+ void givenWrongPath_thenThrowException() {
+ assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> WebClientHelper.load("../wrong/path", PASSWORD, "PKCS12"));
+ }
+
+ @Test
+ void givenCorrectPath_thenLoadSSLContext() {
+ final SslContext sslContext = WebClientHelper.load(KEYSTORE_PATH, PASSWORD, "PKCS12");
+ assertThat(sslContext).isNotNull();
+ }
+
+ @Test
+ void givenWrongPassword_httpConfigErrorIsExpected() {
+ HttpsConfigError error = catchThrowableOfType(() -> WebClientHelper.load("../keystore/localhost/localhost.keystore.p12", WRONG_PASSWORD, "PKCS12"), HttpsConfigError.class);
+ assertThat(error.getCode()).isEqualTo(HTTP_CLIENT_INITIALIZATION_FAILED);
+ assertThat(error.getConfig()).isNull();
+ }
+ }
+}
diff --git a/cloud-gateway-service/src/test/resources/application.yml b/cloud-gateway-service/src/test/resources/application.yml
index f6811128b6..3d3e43eedc 100644
--- a/cloud-gateway-service/src/test/resources/application.yml
+++ b/cloud-gateway-service/src/test/resources/application.yml
@@ -9,9 +9,13 @@ apiml:
id: cloud-gateway
port: 10023
hostname: localhost
+ scheme: https
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
+ cloudGateway:
+ serviceRegistryEnabled: false
forwardClientCertEnabled: false
+
server:
port: ${apiml.service.port}
ssl:
diff --git a/common-service-core/src/main/java/org/zowe/apiml/security/HttpsConfigError.java b/common-service-core/src/main/java/org/zowe/apiml/security/HttpsConfigError.java
index 74a31c2345..ec5e316fde 100644
--- a/common-service-core/src/main/java/org/zowe/apiml/security/HttpsConfigError.java
+++ b/common-service-core/src/main/java/org/zowe/apiml/security/HttpsConfigError.java
@@ -45,6 +45,12 @@ public HttpsConfigError(Throwable cause, ErrorCode code, HttpsConfig config) {
this.config = config;
}
+ public HttpsConfigError(String message, Throwable cause, ErrorCode code) {
+ super(message, cause);
+ this.code = code;
+ this.config = null;
+ }
+
public ErrorCode getCode() {
return this.code;
}
diff --git a/common-service-core/src/main/java/org/zowe/apiml/services/BasicInfoService.java b/common-service-core/src/main/java/org/zowe/apiml/services/BasicInfoService.java
new file mode 100644
index 0000000000..de39a5a5dd
--- /dev/null
+++ b/common-service-core/src/main/java/org/zowe/apiml/services/BasicInfoService.java
@@ -0,0 +1,160 @@
+/*
+ * 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.services;
+
+
+import com.fasterxml.jackson.core.Version;
+import com.netflix.appinfo.InstanceInfo;
+import com.netflix.discovery.EurekaClient;
+import com.netflix.discovery.shared.Application;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.ObjectUtils;
+import org.zowe.apiml.auth.Authentication;
+import org.zowe.apiml.config.ApiInfo;
+import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
+
+import java.util.AbstractMap;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.minBy;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_DESCRIPTION;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_TITLE;
+import static org.zowe.apiml.services.ServiceInfo.ApiInfoExtended;
+import static org.zowe.apiml.services.ServiceInfoUtils.getBasePath;
+import static org.zowe.apiml.services.ServiceInfoUtils.getInstances;
+import static org.zowe.apiml.services.ServiceInfoUtils.getMajorVersion;
+import static org.zowe.apiml.services.ServiceInfoUtils.getStatus;
+import static org.zowe.apiml.services.ServiceInfoUtils.getVersion;
+
+/**
+ * Similar to {@link org.zowe.apiml.gateway.services.ServicesInfoService} service which does not depend on gateway-service components.
+ * Following properties left blank:
+ * {@link ServiceInfo.Service#homePageUrl} and {@link ServiceInfo.ApiInfoExtended#swaggerUrl}
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class BasicInfoService {
+
+ private final EurekaClient eurekaClient;
+ private final EurekaMetadataParser eurekaMetadataParser;
+
+ public List getServicesInfo() {
+ return eurekaClient.getApplications().getRegisteredApplications()
+ .stream().map(this::getServiceInfo)
+ .collect(Collectors.toCollection(LinkedList::new));
+ }
+
+ private ServiceInfo getServiceInfo(Application application) {
+ String serviceId = application.getName().toLowerCase();
+
+ List appInstances = application.getInstances();
+ if (ObjectUtils.isEmpty(appInstances)) {
+ return ServiceInfo.builder()
+ .serviceId(serviceId)
+ .status(InstanceInfo.InstanceStatus.DOWN)
+ .build();
+ }
+
+ return ServiceInfo.builder()
+ .serviceId(serviceId)
+ .status(getStatus(appInstances))
+ .apiml(getApiml(appInstances))
+ .instances(getInstances(appInstances))
+ .build();
+ }
+
+ /**
+ * uses simplified:
+ * - getApiInfos
+ * - getApiInfos
+ */
+ private ServiceInfo.Apiml getApiml(List appInstances) {
+ return ServiceInfo.Apiml.builder()
+ .apiInfo(getApiInfos(appInstances))
+ .service(getService(appInstances))
+ .authentication(getAuthentication(appInstances))
+ .build();
+ }
+
+
+ /**
+ * simplified version, following part is excluded:
+ * - homePageUrl
+ */
+ private ServiceInfo.Service getService(List appInstances) {
+ InstanceInfo instanceInfo = getInstanceWithHighestVersion(appInstances);
+
+ return ServiceInfo.Service.builder()
+ .title(instanceInfo.getMetadata().get(SERVICE_TITLE))
+ .description(instanceInfo.getMetadata().get(SERVICE_DESCRIPTION))
+ .build();
+ }
+
+ /**
+ * Simplified version, following properties are excluded:
+ * - baseUrl
+ * - swaggerUrl
+ */
+ private List getApiInfos(List appInstances) {
+ return appInstances.stream()
+ .map(instanceInfo -> new AbstractMap.SimpleEntry<>(instanceInfo, eurekaMetadataParser.parseApiInfo(instanceInfo.getMetadata())))
+ .flatMap(entry -> entry.getValue().stream()
+ .map(apiInfo -> ApiInfoExtended.builder()
+ .apiId(apiInfo.getApiId())
+ .basePath(getBasePath(apiInfo, entry.getKey()))
+ .gatewayUrl(apiInfo.getGatewayUrl())
+ .documentationUrl(apiInfo.getDocumentationUrl())
+ .version(apiInfo.getVersion())
+ .codeSnippet(apiInfo.getCodeSnippet())
+ .isDefaultApi(apiInfo.isDefaultApi())
+ .build()))
+ .collect(groupingBy(
+ apiInfo -> new AbstractMap.SimpleEntry<>(apiInfo.getApiId(), getMajorVersion(apiInfo)),
+ minBy(Comparator.comparingInt(ServiceInfoUtils::getMajorVersion))
+ ))
+ .values()
+ .stream()
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+ }
+
+ private List getAuthentication(List appInstances) {
+ return appInstances.stream()
+ .map(instanceInfo -> eurekaMetadataParser.parseAuthentication(instanceInfo.getMetadata()))
+ .filter(a -> !a.isEmpty())
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ private InstanceInfo getInstanceWithHighestVersion(List appInstances) {
+ InstanceInfo instanceInfo = appInstances.get(0);
+ Version highestVersion = Version.unknownVersion();
+
+ for (InstanceInfo currentInfo : appInstances) {
+ List apiInfoList = eurekaMetadataParser.parseApiInfo(currentInfo.getMetadata());
+ for (ApiInfo apiInfo : apiInfoList) {
+ Version version = getVersion(apiInfo.getVersion());
+ if (version.compareTo(highestVersion) > 0) {
+ highestVersion = version;
+ instanceInfo = currentInfo;
+ }
+ }
+ }
+ return instanceInfo;
+ }
+}
diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/services/ServiceInfo.java b/common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfo.java
similarity index 86%
rename from apiml-common/src/main/java/org/zowe/apiml/product/services/ServiceInfo.java
rename to common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfo.java
index c91d606d27..954676401a 100644
--- a/apiml-common/src/main/java/org/zowe/apiml/product/services/ServiceInfo.java
+++ b/common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfo.java
@@ -8,13 +8,15 @@
* Copyright Contributors to the Zowe Project.
*/
-package org.zowe.apiml.product.services;
+package org.zowe.apiml.services;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.netflix.appinfo.InstanceInfo;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.config.ApiInfo;
@@ -24,6 +26,8 @@
@Data
@Builder
+@NoArgsConstructor
+@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ServiceInfo {
private String serviceId;
@@ -33,6 +37,8 @@ public class ServiceInfo {
@Data
@Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Apiml {
private List apiInfo;
@@ -42,6 +48,8 @@ public static class Apiml {
@Data
@Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Instances {
private InstanceInfo.InstanceStatus status;
@@ -57,6 +65,8 @@ public static class Instances {
@Data
@Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Service {
private String title;
@@ -67,6 +77,8 @@ public static class Service {
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
+ @NoArgsConstructor
+ @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ApiInfoExtended extends ApiInfo {
private String baseUrl;
diff --git a/common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfoUtils.java b/common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfoUtils.java
new file mode 100644
index 0000000000..24840f8dae
--- /dev/null
+++ b/common-service-core/src/main/java/org/zowe/apiml/services/ServiceInfoUtils.java
@@ -0,0 +1,105 @@
+/*
+ * 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.services;
+
+import com.fasterxml.jackson.core.Version;
+import com.netflix.appinfo.InstanceInfo;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.zowe.apiml.config.ApiInfo;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Utility class containing mapping functions for ServiceInfo formatting
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ServiceInfoUtils {
+
+ public static Map getInstances(List appInstances) {
+ return appInstances.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(
+ InstanceInfo::getInstanceId,
+ instanceInfo -> ServiceInfo.Instances.builder()
+ .status(instanceInfo.getStatus())
+ .hostname(instanceInfo.getHostName())
+ .ipAddr(instanceInfo.getIPAddr())
+ .protocol(getProtocol(instanceInfo))
+ .port(getPort(instanceInfo))
+ .homePageUrl(instanceInfo.getHomePageUrl())
+ .healthCheckUrl(getHealthCheckUrl(instanceInfo))
+ .statusPageUrl(instanceInfo.getStatusPageUrl())
+ .customMetadata(getCustomMetadata(instanceInfo.getMetadata()))
+ .build()
+ ));
+ }
+
+ public static String getBasePath(ApiInfo apiInfo, InstanceInfo instanceInfo) {
+ return String.format("/%s/%s", instanceInfo.getAppName().toLowerCase(), apiInfo.getGatewayUrl());
+ }
+
+ private static String getHealthCheckUrl(InstanceInfo instanceInfo) {
+ return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ?
+ instanceInfo.getSecureHealthCheckUrl() : instanceInfo.getHealthCheckUrl();
+ }
+
+ private static int getPort(InstanceInfo instanceInfo) {
+ return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ?
+ instanceInfo.getSecurePort() : instanceInfo.getPort();
+ }
+
+ private static String getProtocol(InstanceInfo instanceInfo) {
+ return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ? "https" : "http";
+ }
+
+ public static int getMajorVersion(ServiceInfo.ApiInfoExtended apiInfo) {
+ return getVersion(apiInfo.getVersion()).getMajorVersion();
+ }
+
+ public static Map getCustomMetadata(Map metadata) {
+ return metadata.entrySet().stream()
+ .filter(entry -> !entry.getKey().startsWith("apiml."))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ public static Version getVersion(String version) {
+ if (version == null) return Version.unknownVersion();
+
+ String[] versions = version.split("\\.");
+
+ int major = 0;
+ int minor = 0;
+ int patch = 0;
+ try {
+ if (versions.length >= 1) major = Integer.parseInt(versions[0]);
+ if (versions.length >= 2) minor = Integer.parseInt(versions[1]);
+ if (versions.length >= 3) patch = Integer.parseInt(versions[2]);
+ } catch (NumberFormatException ex) {
+ log.debug("Incorrect version {}", version);
+ }
+
+ return new Version(major, minor, patch, null, null, null);
+ }
+
+ public static InstanceInfo.InstanceStatus getStatus(List instances) {
+ if (instances.stream().anyMatch(instance -> instance.getStatus().equals(InstanceInfo.InstanceStatus.UP))) {
+ return InstanceInfo.InstanceStatus.UP;
+ } else {
+ return InstanceInfo.InstanceStatus.DOWN;
+ }
+ }
+}
diff --git a/common-service-core/src/test/java/org/zowe/apiml/services/BasicInfoServiceTest.java b/common-service-core/src/test/java/org/zowe/apiml/services/BasicInfoServiceTest.java
new file mode 100644
index 0000000000..9fb1b81907
--- /dev/null
+++ b/common-service-core/src/test/java/org/zowe/apiml/services/BasicInfoServiceTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.services;
+
+
+import com.netflix.appinfo.InstanceInfo;
+import com.netflix.discovery.EurekaClient;
+import com.netflix.discovery.shared.Application;
+import com.netflix.discovery.shared.Applications;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.zowe.apiml.auth.AuthenticationScheme;
+import org.zowe.apiml.config.ApiInfo;
+import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.AUTHENTICATION_APPLID;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.AUTHENTICATION_SCHEME;
+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;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_DESCRIPTION;
+import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_TITLE;
+
+@ExtendWith(MockitoExtension.class)
+class BasicInfoServiceTest {
+
+ // Client test configuration
+ private static final String CLIENT_SERVICE_ID = "testclient";
+ private static final String CLIENT_INSTANCE_ID = CLIENT_SERVICE_ID + ":";
+ private static final String CLIENT_HOSTNAME = "client";
+ private static final String CLIENT_IP = "192.168.0.1";
+ private static final int CLIENT_PORT = 10;
+ private static final String CLIENT_HOMEPAGE = "https://client:10";
+ private static final String CLIENT_RELATIVE_HEALTH_URL = "/actuator/health";
+ private static final String CLIENT_STATUS_URL = "https://client:10/actuator/info";
+ private static final String CLIENT_API_ID = "zowe.client.api";
+ private static final String CLIENT_API_VERSION = "1.0.0";
+ private static final String CLIENT_API_GW_URL = "api/v1";
+ private static final boolean CLIENT_API_DEFAULT = true;
+ private static final String CLIENT_API_SWAGGER_URL = CLIENT_HOMEPAGE + "/apiDoc";
+ private static final String CLIENT_API_DOC_URL = "https://www.zowe.org";
+ private static final String CLIENT_ROUTE_UI = "ui/v1";
+ private static final String CLIENT_SERVICE_TITLE = "Client service";
+ private static final String CLIENT_SERVICE_DESCRIPTION = "Client test service";
+ private static final AuthenticationScheme CLIENT_AUTHENTICATION_SCHEME = AuthenticationScheme.ZOSMF;
+ private static final String CLIENT_AUTHENTICATION_APPLID = "authid";
+ private static final String CLIENT_CUSTOM_METADATA_KEY = "custom.test.key";
+ private static final String CLIENT_CUSTOM_METADATA_VALUE = "value";
+
+ // ServiceInfo properties
+ private static final String SERVICE_SERVICE_ID = "serviceId";
+
+ @Mock
+ private EurekaClient eurekaClient;
+
+ private final EurekaMetadataParser eurekaMetadataParser = new EurekaMetadataParser();
+
+ private BasicInfoService basicInfoService;
+
+ @BeforeEach
+ void setUp() {
+ basicInfoService = new BasicInfoService(eurekaClient, eurekaMetadataParser);
+ }
+
+ @Test
+ void whenListingAllServices_thenReturnList() {
+ String clientServiceId2 = "testclient2";
+
+ List applications = Arrays.asList(
+ new Application(CLIENT_SERVICE_ID, Collections.singletonList(createFullTestInstance())),
+ new Application(clientServiceId2)
+ );
+ when(eurekaClient.getApplications())
+ .thenReturn(new Applications(null, 1L, applications));
+
+ List servicesInfo = basicInfoService.getServicesInfo();
+
+
+ assertEquals(2, servicesInfo.size());
+ assertThat(servicesInfo, contains(
+ hasProperty(SERVICE_SERVICE_ID, is(CLIENT_SERVICE_ID)),
+ hasProperty(SERVICE_SERVICE_ID, is(clientServiceId2))
+ ));
+ }
+
+ private InstanceInfo createFullTestInstance() {
+ ApiInfo apiInfo = ApiInfo.builder()
+ .apiId(CLIENT_API_ID)
+ .version(CLIENT_API_VERSION)
+ .gatewayUrl(CLIENT_API_GW_URL)
+ .isDefaultApi(CLIENT_API_DEFAULT)
+ .swaggerUrl(CLIENT_API_SWAGGER_URL)
+ .documentationUrl(CLIENT_API_DOC_URL)
+ .build();
+ Map metadata = EurekaMetadataParser.generateMetadata(CLIENT_SERVICE_ID, apiInfo);
+ metadata.put(SERVICE_TITLE, CLIENT_SERVICE_TITLE);
+ metadata.put(SERVICE_DESCRIPTION, CLIENT_SERVICE_DESCRIPTION);
+ metadata.put(ROUTES + ".ui-v1." + ROUTES_SERVICE_URL, "/");
+ metadata.put(ROUTES + ".ui-v1." + ROUTES_GATEWAY_URL, CLIENT_ROUTE_UI);
+ metadata.put(AUTHENTICATION_SCHEME, CLIENT_AUTHENTICATION_SCHEME.getScheme());
+ metadata.put(AUTHENTICATION_APPLID, CLIENT_AUTHENTICATION_APPLID);
+ metadata.put(CLIENT_CUSTOM_METADATA_KEY, CLIENT_CUSTOM_METADATA_VALUE);
+
+ return InstanceInfo.Builder.newBuilder()
+ .setAppName(CLIENT_SERVICE_ID)
+ .setInstanceId(CLIENT_INSTANCE_ID + Math.random())
+ .setHostName(CLIENT_HOSTNAME)
+ .setIPAddr(CLIENT_IP)
+ .enablePort(InstanceInfo.PortType.SECURE, true)
+ .setSecurePort(CLIENT_PORT)
+ .setHomePageUrl(null, CLIENT_HOMEPAGE)
+ .setHealthCheckUrls(CLIENT_RELATIVE_HEALTH_URL, null, null)
+ .setStatusPageUrl(null, CLIENT_STATUS_URL)
+ .setMetadata(metadata)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServerInfoConfig.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServerInfoConfig.java
index de0ee3f94f..144e88fba6 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServerInfoConfig.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServerInfoConfig.java
@@ -16,7 +16,6 @@
import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
import org.zowe.apiml.product.gateway.GatewayConfigProperties;
import org.zowe.apiml.product.routing.transform.TransformService;
-import org.zowe.apiml.product.services.ServicesInfoService;
@Configuration
public class ServerInfoConfig {
@@ -26,7 +25,6 @@ public EurekaMetadataParser getEurekaMetadataParser() {
return new EurekaMetadataParser();
}
-
@Bean
public ServicesInfoService servicesInfoService(EurekaClient eurekaClient,
EurekaMetadataParser eurekaMetadataParser, GatewayConfigProperties gatewayConfigProperties, TransformService transformService) {
diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoController.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoController.java
index e81da26350..513a5df9f8 100644
--- a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoController.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoController.java
@@ -22,13 +22,12 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import org.zowe.apiml.product.services.ServiceInfo;
-import org.zowe.apiml.product.services.ServicesInfoService;
+import org.zowe.apiml.services.ServiceInfo;
import java.util.List;
-import static org.zowe.apiml.product.services.ServicesInfoService.CURRENT_VERSION;
-import static org.zowe.apiml.product.services.ServicesInfoService.VERSION_HEADER;
+import static org.zowe.apiml.gateway.services.ServicesInfoService.CURRENT_VERSION;
+import static org.zowe.apiml.gateway.services.ServicesInfoService.VERSION_HEADER;
@RestController
diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/services/ServicesInfoService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java
similarity index 74%
rename from apiml-common/src/main/java/org/zowe/apiml/product/services/ServicesInfoService.java
rename to gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java
index 45280f5491..b40d5c9c68 100644
--- a/apiml-common/src/main/java/org/zowe/apiml/product/services/ServicesInfoService.java
+++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java
@@ -8,7 +8,7 @@
* Copyright Contributors to the Zowe Project.
*/
-package org.zowe.apiml.product.services;
+package org.zowe.apiml.gateway.services;
import com.fasterxml.jackson.core.Version;
import com.netflix.appinfo.InstanceInfo;
@@ -26,13 +26,14 @@
import org.zowe.apiml.product.routing.ServiceType;
import org.zowe.apiml.product.routing.transform.TransformService;
import org.zowe.apiml.product.routing.transform.URLTransformationException;
+import org.zowe.apiml.services.ServiceInfo;
+import org.zowe.apiml.services.ServiceInfoUtils;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -41,6 +42,10 @@
import static java.util.stream.Collectors.minBy;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_DESCRIPTION;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_TITLE;
+import static org.zowe.apiml.services.ServiceInfoUtils.getBasePath;
+import static org.zowe.apiml.services.ServiceInfoUtils.getInstances;
+import static org.zowe.apiml.services.ServiceInfoUtils.getMajorVersion;
+import static org.zowe.apiml.services.ServiceInfoUtils.getVersion;
@Slf4j
@RequiredArgsConstructor
@@ -90,6 +95,11 @@ public ServiceInfo getServiceInfo(String serviceId) {
return getServiceInfo(application);
}
+ private String getBaseUrl(ApiInfo apiInfo, InstanceInfo instanceInfo) {
+ return String.format("%s://%s%s",
+ gatewayConfigProperties.getScheme(), gatewayConfigProperties.getHostname(), getBasePath(apiInfo, instanceInfo));
+ }
+
private ServiceInfo getServiceInfo(Application application) {
String serviceId = application.getName().toLowerCase();
@@ -145,7 +155,7 @@ private List getApiInfos(List appInst
return completeList.stream()
.collect(groupingBy(
apiInfo -> new AbstractMap.SimpleEntry<>(apiInfo.getApiId(), getMajorVersion(apiInfo)),
- minBy(Comparator.comparingInt(this::getMajorVersion))
+ minBy(Comparator.comparingInt(ServiceInfoUtils::getMajorVersion))
))
.values()
.stream()
@@ -175,24 +185,6 @@ private List getAuthentication(List appInstances)
.collect(Collectors.toList());
}
- private Map getInstances(List appInstances) {
- return appInstances.stream()
- .collect(Collectors.toMap(
- InstanceInfo::getInstanceId,
- instanceInfo -> ServiceInfo.Instances.builder()
- .status(instanceInfo.getStatus())
- .hostname(instanceInfo.getHostName())
- .ipAddr(instanceInfo.getIPAddr())
- .protocol(getProtocol(instanceInfo))
- .port(getPort(instanceInfo))
- .homePageUrl(instanceInfo.getHomePageUrl())
- .healthCheckUrl(getHealthCheckUrl(instanceInfo))
- .statusPageUrl(instanceInfo.getStatusPageUrl())
- .customMetadata(getCustomMetadata(instanceInfo.getMetadata()))
- .build()
- ));
- }
-
private String getGatewayUrl(String url, String serviceId, ServiceType type, RoutedServices routes) {
if (url == null) return null;
@@ -207,25 +199,6 @@ private String getGatewayUrl(String url, String serviceId, ServiceType type, Rou
}
}
- private Version getVersion(String version) {
- if (version == null) return Version.unknownVersion();
-
- String[] versions = version.split("\\.");
-
- int major = 0;
- int minor = 0;
- int patch = 0;
- try {
- if (versions.length >= 1) major = Integer.parseInt(versions[0]);
- if (versions.length >= 2) minor = Integer.parseInt(versions[1]);
- if (versions.length >= 3) patch = Integer.parseInt(versions[2]);
- } catch (NumberFormatException ex) {
- log.debug("Incorrect version {}", version);
- }
-
- return new Version(major, minor, patch, null, null, null);
- }
-
private InstanceInfo.InstanceStatus getStatus(List instances) {
if (instances.stream().anyMatch(instance -> instance.getStatus().equals(InstanceInfo.InstanceStatus.UP))) {
return InstanceInfo.InstanceStatus.UP;
@@ -234,39 +207,6 @@ private InstanceInfo.InstanceStatus getStatus(List instances) {
}
}
- private Map getCustomMetadata(Map metadata) {
- return metadata.entrySet().stream()
- .filter(entry -> !entry.getKey().startsWith("apiml."))
- .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- }
-
- private String getBasePath(ApiInfo apiInfo, InstanceInfo instanceInfo) {
- return String.format("/%s/%s", instanceInfo.getAppName().toLowerCase(), apiInfo.getGatewayUrl());
- }
-
- private String getBaseUrl(ApiInfo apiInfo, InstanceInfo instanceInfo) {
- return String.format("%s://%s%s",
- gatewayConfigProperties.getScheme(), gatewayConfigProperties.getHostname(), getBasePath(apiInfo, instanceInfo));
- }
-
- private String getHealthCheckUrl(InstanceInfo instanceInfo) {
- return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ?
- instanceInfo.getSecureHealthCheckUrl() : instanceInfo.getHealthCheckUrl();
- }
-
- private int getPort(InstanceInfo instanceInfo) {
- return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ?
- instanceInfo.getSecurePort() : instanceInfo.getPort();
- }
-
- private String getProtocol(InstanceInfo instanceInfo) {
- return instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE) ? "https" : "http";
- }
-
- private int getMajorVersion(ServiceInfo.ApiInfoExtended apiInfo) {
- return getVersion(apiInfo.getVersion()).getMajorVersion();
- }
-
private InstanceInfo getInstanceWithHighestVersion(List appInstances) {
InstanceInfo instanceInfo = appInstances.get(0);
Version highestVersion = Version.unknownVersion();
diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoControllerTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoControllerTest.java
index 9b74508175..e2da7ce804 100644
--- a/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoControllerTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoControllerTest.java
@@ -16,8 +16,7 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
-import org.zowe.apiml.product.services.ServiceInfo;
-import org.zowe.apiml.product.services.ServicesInfoService;
+import org.zowe.apiml.services.ServiceInfo;
import java.util.Arrays;
import java.util.Collections;
@@ -26,8 +25,8 @@
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.when;
import static org.zowe.apiml.gateway.services.ServicesInfoController.SERVICES_URL;
-import static org.zowe.apiml.product.services.ServicesInfoService.CURRENT_VERSION;
-import static org.zowe.apiml.product.services.ServicesInfoService.VERSION_HEADER;
+import static org.zowe.apiml.gateway.services.ServicesInfoService.CURRENT_VERSION;
+import static org.zowe.apiml.gateway.services.ServicesInfoService.VERSION_HEADER;
@ExtendWith(MockitoExtension.class)
class ServicesInfoControllerTest {
diff --git a/apiml-common/src/test/java/org/zowe/apiml/product/services/ServicesInfoServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoServiceTest.java
similarity index 99%
rename from apiml-common/src/test/java/org/zowe/apiml/product/services/ServicesInfoServiceTest.java
rename to gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoServiceTest.java
index 524cf037b8..5ef11637ce 100644
--- a/apiml-common/src/test/java/org/zowe/apiml/product/services/ServicesInfoServiceTest.java
+++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/services/ServicesInfoServiceTest.java
@@ -8,7 +8,8 @@
* Copyright Contributors to the Zowe Project.
*/
-package org.zowe.apiml.product.services;
+package org.zowe.apiml.gateway.services;
+
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
@@ -27,6 +28,7 @@
import org.zowe.apiml.product.gateway.GatewayClient;
import org.zowe.apiml.product.gateway.GatewayConfigProperties;
import org.zowe.apiml.product.routing.transform.TransformService;
+import org.zowe.apiml.services.ServiceInfo;
import java.util.Arrays;
import java.util.Collections;
diff --git a/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java b/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java
index e18988e500..182f0af78e 100644
--- a/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java
+++ b/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java
@@ -95,7 +95,7 @@ private boolean areAllServicesUp() {
}
String allComponents = context.read("$.components.discoveryComposite.components.discoveryClient.details.services").toString();
boolean isTestApplicationUp = allComponents.contains("discoverableclient");
- boolean isCloudGatewayUp = allComponents.contains("cloudgateway");
+ boolean isCloudGatewayUp = allComponents.contains("cloud-gateway");
log.debug("Discoverable Client is {}", isTestApplicationUp);
log.debug("Cloud gateway is {}", isCloudGatewayUp);
diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java
index 5933745d16..5973b367aa 100644
--- a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java
+++ b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java
@@ -65,8 +65,8 @@ private void prepareNodeJsSampleApp() {
// If NODE_HOME is defined in environment variable, use it, otherwise assume in PATH
String path = Optional.ofNullable(System.getenv("NODE_HOME"))
- .map(javaHome -> javaHome + "/bin/")
- .orElse("");
+ .map(javaHome -> javaHome + "/bin/")
+ .orElse("");
parameters.add(path + "node");
parameters.add("src/index.js");
@@ -92,7 +92,7 @@ public void prepareCaching() {
}
public void prepareCloudGateway() {
- cloudGatewayService = new RunningService("cloudgateway","cloud-gateway-service/build/libs", null,null);
+ cloudGatewayService = new RunningService("cloud-gateway", "cloud-gateway-service/build/libs", null, null);
}
private void prepareMockServices() {
@@ -134,9 +134,9 @@ public void start() {
Map cachingEnv = new HashMap<>(env);
cachingEnv.put("ZWE_configs_port", "10016");
cachingService.startWithScript("caching-service-package/src/main/resources/bin", cachingEnv);
- Map cloudGWEnv = new HashMap<>(env);
- cloudGWEnv.put("ZWE_configs_port","10023");
- cloudGatewayService.startWithScript("cloud-gateway-package/src/main/resources/bin",cloudGWEnv);
+ Map cloudGWEnv = new HashMap<>(env);
+ cloudGWEnv.put("ZWE_configs_port", "10023");
+ cloudGatewayService.startWithScript("cloud-gateway-package/src/main/resources/bin", cloudGWEnv);
if (!attlsEnabled) {
nodeJsSampleApp = nodeJsBuilder.start();
}