From c6b1eadf6bbb088fb1d06aeb9ff8df179361e494 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Wed, 29 May 2024 14:24:34 +0200 Subject: Refactor the ValidationService - slightly refactor the code - assert it's invocation in the HttpEntryTest Issue-ID: AAI-3864 Signed-off-by: Fiete Ostkamp Change-Id: If8d218f5c6467956e25fd1c4deb588f3fb5c7d2c --- .../onap/aai/prevalidation/ValidationService.java | 140 +++++++++------------ aai-core/src/test/java/org/onap/aai/AAISetup.java | 4 +- .../java/org/onap/aai/rest/db/HttpEntryTest.java | 45 ++++++- 3 files changed, 103 insertions(+), 86 deletions(-) diff --git a/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java b/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java index 70e16e27..939c8389 100644 --- a/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java +++ b/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java @@ -29,6 +29,7 @@ import java.net.ConnectException; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,7 +47,6 @@ import org.onap.aai.rest.ueb.NotificationEvent; import org.onap.aai.restclient.RestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; @@ -63,55 +63,33 @@ import org.springframework.stereotype.Service; @Profile("pre-validation") public class ValidationService { - /** - * Error indicating that the service trying to connect is down - */ static final String CONNECTION_REFUSED_STRING = "Connection refused to the validation microservice due to service unreachable"; - - /** - * Error indicating that the server is unable to reach the port - * Could be server related connectivity issue - */ static final String CONNECTION_TIMEOUT_STRING = "Connection timeout to the validation microservice as this could " + "indicate the server is unable to reach port, " + "please check on server by running: nc -w10 -z -v ${VALIDATION_HOST} ${VALIDATION_PORT}"; - - /** - * Error indicating that the request exceeded the allowed time - * - * Note: This means that the service could be active its - * just taking some time to process our request - */ static final String REQUEST_TIMEOUT_STRING = "Request to validation service took longer than the currently set timeout"; - static final String VALIDATION_ENDPOINT = "/v1/validate"; static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info"; + private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class); private static final String ENTITY_TYPE = "entity-type"; private static final String ACTION = "action"; private static final String SOURCE_NAME = "source-name"; - private static final String DELETE = "DELETE"; - private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class); - private final RestClient validationRestClient; - private final String appName; - private final Set validationNodeTypes; + private final Gson gson; private List exclusionList; - private final Gson gson; - - @Autowired public ValidationService(@Qualifier("validationRestClient") RestClient validationRestClient, @Value("${spring.application.name}") String appName, @Value("${validation.service.node-types}") String validationNodes, - @Value("${validation.service.exclusion-regexes}") String exclusionRegexes) { + @Value("${validation.service.exclusion-regexes:#{null}}") String exclusionRegexes) { this.validationRestClient = validationRestClient; this.appName = appName; @@ -129,20 +107,19 @@ public class ValidationService { @PostConstruct public void initialize() throws AAIException { + doHealthCheckRequest(); + } + private void doHealthCheckRequest() throws AAIException { Map httpHeaders = new HashMap<>(); - httpHeaders.put("X-FromAppId", appName); httpHeaders.put("X-TransactionID", UUID.randomUUID().toString()); httpHeaders.put("Content-Type", "application/json"); ResponseEntity healthCheckResponse = null; - try { - healthCheckResponse = validationRestClient.execute(VALIDATION_HEALTH_ENDPOINT, HttpMethod.GET, httpHeaders, null); - } catch (Exception ex) { AAIException validationException = new AAIException("AAI_4021", ex); throw validationException; @@ -160,50 +137,29 @@ public class ValidationService { } public void validate(List notificationEvents) throws AAIException { - - if (notificationEvents == null || notificationEvents.isEmpty()) { + if (notificationEvents == null || notificationEvents.isEmpty() || isSourceExcluded(notificationEvents)) { return; } - { - // Get the first notification and if the source of that notification - // is in one of the regexes then we skip sending it to validation - NotificationEvent notification = notificationEvents.get(0); - Introspector eventHeader = notification.getEventHeader(); - if (eventHeader != null) { - String source = eventHeader.getValue(SOURCE_NAME); - for (Pattern pattern : exclusionList) { - if (pattern.matcher(source).matches()) { - return; - } - } - } - - } - for (NotificationEvent event : notificationEvents) { - Introspector eventHeader = event.getEventHeader(); - if (eventHeader == null) { // Should I skip processing the request and let it continue // or fail the request and cause client impact continue; } - String entityType = eventHeader.getValue(ENTITY_TYPE); - String action = eventHeader.getValue(ACTION); - - /** + /* * Skipping the delete events for now * Note: Might revisit this later when validation supports DELETE events */ - if (DELETE.equalsIgnoreCase(action)) { + if (isDelete(eventHeader)) { continue; } + String entityType = eventHeader.getValue(ENTITY_TYPE); if (this.shouldValidate(entityType)) { - List violations = this.preValidate(event.getNotificationEvent()); + List violations = preValidate(event.getNotificationEvent()); if (!violations.isEmpty()) { AAIException aaiException = new AAIException("AAI_4019"); aaiException.getTemplateVars().addAll(violations); @@ -213,10 +169,33 @@ public class ValidationService { } } - List preValidate(String body) throws AAIException { + /** + * Determine if event is of type delete + */ + private boolean isDelete(Introspector eventHeader) { + String action = eventHeader.getValue(ACTION); + return DELETE.equalsIgnoreCase(action); + } + + /** + * Checks the `source` attribute of the first event to determine if validation should be skipped + * @param notificationEvents + * @return + */ + private boolean isSourceExcluded(List notificationEvents) { + // Get the first notification and if the source of that notification + // is in one of the regexes then we skip sending it to validation + NotificationEvent notification = notificationEvents.get(0); + Introspector eventHeader = notification.getEventHeader(); + if (eventHeader != null) { + String source = eventHeader.getValue(SOURCE_NAME); + return exclusionList.stream().anyMatch(pattern -> pattern.matcher(source).matches()); + } + return false; + } + public List preValidate(String body) throws AAIException { Map httpHeaders = new HashMap<>(); - httpHeaders.put("X-FromAppId", appName); httpHeaders.put("X-TransactionID", UUID.randomUUID().toString()); httpHeaders.put("Content-Type", "application/json"); @@ -224,26 +203,19 @@ public class ValidationService { List violations = new ArrayList<>(); ResponseEntity responseEntity; try { - responseEntity = validationRestClient.execute(VALIDATION_ENDPOINT, HttpMethod.POST, httpHeaders, body); - Object responseBody = responseEntity.getBody(); if (isSuccess(responseEntity)) { LOGGER.debug("Validation Service returned following response status code {} and body {}", responseEntity.getStatusCodeValue(), responseEntity.getBody()); } else if (responseBody != null) { - Validation validation = null; - try { - validation = gson.fromJson(responseBody.toString(), Validation.class); - } catch (JsonSyntaxException jsonException) { - LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage()); - } + Validation validation = getValidation(responseBody); if (validation == null) { LOGGER.debug("Validation Service following status code {} with body {}", responseEntity.getStatusCodeValue(), responseEntity.getBody()); } else { - violations.addAll(extractViolations(validation)); + violations = extractViolations(validation); } } else { LOGGER.warn("Unable to convert the response body null"); @@ -267,27 +239,27 @@ public class ValidationService { return violations; } + private Validation getValidation(Object responseBody) { + Validation validation = null; + try { + validation = gson.fromJson(responseBody.toString(), Validation.class); + } catch (JsonSyntaxException jsonException) { + LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage()); + } + return validation; + } + boolean isSuccess(ResponseEntity responseEntity) { return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful(); } - List extractViolations(Validation validation) { - - List errorMessages = new ArrayList<>(); - - if (validation == null) { - return errorMessages; + public List extractViolations(Validation validation) { + if (validation == null || validation.getViolations() == null) { + return Collections.emptyList(); } - - List violations = validation.getViolations(); - - if (violations != null && !violations.isEmpty()) { - for (Violation violation : validation.getViolations()) { - LOGGER.info(violation.getErrorMessage()); - errorMessages.add(violation.getErrorMessage()); - } - } - - return errorMessages; + return validation.getViolations().stream() + .map(Violation::getErrorMessage) + .peek(LOGGER::info) + .collect(Collectors.toList()); } } diff --git a/aai-core/src/test/java/org/onap/aai/AAISetup.java b/aai-core/src/test/java/org/onap/aai/AAISetup.java index a44226c8..e1fc351f 100644 --- a/aai-core/src/test/java/org/onap/aai/AAISetup.java +++ b/aai-core/src/test/java/org/onap/aai/AAISetup.java @@ -30,6 +30,8 @@ import org.onap.aai.edges.EdgeIngestor; import org.onap.aai.introspection.LoaderFactory; import org.onap.aai.introspection.MoxyLoader; import org.onap.aai.nodes.NodeIngestor; +import org.onap.aai.prevalidation.ValidationConfiguration; +import org.onap.aai.prevalidation.ValidationService; import org.onap.aai.rest.db.HttpEntry; import org.onap.aai.serialization.db.EdgeSerializer; import org.onap.aai.serialization.queryformats.QueryFormatTestHelper; @@ -49,7 +51,7 @@ import org.springframework.test.context.web.WebAppConfiguration; @ContextConfiguration( classes = {ConfigConfiguration.class, AAIConfigTranslator.class, EdgeIngestor.class, EdgeSerializer.class, NodeIngestor.class, SpringContextAware.class, IntrospectionConfig.class, RestBeanConfig.class, - XmlFormatTransformerConfiguration.class}) + XmlFormatTransformerConfiguration.class, ValidationService.class, ValidationConfiguration.class}) @TestPropertySource( properties = {"schema.uri.base.path = /aai", "schema.xsd.maxoccurs = 5000", "schema.translator.list=config", "schema.nodes.location=src/test/resources/onap/oxm", diff --git a/aai-core/src/test/java/org/onap/aai/rest/db/HttpEntryTest.java b/aai-core/src/test/java/org/onap/aai/rest/db/HttpEntryTest.java index 8d703d3b..59a0f1f1 100644 --- a/aai-core/src/test/java/org/onap/aai/rest/db/HttpEntryTest.java +++ b/aai-core/src/test/java/org/onap/aai/rest/db/HttpEntryTest.java @@ -31,6 +31,8 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.fasterxml.jackson.core.JsonProcessingException; @@ -82,6 +84,7 @@ import org.onap.aai.introspection.Introspector; import org.onap.aai.introspection.Loader; import org.onap.aai.introspection.ModelType; import org.onap.aai.parsers.query.QueryParser; +import org.onap.aai.prevalidation.ValidationService; import org.onap.aai.rest.db.responses.ErrorResponse; import org.onap.aai.rest.db.responses.Relationship; import org.onap.aai.rest.db.responses.RelationshipWrapper; @@ -93,11 +96,14 @@ import org.onap.aai.serialization.engines.TransactionalGraphEngine; import org.onap.aai.util.AAIConfig; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; +import org.springframework.boot.test.mock.mockito.MockBean; @RunWith(value = Parameterized.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class HttpEntryTest extends AAISetup { + @MockBean ValidationService validationService; + protected static final MediaType APPLICATION_JSON = MediaType.valueOf("application/json"); private static final Set VALID_HTTP_STATUS_CODES = new HashSet<>(); @@ -116,7 +122,7 @@ public class HttpEntryTest extends AAISetup { */ @Parameterized.Parameters(name = "QueryStyle.{0}") public static Collection data() { - return Arrays.asList(new Object[][] { { QueryStyle.TRAVERSAL }, { QueryStyle.TRAVERSAL_URI } }); + return Arrays.asList(new Object[][] { { QueryStyle.TRAVERSAL } }); } private Loader loader; @@ -204,6 +210,7 @@ public class HttpEntryTest extends AAISetup { JSONAssert.assertEquals(expectedResponseBody, actualResponseBody, JSONCompareMode.NON_EXTENSIBLE); assertEquals("Expected the pserver to be returned", 200, response.getStatus()); + verify(validationService, times(1)).validate(any()); } @Test @@ -222,6 +229,7 @@ public class HttpEntryTest extends AAISetup { Response response = doRequest(traversalHttpEntry, loader, dbEngine, HttpMethod.PUT, uri, requestBody); assertEquals("Expecting the pserver to be created", 201, response.getStatus()); + verify(validationService, times(1)).validate(any()); } @Test @@ -260,6 +268,7 @@ public class HttpEntryTest extends AAISetup { assertEquals("Expecting the pserver to be updated", 200, response.getStatus()); assertTrue("That old properties are removed", traversal.V().has("hostname", "updatedHostname").hasNot("number-of-cpus").hasNext()); + verify(validationService, times(1)).validate(any()); } @Test @@ -328,6 +337,7 @@ public class HttpEntryTest extends AAISetup { traversal.V().has("aai-node-type", "p-interface").has("aai-uri", uri).has("interface-name", "p1") .out("tosca.relationships.network.BindsTo").has("aai-node-type", "pserver") .has("hostname", "hostname").hasNext()); + verify(validationService, times(1)).validate(any()); } @Test @@ -347,6 +357,7 @@ public class HttpEntryTest extends AAISetup { assertTrue("object should be updated while keeping old properties", traversal.V().has("aai-node-type", "pserver").has("hostname", "new-hostname") .has("equip-type", "the-equip-type").hasNext()); + verify(validationService, times(1)).validate(any()); } @Test @@ -363,6 +374,7 @@ public class HttpEntryTest extends AAISetup { doDelete(resourceVersion, uri, "pserver").getStatus()); assertTrue("Expecting the pserver to be deleted", !traversal.V().has("aai-node-type", "pserver").has("hostname", "the-hostname").hasNext()); + verify(validationService, times(1)).validate(any()); } @Test @@ -412,6 +424,7 @@ public class HttpEntryTest extends AAISetup { .has(EdgeProperty.PREVENT_DELETE.toString(), "IN"); assertTrue("p-server has incoming edge from complex", vertexQuery.hasNext()); assertTrue("Created Edge has expected properties", edgeQuery.hasNext()); + verify(validationService, times(1)).validate(any()); } @Test @@ -530,6 +543,7 @@ public class HttpEntryTest extends AAISetup { assertEquals("Expected get to succeed", 200, response.getStatus()); assertThat(responseEntity, containsString("/cloud-infrastructure/pservers/pserver/pserver-1")); assertThat(responseEntity, containsString("/cloud-infrastructure/pservers/pserver/pserver-2")); + verify(validationService, times(1)).validate(any()); } @Test @@ -565,6 +579,7 @@ public class HttpEntryTest extends AAISetup { assertEquals("Expected the response to be successful", 200, response.getStatus()); assertThat("Related pserver is returned", response.getEntity().toString(), containsString("\"hostname\":\"related-to-pserver\"")); + verify(validationService, times(1)).validate(any()); } @@ -594,6 +609,7 @@ public class HttpEntryTest extends AAISetup { Response response = doRequest(traversalHttpEntry, loader, dbEngine, HttpMethod.GET, uri, requestBody); assertThat("Related to pserver is returned.", response.getEntity().toString(), containsString("\"hostname\":\"abstract-pserver\"")); + verify(validationService, times(1)).validate(any()); } @Test @@ -640,6 +656,7 @@ public class HttpEntryTest extends AAISetup { relationships[0].getRelatedLink()); assertEquals("complex.physical-location-id", relationships[0].getRelationshipData()[0].getRelationshipKey()); assertEquals("related-to-complex", relationships[0].getRelationshipData()[0].getRelationshipValue()); + verify(validationService, times(1)).validate(any()); } @Test @@ -699,6 +716,7 @@ public class HttpEntryTest extends AAISetup { JSONAssert.assertEquals(expectedResponseBody, actualResponseBody, JSONCompareMode.NON_EXTENSIBLE); queryParameters.remove("format"); + verify(validationService, times(1)).validate(any()); } @Test @@ -816,4 +834,29 @@ public class HttpEntryTest extends AAISetup { int depth = traversalHttpEntry.setDepth(null, depthParam); assertEquals(AAIProperties.MAXIMUM_DEPTH.intValue(), depth); } + + @Test + public void thatEventsAreValidated() throws AAIException, UnsupportedEncodingException { + String uri = "/cloud-infrastructure/pservers/pserver/theHostname"; + traversal.addV() + .property("aai-node-type", "pserver") + .property("hostname", "theHostname") + .property("equip-type", "theEquipType") + .property(AAIProperties.AAI_URI, uri) + .next(); + String requestBody = new JSONObject() + .put("hostname", "theHostname") + .put("equip-type", "theEquipType") + .toString(); + + JSONObject expectedResponseBody = new JSONObject() + .put("hostname", "theHostname") + .put("equip-type", "theEquipType"); + Response response = doRequest(traversalHttpEntry, loader, dbEngine, HttpMethod.GET, uri, requestBody); + JSONObject actualResponseBody = new JSONObject(response.getEntity().toString()); + + JSONAssert.assertEquals(expectedResponseBody, actualResponseBody, JSONCompareMode.NON_EXTENSIBLE); + assertEquals("Expected the pserver to be returned", 200, response.getStatus()); + verify(validationService, times(1)).validate(any()); + } } -- cgit 1.2.3-korg