From 9a5b5a0edf34e8311e6a67e6d9056e41180920e0 Mon Sep 17 00:00:00 2001 From: Grzegorz Wielgosinski Date: Wed, 7 Jul 2021 13:29:34 +0200 Subject: Expose CNF health check api Issue-ID: SO-3690 Signed-off-by: Grzegorz Wielgosinski Change-Id: If4f60efb27bd057f562e8f8c01dd01c9e769b393 --- .../cnf/model/healthcheck/HealthCheckInstance.java | 27 ++++ .../cnf/model/healthcheck/HealthCheckResponse.java | 15 ++- .../cnf/model/statuscheck/StatusCheckResponse.java | 13 +- .../onap/so/adapters/cnf/rest/CnfAdapterRest.java | 51 +++++-- .../so/adapters/cnf/service/CnfAdapterService.java | 33 ++--- .../service/healthcheck/HealthCheckService.java | 147 +++++++++++++++++++++ .../so/adapters/cnf/rest/CnfAdapterRestTest.java | 18 +-- .../cnf/service/CnfAdapterServiceTest.java | 11 +- 8 files changed, 257 insertions(+), 58 deletions(-) create mode 100644 so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckInstance.java create mode 100644 so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/healthcheck/HealthCheckService.java diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckInstance.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckInstance.java new file mode 100644 index 0000000..df87768 --- /dev/null +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckInstance.java @@ -0,0 +1,27 @@ +package org.onap.so.adapters.cnf.model.healthcheck; + +public class HealthCheckInstance { + private final String instanceId; + private final String healthCheckInstance; + + public HealthCheckInstance(String instanceId, String healthCheckInstance) { + this.instanceId = instanceId; + this.healthCheckInstance = healthCheckInstance; + } + + public String getInstanceId() { + return instanceId; + } + + public String getHealthCheckInstance() { + return healthCheckInstance; + } + + @Override + public String toString() { + return "HealthCheckInstance{" + + "instanceId='" + instanceId + '\'' + + ", healthCheckInstance='" + healthCheckInstance + '\'' + + '}'; + } +} diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckResponse.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckResponse.java index aabd490..2d0bb88 100644 --- a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckResponse.java +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/healthcheck/HealthCheckResponse.java @@ -13,6 +13,9 @@ public class HealthCheckResponse { @JsonProperty("result") private List instanceResponse; + @JsonProperty("error") + private String errorMessage; + public List getInstanceResponse() { return instanceResponse; } @@ -21,11 +24,19 @@ public class HealthCheckResponse { this.instanceResponse = instanceResponse; } + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + @Override public String toString() { - return "StatusCheckResponse{" + + return "HealthCheckResponse{" + "instanceResponse=" + instanceResponse + + ", errorMessage='" + errorMessage + '\'' + '}'; } - } diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/statuscheck/StatusCheckResponse.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/statuscheck/StatusCheckResponse.java index 36c3068..86f4812 100644 --- a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/statuscheck/StatusCheckResponse.java +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/model/statuscheck/StatusCheckResponse.java @@ -13,6 +13,9 @@ public class StatusCheckResponse { @JsonProperty("result") private List instanceResponse; + @JsonProperty("errorMessage") + private String errorMessage; + public List getInstanceResponse() { return instanceResponse; } @@ -21,11 +24,19 @@ public class StatusCheckResponse { this.instanceResponse = instanceResponse; } + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + @Override public String toString() { return "StatusCheckResponse{" + "instanceResponse=" + instanceResponse + + ", errorMessage='" + errorMessage + '\'' + '}'; } - } diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/rest/CnfAdapterRest.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/rest/CnfAdapterRest.java index 2036f46..aab138e 100644 --- a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/rest/CnfAdapterRest.java +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/rest/CnfAdapterRest.java @@ -20,8 +20,10 @@ package org.onap.so.adapters.cnf.rest; -import java.io.File; -import java.io.IOException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; @@ -37,7 +39,9 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.onap.so.adapters.cnf.MulticloudConfiguration; +import org.onap.so.adapters.cnf.client.SoCallbackClient; import org.onap.so.adapters.cnf.model.BpmnInstanceRequest; +import org.onap.so.adapters.cnf.model.CheckInstanceRequest; import org.onap.so.adapters.cnf.model.ConfigTemplateEntity; import org.onap.so.adapters.cnf.model.ConfigurationEntity; import org.onap.so.adapters.cnf.model.ConfigurationRollbackEntity; @@ -45,10 +49,13 @@ import org.onap.so.adapters.cnf.model.ConnectivityInfo; import org.onap.so.adapters.cnf.model.ProfileEntity; import org.onap.so.adapters.cnf.model.ResourceBundleEntity; import org.onap.so.adapters.cnf.model.Tag; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckResponse; +import org.onap.so.adapters.cnf.model.statuscheck.StatusCheckResponse; import org.onap.so.adapters.cnf.service.CnfAdapterService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -56,11 +63,12 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.multipart.MultipartFile; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ForkJoinPool; @RestController public class CnfAdapterRest { @@ -68,22 +76,41 @@ public class CnfAdapterRest { private static final Logger logger = LoggerFactory.getLogger(CnfAdapterRest.class); private final CloseableHttpClient httpClient = HttpClients.createDefault(); private final CnfAdapterService cnfAdapterService; + private final SoCallbackClient callbackClient; private final String uri; @Autowired - public CnfAdapterRest(CnfAdapterService cnfAdapterService, MulticloudConfiguration multicloudConfiguration) { + public CnfAdapterRest(CnfAdapterService cnfAdapterService, + SoCallbackClient callbackClient, + MulticloudConfiguration multicloudConfiguration) { this.cnfAdapterService = cnfAdapterService; + this.callbackClient = callbackClient; this.uri = multicloudConfiguration.getMulticloudUrl(); } @ResponseBody - @RequestMapping(value = {"/api/cnf-adapter/v1/healthcheck"}, method = RequestMethod.GET, + @RequestMapping(value = {"/api/cnf-adapter/v1/healthcheck"}, method = RequestMethod.POST, produces = "application/json") - public String healthCheck() throws Exception { - + public DeferredResult healthCheck(@RequestBody CheckInstanceRequest healthCheckRequest) { logger.info("healthCheck called."); - return cnfAdapterService.healthCheck(); - + DeferredResult response = new DeferredResult<>(); + + ForkJoinPool.commonPool().submit(() -> { + logger.info("Processing healthCheck service"); + HealthCheckResponse healthCheckResponse = null; + try { + healthCheckResponse = cnfAdapterService.healthCheck(healthCheckRequest); + } catch (Exception e) { + HealthCheckResponse errorHealthCheck = new HealthCheckResponse(); + errorHealthCheck.setErrorMessage(e.getMessage()); + callbackClient.sendPostCallback(healthCheckRequest.getCallbackUrl(), errorHealthCheck); + return; + } + callbackClient.sendPostCallback(healthCheckRequest.getCallbackUrl(), healthCheckResponse); + }); + + response.setResult(ResponseEntity.accepted().build()); + return response; } @ResponseBody diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/CnfAdapterService.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/CnfAdapterService.java index faef1d0..2008280 100644 --- a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/CnfAdapterService.java +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/CnfAdapterService.java @@ -28,7 +28,10 @@ import javax.ws.rs.core.UriBuilder; import org.apache.http.HttpStatus; import org.onap.so.adapters.cnf.MulticloudConfiguration; import org.onap.so.adapters.cnf.model.BpmnInstanceRequest; +import org.onap.so.adapters.cnf.model.CheckInstanceRequest; import org.onap.so.adapters.cnf.model.MulticloudInstanceRequest; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckResponse; +import org.onap.so.adapters.cnf.service.healthcheck.HealthCheckService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,43 +51,23 @@ import com.fasterxml.jackson.databind.JsonMappingException; public class CnfAdapterService { private static final Logger logger = LoggerFactory.getLogger(CnfAdapterService.class); private static final String INSTANCE_CREATE_PATH = "/v1/instance"; - private static final String HEALTH_CHECK = "/v1/healthcheck"; private final RestTemplate restTemplate; + private HealthCheckService healthCheckService; private final String uri; @Autowired public CnfAdapterService(RestTemplate restTemplate, + HealthCheckService healthCheckService, MulticloudConfiguration multicloudConfiguration) { this.restTemplate = restTemplate; + this.healthCheckService = healthCheckService; this.uri = multicloudConfiguration.getMulticloudUrl(); } - public String healthCheck() { - + public HealthCheckResponse healthCheck(CheckInstanceRequest healthCheckRequest) throws Exception { logger.info("CnfAdapterService healthCheck called"); - ResponseEntity result = null; - try { - - // String uri = env.getRequiredProperty("multicloud.endpoint"); //TODO: - // This needs to be added as well - // for configuration - String endpoint = UriBuilder.fromUri(uri).path(HEALTH_CHECK).build().toString(); - HttpEntity requestEntity = new HttpEntity<>(getHttpHeaders()); - logger.info("request: " + requestEntity); - result = restTemplate.exchange(endpoint, HttpMethod.GET, requestEntity, String.class); - logger.info("response: " + result); - return result.getBody(); - } catch (HttpClientErrorException e) { - logger.error("Error Calling Multicloud, e"); - if (HttpStatus.SC_NOT_FOUND == e.getStatusCode().value()) { - throw new EntityNotFoundException(e.getResponseBodyAsString()); - } - throw e; - } catch (HttpStatusCodeException e) { - logger.error("Error in Multicloud", e); - throw e; - } + return healthCheckService.healthCheck(healthCheckRequest); } public String createInstance(BpmnInstanceRequest bpmnInstanceRequest) diff --git a/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/healthcheck/HealthCheckService.java b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/healthcheck/HealthCheckService.java new file mode 100644 index 0000000..2f91be8 --- /dev/null +++ b/so-cnf-adapter-application/src/main/java/org/onap/so/adapters/cnf/service/healthcheck/HealthCheckService.java @@ -0,0 +1,147 @@ +package org.onap.so.adapters.cnf.service.healthcheck; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.onap.so.adapters.cnf.client.MulticloudClient; +import org.onap.so.adapters.cnf.model.CheckInstanceRequest; +import org.onap.so.adapters.cnf.model.InstanceRequest; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckInstance; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckInstanceResponse; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckResponse; +import org.onap.so.adapters.cnf.model.healthcheck.K8sRbInstanceHealthCheck; +import org.onap.so.adapters.cnf.model.healthcheck.K8sRbInstanceHealthCheckSimple; +import org.onap.so.adapters.cnf.service.CnfAdapterService; +import org.onap.so.client.exception.BadResponseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.stream.Collectors; + +import static java.lang.Thread.sleep; +import static java.util.concurrent.Executors.newFixedThreadPool; + +@Service +public class HealthCheckService { + + private static final Logger log = LoggerFactory.getLogger(CnfAdapterService.class); + + private final MulticloudClient instanceApi; + + @Autowired + public HealthCheckService(MulticloudClient multicloudClient) { + this.instanceApi = multicloudClient; + } + + public HealthCheckResponse healthCheck(CheckInstanceRequest healthCheckRequest) throws Exception { + log.info("Health check - START"); + List instanceHealthCheckList = startInstanceHealthCheck(healthCheckRequest); + HealthCheckResponse statuses = getStatuses(instanceHealthCheckList); + log.info("Health check - END"); + + return statuses; + } + + private List startInstanceHealthCheck(CheckInstanceRequest healthCheckRequest) throws Exception { + log.debug("startInstanceHealthCheck - START"); + List healthCheckInstanceList = new ArrayList<>(); + + for (InstanceRequest instance : healthCheckRequest.getInstances()) { + String instanceId = instance.getInstanceId(); + K8sRbInstanceHealthCheckSimple response = instanceApi.startInstanceHealthCheck(instanceId); + log.info("K8sRbInstanceHealthCheckSimple: {}", response); + healthCheckInstanceList.add(new HealthCheckInstance(instanceId, response.getId())); + } + + log.info("healthCheckInstanceList: {}", healthCheckInstanceList); + log.debug("startInstanceHealthCheck - END"); + return healthCheckInstanceList; + } + + private HealthCheckResponse getStatuses(List instanceHealthCheckList) throws Exception { + log.debug("getStatuses - START"); + List threads = instanceHealthCheckList.stream() + .map(HealthCheckThread::new) + .collect(Collectors.toList()); + + int processors = Runtime.getRuntime().availableProcessors(); + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("Health-check-thread-%d").build(); + ExecutorService executorService = newFixedThreadPool(processors, threadFactory); + HealthCheckResponse response = new HealthCheckResponse(); + List healthCheckInstance = null; + healthCheckInstance = executorService.invokeAll(threads).stream() + .map(future -> { + try { + InstanceStatusTuple instanceStatusTuple = future.get(); + String instanceId = instanceStatusTuple.getInstanceId(); + String status = instanceStatusTuple.getStatus(); + String reason = null; + return new HealthCheckInstanceResponse(instanceId, reason, status); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + response.setInstanceResponse(healthCheckInstance); + log.info("Get statuses response: \n {}", response); + log.debug("getStatuses - END"); + return response; + } + + private class HealthCheckThread implements Callable { + + private final HealthCheckInstance healthCheckInstance; + + HealthCheckThread(HealthCheckInstance healthCheckInstance) { + this.healthCheckInstance = healthCheckInstance; + } + + /** + * Approx 5 minutes thread + * If timeout method returns tuple with HeatStackId => Timeout + * @return InstanceStatusTuple + * @throws InterruptedException + * @throws BadResponseException + */ + @Override + public InstanceStatusTuple call() throws InterruptedException, BadResponseException { + log.info("{} started for: {}", Thread.currentThread().getName(), healthCheckInstance); + for (int retry = 0; retry < 30; retry++) { + K8sRbInstanceHealthCheck response = instanceApi.getInstanceHealthCheck(healthCheckInstance.getInstanceId(), healthCheckInstance.getHealthCheckInstance()); + log.debug("Response for instanceId={}: {}", healthCheckInstance, response); + String status = response.getStatus(); + if (!"RUNNING".equals(status.toUpperCase())) { + log.info("Poll status: {} for {}", status, healthCheckInstance); + instanceApi.deleteInstanceHealthCheck(healthCheckInstance.getInstanceId(), healthCheckInstance.getHealthCheckInstance()); + return new InstanceStatusTuple(healthCheckInstance.getInstanceId(), status); + } + sleep(10_000L); + } + return new InstanceStatusTuple(healthCheckInstance.getInstanceId(), "Timeout"); + } + } + + private class InstanceStatusTuple { + private final String instanceId; + private final String status; + + InstanceStatusTuple(String instanceId, String status) { + this.instanceId = instanceId; + this.status = status; + } + + String getInstanceId() { + return instanceId; + } + + String getStatus() { + return status; + } + } + +} diff --git a/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/rest/CnfAdapterRestTest.java b/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/rest/CnfAdapterRestTest.java index 979f13b..c85031a 100644 --- a/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/rest/CnfAdapterRestTest.java +++ b/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/rest/CnfAdapterRestTest.java @@ -28,10 +28,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.onap.so.adapters.cnf.MulticloudConfiguration; import org.onap.so.adapters.cnf.model.*; +import org.onap.so.adapters.cnf.model.healthcheck.HealthCheckResponse; import org.onap.so.adapters.cnf.service.CnfAdapterService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; import java.util.HashMap; @@ -61,13 +63,16 @@ public class CnfAdapterRestTest { @Test public void healthCheckTest() throws Exception { - - ResponseEntity response = new ResponseEntity(HttpStatus.OK); + HealthCheckResponse response = new HealthCheckResponse(); + DeferredResult deferredResponse = new DeferredResult<>(); + deferredResponse.setResult(response); CnfAdapterService cnfAdapterService = Mockito.mock(CnfAdapterService.class); - Mockito.when(cnfAdapterService.healthCheck()).thenReturn(String.valueOf(response)); - cnfAdapterRest.healthCheck(); + CheckInstanceRequest healthCheckRequest = Mockito.mock(CheckInstanceRequest.class); + Mockito.when(cnfAdapterService.healthCheck(healthCheckRequest)).thenReturn(response); + + cnfAdapterRest.healthCheck(healthCheckRequest); + Assert.assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatusCode()); } @Test @@ -573,6 +578,3 @@ public class CnfAdapterRestTest { } } } - - - diff --git a/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/service/CnfAdapterServiceTest.java b/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/service/CnfAdapterServiceTest.java index bdad347..8e8cd5c 100644 --- a/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/service/CnfAdapterServiceTest.java +++ b/so-cnf-adapter-application/src/test/java/org/onap/so/adapters/cnf/service/CnfAdapterServiceTest.java @@ -48,16 +48,7 @@ public class CnfAdapterServiceTest { @Mock ResponseEntity instanceResponse; - @Test - public void healthCheckTest() throws Exception { - try { - cnfAdapterService.healthCheck(); - } - catch (Exception exp) { - assert(true); - } - - } + @Test public void createInstanceTest() throws Exception { Map labels = new HashMap(); -- cgit 1.2.3-korg