summaryrefslogtreecommitdiffstats
path: root/catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java')
-rw-r--r--catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java437
1 files changed, 437 insertions, 0 deletions
diff --git a/catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java b/catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java
new file mode 100644
index 0000000000..61d7597e37
--- /dev/null
+++ b/catalog-fe/src/main/java/org/openecomp/sdc/fe/impl/HealthCheckScheduledTask.java
@@ -0,0 +1,437 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.fe.impl;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpStatus;
+import org.openecomp.sdc.common.api.Constants;
+import org.openecomp.sdc.common.api.HealthCheckInfo;
+import org.openecomp.sdc.common.api.HealthCheckWrapper;
+import org.openecomp.sdc.common.config.EcompErrorEnum;
+import org.openecomp.sdc.common.http.client.api.HttpRequest;
+import org.openecomp.sdc.common.http.client.api.HttpResponse;
+import org.openecomp.sdc.common.http.config.HttpClientConfig;
+import org.openecomp.sdc.common.http.config.Timeouts;
+import org.openecomp.sdc.common.impl.ExternalConfiguration;
+import org.openecomp.sdc.common.log.elements.ErrorLogOptionalData;
+import org.openecomp.sdc.common.log.elements.LogFieldsMdcHandler;
+import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
+import org.openecomp.sdc.common.log.wrappers.Logger;
+import org.openecomp.sdc.common.util.HealthCheckUtil;
+import org.openecomp.sdc.fe.config.Configuration;
+import org.openecomp.sdc.fe.config.FeEcompErrorManager;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.openecomp.sdc.common.api.Constants.HC_COMPONENT_CATALOG_FACADE_MS;
+import static org.openecomp.sdc.common.api.Constants.HC_COMPONENT_DCAE;
+import static org.openecomp.sdc.common.api.Constants.HC_COMPONENT_ON_BOARDING;
+
+public class HealthCheckScheduledTask implements Runnable {
+ private static final Logger healthLogger = Logger.getLogger("asdc.fe.healthcheck");
+ private static final Logger log = Logger.getLogger(HealthCheckScheduledTask.class.getName());
+ private static final String LOG_PARTNER_NAME = "SDC.FE";
+ private static final String LOG_TARGET_ENTITY_BE = "SDC.BE";
+ private static final String LOG_TARGET_ENTITY_CONFIG = "SDC.FE.Configuration";
+ private static final String LOG_TARGET_SERVICE_NAME_OB = "getOnboardingConfig";
+ private static final String LOG_TARGET_SERVICE_NAME_DCAE = "getDCAEConfig";
+ private static final String LOG_TARGET_SERVICE_NAME_FACADE = "getCatalogFacadeConfig";
+ private static final String LOG_SERVICE_NAME = "/rest/healthCheck";
+ private static LogFieldsMdcHandler mdcFieldsHandler = new LogFieldsMdcHandler();
+
+ private static final String URL = "%s://%s:%s/sdc2/rest/healthCheck";
+
+ private final List<String> healthCheckFeComponents =
+ Arrays.asList(HC_COMPONENT_ON_BOARDING, HC_COMPONENT_DCAE, HC_COMPONENT_CATALOG_FACADE_MS);
+ private static final HealthCheckUtil healthCheckUtil = new HealthCheckUtil();
+ private static final String DEBUG_CONTEXT = "HEALTH_FE";
+ private static final String EXTERNAL_HC_URL = "%s://%s:%s%s";
+ private static String ONBOARDING_HC_URL;
+ private static String DCAE_HC_URL;
+ private static String CATALOG_FACADE_MS_HC_URL;
+
+ private final HealthCheckService service;
+
+ HealthCheckScheduledTask(HealthCheckService service) {
+ this.service = service;
+ }
+
+ static String getOnboardingHcUrl() {
+ return ONBOARDING_HC_URL;
+ }
+
+ static String getDcaeHcUrl() {
+ return DCAE_HC_URL;
+ }
+
+ static String getCatalogFacadeMsHcUrl() {
+ return CATALOG_FACADE_MS_HC_URL;
+ }
+
+
+ @Override
+ public void run() {
+ mdcFieldsHandler.addInfoForErrorAndDebugLogging(LOG_PARTNER_NAME);
+ healthLogger.trace("Executing FE Health Check Task - Start");
+ HealthCheckService.HealthStatus currentHealth = checkHealth();
+ int currentHealthStatus = currentHealth.getStatusCode();
+ healthLogger.trace("Executing FE Health Check Task - Status = {}", currentHealthStatus);
+
+ // In case health status was changed, issue alarm/recovery
+ if (currentHealthStatus != service.getLastHealthStatus().getStatusCode()) {
+ log.trace("FE Health State Changed to {}. Issuing alarm / recovery alarm...", currentHealthStatus);
+ logFeAlarm(currentHealthStatus);
+ }
+ // Anyway, update latest response
+ service.setLastHealthStatus(currentHealth);
+ }
+
+ private List<HealthCheckInfo> addHostedComponentsFeHealthCheck(String baseComponent, boolean requestedByBE) {
+ String healthCheckUrl = getExternalComponentHcUrl(baseComponent);
+ String serviceName = getExternalComponentHcUri(baseComponent);
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(baseComponent)
+ .targetServiceName(serviceName).build();
+
+ StringBuilder description = new StringBuilder("");
+ int connectTimeoutMs = 3000;
+ int readTimeoutMs = service.getConfig().getHealthCheckSocketTimeoutInMs(5000);
+
+ if (healthCheckUrl != null) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ HttpResponse<String> response = HttpRequest.get(healthCheckUrl, new HttpClientConfig(new Timeouts(connectTimeoutMs, readTimeoutMs)));
+ int beStatus = response.getStatusCode();
+ if (beStatus == HttpStatus.SC_OK || beStatus == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+ String beJsonResponse = response.getResponse();
+ return convertResponse(beJsonResponse, mapper, baseComponent, description, beStatus);
+ } else {
+ description.append("Response code: " + beStatus);
+ log.trace("{} Health Check Response code: {}", baseComponent, beStatus);
+ }
+ } catch (Exception e) {
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, serviceName, errorLogOptionalData, baseComponent + " unexpected response ", e);
+ description.append(baseComponent + " Unexpected response: " + e.getMessage());
+ }
+ } else {
+ description.append(baseComponent + " health check Configuration is missing");
+ }
+
+ String compName = requestedByBE ? Constants.HC_COMPONENT_FE : baseComponent;
+ return Collections.singletonList(new HealthCheckInfo(
+ compName,
+ HealthCheckInfo.HealthCheckStatus.DOWN,
+ null,
+ description.toString()));
+ }
+
+ private String getExternalComponentHcUri(String baseComponent) {
+ String healthCheckUri = null;
+ switch (baseComponent) {
+ case HC_COMPONENT_ON_BOARDING:
+ healthCheckUri = service.getConfig().getOnboarding().getHealthCheckUriFe();
+ break;
+ case HC_COMPONENT_DCAE:
+ healthCheckUri = service.getConfig().getDcae().getHealthCheckUri();
+ break;
+ case HC_COMPONENT_CATALOG_FACADE_MS:
+ healthCheckUri = service.getConfig().getCatalogFacadeMs().getHealthCheckUri();
+ break;
+ default:
+ log.debug("Unsupported base component {}", baseComponent);
+ break;
+ }
+ return healthCheckUri;
+ }
+
+
+ @VisibleForTesting
+ String getExternalComponentHcUrl(String baseComponent) {
+ String healthCheckUrl = null;
+ switch (baseComponent) {
+ case HC_COMPONENT_ON_BOARDING:
+ healthCheckUrl = getOnboardingHealthCheckUrl();
+ break;
+ case HC_COMPONENT_DCAE:
+ healthCheckUrl = getDcaeHealthCheckUrl();
+ break;
+ case HC_COMPONENT_CATALOG_FACADE_MS:
+ healthCheckUrl = getCatalogFacadeHealthCheckUrl();
+ break;
+ default:
+ log.debug("Unsupported base component {}", baseComponent);
+ break;
+ }
+ return healthCheckUrl;
+ }
+
+ private void logFeAlarm(int lastFeStatus) {
+ switch (lastFeStatus) {
+ case 200:
+ FeEcompErrorManager.getInstance().processEcompError(DEBUG_CONTEXT, EcompErrorEnum.FeHealthCheckRecovery, "FE Health Recovered");
+ FeEcompErrorManager.getInstance().logFeHealthCheckRecovery("FE Health Recovered");
+ break;
+ case 500:
+ FeEcompErrorManager.getInstance().processEcompError(DEBUG_CONTEXT, EcompErrorEnum.FeHealthCheckError, "Connection with ASDC-BE is probably down");
+ FeEcompErrorManager.getInstance().logFeHealthCheckError("Connection with ASDC-BE is probably down");
+ break;
+ default:
+ break;
+ }
+ }
+
+ private HealthCheckService.HealthStatus checkHealth() {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ Configuration config = service.getConfig();
+
+ HealthCheckWrapper feAggHealthCheck;
+ boolean aggregateFeStatus = false;
+ String redirectedUrl = String.format(URL, config.getBeProtocol(), config.getBeHost(),
+ Constants.HTTPS.equals(config.getBeProtocol()) ? config.getBeSslPort() : config.getBeHttpPort());
+ int connectTimeoutMs = 3000;
+ int readTimeoutMs = config.getHealthCheckSocketTimeoutInMs(5000);
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(LOG_TARGET_ENTITY_BE)
+ .targetServiceName(LOG_SERVICE_NAME).build();
+
+ try {
+ HttpResponse<String> response = HttpRequest.get(redirectedUrl, new HttpClientConfig(new Timeouts(connectTimeoutMs, readTimeoutMs)));
+ log.debug("HC call to BE - status code is {}", response.getStatusCode());
+ String beJsonResponse = response.getResponse();
+ feAggHealthCheck = getFeHealthCheckInfos(gson, beJsonResponse);
+ if (response.getStatusCode() != HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+ aggregateFeStatus = healthCheckUtil.getAggregateStatus(feAggHealthCheck.getComponentsInfo(), getExcludedComponentList());
+ }
+ //Getting aggregate FE status
+ return new HealthCheckService.HealthStatus(aggregateFeStatus ? HttpStatus.SC_OK : HttpStatus.SC_INTERNAL_SERVER_ERROR, gson.toJson(feAggHealthCheck));
+
+ }
+ catch (Exception e) {
+ log.debug("Health Check error when trying to connect to BE or external FE. Error: {}", e);
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, LOG_SERVICE_NAME, errorLogOptionalData,
+ "Health Check error when trying to connect to BE or external FE.", e.getMessage());
+ FeEcompErrorManager.getInstance().processEcompError(DEBUG_CONTEXT,EcompErrorEnum.FeHealthCheckGeneralError, "Unexpected FE Health check error");
+ FeEcompErrorManager.getInstance().logFeHealthCheckGeneralError("Unexpected FE Health check error");
+ return new HealthCheckService.HealthStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR, gson.toJson(getBeDownCheckInfos()));
+ }
+ }
+
+ @VisibleForTesting
+ List<String> getExcludedComponentList() {
+ List <String> excludedComponentList = Lists.newArrayList(service.getConfig().getHealthStatusExclude());
+ if (isCatalogFacadeMsExcluded()) {
+ if (log.isInfoEnabled()) {
+ log.info(HC_COMPONENT_CATALOG_FACADE_MS + " has been added to the Healthcheck exclude list");
+ }
+ excludedComponentList.add(HC_COMPONENT_CATALOG_FACADE_MS);
+ }
+ return excludedComponentList;
+ }
+
+ private boolean isCatalogFacadeMsExcluded() {
+ //CATALOG_FACADE_MS is excluded if it is not configured
+ return service.getConfig().getCatalogFacadeMs() == null || StringUtils.isEmpty(service.getConfig().getCatalogFacadeMs().getPath());
+ }
+
+ private HealthCheckWrapper getFeHealthCheckInfos(Gson gson, String responseString) {
+ Type wrapperType = new TypeToken<HealthCheckWrapper>() {
+ }.getType();
+ HealthCheckWrapper healthCheckWrapper = gson.fromJson(responseString, wrapperType);
+ String description = "OK";
+ healthCheckWrapper.getComponentsInfo()
+ .add(new HealthCheckInfo(Constants.HC_COMPONENT_FE, HealthCheckInfo.HealthCheckStatus.UP, ExternalConfiguration.getAppVersion(), description));
+
+ //add FE hosted components
+ for (String component : healthCheckFeComponents) {
+ buildHealthCheckListForComponent(component, healthCheckWrapper);
+ }
+ return healthCheckWrapper;
+ }
+
+ private void buildHealthCheckListForComponent(String component, HealthCheckWrapper healthCheckWrapper) {
+
+ HealthCheckInfo componentHCInfoFromBE = getComponentHcFromList(component, healthCheckWrapper.getComponentsInfo());
+ List<HealthCheckInfo> componentHCInfoList = addHostedComponentsFeHealthCheck(component, componentHCInfoFromBE != null);
+ HealthCheckInfo calculateStatusFor;
+ if (componentHCInfoFromBE != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("{} component healthcheck info has been received from the BE and from the component itself", component);
+ }
+ //update the subcomponents's HC if exist and recalculate the component status according to the subcomponets HC
+ calculateStatusFor = updateSubComponentsInfoOfBeHc(componentHCInfoFromBE, componentHCInfoList);
+ }
+ else {
+
+ //this component is not in the BE HC response, need to add it and calculate the aggregated status
+ if (log.isDebugEnabled()) {
+ log.debug("{} component healthcheck info has been received from the component itself, it is not monitored by the BE", component);
+ }
+ //we assume that response from components which HC is not requested by BE have only one entry in the responded list
+ calculateStatusFor = componentHCInfoList.get(0);
+ healthCheckWrapper.getComponentsInfo()
+ .add(calculateStatusFor);
+
+ }
+ calculateAggregatedStatus(calculateStatusFor);
+
+ }
+
+ @VisibleForTesting
+ HealthCheckInfo updateSubComponentsInfoOfBeHc(HealthCheckInfo componentHCInfoFromBE, List<HealthCheckInfo> componentHcReceivedByFE) {
+ if (!CollectionUtils.isEmpty(componentHcReceivedByFE)) {
+ //this component HC is received from BE, just need to calculate the status for that
+ if (componentHCInfoFromBE.getComponentsInfo() == null) {
+ componentHCInfoFromBE.setComponentsInfo(new ArrayList<>());
+ }
+ componentHCInfoFromBE.getComponentsInfo().addAll(componentHcReceivedByFE);
+ }
+ return componentHCInfoFromBE;
+ }
+
+ private HealthCheckInfo getComponentHcFromList(String component, List<HealthCheckInfo> hcList) {
+ return hcList.stream().filter(c -> c.getHealthCheckComponent().equals(component)).findFirst().orElse(null);
+ }
+
+ private void calculateAggregatedStatus(HealthCheckInfo baseComponentHCInfo) {
+ if (!CollectionUtils.isEmpty(baseComponentHCInfo.getComponentsInfo())) {
+ boolean status = healthCheckUtil.getAggregateStatus(baseComponentHCInfo.getComponentsInfo(), getExcludedComponentList());
+ baseComponentHCInfo.setHealthCheckStatus(status ?
+ HealthCheckInfo.HealthCheckStatus.UP : HealthCheckInfo.HealthCheckStatus.DOWN);
+
+ String componentsDesc = healthCheckUtil.getAggregateDescription(baseComponentHCInfo.getComponentsInfo());
+ if (!StringUtils.isEmpty(componentsDesc)) { //aggregated description contains all the internal components desc
+ baseComponentHCInfo.setDescription(componentsDesc);
+ }
+ }
+ }
+
+ private HealthCheckWrapper getBeDownCheckInfos() {
+ List<HealthCheckInfo> healthCheckInfos = new ArrayList<>();
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_FE, HealthCheckInfo.HealthCheckStatus.UP,
+ ExternalConfiguration.getAppVersion(), "OK"));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_BE, HealthCheckInfo.HealthCheckStatus.DOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_JANUSGRAPH, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_CASSANDRA, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_DISTRIBUTION_ENGINE, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_ON_BOARDING, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(Constants.HC_COMPONENT_DCAE, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ healthCheckInfos.add(new HealthCheckInfo(HC_COMPONENT_CATALOG_FACADE_MS, HealthCheckInfo.HealthCheckStatus.UNKNOWN, null, null));
+ return new HealthCheckWrapper(healthCheckInfos, "UNKNOWN", "UNKNOWN");
+ }
+
+ String buildHealthCheckUrl(String protocol, String host, Integer port, String uri) {
+ return String.format(EXTERNAL_HC_URL, protocol, host, port, uri);
+ }
+
+ private String getOnboardingHealthCheckUrl() {
+ Configuration.OnboardingConfig onboardingConfig = service.getConfig().getOnboarding();
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(LOG_TARGET_ENTITY_CONFIG)
+ .targetServiceName(LOG_TARGET_SERVICE_NAME_OB).build();
+
+ if (StringUtils.isEmpty(ONBOARDING_HC_URL)) {
+ if (onboardingConfig != null) {
+ ONBOARDING_HC_URL = buildHealthCheckUrl(
+ onboardingConfig.getProtocolFe(), onboardingConfig.getHostFe(),
+ onboardingConfig.getPortFe(), onboardingConfig.getHealthCheckUriFe());
+ }
+ else {
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, LOG_SERVICE_NAME, errorLogOptionalData,
+ "Onboarding health check configuration is missing.");
+ }
+ }
+ return ONBOARDING_HC_URL;
+ }
+
+ private String getDcaeHealthCheckUrl() {
+ Configuration.DcaeConfig dcaeConfig = service.getConfig().getDcae();
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(LOG_TARGET_ENTITY_CONFIG)
+ .targetServiceName(LOG_TARGET_SERVICE_NAME_DCAE).build();
+
+ if (StringUtils.isEmpty(DCAE_HC_URL)) {
+ if (dcaeConfig != null) {
+ DCAE_HC_URL = buildHealthCheckUrl(
+ dcaeConfig.getProtocol(), dcaeConfig.getHost(),
+ dcaeConfig.getPort(), dcaeConfig.getHealthCheckUri());
+ }
+ else {
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, LOG_SERVICE_NAME, errorLogOptionalData,
+ "DCAE health check configuration is missing.");
+ }
+ }
+ return DCAE_HC_URL;
+ }
+
+ private String getCatalogFacadeHealthCheckUrl() {
+ Configuration.CatalogFacadeMsConfig catalogFacadeMsConfig = service.getConfig().getCatalogFacadeMs();
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(LOG_TARGET_ENTITY_CONFIG)
+ .targetServiceName(LOG_TARGET_SERVICE_NAME_FACADE).build();
+
+ if (StringUtils.isEmpty(CATALOG_FACADE_MS_HC_URL)) {
+ if (catalogFacadeMsConfig != null) {
+ CATALOG_FACADE_MS_HC_URL = buildHealthCheckUrl(
+ catalogFacadeMsConfig.getProtocol(), catalogFacadeMsConfig.getHost(),
+ catalogFacadeMsConfig.getPort(), catalogFacadeMsConfig.getHealthCheckUri());
+ }
+ else {
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, LOG_SERVICE_NAME, errorLogOptionalData,
+ "Catalog Facade MS health check configuration is missing.");
+ }
+ }
+ return CATALOG_FACADE_MS_HC_URL;
+ }
+
+
+ private List<HealthCheckInfo> convertResponse(String beJsonResponse, ObjectMapper mapper, String baseComponent, StringBuilder description, int beStatus) {
+ ErrorLogOptionalData errorLogOptionalData = ErrorLogOptionalData.newBuilder().targetEntity(baseComponent)
+ .targetServiceName(LOG_SERVICE_NAME).build();
+
+ try {
+ Map<String, Object> healthCheckMap = mapper.readValue(beJsonResponse, new TypeReference<Map<String, Object>>() {
+ });
+ if (healthCheckMap.containsKey("componentsInfo")) {
+ return mapper.convertValue(healthCheckMap.get("componentsInfo"), new TypeReference<List<HealthCheckInfo>>() {
+ });
+ } else {
+ description.append("Internal components are missing");
+ }
+ } catch (JsonSyntaxException | IOException e) {
+ log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, LOG_SERVICE_NAME, errorLogOptionalData,
+ baseComponent + " Unexpected response body ", e);
+ description.append(baseComponent)
+ .append("Unexpected response body. Response code: ")
+ .append(beStatus);
+ }
+ return new ArrayList<>();
+ }
+} \ No newline at end of file