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