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