Skip to content

Commit

Permalink
Fix stacktrace on unreachable swagger and remove handling for depreca…
Browse files Browse the repository at this point in the history
…ted z/os

Signed-off-by: Richard Salac <richard.salac@broadcom.com>
  • Loading branch information
richard-salac committed Aug 27, 2024
1 parent e413a9d commit 9c45dc9
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
Expand All @@ -35,9 +32,11 @@
import org.zowe.apiml.apicatalog.swagger.SubstituteSwaggerGenerator;
import org.zowe.apiml.config.ApiInfo;
import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.gateway.GatewayClient;
import org.zowe.apiml.product.instance.ServiceAddress;
import org.zowe.apiml.product.instance.InstanceInitializationException;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import org.zowe.apiml.product.routing.RoutedService;
import org.zowe.apiml.product.routing.RoutedServices;

Expand Down Expand Up @@ -70,6 +69,9 @@ public class APIDocRetrievalService {
private final EurekaMetadataParser metadataParser = new EurekaMetadataParser();
private final SubstituteSwaggerGenerator swaggerGenerator = new SubstituteSwaggerGenerator();

@InjectApimlLogger
ApimlLogger apimlLogger = ApimlLogger.empty();

/**
* Retrieves the available API versions for a registered service.
* Takes the versions available in each 'apiml.service.apiInfo' element.
Expand Down Expand Up @@ -175,26 +177,12 @@ private ApiDocInfo buildApiDocInfo(String serviceId, ApiInfo apiInfo, InstanceIn
return getApiDocInfoBySubstituteSwagger(instanceInfo, routes, apiInfo);
}

// The Swagger generated by zOSMF is invalid because it has null in enum values for boolean.
// Remove once we know that zOSMF APARs for zOS2.2 and 2.3 is prerequisite
if (serviceId.equals("zosmf") || serviceId.equals("ibmzosmf")) {
try {
String apiDocContent = getApiDocContentByUrl(serviceId, apiDocUrl);
if (apiDocContent != null) {
apiDocContent = apiDocContent.replace("null, null", "true, false");
}
return new ApiDocInfo(apiInfo, apiDocContent, routes);
} catch (Exception e) {
log.error("Error retrieving api doc for '{}'", serviceId, e);
return getApiDocInfoBySubstituteSwagger(instanceInfo, routes, apiInfo);
}
}

String apiDocContent = "";
try {
apiDocContent = getApiDocContentByUrl(serviceId, apiDocUrl);
} catch (IOException | ParseException e) { //TODO: Is it handled correctly?
log.error("Error retrieving api doc for '{}'", serviceId, e);
} catch (IOException | ParseException e) {
apimlLogger.log("org.zowe.apiml.apicatalog.apiDocHostCommunication", serviceId, e.getMessage());
log.debug("Error retrieving api doc for '{}'", serviceId, e);
}
return new ApiDocInfo(apiInfo, apiDocContent, routes);
}
Expand Down Expand Up @@ -310,7 +298,6 @@ private String getApiDocUrl(ApiInfo apiInfo, InstanceInfo instanceInfo, RoutedSe
return apiDocUrl;
}


/**
* Get ApiDoc content by Url
*
Expand All @@ -323,20 +310,12 @@ private String getApiDocContentByUrl(@NonNull String serviceId, String apiDocUrl
HttpGet httpGet = new HttpGet(apiDocUrl);
httpGet.setHeader(org.apache.http.HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);

String responseBody = null;
try {
CloseableHttpResponse response = secureHttpClientWithoutKeystore.execute(httpGet);
final HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
responseBody = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
}
if (response.getCode() != HttpStatus.SC_OK) {
throw new ApiDocNotFoundException("No API Documentation was retrieved due to " + serviceId +
" server error: '" + responseBody + "'.");
}
} catch (Exception e) {
log.error("Error retrieving api doc by url for {} at {}", serviceId, apiDocUrl, e);
throw e;
var response = secureHttpClientWithoutKeystore.execute(httpGet, r -> r);
var responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);

if (response.getCode() != HttpStatus.SC_OK) {
throw new ApiDocNotFoundException("No API Documentation was retrieved due to " + serviceId +
" server error: '" + responseBody + "'.");
}
return responseBody;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ messages:
reason: "The status of one or more containers could not be retrieved."
action: "Check the status of the message for more information and the health of the Discovery Service."

- key: org.zowe.apiml.apicatalog.apiDocHostCommunication
number: ZWEAC105
type: WARNING
text: "API Documentation not retrieved for service '%s' due to communication error, %s"
reason: "Unable to fetch API documentation."
action: "Make sure the service documentation url or server transport encoding is configured correctly."

# HTTP, Protocol messages
# 400-499\
# TLS, Certificate messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
Expand All @@ -39,9 +40,6 @@
import java.util.Map;

import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
Expand All @@ -52,7 +50,6 @@
@MockitoSettings(strictness = Strictness.LENIENT)
class LocalApiDocServiceTest {
private static final String SERVICE_ID = "service";
private static final String ZOSMF_ID = "ibmzosmf";
private static final String SERVICE_HOST = "service";
private static final int SERVICE_PORT = 8080;
private static final String SERVICE_VERSION = "1.0.0";
Expand All @@ -78,7 +75,7 @@ class LocalApiDocServiceTest {
void setup() throws IOException {
response = mock(CloseableHttpResponse.class);
when(response.getCode()).thenReturn(HttpStatus.SC_OK);
when(httpClient.execute(any())).thenReturn(response);
when(httpClient.execute(any(), any(HttpClientResponseHandler.class))).thenReturn(response);

GatewayClient gatewayClient = new GatewayClient(getProperties());
apiDocRetrievalService = new APIDocRetrievalService(
Expand Down Expand Up @@ -150,6 +147,7 @@ void givenNoInstanceMetadata() {
assertEquals("No API Documentation defined for service " + SERVICE_ID + ".", exception.getMessage());
}
}

@Test
void givenNoSwaggerUrl_thenReturnSubstituteApiDoc() {
String generatedResponseBody = "{\n" +
Expand Down Expand Up @@ -238,48 +236,6 @@ void shouldCreateApiDocUrlFromRoutingAndUseHttp() {

assertEquals(responseBody, actualResponse.getApiDocContent());
}

@Nested
class GivenZosmfId {
@Test
void thenReturnApiDoc() {
String responseBody = "api-doc [ null, null ] body";
String expectedResponseBody = "api-doc [ true, false ] body";

when(instanceRetrievalService.getInstanceInfo(ZOSMF_ID))
.thenReturn(getStandardInstance(getStandardMetadata(), true));

BasicHttpEntity responseEntity = new BasicHttpEntity(IOUtils.toInputStream(responseBody, StandardCharsets.UTF_8), APPLICATION_JSON);
when(response.getEntity()).thenReturn(responseEntity);

ApiDocInfo actualResponse = apiDocRetrievalService.retrieveApiDoc(ZOSMF_ID, SERVICE_VERSION_V);

assertEquals(API_ID, actualResponse.getApiInfo().getApiId());
assertEquals(GATEWAY_URL, actualResponse.getApiInfo().getGatewayUrl());
assertEquals(SERVICE_VERSION, actualResponse.getApiInfo().getVersion());
assertEquals(SWAGGER_URL, actualResponse.getApiInfo().getSwaggerUrl());

assertNotNull(actualResponse);
assertNotNull(actualResponse.getApiDocContent());
assertEquals(expectedResponseBody, actualResponse.getApiDocContent());

assertEquals("[api -> api=RoutedService(subServiceId=api-v1, gatewayUrl=api, serviceUrl=/)]", actualResponse.getRoutes().toString());
}

@Test
void whenIncorrectResponseFromServer_thenReturnDefaultDoc() {
String responseBody = "Server not found";

when(instanceRetrievalService.getInstanceInfo(ZOSMF_ID))
.thenReturn(getStandardInstance(getStandardMetadata(), true));

BasicHttpEntity responseEntity = new BasicHttpEntity(IOUtils.toInputStream(responseBody, StandardCharsets.UTF_8), APPLICATION_JSON);
when(response.getEntity()).thenReturn(responseEntity);

ApiDocInfo result = apiDocRetrievalService.retrieveApiDoc(ZOSMF_ID, SERVICE_VERSION_V);
assertThat(result.getApiDocContent(), is(notNullValue()));
}
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull Stri
@Override
public Object postProcessBeforeInitialization(final Object bean, @Nonnull String name) {
ReflectionUtils.doWithFields(bean.getClass(), field -> {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(InjectApimlLogger.class) != null) {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
Class<?> clazz = getClass(bean);
ApimlLogger log = ApimlLogger.of(clazz, YamlMessageServiceInstance.getInstance());
field.set(bean, log);
Expand Down

0 comments on commit 9c45dc9

Please sign in to comment.