diff options
Diffstat (limited to 'aai-core/src')
5 files changed, 157 insertions, 146 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<String> validationNodeTypes; + private final Gson gson; private List<Pattern> 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<String, String> httpHeaders = new HashMap<>(); - httpHeaders.put("X-FromAppId", appName); httpHeaders.put("X-TransactionID", UUID.randomUUID().toString()); httpHeaders.put("Content-Type", "application/json"); ResponseEntity<String> 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<NotificationEvent> 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<String> violations = this.preValidate(event.getNotificationEvent()); + List<String> 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<String> 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<NotificationEvent> 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<String> preValidate(String body) throws AAIException { Map<String, String> 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<String> violations = new ArrayList<>(); ResponseEntity<String> 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<String> responseEntity) { return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful(); } - List<String> extractViolations(Validation validation) { - - List<String> errorMessages = new ArrayList<>(); - - if (validation == null) { - return errorMessages; + public List<String> extractViolations(Validation validation) { + if (validation == null || validation.getViolations() == null) { + return Collections.emptyList(); } - - List<Violation> 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/main/java/org/onap/aai/rest/db/HttpEntry.java b/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java index 28d66dd0..7ecdd6d5 100644 --- a/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java +++ b/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java @@ -155,49 +155,22 @@ public class HttpEntry { } public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) { - this.version = version; - this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version); - this.dbEngine = new JanusGraphDBEngine(queryStyle, loader); - - getDbEngine().startTransaction(); - this.notification = new UEBNotification(loader, loaderFactory, schemaVersions); - if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) { - this.notificationDepth = AAIProperties.MAXIMUM_DEPTH; - } else { - this.notificationDepth = AAIProperties.MINIMUM_DEPTH; - } - + setHttpEntryProperties(version); this.serverBase = serverBase; return this; } public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) { - this.version = version; - this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version); - this.dbEngine = new JanusGraphDBEngine(queryStyle, loader); - + setHttpEntryProperties(version); this.notification = notification; - - if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) { - this.notificationDepth = AAIProperties.MAXIMUM_DEPTH; - } else { - this.notificationDepth = AAIProperties.MINIMUM_DEPTH; - } - // start transaction on creation - getDbEngine().startTransaction(); return this; } public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification, int notificationDepth) { - this.version = version; - this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version); - this.dbEngine = new JanusGraphDBEngine(queryStyle, loader); - + setHttpEntryProperties(version); this.notification = notification; this.notificationDepth = notificationDepth; - // start transaction on creation - getDbEngine().startTransaction(); return this; } diff --git a/aai-core/src/main/java/org/onap/aai/rest/ueb/UEBNotification.java b/aai-core/src/main/java/org/onap/aai/rest/ueb/UEBNotification.java index be30c468..0c8fde62 100644 --- a/aai-core/src/main/java/org/onap/aai/rest/ueb/UEBNotification.java +++ b/aai-core/src/main/java/org/onap/aai/rest/ueb/UEBNotification.java @@ -98,42 +98,15 @@ public class UEBNotification { Introspector obj, HashMap<String, Introspector> relatedObjects, String basePath) throws AAIException, UnsupportedEncodingException { - String action = "UPDATE"; - - if (status.equals(Status.CREATED)) { - action = "CREATE"; - } else if (status.equals(Status.OK)) { - action = "UPDATE"; - } else if (status.equals(Status.NO_CONTENT)) { - action = "DELETE"; - } + String action = getAction(status); try { Introspector eventHeader = currentVersionLoader.introspectorFromName("notification-event-header"); URIToObject parser = new URIToObject(currentVersionLoader, uri, relatedObjects); - if ((basePath != null) && (!basePath.isEmpty())) { - if (!(basePath.startsWith("/"))) { - basePath = "/" + basePath; - } - if (!(basePath.endsWith("/"))) { - basePath = basePath + "/"; - } - } else { - // default - basePath = "/aai/"; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Please check the schema.uri.base.path as it didn't seem to be set"); - } - } + basePath = formatBasePath(basePath); - String uriStr = getUri(uri.toString(), basePath); - String entityLink; - if (uriStr.startsWith("/")) { - entityLink = basePath + notificationVersion + uriStr; - } else { - entityLink = basePath + notificationVersion + "/" + uriStr; - } + String entityLink = formatEntityLink(uri, basePath); eventHeader.setValue("entity-link", entityLink); eventHeader.setValue("action", action); @@ -191,6 +164,48 @@ public class UEBNotification { } } + private String formatEntityLink(URI uri, String basePath) { + String uriStr = getUri(uri.toString(), basePath); + String entityLink; + if (uriStr.startsWith("/")) { + entityLink = basePath + notificationVersion + uriStr; + } else { + entityLink = basePath + notificationVersion + "/" + uriStr; + } + return entityLink; + } + + private String formatBasePath(String basePath) { + if ((basePath != null) && (!basePath.isEmpty())) { + if (!(basePath.startsWith("/"))) { + basePath = "/" + basePath; + } + if (!(basePath.endsWith("/"))) { + basePath = basePath + "/"; + } + } else { + // default + basePath = "/aai/"; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Please check the schema.uri.base.path as it didn't seem to be set"); + } + } + return basePath; + } + + private String getAction(Status status) { + String action = "UPDATE"; + + if (status.equals(Status.CREATED)) { + action = "CREATE"; + } else if (status.equals(Status.OK)) { + action = "UPDATE"; + } else if (status.equals(Status.NO_CONTENT)) { + action = "DELETE"; + } + return action; + } + /** * Trigger events. * 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 371c07a5..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<Integer> VALID_HTTP_STATUS_CODES = new HashSet<>(); @@ -116,7 +122,7 @@ public class HttpEntryTest extends AAISetup { */ @Parameterized.Parameters(name = "QueryStyle.{0}") public static Collection<Object[]> data() { - return Arrays.asList(new Object[][] { { QueryStyle.TRAVERSAL }, { QueryStyle.TRAVERSAL_URI } }); + return Arrays.asList(new Object[][] { { QueryStyle.TRAVERSAL } }); } private Loader loader; @@ -196,8 +202,15 @@ public class HttpEntryTest extends AAISetup { .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()); } @Test @@ -216,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 @@ -254,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 @@ -322,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 @@ -341,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 @@ -357,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 @@ -406,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 @@ -524,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 @@ -559,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()); } @@ -588,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 @@ -634,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 @@ -693,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 @@ -810,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()); + } } |