From e5da3da7d7e0430a8d8d5c7c4a031c3ba557e247 Mon Sep 17 00:00:00 2001 From: da490c Date: Thu, 8 Mar 2018 12:54:22 -0500 Subject: Integrate Sparky-BE with Gizmo Issue-ID: AAI-847 Change-Id: I8c61e681c4f427f6e567dd8498b6f5f7baa19c85 Signed-off-by: da490c --- .../java/org/onap/aai/sparky/dal/GizmoAdapter.java | 334 +++++++ .../sparky/sync/task/PerformGizmoRetrieval.java | 93 ++ .../java/org/onap/aai/sparky/util/NodeUtils.java | 104 ++- .../config/VisualizationConfigs.java | 47 +- .../viewandinspect/entity/ActiveInventoryNode.java | 40 + .../sparky/viewandinspect/entity/GizmoEntity.java | 96 ++ .../entity/GizmoRelationshipEntity.java | 101 +++ .../entity/GizmoRelationshipHint.java | 75 ++ .../services/BaseGizmoVisualizationContext.java | 988 +++++++++++++++++++++ .../services/BaseVisualizationService.java | 23 +- .../PerformGizmoNodeSelfLinkProcessingTask.java | 126 +++ 11 files changed, 1995 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java create mode 100644 src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java (limited to 'src/main') diff --git a/src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java b/src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java new file mode 100644 index 0000000..99a967c --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/dal/GizmoAdapter.java @@ -0,0 +1,334 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.dal; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; +import org.onap.aai.restclient.enums.RestAuthenticationMode; +import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException; +import org.onap.aai.sparky.dal.rest.RestClientConstructionException; +import org.onap.aai.sparky.dal.rest.RestClientFactory; +import org.onap.aai.sparky.dal.rest.config.RestEndpointConfig; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.util.NodeUtils; + +/** + * The Class GizmoAdapter. + */ + +public class GizmoAdapter { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(GizmoAdapter.class); + + private static final String HEADER_TRANS_ID = "X-TransactionId"; + private static final String HEADER_FROM_APP_ID = "X-FromAppId"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + + private static final String HTTP_SCHEME = "http"; + private static final String HTTPS_SCHEME = "https"; + + private static final String TRANSACTION_ID_PREFIX = "txnId-"; + private static final String UI_APP_NAME = "AAI-UI"; + + private OxmModelLoader oxmModelLoader; + + private RestEndpointConfig endpointConfig; + + private RestClient restClient; + + private String inventoryBasePath; + private String relationshipsBasePath; + + /** + * Instantiates a new active inventory adapter. + * + * @throws RestClientConstructionException + * + */ + + public GizmoAdapter(OxmModelLoader oxmModelLoader, RestEndpointConfig endpointConfig) + throws ElasticSearchOperationException, IOException, RestClientConstructionException { + + this.oxmModelLoader = oxmModelLoader; + this.endpointConfig = endpointConfig; + this.restClient = RestClientFactory.buildClient(endpointConfig); + + } + + public String getRelationshipsBasePath() { + return relationshipsBasePath; + } + + public void setRelationshipsBasePath(String relationshipsBasePath) { + this.relationshipsBasePath = relationshipsBasePath; + } + + public String getInventoryBasePath() { + return inventoryBasePath; + } + + public void setInventoryBasePath(String inventoryBasePath) { + this.inventoryBasePath = inventoryBasePath; + } + + public String getFullInventoryUrl(String resourceUrl) throws Exception { + final String host = endpointConfig.getEndpointIpAddress(); + final String port = endpointConfig.getEndpointServerPort(); + final String basePath = getInventoryBasePath(); + return String.format("https://%s:%s%s%s", host, port, basePath, resourceUrl); + } + + public String addServerDetailsToUrl(String resourceUrl) throws Exception { + final String host = endpointConfig.getEndpointIpAddress(); + final String port = endpointConfig.getEndpointServerPort(); + return String.format("https://%s:%s/%s", host, port, resourceUrl); + } + + public String getFullRelationshipUrl(String resourceUrl) throws Exception { + final String host = endpointConfig.getEndpointIpAddress(); + final String port = endpointConfig.getEndpointServerPort(); + final String basePath = getRelationshipsBasePath(); + return String.format("https://%s:%s%s%s", host, port, basePath, resourceUrl); + } + + protected Map> getMessageHeaders() { + + Map> headers = new HashMap>(); + + headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList()); + headers.get(HEADER_FROM_APP_ID).add(UI_APP_NAME); + + headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList()); + headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId()); + + if (endpointConfig.getRestAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) { + + headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList()); + headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials()); + + } + + return headers; + } + + protected String getBasicAuthenticationCredentials() { + String usernameAndPassword = String.join(":", endpointConfig.getBasicAuthUserName(), + endpointConfig.getBasicAuthPassword()); + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); + } + + /** + * Our retry conditions should be very specific. + * + * @param r + * the r + * @return true, if successful + */ + private boolean shouldRetryRequest(OperationResult r) { + + if (r == null) { + return true; + } + + int rc = r.getResultCode(); + + if (rc == 200) { + return false; + } + + if (rc == 404) { + return false; + } + + return true; + + } + + /** + * Query active inventory. + * + * @param url + * the url + * @param acceptContentType + * the accept content type + * @return the operation result + */ + OperationResult queryGizmo(String url, String acceptContentType) { + + return restClient.get(url, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE); + + } + + public RestEndpointConfig getEndpointConfig() { + return endpointConfig; + } + + public void setEndpointConfig(RestEndpointConfig endpointConfig) { + this.endpointConfig = endpointConfig; + } + + public OperationResult queryGizmoWithRetries(String url, String responseType, int numRetries) { + + OperationResult result = null; + + for (int retryCount = 0; retryCount < numRetries; retryCount++) { + + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(retryCount + 1)); + + result = queryGizmo(url, responseType); + + /** + * Record number of times we have attempted the request to later + * summarize how many times we are generally retrying over thousands + * of messages in a sync. + * + * If the number of retries is surprisingly high, then we need to + * understand why that is as the number of retries is also causing a + * heavier load on AAI beyond the throttling controls we already + * have in place in term of the transaction rate controller and + * number of parallelized threads per task processor. + */ + + result.setNumRetries(retryCount); + + if (!shouldRetryRequest(result)) { + + result.setFromCache(false); + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(retryCount + 1)); + + return result; + } + + try { + /* + * Sleep between re-tries to be nice to the target system. + */ + Thread.sleep(50); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage()); + break; + } + LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(retryCount + 1)); + + } + + LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url); + + return result; + + } + + /** + * This method adds a scheme, host and port (if missing) to the passed-in + * URI. If these parts of the URI are already present, they will not be + * duplicated. + * + * @param selflink + * The URI to repair + * @param queryParams + * The query parameters as a single string + * @return The corrected URI (i.e. includes a scheme/host/port) + */ + + private String repairGizmoSelfLink(String baseUrlPath, String selfLink, String queryParams) { + + if (selfLink == null) { + return selfLink; + } + + if (selfLink.startsWith("http") || selfLink.startsWith("https")) { + return selfLink; + } + + UriBuilder builder = UriBuilder.fromPath(baseUrlPath + "/" + selfLink) + .host(endpointConfig.getEndpointIpAddress()) + .port(Integer.parseInt(endpointConfig.getEndpointServerPort())); + + switch (endpointConfig.getRestAuthenticationMode()) { + + case SSL_BASIC: + case SSL_CERT: { + builder.scheme(HTTPS_SCHEME); + break; + } + + default: { + builder.scheme(HTTP_SCHEME); + } + } + + boolean includeQueryParams = ((null != queryParams) && (!"".equals(queryParams))); + + /* + * builder.build().toString() will encode special characters to hexadecimal pairs prefixed with + * a '%' so we're adding the query parameters separately, in their UTF-8 representations, so + * that characters such as '?', '&', etc. remain intact as needed by the synchronizer + */ + return (builder.build().toString() + (includeQueryParams ? queryParams : "")); + + } + + public String repairRelationshipSelfLink(String selflink, String queryParams) { + return repairGizmoSelfLink(relationshipsBasePath, selflink, queryParams); + } + + public String repairInventorySelfLink(String selflink, String queryParams) { + return repairGizmoSelfLink(inventoryBasePath, selflink, queryParams); + } + + public OperationResult getSelfLinksByEntityType(String entityType) throws Exception { + + if (entityType == null) { + throw new NullPointerException("Failed to getSelfLinksByEntityType() because entityType is null"); + } + + String link = getFullInventoryUrl(entityType); + + return queryGizmoWithRetries(link, "application/json", endpointConfig.getNumRequestRetries()); + + } + + public static String extractResourcePath(String selflink) { + try { + return new URI(selflink).getRawPath(); + } catch (URISyntaxException uriSyntaxException) { + LOG.error(AaiUiMsgs.ERROR_EXTRACTING_RESOURCE_PATH_FROM_LINK, uriSyntaxException.getMessage()); + return selflink; + } + } + +} diff --git a/src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java b/src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java new file mode 100644 index 0000000..374163f --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/task/PerformGizmoRetrieval.java @@ -0,0 +1,93 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.GizmoAdapter; +import org.onap.aai.sparky.dal.NetworkTransaction; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.slf4j.MDC; + +/* + * Consider abstraction the tasks into common elemnts, because most of them repeat a generic call + * flow pattern + */ + +/** + * The Class PerformActiveInventoryRetrieval. + */ +public class PerformGizmoRetrieval implements Supplier { + + private static Logger logger = LoggerFactory.getInstance().getLogger(PerformGizmoRetrieval.class); + + private NetworkTransaction txn; + private GizmoAdapter gizmoAdapter; + private Map contextMap; + + /** + * Instantiates a new perform active inventory retrieval. + * + * @param txn the txn + * @param aaiProvider the aai provider + */ + public PerformGizmoRetrieval(NetworkTransaction txn, + GizmoAdapter gizmoAdapter) { + this.txn = txn; + this.gizmoAdapter = gizmoAdapter; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + OperationResult result = null; + try { + result = gizmoAdapter.queryGizmoWithRetries(txn.getLink(), "application/json", 5); + } catch (Exception exc) { + logger.error(AaiUiMsgs.ERROR_GENERIC,"Failure to resolve self link from AAI. Error = " + exc.getMessage()); + result = new OperationResult(500, + "Caught an exception while trying to resolve link = " + exc.getMessage()); + } finally { + txn.setOperationResult(result); + txn.setOpTimeInMs(System.currentTimeMillis() - startTimeInMs); + } + + return txn; + } + + protected void setContextMap(Map contextMap) { + this.contextMap = contextMap; + } +} diff --git a/src/main/java/org/onap/aai/sparky/util/NodeUtils.java b/src/main/java/org/onap/aai/sparky/util/NodeUtils.java index 7657d7e..dd8e882 100644 --- a/src/main/java/org/onap/aai/sparky/util/NodeUtils.java +++ b/src/main/java/org/onap/aai/sparky/util/NodeUtils.java @@ -73,7 +73,9 @@ public class NodeUtils { private static SecureRandom sRandom = new SecureRandom(); private static final Pattern AAI_VERSION_PREFIX = Pattern.compile("/aai/v[0-9]+/(.*)"); - + private static final Pattern GIZMO_VERSION_PREFIX = Pattern.compile("[/]*services/inventory/v[0-9]+/(.*)"); + private static final Pattern GIZMO_RELATIONSHIP_VERSION_PREFIX = Pattern.compile("services/inventory/relationships/v[0-9]+/(.*)"); + public static synchronized String getRandomTxnId(){ byte bytes[] = new byte[6]; @@ -119,6 +121,53 @@ public class NodeUtils { return null; } + + public static String extractRawGizmoPathWithoutVersion(String resourceLink) { + + try { + + String rawPath = new URI(resourceLink).getRawPath(); + + Matcher m = GIZMO_VERSION_PREFIX.matcher(rawPath); + + if (m.matches()) { + + if ( m.groupCount() >= 1) { + return m.group(1); + } + + } + } catch (Exception e) { + } + + return null; + + } + + public static String extractRawGizmoRelationshipPathWithoutVersion(String resourceLink) { + + try { + + String rawPath = new URI(resourceLink).getRawPath(); + + Matcher m = GIZMO_RELATIONSHIP_VERSION_PREFIX.matcher(rawPath); + + if (m.matches()) { + + if ( m.groupCount() >= 1) { + return m.group(1); + } + + } + } catch (Exception e) { + } + + return null; + + } + + + /** * Checks if is numeric. @@ -163,12 +212,7 @@ public class NodeUtils { return Executors.newScheduledThreadPool(numWorkers + 1, namedThreadFactory); } - /** - * Calculate edit attribute uri. - * - * @param link the link - * @return the string - */ + public static String calculateEditAttributeUri(String link) { String uri = null; @@ -183,6 +227,7 @@ public class NodeUtils { return uri; } + /** * Generate unique sha digest. * @@ -612,6 +657,51 @@ public class NodeUtils { } } + } + + public static String extractObjectValueByKey(JsonNode node, String searchKey) { + + if (node == null) { + return null; + } + + if (node.isObject()) { + Iterator> nodeIterator = node.fields(); + + while (nodeIterator.hasNext()) { + Map.Entry entry = nodeIterator.next(); + if (!entry.getValue().isValueNode()) { + return extractObjectValueByKey(entry.getValue(), searchKey); + } + + String name = entry.getKey(); + if (name.equalsIgnoreCase(searchKey)) { + + JsonNode entryNode = entry.getValue(); + + if (entryNode.isArray()) { + + Iterator arrayItemsIterator = entryNode.elements(); + while (arrayItemsIterator.hasNext()) { + return arrayItemsIterator.next().asText(); + } + + } else { + return entry.getValue().asText(); + } + + + } + } + } else if (node.isArray()) { + Iterator arrayItemsIterator = node.elements(); + while (arrayItemsIterator.hasNext()) { + return extractObjectValueByKey(arrayItemsIterator.next(), searchKey); + } + + } + + return null; } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java b/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java index a0be371..aff24bc 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java @@ -46,11 +46,15 @@ public class VisualizationConfigs { private boolean makeAllNeighborsBidirectional; private ArrayList shallowEntities; + + private boolean gizmoEnabled; /** * Instantiates a new visualization config. */ - public VisualizationConfigs() {} + public VisualizationConfigs() { + this.gizmoEnabled = false; + } public ArrayList getShallowEntities() { return shallowEntities; @@ -129,23 +133,28 @@ public class VisualizationConfigs { this.numOfThreadsToFetchNodeIntegrity = numOfThreadsToFetchNodeIntegrity; } - - - @Override - public String toString() { - return "VisualizationConfigs [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth - + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", " - + (aaiEntityNodeDescriptors != null - ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " : "") - + (generalNodeClassName != null ? "generalNodeClassName=" + generalNodeClassName + ", " - : "") - + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "") - + (selectedSearchedNodeClassName != null - ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "") - + "numOfThreadsToFetchNodeIntegrity=" + numOfThreadsToFetchNodeIntegrity - + ", makeAllNeighborsBidirectional=" + makeAllNeighborsBidirectional + "]"; - } - - + public boolean isGizmoEnabled() { + return gizmoEnabled; + } + + public void setGizmoEnabled(boolean gizmoEnabled) { + this.gizmoEnabled = gizmoEnabled; + } + + @Override + public String toString() { + return "VisualizationConfigs [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth + + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", " + + (aaiEntityNodeDescriptors != null ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " + : "") + + (generalNodeClassName != null ? "generalNodeClassName=" + generalNodeClassName + ", " : "") + + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "") + + (selectedSearchedNodeClassName != null + ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "") + + "numOfThreadsToFetchNodeIntegrity=" + numOfThreadsToFetchNodeIntegrity + + ", makeAllNeighborsBidirectional=" + makeAllNeighborsBidirectional + ", " + + (shallowEntities != null ? "shallowEntities=" + shallowEntities + ", " : "") + "gizmoEnabled=" + + gizmoEnabled + "]"; + } } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java index c5d9b4a..e90d469 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java @@ -66,6 +66,10 @@ public class ActiveInventoryNode { private boolean isRootNode; private ConcurrentLinkedDeque inboundNeighbors; private ConcurrentLinkedDeque outboundNeighbors; + + private ConcurrentLinkedDeque inboundNeighborSelfLinks; + private ConcurrentLinkedDeque outboundNeighborSelfLinks; + private List complexGroups; private List relationshipLists; private int nodeDepth; @@ -139,6 +143,10 @@ public class ActiveInventoryNode { isRootNode = false; inboundNeighbors = new ConcurrentLinkedDeque(); outboundNeighbors = new ConcurrentLinkedDeque(); + + inboundNeighborSelfLinks = new ConcurrentLinkedDeque(); + outboundNeighborSelfLinks = new ConcurrentLinkedDeque(); + complexGroups = new ArrayList(); relationshipLists = new ArrayList(); nodeDepth = DEFAULT_INIT_NODE_DEPTH; @@ -164,6 +172,38 @@ public class ActiveInventoryNode { } } + public void addInboundSelfLink(String link) { + + if (link == null) { + return; + } + + if (!inboundNeighborSelfLinks.contains(link)) { + inboundNeighborSelfLinks.add(link); + } + + } + + public void addOutboundSelfLink(String link) { + + if (link == null) { + return; + } + + if (!outboundNeighborSelfLinks.contains(link)) { + outboundNeighborSelfLinks.add(link); + } + + } + + public Collection getInboundNeighborSelfLinks() { + return inboundNeighborSelfLinks; + } + + public Collection getOutboundNeighborSelfLinks() { + return outboundNeighborSelfLinks; + } + public void addQueryParams(Collection params) { if (params != null & params.size() > 0) { diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java new file mode 100644 index 0000000..211efa5 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoEntity.java @@ -0,0 +1,96 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.entity; + +import java.util.Arrays; +import java.util.Map; + +public class GizmoEntity { + + private String id; + private String type; + private String url; + private Map properties; + private GizmoRelationshipHint[] in; + private GizmoRelationshipHint[] out; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public GizmoRelationshipHint[] getIn() { + return in; + } + + public void setIn(GizmoRelationshipHint[] in) { + this.in = in; + } + + public GizmoRelationshipHint[] getOut() { + return out; + } + + public void setOut(GizmoRelationshipHint[] out) { + this.out = out; + } + + @Override + public String toString() { + return "GizmoEntity [" + (id != null ? "id=" + id + ", " : "") + + (type != null ? "type=" + type + ", " : "") + (url != null ? "url=" + url + ", " : "") + + (properties != null ? "properties=" + properties + ", " : "") + + (in != null ? "in=" + Arrays.toString(in) + ", " : "") + + (out != null ? "out=" + Arrays.toString(out) : "") + "]"; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java new file mode 100644 index 0000000..a566d69 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipEntity.java @@ -0,0 +1,101 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.entity; + +import java.util.Map; + +public class GizmoRelationshipEntity { + + /* + * {"id":"oe4ur-3a0-27th-fu8","type":"has","url": + * "services/inventory/relationships/v8/has/oe4ur-3a0-27th-fu8","source": + * "services/inventory/v8/generic-vnf/4248","target": + * "services/inventory/v8/vserver/20528", + * "properties":{"is-parent":"true","multiplicity":"many","has-del-target": + * "true","uses-resource": "true"}} + */ + + private String id; + private String type; + private String url; + private String source; + private String target; + private Map properties; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + @Override + public String toString() { + return "GizmoRelationshipEntity [" + (id != null ? "id=" + id + ", " : "") + + (type != null ? "type=" + type + ", " : "") + (url != null ? "url=" + url + ", " : "") + + (source != null ? "source=" + source + ", " : "") + (target != null ? "target=" + target + ", " : "") + + (properties != null ? "properties=" + properties : "") + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java new file mode 100644 index 0000000..f28ba5b --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GizmoRelationshipHint.java @@ -0,0 +1,75 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.entity; + +public class GizmoRelationshipHint { + + private String id; + private String type; + private String url; + private String source; + private String target; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + + + } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java new file mode 100644 index 0000000..8963ff7 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseGizmoVisualizationContext.java @@ -0,0 +1,988 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.services; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.dal.GizmoAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.entity.SearchableEntity; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.SparkyConstants; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; +import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.onap.aai.sparky.viewandinspect.entity.GizmoEntity; +import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipEntity; +import org.onap.aai.sparky.viewandinspect.entity.GizmoRelationshipHint; +import org.onap.aai.sparky.viewandinspect.entity.InlineMessage; +import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction; +import org.onap.aai.sparky.viewandinspect.entity.QueryParams; +import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction; +import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState; +import org.onap.aai.sparky.viewandinspect.task.PerformGizmoNodeSelfLinkProcessingTask; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +/** + * The Class SelfLinkNodeCollector. + */ +public class BaseGizmoVisualizationContext implements VisualizationContext { + + private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100; + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(BaseGizmoVisualizationContext.class); + + private final GizmoAdapter gizmoAdapter; + + private AtomicInteger numLinksDiscovered; + private AtomicInteger numSuccessfulLinkResolveFromCache; + private AtomicInteger numSuccessfulLinkResolveFromFromServer; + private AtomicInteger numFailedLinkResolve; + private AtomicInteger aaiWorkOnHand; + + private VisualizationConfigs visualizationConfigs; + + private AtomicInteger totalLinksRetrieved; + + private final long contextId; + private final String contextIdStr; + private long lastProcessStatesSummaryLogInMs = -1; + + + private ObjectMapper mapper; + private InlineMessage inlineMessage = null; + + private ExecutorService graphExecutorService; + private OxmEntityLookup oxmEntityLookup; + private boolean rootNodeFound; + + /* + * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly + * re-requesting the same self-links over-and-over again, to speed up the overall render time and + * more importantly to reduce the network cost of determining information we already have. + */ + private ConcurrentHashMap nodeCache; + + /** + * Instantiates a new self link node collector. + * + * @param loader the loader + * @throws Exception the exception + */ + public BaseGizmoVisualizationContext(long contextId, GizmoAdapter gizmoAdapter, + ExecutorService graphExecutorService, VisualizationConfigs visualizationConfigs, + OxmEntityLookup oxmEntityLookup) throws Exception { + + this.contextId = contextId; + this.contextIdStr = "[Context-Id=" + contextId + "]"; + this.gizmoAdapter = gizmoAdapter; + this.graphExecutorService = graphExecutorService; + this.visualizationConfigs = visualizationConfigs; + this.oxmEntityLookup = oxmEntityLookup; + + this.nodeCache = new ConcurrentHashMap(); + this.numLinksDiscovered = new AtomicInteger(0); + this.totalLinksRetrieved = new AtomicInteger(0); + this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0); + this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0); + this.numFailedLinkResolve = new AtomicInteger(0); + this.aaiWorkOnHand = new AtomicInteger(0); + + this.mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_EMPTY); + mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy()); + this.rootNodeFound = false; + } + + protected boolean isRootNodeFound() { + return rootNodeFound; + } + + protected void setRootNodeFound(boolean rootNodeFound) { + this.rootNodeFound = rootNodeFound; + } + + public long getContextId() { + return contextId; + } + + public GizmoAdapter getGizmoAdapter() { + return gizmoAdapter; + } + + /** + * Process self link response. + * + * @param nodeId the node id + */ + private void processSelfLinkResponse(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, + "Cannot process self link" + " response because nodeId is null"); + return; + } + + ActiveInventoryNode ain = nodeCache.get(nodeId); + + if (ain == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, + "Cannot process self link response" + " because can't find node for id = " + nodeId); + return; + } + + GizmoEntity gizmoEntity = null; + + try { + gizmoEntity = mapper.readValue(ain.getOpResult().getResult(), GizmoEntity.class); + } catch (Exception exc) { + exc.printStackTrace(); + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json" + + " response str into JsonNode with error, " + exc.getLocalizedMessage()); + ain.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR); + return; + } + + if (gizmoEntity == null) { + + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, + "Failed to parse json node str." + " Parse resulted a null value."); + ain.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR); + return; + } + + /* + * Now that we have the gizmo entity we can populate the AIN node with it, as well as the + * relationships + */ + + ain.setEntityType(gizmoEntity.getType()); + + ain.setPrimaryKeyName(getEntityTypePrimaryKeyName(gizmoEntity.getType())); + + OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(gizmoEntity); + + if (descriptor != null) { + ain.setPrimaryKeyValue(getPrimaryKeyValues(gizmoEntity.getProperties(), + descriptor.getPrimaryKeyAttributeNames())); + } else { + LOG.error(AaiUiMsgs.ERROR_GENERIC, "Could not determine oxm descriptor for entity type = " + gizmoEntity.getType()); + } + + gizmoEntity.getProperties().forEach((key, value) -> { + ain.getProperties().put(key, value); + }); + + // add edit attributes link + if (ain.getSelfLink() != null) { + ain.addProperty(SparkyConstants.URI_ATTR_NAME, ain.getSelfLink()); + } + + + + /* + * Only discover neighbors if our depth is less than the Max-Traversal-Depth + */ + + if (ain.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) { + + /* + * I think the next thing to do is: + * + * 1. Calculate the source / target node id 2. Add the nodeId to the incoming / outgoing links + * collection 3. Add the node to the node cache for processing + */ + + String resourceLink = null; + String relationshipNodeId = null; + ActiveInventoryNode relationshipNode = null; + + for (GizmoRelationshipHint inRelationship : gizmoEntity.getIn()) { + + if (inRelationship.getSource() != null) { + + resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(inRelationship.getSource()); + relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink); + + if (!nodeCache.containsKey(relationshipNodeId)) { + + relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup); + relationshipNode.setNodeId(relationshipNodeId); + relationshipNode.setSelfLink(resourceLink); + relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, + NodeProcessingAction.NEW_NODE_PROCESSED); + + ain.addInboundNeighbor(relationshipNodeId); + + addNode(relationshipNode); + + } + } + + } + + for (GizmoRelationshipHint outRelationship : gizmoEntity.getOut()) { + + if (outRelationship.getTarget() != null) { + + resourceLink = NodeUtils.extractRawGizmoPathWithoutVersion(outRelationship.getTarget()); + relationshipNodeId = NodeUtils.generateUniqueShaDigest(resourceLink); + + if (!nodeCache.containsKey(relationshipNodeId)) { + + relationshipNode = new ActiveInventoryNode(visualizationConfigs, oxmEntityLookup); + relationshipNode.setNodeId(relationshipNodeId); + relationshipNode.setSelfLink(resourceLink); + relationshipNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, + NodeProcessingAction.NEW_NODE_PROCESSED); + + ain.addOutboundNeighbor(relationshipNodeId); + + addNode(relationshipNode); + + } + } + + } + } + + ain.changeState(NodeProcessingState.READY, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + + } + + /** + * Perform self link resolve. + * + * @param nodeId the node id + */ + private void performSelfLinkResolve(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, + "Resolve of self-link" + " has been skipped because provided nodeId is null"); + return; + } + + ActiveInventoryNode ain = nodeCache.get(nodeId); + + if (ain == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId + + ", from node cache. Resolve self-link method has been skipped."); + return; + } + + if (!ain.isSelfLinkPendingResolve()) { + + ain.setSelfLinkPendingResolve(true); + + // kick off async self-link resolution + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink()); + } + + numLinksDiscovered.incrementAndGet(); + + /* + * If the current node is the search target, we want to see everything the node has to offer + * from the self-link and not filter it to a single node. + */ + + NodeProcessingTransaction txn = new NodeProcessingTransaction(); + txn.setProcessingNode(ain); + txn.setRequestParameters(null); + aaiWorkOnHand.incrementAndGet(); + supplyAsync(new PerformGizmoNodeSelfLinkProcessingTask(txn, null, gizmoAdapter), + graphExecutorService).whenComplete((nodeTxn, error) -> { + + if (error != null) { + + /* + * an error processing the self link should probably result in the node processing + * state shifting to ERROR + */ + + nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true); + + nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESOLVE_ERROR); + + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } else { + + totalLinksRetrieved.incrementAndGet(); + + OperationResult opResult = nodeTxn.getOpResult(); + + if (opResult != null && opResult.wasSuccessful()) { + + if (!opResult.wasSuccessful()) { + numFailedLinkResolve.incrementAndGet(); + } + + if (opResult.isFromCache()) { + numSuccessfulLinkResolveFromCache.incrementAndGet(); + } else { + numSuccessfulLinkResolveFromFromServer.incrementAndGet(); + } + + // success path + nodeTxn.getProcessingNode().setOpResult(opResult); + nodeTxn.getProcessingNode().changeState( + NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESOLVE_OK); + + nodeTxn.getProcessingNode().setSelfLinkProcessed(true); + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } else { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, + "Self Link retrieval for link," + txn.getSelfLinkWithModifiers() + + ", failed with error code," + nodeTxn.getOpResult().getResultCode() + + ", and message," + nodeTxn.getOpResult().getResult()); + + nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true); + nodeTxn.getProcessingNode().setSelfLinkProcessed(true); + + nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESOLVE_ERROR); + + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } + } + + aaiWorkOnHand.decrementAndGet(); + + }); + + } + + } + + public GizmoRelationshipEntity getGizmoRelationshipEntity(String gizmoJsonResponse) { + + GizmoRelationshipEntity gizmoRelationship = null; + try { + gizmoRelationship = mapper.readValue(gizmoJsonResponse, GizmoRelationshipEntity.class); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, "Failed to map json to GizmoRelationshipEntity. Error: " + exc.getMessage()); + } + + return gizmoRelationship; + + } + + public String getPrimaryKeyValues(Map properties, List pkeyNames) { + + StringBuilder sb = new StringBuilder(64); + + if (pkeyNames.size() > 0) { + String primaryKey = properties.get(pkeyNames.get(0)); + if (primaryKey != null) { + sb.append(primaryKey); + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + " keyName, " + + pkeyNames.get(0) + ", from properties , " + properties); + return null; + } + + for (int i = 1; i < pkeyNames.size(); i++) { + + String kv = properties.get(pkeyNames.get(i)); + if (kv != null) { + sb.append("/").append(kv); + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, " + + pkeyNames.get(i) + ", from properties, " + properties); + return null; + } + } + + return sb.toString(); + + } + + return null; + + } + + + + /** + * Find and mark root node. + * + * @param queryParams the query params + * @return true, if successful + */ + private void findAndMarkRootNode(QueryParams queryParams) { + + if (isRootNodeFound()) { + return; + } + + for (ActiveInventoryNode cacheNode : nodeCache.values()) { + + if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) { + cacheNode.setNodeDepth(0); + cacheNode.setRootNode(true); + LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId()); + setRootNodeFound(true); + } + } + + } + + public void addNode(ActiveInventoryNode node) { + + if (node == null) { + return; + } + + nodeCache.putIfAbsent(node.getNodeId(), node); + } + + public VisualizationConfigs getVisualizationConfigs() { + return visualizationConfigs; + } + + public void setVisualizationConfigs(VisualizationConfigs visualizationConfigs) { + this.visualizationConfigs = visualizationConfigs; + } + + public OxmEntityLookup getOxmEntityLookup() { + return oxmEntityLookup; + } + + public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) { + this.oxmEntityLookup = oxmEntityLookup; + } + + public ObjectMapper getMapper() { + return mapper; + } + + public void setMapper(ObjectMapper mapper) { + this.mapper = mapper; + } + + private void dumpThrottledWorkOnHandLog() { + dumpThrottledWorkOnHandLog(false); + } + + private void dumpThrottledWorkOnHandLog(boolean override) { + + if ((lastProcessStatesSummaryLogInMs < 0) + || ((System.currentTimeMillis() > (lastProcessStatesSummaryLogInMs + 5000))) || override) { + + lastProcessStatesSummaryLogInMs = System.currentTimeMillis(); + + int numInit = 0; + int numReady = 0; + int numError = 0; + int numSelfLinkUnresolved = 0; + int numSelfLinkResponseUnprocessed = 0; + + for (ActiveInventoryNode cacheNode : nodeCache.values()) { + + switch (cacheNode.getState()) { + + case INIT: { + numInit++; + break; + } + + case READY: { + numReady++; + break; + } + case ERROR: { + numError++; + break; + } + + case SELF_LINK_UNRESOLVED: { + numSelfLinkUnresolved++; + break; + } + + case SELF_LINK_RESPONSE_UNPROCESSED: { + numSelfLinkResponseUnprocessed++; + break; + } + + default: + break; + } + + } + + LOG.info(AaiUiMsgs.INFO_GENERIC, + String.format( + "ProcessCurrentStates for ContextId=%s, [PendingTxns=%d, numInit=%d, numSelfLinkUnresolved=%d, numSelfLinkResponseUnProcessed=%d, numReady=%d, numError=%d]", + contextIdStr, aaiWorkOnHand.get(), numInit, numSelfLinkUnresolved, numSelfLinkResponseUnprocessed, + numReady, numError)); + } + + } + + /** + * Process current node states. + * + * @param rootNodeDiscovered the root node discovered + */ + private void processCurrentNodeStates(QueryParams queryParams) { + /* + * Force an evaluation of node depths before determining if we should limit state-based + * traversal or processing. + */ + + findAndMarkRootNode(queryParams); + + verifyOutboundNeighbors(); + + for (ActiveInventoryNode cacheNode : nodeCache.values()) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "processCurrentNodeState(), nid = " + + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth()); + } + + switch (cacheNode.getState()) { + + case INIT: { + processInitialState(cacheNode.getNodeId()); + break; + } + + case READY: + case ERROR: { + break; + } + + case SELF_LINK_UNRESOLVED: { + performSelfLinkResolve(cacheNode.getNodeId()); + break; + } + + case SELF_LINK_RESPONSE_UNPROCESSED: { + processSelfLinkResponse(cacheNode.getNodeId()); + break; + } + + default: + break; + } + + } + + dumpThrottledWorkOnHandLog(); + + } + + + + public int getNumSuccessfulLinkResolveFromCache() { + return numSuccessfulLinkResolveFromCache.get(); + } + + public int getNumSuccessfulLinkResolveFromFromServer() { + return numSuccessfulLinkResolveFromFromServer.get(); + } + + public int getNumFailedLinkResolve() { + return numFailedLinkResolve.get(); + } + + public InlineMessage getInlineMessage() { + return inlineMessage; + } + + public void setInlineMessage(InlineMessage inlineMessage) { + this.inlineMessage = inlineMessage; + } + + public ConcurrentHashMap getNodeCache() { + return nodeCache; + } + + + + /** + * Process initial state. + * + * @param nodeId the node id + */ + private void processInitialState(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null"); + return; + } + + ActiveInventoryNode cachedNode = nodeCache.get(nodeId); + + if (cachedNode == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, + "Node cannot be" + " found for nodeId, " + nodeId); + return; + } + + if (cachedNode.getSelfLink() == null) { + + if (cachedNode.getNodeId() == null) { + + /* + * if the self link is null at the INIT state, which could be valid if this node is a + * complex attribute group which didn't originate from a self-link, but in that situation + * both the node id and node key should already be set. + */ + + cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR); + + } + + if (cachedNode.getNodeId() != null) { + + /* + * This should be the success path branch if the self-link is not set + */ + + cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + + } + + } else { + + if (cachedNode.hasResolvedSelfLink()) { + LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT); + cachedNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.UNEXPECTED_STATE_TRANSITION); + } else { + cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, + NodeProcessingAction.SELF_LINK_SET); + } + } + } + + /** + * Process skeleton node. + * + * @param skeletonNode the skeleton node + * @param queryParams the query params + */ + private void processSearchableEntity(SearchableEntity searchTargetEntity, + QueryParams queryParams) { + + if (searchTargetEntity == null) { + return; + } + + if (searchTargetEntity.getId() == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton" + + " node because nodeId is null for node, " + searchTargetEntity.getLink()); + return; + } + + ActiveInventoryNode newNode = + new ActiveInventoryNode(this.visualizationConfigs, oxmEntityLookup); + + newNode.setNodeId(searchTargetEntity.getId()); + + newNode.setNodeDepth(0); + newNode.setRootNode(true); + LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId()); + setRootNodeFound(true); + + newNode.setSelfLink(searchTargetEntity.getLink()); + + nodeCache.putIfAbsent(newNode.getNodeId(), newNode); + } + + private int getTotalWorkOnHand() { + + int numNodesWithPendingStates = 0; + + if (isRootNodeFound()) { + evaluateNodeDepths(); + } + + for (ActiveInventoryNode n : nodeCache.values()) { + + switch (n.getState()) { + + case READY: + case ERROR: { + // do nothing, these are our normal + // exit states + break; + } + + default: { + + /* + * for all other states, there is work to be done + */ + numNodesWithPendingStates++; + } + + } + + } + + return (aaiWorkOnHand.get() + numNodesWithPendingStates); + + } + + /** + * Checks for out standing work. + * + * @return true, if successful + */ + private void processOutstandingWork(QueryParams queryParams) { + + while (getTotalWorkOnHand() > 0) { + + /* + * Force an evaluation of node depths before determining if we should limit state-based + * traversal or processing. + */ + + processCurrentNodeStates(queryParams); + + try { + Thread.sleep(10); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage()); + return; + } + + } + + dumpThrottledWorkOnHandLog(true); + + } + + /* + * (non-Javadoc) + * + * @see + * org.onap.aai.sparky.viewandinspect.services.VisualizationContext#processSelfLinks(org.onap.aai. + * sparky.sync.entity.SearchableEntity, org.onap.aai.sparky.viewandinspect.entity.QueryParams) + */ + @Override + public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) { + + try { + + + if (searchtargetEntity == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, + contextIdStr + " - Failed to" + " processSelfLinks, searchtargetEntity is null"); + return; + } + + long startTimeInMs = System.currentTimeMillis(); + + processSearchableEntity(searchtargetEntity, queryParams); + + /* + * This method is blocking until we decouple it with a CountDownLatch await condition, and + * make the internal graph processing more event-y. + */ + + processOutstandingWork(queryParams); + + long totalResolveTime = (System.currentTimeMillis() - startTimeInMs); + + long opTime = System.currentTimeMillis() - startTimeInMs; + + LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime), + String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime)); + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage()); + } + + } + + /** + * Verify outbound neighbors. + */ + private void verifyOutboundNeighbors() { + + for (ActiveInventoryNode srcNode : nodeCache.values()) { + + for (String targetNodeId : srcNode.getOutboundNeighbors()) { + + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null && srcNode.getNodeId() != null) { + + targetNode.addInboundNeighbor(srcNode.getNodeId()); + + if (this.visualizationConfigs.makeAllNeighborsBidirectional()) { + targetNode.addOutboundNeighbor(srcNode.getNodeId()); + } + + } + + } + + } + + } + + /** + * Evaluate node depths. + */ + private void evaluateNodeDepths() { + + int numChanged = -1; + int numAttempts = 0; + + while (numChanged != 0) { + + numChanged = 0; + numAttempts++; + + for (ActiveInventoryNode srcNode : nodeCache.values()) { + + if (srcNode.getState() == NodeProcessingState.INIT) { + + /* + * this maybe the only state that we don't want to to process the node depth on, because + * typically it won't have any valid fields set, and it may remain in a partial state + * until we have processed the self-link. + */ + + continue; + + } + + for (String targetNodeId : srcNode.getOutboundNeighbors()) { + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null) { + + if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) { + numChanged++; + } + } + } + + for (String targetNodeId : srcNode.getInboundNeighbors()) { + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null) { + + if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) { + numChanged++; + } + } + } + } + + if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) { + LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED); + return; + } + + } + + if (LOG.isDebugEnabled()) { + if (numAttempts > 0) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Evaluate node depths completed in " + numAttempts + " attempts"); + } else { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Evaluate node depths completed in 0 attempts because all nodes at correct depth"); + } + } + + } + + + /** + * Gets the entity type primary key name. + * + * @param entityType the entity type + * @return the entity type primary key name + */ + + + private String getEntityTypePrimaryKeyName(String entityType) { + + if (entityType == null) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, + "node primary key" + " name because entity type is null"); + return null; + } + + OxmEntityDescriptor descriptor = oxmEntityLookup.getEntityDescriptors().get(entityType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, + "oxm entity" + " descriptor for entityType = " + entityType); + return null; + } + + List pkeyNames = descriptor.getPrimaryKeyAttributeNames(); + + if (pkeyNames == null || pkeyNames.size() == 0) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, + "node primary" + " key because descriptor primary key names is empty"); + return null; + } + + return NodeUtils.concatArray(pkeyNames, "/"); + + } + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseVisualizationService.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseVisualizationService.java index ed5ec8d..9c3c7da 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseVisualizationService.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/services/BaseVisualizationService.java @@ -37,6 +37,7 @@ import org.onap.aai.sparky.config.oxm.OxmEntityLookup; import org.onap.aai.sparky.config.oxm.OxmModelLoader; import org.onap.aai.sparky.dal.ActiveInventoryAdapter; import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.dal.GizmoAdapter; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.subscription.config.SubscriptionConfig; import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; @@ -64,6 +65,7 @@ public class BaseVisualizationService implements VisualizationService { private ObjectMapper mapper = new ObjectMapper(); private final ActiveInventoryAdapter aaiAdapter; + private final GizmoAdapter gizmoAdapter; private final ElasticSearchAdapter esAdapter; private final ExecutorService aaiExecutorService; @@ -76,16 +78,18 @@ public class BaseVisualizationService implements VisualizationService { private ElasticSearchSchemaConfig schemaEConfig; private OxmEntityLookup oxmEntityLookup; - public BaseVisualizationService(OxmModelLoader loader, VisualizationConfigs visualizationConfigs, - ActiveInventoryAdapter aaiAdapter,ElasticSearchAdapter esAdapter, - ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig, int numActiveInventoryWorkers, - OxmEntityLookup oxmEntityLookup, SubscriptionConfig subscriptionConfig) throws Exception { + public BaseVisualizationService(OxmModelLoader loader, VisualizationConfigs visualizationConfigs, + ActiveInventoryAdapter aaiAdapter, GizmoAdapter gizmoAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig, + int numActiveInventoryWorkers, OxmEntityLookup oxmEntityLookup, SubscriptionConfig subscriptionConfig) + throws Exception { this.visualizationConfigs = visualizationConfigs; this.endpointEConfig = endpointConfig; this.schemaEConfig = schemaConfig; this.oxmEntityLookup = oxmEntityLookup; this.subConfig = subscriptionConfig; + secureRandom = new SecureRandom(); @@ -94,6 +98,7 @@ public class BaseVisualizationService implements VisualizationService { */ this.aaiAdapter = aaiAdapter; + this.gizmoAdapter = gizmoAdapter; this.esAdapter = esAdapter; this.mapper = new ObjectMapper(); @@ -276,8 +281,14 @@ public class BaseVisualizationService implements VisualizationService { VisualizationContext visContext = null; long contextId = secureRandom.nextLong(); try { - visContext = new BaseVisualizationContext(contextId, this.aaiAdapter, aaiExecutorService, - this.visualizationConfigs, oxmEntityLookup); + if ( visualizationConfigs.isGizmoEnabled()) { + visContext = new BaseGizmoVisualizationContext(contextId, this.gizmoAdapter, aaiExecutorService, + this.visualizationConfigs, oxmEntityLookup); + } else { + visContext = new BaseVisualizationContext(contextId, this.aaiAdapter, aaiExecutorService, + this.visualizationConfigs, oxmEntityLookup); + } + contextMap.putIfAbsent(contextId, visContext); } catch (Exception e1) { LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java b/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java new file mode 100644 index 0000000..bccd7aa --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformGizmoNodeSelfLinkProcessingTask.java @@ -0,0 +1,126 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.GizmoAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction; +import org.slf4j.MDC; + +/** + * The Class PerformNodeSelfLinkProcessingTask. + */ +public class PerformGizmoNodeSelfLinkProcessingTask implements Supplier { + + private static final Logger logger = + LoggerFactory.getInstance().getLogger(PerformGizmoNodeSelfLinkProcessingTask.class); + + private NodeProcessingTransaction txn; + private GizmoAdapter gizmoAdapter; + private Map contextMap; + + /** + * Instantiates a new perform node self link processing task. + * + * @param txn the txn + * @param aaiProvider the aai provider + * @param aaiConfig the aai config + */ + /** + * + * @param txn + * @param requestParameters + * @param aaiProvider + * @param aaiConfig + */ + public PerformGizmoNodeSelfLinkProcessingTask(NodeProcessingTransaction txn, String requestParameters, + GizmoAdapter gizmoAdapter) { + this.gizmoAdapter = gizmoAdapter; + this.txn = txn; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* + * (non-Javadoc) + * + * @see java.util.function.Supplier#get() + */ + @Override + public NodeProcessingTransaction get() { + MDC.setContextMap(contextMap); + OperationResult opResult = new OperationResult(); + String link = txn.getSelfLink(); + + if (link == null) { + opResult.setResult(500, "Aborting self-link processing because self link is null"); + txn.setOpResult(opResult); + return txn; + } + + /** + * Rebuild the self link: + * + *
  • build the base url with the configured scheme + authority (server:port) + *
  • recombine baseUrl + originalEncodedLink + queryStringParameters + * + */ + + final String urlSchemeAndAuthority = gizmoAdapter.repairInventorySelfLink("", null); + + String parameters = txn.getRequestParameters(); + link = urlSchemeAndAuthority + link; + + if (parameters != null) { + link += parameters; + } + + if (logger.isDebugEnabled()) { + logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Collecting " + link); + } + + try { + + opResult = gizmoAdapter.queryGizmoWithRetries(link, "application/json", + gizmoAdapter.getEndpointConfig().getNumRequestRetries()); + } catch (Exception exc) { + opResult = new OperationResult(); + opResult.setResult(500, "Querying AAI with retry failed due to an exception."); + logger.error(AaiUiMsgs.ERROR_AAI_QUERY_WITH_RETRY, exc.getMessage()); + } + + if (logger.isDebugEnabled()) { + logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Operation result = " + opResult.toString()); + } + + txn.setOpResult(opResult); + return txn; + + } + +} -- cgit 1.2.3-korg