Merge "remove internal urls"
[vid.git] / vid-app-common / src / main / java / org / onap / vid / services / AAITreeNodeBuilder.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.vid.services;
22
23 import static java.util.stream.Collectors.toList;
24 import static java.util.stream.Collectors.toMap;
25 import static java.util.stream.Collectors.toSet;
26 import static org.onap.vid.utils.KotlinUtilsKt.JACKSON_OBJECT_MAPPER;
27 import static org.onap.vid.utils.Streams.not;
28
29 import com.fasterxml.jackson.databind.JsonNode;
30 import com.google.common.collect.ImmutableList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.ConcurrentSkipListSet;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Future;
41 import java.util.concurrent.TimeUnit;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 import javax.inject.Inject;
45 import org.apache.commons.lang3.StringUtils;
46 import org.apache.commons.lang3.tuple.ImmutablePair;
47 import org.apache.commons.lang3.tuple.Pair;
48 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
49 import org.onap.portalsdk.core.util.SystemProperties;
50 import org.onap.vid.aai.AaiClientInterface;
51 import org.onap.vid.aai.ExceptionWithRequestInfo;
52 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.Relationship;
53 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.RelationshipData;
54 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.RelationshipList;
55 import org.onap.vid.aai.util.AAITreeNodeUtils;
56 import org.onap.vid.exceptions.GenericUncheckedException;
57 import org.onap.vid.model.aaiTree.AAITreeNode;
58 import org.onap.vid.model.aaiTree.FailureAAITreeNode;
59 import org.onap.vid.model.aaiTree.NodeType;
60 import org.onap.vid.mso.model.CloudConfiguration;
61 import org.onap.vid.properties.VidProperties;
62 import org.onap.vid.utils.Streams;
63 import org.onap.vid.utils.Tree;
64 import org.onap.vid.utils.Unchecked;
65 import org.slf4j.MDC;
66 import org.springframework.http.HttpMethod;
67 import org.springframework.stereotype.Component;
68
69
70 @Component
71 public class AAITreeNodeBuilder {
72
73     private static final String RESULTS = "results";
74     private AaiClientInterface aaiClient;
75
76     private static final EELFLoggerDelegate LOGGER = EELFLoggerDelegate.getLogger(AAITreeNodeBuilder.class);
77
78
79     public enum AAIBaseProperties {
80         ORCHESTRATION_STATUS("orchestration-status"),
81         PROV_STATUS("prov-status"),
82         IN_MAINT("in-maint"),
83         MODEL_VERSION_ID("model-version-id"),
84         MODEL_CUSTOMIZATION_ID("model-customization-id"),
85         MODEL_INVARIANT_ID("model-invariant-id"),
86         RELATIONSHIP_LIST("relationship-list");
87
88         private final String aaiKey;
89
90         AAIBaseProperties(String aaiKey) {
91             this.aaiKey = aaiKey;
92         }
93
94         public String getAaiKey() {
95             return aaiKey;
96         }
97     }
98
99     @Inject
100     public AAITreeNodeBuilder(AaiClientInterface aaiClient) {
101         this.aaiClient = aaiClient;
102     }
103
104     List<AAITreeNode> buildNode(NodeType nodeType,
105                                 String requestURL,
106                                 String payload,
107                                 HttpMethod method,
108                                 ConcurrentSkipListSet<AAITreeNode> nodesAccumulator,
109                                 ExecutorService threadPool,
110                                 Tree<AAIServiceTree.AaiRelationship> pathsTree) {
111
112         JsonNode jsonNode = aaiClient.typedAaiRest(Unchecked.toURI(requestURL), JsonNode.class, payload, method, false);
113
114         List<Pair<AAITreeNode, List<Relationship>>> nodes = getNodesWithRelationships(jsonNode, nodeType, nodesAccumulator, pathsTree);
115
116         String timeout = SystemProperties.getProperty(VidProperties.VID_THREAD_TIMEOUT);
117         long timeoutNum = Long.parseLong(StringUtils.defaultIfEmpty(timeout, "30"));
118
119         for (Pair<AAITreeNode, List<Relationship>> entry : nodes) {
120             fetchChildrenAsync(threadPool, nodesAccumulator, entry.getKey(), entry.getValue(), pathsTree, timeoutNum);
121
122             if (getNextLevelInPathsTree(pathsTree, NodeType.VF_MODULE.getType()) != null) {
123                 getRelatedVfModules(threadPool, nodesAccumulator, requestURL, entry.getKey());
124             }
125         }
126
127         return nodes.stream()
128                 .map(Pair::getKey)
129                 .collect(Collectors.toList());
130     }
131
132     private List<Pair<AAITreeNode, List<Relationship>>> getNodesWithRelationships(JsonNode jsonNode, NodeType nodeType,
133                                                                                   ConcurrentSkipListSet<AAITreeNode> nodesAccumulator,
134                                                                                   Tree<AAIServiceTree.AaiRelationship> pathsTree) {
135         if (isListOfKeyResults(jsonNode)) {
136             return Streams.fromIterable(jsonNode.get(RESULTS))
137                     .filter(item -> item.has(nodeType.getType()))
138                     .map(item -> item.get(nodeType.getType()))
139                     .map(item -> parseNodeAndFilterRelationships(item, nodeType, nodesAccumulator, pathsTree))
140                     .collect(Collectors.toList());
141         } else if (isArray(jsonNode, nodeType)) {
142             return Streams.fromIterable(jsonNode.get(nodeType.getType()))
143                     .map(item -> parseNodeAndFilterRelationships(item, nodeType, nodesAccumulator, pathsTree))
144                     .collect(Collectors.toList());
145         } else {
146             return ImmutableList.of(parseNodeAndFilterRelationships(jsonNode, nodeType, nodesAccumulator, pathsTree));
147         }
148     }
149
150     Pair<AAITreeNode, List<Relationship>> parseNodeAndFilterRelationships(JsonNode jsonNode, NodeType nodeType,
151                                                                           ConcurrentSkipListSet<AAITreeNode> nodesAccumulator,
152                                                                           Tree<AAIServiceTree.AaiRelationship> pathsTree) {
153         AAITreeNode node = createAaiNode(nodeType, jsonNode, nodesAccumulator);
154
155         enrichPlacementData(node);
156
157         List<Relationship> filteredRelationships = getFilteredRelationships(jsonNode, pathsTree);
158
159         return ImmutablePair.of(node, filteredRelationships);
160     }
161
162     boolean isArray(JsonNode json, NodeType nodeType) {
163         return json != null && json.has(nodeType.getType()) && json.get(nodeType.getType()).isArray();
164     }
165
166     boolean isListOfKeyResults(JsonNode jsonNode) {
167         return jsonNode != null && jsonNode.has(RESULTS) && jsonNode.get(RESULTS).isArray();
168     }
169
170     AAITreeNode createAaiNode(NodeType nodeType, JsonNode jsonNode, ConcurrentSkipListSet<AAITreeNode> nodesAccumulator) {
171         AAITreeNode node = jsonNodeToAaiNode(nodeType, jsonNode);
172
173         nodesAccumulator.add(node);
174
175         return node;
176     }
177
178     private void addChildren(AAITreeNode node, Future<List<AAITreeNode>> children) {
179         try {
180             node.addChildren(children.get());
181         } catch (Exception e) {
182             node.getChildren().add(createFailureNode(e));
183         }
184     }
185
186     private Map<String,String> convertRelationshipDataToMap(List<RelationshipData> relationshipData) {
187         return relationshipData.stream().collect(
188                 Collectors.toMap(RelationshipData::getKey, RelationshipData::getValue));
189     }
190
191     void enrichPlacementData(AAITreeNode node){
192         Optional<Relationship> tenantRelationShip = AAITreeNodeUtils.findFirstRelationshipByRelatedTo(node.getRelationshipList(), "tenant");
193         enrichPlacementDataUsingTenantInfo(node, tenantRelationShip);
194     }
195
196     void enrichPlacementDataUsingTenantInfo(AAITreeNode node, Optional<Relationship> tenantRelationShip) {
197         //no tenant relationship in this node - so no placement data
198         if (!tenantRelationShip.isPresent()) {
199             return;
200         }
201         try {
202             Map<String, String> relationshipsDataMap = convertRelationshipDataToMap(tenantRelationShip.get().getRelationDataList());
203             node.setCloudConfiguration(new CloudConfiguration(
204                     relationshipsDataMap.get("cloud-region.cloud-region-id"),
205                     relationshipsDataMap.get("tenant.tenant-id"),
206                     relationshipsDataMap.get("cloud-region.cloud-owner")));
207         }
208         catch (Exception exception) {
209             LOGGER.error("Failed to extract placement form tenant relationship of {}:{}", node.getType(), node.getId(), exception);
210         }
211     }
212
213     private void getRelatedVfModules(ExecutorService threadPool, ConcurrentSkipListSet<AAITreeNode> nodesAccumulator, String parentURL, AAITreeNode parentNode) {
214         /*
215         VNFs do not report their direct related-to vf-modules, so try
216         directly fetching a resource URI.
217          */
218
219         Future<?> vfModulesTask = threadPool.submit(withCopyOfMDC(() -> {
220             // the response is an array of vf-modules
221             final JsonNode jsonNode;
222             try {
223                 jsonNode = aaiClient.typedAaiGet(Unchecked.toURI(parentURL + "/vf-modules"), JsonNode.class);
224             } catch (ExceptionWithRequestInfo e) {
225                 if (e.getHttpCode().equals(404)) {
226                     // it's ok, as we're just optimistically fetching
227                     // the /vf-modules uri; 404 says this time it was a bad guess
228                     return true;
229                 } else {
230                     throw e;
231                 }
232             }
233
234             if (isArray(jsonNode, NodeType.VF_MODULE)) {
235                 //create list of AAITreeNode represent the VfModules from AAI result
236                 List<AAITreeNode> vfModules = Streams.fromIterable(jsonNode.get(NodeType.VF_MODULE.getType()))
237                     .map(vfModuleNode -> createAaiNode(NodeType.VF_MODULE, vfModuleNode, nodesAccumulator))
238                     .collect(toList());
239                 //enrich each of the VfModule with placement info
240                 vfModules.forEach(vfModule -> enrichPlacementDataUsingTenantInfo(
241                     vfModule,
242                     AAITreeNodeUtils.findFirstRelationshipByRelatedTo(vfModule.getRelationshipList(), "vserver")
243                 ));
244                 //add all VfModules to children list of parent node
245                 parentNode.getChildren().addAll(vfModules);
246             } else {
247                 LOGGER.error(EELFLoggerDelegate.errorLogger, "Failed to get vf-modules for vnf " + parentNode.getId());
248             }
249
250             return true; // the Callable<> contract requires a return value
251         }));
252
253         waitForCompletion(vfModulesTask);
254     }
255
256     private void waitForCompletion(Future<?> future) {
257         try {
258             future.get();
259         } catch (Exception e) {
260             throw new GenericUncheckedException(e);
261         }
262     }
263
264     List<Relationship> getFilteredRelationships(JsonNode json, Tree<AAIServiceTree.AaiRelationship> pathsTree) {
265         RelationshipList relationshipList = JACKSON_OBJECT_MAPPER.convertValue(json.get(AAIBaseProperties.RELATIONSHIP_LIST.getAaiKey()), RelationshipList.class);
266         if (relationshipList != null) {
267             return relationshipList.getRelationship().stream()
268                     .filter(rel -> getNextLevelInPathsTree(pathsTree, rel.getRelatedTo()) != null)
269                     .filter(rel -> !Objects.equals(rel.getRelatedTo(), NodeType.VF_MODULE.getType())) // vf-modules are handled separately
270                     .collect(toList());
271         }
272
273         return Collections.emptyList();
274     }
275
276     void fetchChildrenAsync(ExecutorService threadPool, ConcurrentSkipListSet<AAITreeNode> nodesAccumulator,
277                             AAITreeNode node, List<Relationship> relationships, Tree<AAIServiceTree.AaiRelationship> pathsTree, long timeout) {
278
279         if (!relationships.isEmpty()) {
280             List<Callable<List<AAITreeNode>>> tasks = relationships.stream()
281                 .map(relationship ->
282                     withCopyOfMDC(() -> getChildNode(threadPool, nodesAccumulator, relationship.getRelatedTo(),
283                             relationship.getRelatedLink(), pathsTree)))
284                 .collect(Collectors.toList());
285
286             try {
287                 int depth = pathsTree.getChildrenDepth();
288                 threadPool.invokeAll(tasks, timeout * depth, TimeUnit.SECONDS)
289                     .forEach(future ->
290                         addChildren(node, future)
291                     );
292             } catch (Exception e) {
293                 throw new GenericUncheckedException(e);
294             }
295         }
296     }
297
298     private <V> Callable<V> withCopyOfMDC(Callable<V> callable) {
299         //in order to be able to write the correct data while creating the node on a new thread
300         // save a copy of the current thread's context map, with keys and values of type String.
301         final Map<String, String> copyOfParentMDC = MDC.getCopyOfContextMap();
302         return () -> {
303             MDC.setContextMap(copyOfParentMDC);
304             return callable.call();
305         };
306     }
307
308     private List<AAITreeNode> getChildNode(ExecutorService threadPool, ConcurrentSkipListSet<AAITreeNode> nodesAccumulator,
309                                            String childNodeType, String childNodeUrl,
310                                            Tree<AAIServiceTree.AaiRelationship> pathsTree) {
311
312         Tree<AAIServiceTree.AaiRelationship> subTree = getNextLevelInPathsTree(pathsTree, childNodeType);
313
314         return buildNode(NodeType.fromString(childNodeType), childNodeUrl, null, HttpMethod.GET, nodesAccumulator, threadPool, subTree);
315     }
316
317     Tree<AAIServiceTree.AaiRelationship> getNextLevelInPathsTree(Tree<AAIServiceTree.AaiRelationship> pathsTree, String nodeType) {
318         return pathsTree.getSubTree(new AAIServiceTree.AaiRelationship(nodeType));
319     }
320
321     //ADD TEST
322     private AAITreeNode jsonNodeToAaiNode(NodeType nodeType, JsonNode jsonNode) {
323         AAITreeNode node = new AAITreeNode();
324         node.setType(nodeType);
325         node.setOrchestrationStatus(getStringDataFromJsonIfExists(jsonNode, AAIBaseProperties.ORCHESTRATION_STATUS.getAaiKey()));
326         node.setProvStatus(getStringDataFromJsonIfExists(jsonNode, AAIBaseProperties.PROV_STATUS.getAaiKey()));
327         node.setInMaint(getBooleanDataFromJsonIfExists(jsonNode, AAIBaseProperties.IN_MAINT.getAaiKey()));
328         node.setModelVersionId(getStringDataFromJsonIfExists(jsonNode, AAIBaseProperties.MODEL_VERSION_ID.getAaiKey()));
329         node.setModelCustomizationId(getStringDataFromJsonIfExists(jsonNode, AAIBaseProperties.MODEL_CUSTOMIZATION_ID.getAaiKey()));
330         node.setModelInvariantId(getStringDataFromJsonIfExists(jsonNode, AAIBaseProperties.MODEL_INVARIANT_ID.getAaiKey()));
331         node.setId(getStringDataFromJsonIfExists(jsonNode, nodeType.getId()));
332         node.setName(getStringDataFromJsonIfExists(jsonNode, nodeType.getName()));
333         node.setAdditionalProperties(aggregateAllOtherProperties(jsonNode, nodeType));
334         node.setRelationshipList(JACKSON_OBJECT_MAPPER.convertValue(jsonNode.get(AAIBaseProperties.RELATIONSHIP_LIST.getAaiKey()), RelationshipList.class));
335         return node;
336     }
337
338     private AAITreeNode createFailureNode(Exception exception) {
339         return FailureAAITreeNode.of(exception);
340     }
341
342     private String getStringDataFromJsonIfExists(JsonNode model, String key) {
343         if (!NodeType.NONE.equals(key) && model.has(key)) {
344             return model.get(key).asText();
345         }
346         return null;
347     }
348
349     private Boolean getBooleanDataFromJsonIfExists(JsonNode model, String key) {
350         if (model.has(key)) {
351             return model.get(key).asBoolean();
352         }
353         return false;
354     }
355
356     Map<String, Object> aggregateAllOtherProperties(JsonNode model, NodeType nodeType) {
357         Set<String> ignoreProperties = Stream.of(AAIBaseProperties.values())
358                 .map(AAIBaseProperties::getAaiKey).collect(toSet());
359         return Streams.fromIterator(model.fields())
360                 .filter(not(field -> StringUtils.equals(field.getKey(), nodeType.getId())))
361                 .filter(not(field -> StringUtils.equals(field.getKey(), nodeType.getName())))
362                 .filter(not(field -> ignoreProperties.contains(field.getKey())))
363                 .collect(toMap(Map.Entry::getKey, v -> ifTextualGetAsText(v.getValue())));
364     }
365
366     private Object ifTextualGetAsText(JsonNode jsonNode) {
367         return jsonNode.isTextual() ? jsonNode.asText() : jsonNode;
368     }
369 }