Support functions in TOSCA Simple Profile in YAML
[sdc.git] / openecomp-be / lib / openecomp-sdc-vendor-software-product-lib / openecomp-sdc-vendor-software-product-core / src / main / java / org / openecomp / sdc / vendorsoftwareproduct / services / impl / composition / CompositionDataExtractorImpl.java
1 /*
2  * Copyright © 2016-2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.openecomp.sdc.vendorsoftwareproduct.services.impl.composition;
18
19 import com.fasterxml.jackson.databind.ObjectMapper;
20 import org.apache.commons.collections4.CollectionUtils;
21 import org.apache.commons.collections4.MapUtils;
22 import org.onap.sdc.tosca.datatypes.model.NodeTemplate;
23 import org.onap.sdc.tosca.datatypes.model.ParameterDefinition;
24 import org.onap.sdc.tosca.datatypes.model.RequirementAssignment;
25 import org.onap.sdc.tosca.datatypes.model.ServiceTemplate;
26 import org.onap.sdc.tosca.services.ToscaExtensionYamlUtil;
27 import org.openecomp.sdc.common.errors.CoreException;
28 import org.openecomp.sdc.logging.api.Logger;
29 import org.openecomp.sdc.logging.api.LoggerFactory;
30 import org.openecomp.sdc.tosca.datatypes.*;
31 import org.openecomp.sdc.tosca.errors.ToscaInvalidEntryNotFoundErrorBuilder;
32 import org.openecomp.sdc.tosca.errors.ToscaInvalidSubstituteNodeTemplateErrorBuilder;
33 import org.openecomp.sdc.tosca.errors.ToscaMissingSubstitutionMappingForReqCapErrorBuilder;
34 import org.openecomp.sdc.tosca.services.ToscaAnalyzerService;
35 import org.openecomp.sdc.tosca.services.ToscaConstants;
36 import org.openecomp.sdc.tosca.services.impl.ToscaAnalyzerServiceImpl;
37 import org.openecomp.sdc.vendorsoftwareproduct.services.composition.CompositionDataExtractor;
38 import org.openecomp.sdc.vendorsoftwareproduct.types.composition.*;
39
40 import java.util.*;
41 import java.util.stream.Collectors;
42
43 public class CompositionDataExtractorImpl implements CompositionDataExtractor {
44
45   protected static Logger logger;
46   private static ToscaAnalyzerService toscaAnalyzerService;
47   static {
48     logger = LoggerFactory.getLogger(CompositionDataExtractorImpl.class);
49     toscaAnalyzerService = new ToscaAnalyzerServiceImpl();
50   }
51
52   /**
53    * Extract service composition data composition data.
54    *
55    * @param toscaServiceModel the tosca service model
56    * @return the composition data
57    */
58   public CompositionData extractServiceCompositionData(ToscaServiceModel toscaServiceModel) {
59     ExtractCompositionDataContext context = new ExtractCompositionDataContext();
60     String entryDefinitionServiceTemplateFileName =
61         toscaServiceModel.getEntryDefinitionServiceTemplate();
62     ServiceTemplate entryDefinitionServiceTemplate =
63         toscaServiceModel.getServiceTemplates().get(entryDefinitionServiceTemplateFileName);
64     extractServiceCompositionData(entryDefinitionServiceTemplateFileName,
65         entryDefinitionServiceTemplate, toscaServiceModel, context);
66
67     CompositionData compositionData = new CompositionData();
68     compositionData.setNetworks(context.getNetworks());
69     compositionData.setComponents(context.getComponents());
70     return compositionData;
71   }
72
73   private void extractServiceCompositionData(String serviceTemplateFileName,
74                                                     ServiceTemplate serviceTemplate,
75                                                     ToscaServiceModel toscaServiceModel,
76                                                     ExtractCompositionDataContext context) {
77     if (context.getHandledServiceTemplates().contains(serviceTemplateFileName)) {
78       return;
79     }
80     context.addNetworks(extractNetworks(serviceTemplate, toscaServiceModel));
81     extractComponents(serviceTemplate, toscaServiceModel, context);
82     handleSubstitution(serviceTemplate, toscaServiceModel, context);
83     context.addHandledServiceTemplates(serviceTemplateFileName);
84   }
85
86   private void handleSubstitution(ServiceTemplate serviceTemplate,
87                                          ToscaServiceModel toscaServiceModel,
88                                          ExtractCompositionDataContext context) {
89     Map<String, NodeTemplate> substitutableNodeTemplates =
90         toscaAnalyzerService.getSubstitutableNodeTemplates(serviceTemplate);
91
92     if (substitutableNodeTemplates != null) {
93       for (String substitutableNodeTemplateId : substitutableNodeTemplates.keySet()) {
94         handleSubstitutableNodeTemplate(serviceTemplate, toscaServiceModel,
95             substitutableNodeTemplateId,
96             substitutableNodeTemplates.get(substitutableNodeTemplateId), context);
97       }
98     }
99   }
100
101   private void handleSubstitutableNodeTemplate(ServiceTemplate serviceTemplate,
102                                                       ToscaServiceModel toscaServiceModel,
103                                                       String substitutableNodeTemplateId,
104                                                       NodeTemplate substitutableNodeTemplate,
105                                                       ExtractCompositionDataContext context) {
106     ToscaExtensionYamlUtil toscaExtensionYamlUtil = new ToscaExtensionYamlUtil();
107     Optional<String> substituteServiceTemplateFileName = toscaAnalyzerService
108         .getSubstituteServiceTemplateName(substitutableNodeTemplateId, substitutableNodeTemplate);
109     if (!substituteServiceTemplateFileName.isPresent()) {
110       throw new CoreException(
111           new ToscaInvalidSubstituteNodeTemplateErrorBuilder(substitutableNodeTemplateId).build());
112     }
113     if (context.getHandledServiceTemplates().contains(substituteServiceTemplateFileName.get())) {
114       //each substitution is should be handled once, and will get the connection to the upper
115       // service level according to the first one which was processed
116       return;
117     }
118
119     ServiceTemplate substituteServiceTemplate =
120         toscaServiceModel.getServiceTemplates().get(substituteServiceTemplateFileName.get());
121     extractServiceCompositionData(substituteServiceTemplateFileName.get(),
122         substituteServiceTemplate, toscaServiceModel, context);
123
124     List<Map<String, RequirementAssignment>> substitutableRequirements =
125         substitutableNodeTemplate.getRequirements();
126
127     if (CollectionUtils.isEmpty(substitutableRequirements)) {
128       return;
129     }
130
131     for (Map<String, RequirementAssignment> substitutableReq : substitutableRequirements) {
132       substitutableReq.keySet().stream().filter(reqId -> {
133         RequirementAssignment reqAssignment = toscaExtensionYamlUtil
134             .yamlToObject(toscaExtensionYamlUtil.objectToYaml(substitutableReq.get(reqId)),
135                 RequirementAssignment.class);
136         return isLinkToNetworkRequirementAssignment(reqAssignment);
137       }).forEach(reqId -> {
138         RequirementAssignment linkToNetworkRequirement = toscaExtensionYamlUtil
139             .yamlToObject(toscaExtensionYamlUtil.objectToYaml(substitutableReq.get(reqId)),
140                 RequirementAssignment.class);
141         String connectedNodeId = linkToNetworkRequirement.getNode();
142         Optional<NodeTemplate> connectedNodeTemplate =
143             toscaAnalyzerService.getNodeTemplateById(serviceTemplate, connectedNodeId);
144
145         if (connectedNodeTemplate.isPresent() && toscaAnalyzerService
146             .isTypeOf(connectedNodeTemplate.get(), ToscaNodeType.NATIVE_NETWORK,
147                 serviceTemplate, toscaServiceModel)) {
148           Optional<Map.Entry<String, NodeTemplate>> mappedNodeTemplate = toscaAnalyzerService
149               .getSubstitutionMappedNodeTemplateByExposedReq(
150                   substituteServiceTemplateFileName.get(), substituteServiceTemplate, reqId);
151           if (!mappedNodeTemplate.isPresent()) {
152             throw new CoreException(new ToscaMissingSubstitutionMappingForReqCapErrorBuilder(
153                 ToscaMissingSubstitutionMappingForReqCapErrorBuilder.MappingExposedEntry
154                     .REQUIREMENT, connectedNodeId).build());
155           }
156
157           if (toscaAnalyzerService.isTypeOf(mappedNodeTemplate.get().getValue(),
158               ToscaNodeType.NATIVE_NETWORK_PORT, serviceTemplate,
159               toscaServiceModel)) {
160             Nic port = context.getNics().get(mappedNodeTemplate.get().getKey());
161             if (port != null) {
162               port.setNetworkName(connectedNodeId);
163             } else {
164               logger.warn(
165                   "Different ports define for the same component which is used in different "
166                       + "substitution service templates.");
167             }
168           }
169         } else if (!connectedNodeTemplate.isPresent()) {
170           throw new CoreException(
171               new ToscaInvalidEntryNotFoundErrorBuilder("Node Template", connectedNodeId).build());
172         }
173       });
174     }
175   }
176
177   private boolean isLinkToNetworkRequirementAssignment(RequirementAssignment requirement) {
178     return toscaAnalyzerService.isDesiredRequirementAssignment(requirement,
179         ToscaCapabilityType.NATIVE_NETWORK_LINKABLE, null,
180         ToscaRelationshipType.NATIVE_NETWORK_LINK_TO);
181   }
182
183
184   private void connectPortToNetwork(Nic port, NodeTemplate portNodeTemplate) {
185     List<RequirementAssignment> linkRequirementsToNetwork =
186         toscaAnalyzerService.getRequirements(portNodeTemplate, ToscaConstants.LINK_REQUIREMENT_ID);
187
188     //port is connected to one network
189     for (RequirementAssignment linkRequirementToNetwork : linkRequirementsToNetwork) {
190       port.setNetworkName(linkRequirementToNetwork.getNode());
191     }
192   }
193
194   /*
195   return Map with key - compute node template id, value - list of connected port node template id
196    */
197   private Map<String, List<String>> getComputeToPortsConnection(
198       Map<String, NodeTemplate> portNodeTemplates) {
199     Map<String, List<String>> computeToPortConnection = new HashMap<>();
200     if (MapUtils.isEmpty(portNodeTemplates)) {
201       return computeToPortConnection;
202     }
203     for (String portId : portNodeTemplates.keySet()) {
204       List<RequirementAssignment> bindingRequirementsToCompute = toscaAnalyzerService
205           .getRequirements(portNodeTemplates.get(portId), ToscaConstants.BINDING_REQUIREMENT_ID);
206       for (RequirementAssignment bindingRequirementToCompute : bindingRequirementsToCompute) {
207         computeToPortConnection
208             .putIfAbsent(bindingRequirementToCompute.getNode(), new ArrayList<>());
209         computeToPortConnection.get(bindingRequirementToCompute.getNode()).add(portId);
210       }
211     }
212     return computeToPortConnection;
213   }
214
215   private void extractComponents(ServiceTemplate serviceTemplate,
216                                         ToscaServiceModel toscaServiceModel,
217                                         ExtractCompositionDataContext context) {
218     Map<String, NodeTemplate> computeNodeTemplates = toscaAnalyzerService
219         .getNodeTemplatesByType(serviceTemplate, ToscaNodeType.NATIVE_COMPUTE,
220             toscaServiceModel);
221     if (MapUtils.isEmpty(computeNodeTemplates)) {
222       return;
223     }
224     Map<String, List<String>> imageNodeTemplates = getComponentImages(computeNodeTemplates,
225         toscaServiceModel);
226     Map<String, List<String>> computeFlavorNodeTemplates =
227         getComponentComputeFlavor(computeNodeTemplates, toscaServiceModel);
228     Map<String, NodeTemplate> portNodeTemplates = toscaAnalyzerService
229         .getNodeTemplatesByType(serviceTemplate, ToscaNodeType.NATIVE_NETWORK_PORT,
230             toscaServiceModel);
231     Map<String, List<String>> computeToPortsConnection =
232         getComputeToPortsConnection(portNodeTemplates);
233     Map<String, List<String>> computesGroupedByType =
234         getNodeTemplatesGroupedByType(computeNodeTemplates);
235
236     computesGroupedByType.keySet()
237         .stream()
238         .filter(nodeType ->
239             !context.getCreatedComponents().contains(nodeType))
240         .forEach(nodeType -> extractComponent(serviceTemplate, computeToPortsConnection,
241             computesGroupedByType, imageNodeTemplates, computeFlavorNodeTemplates, nodeType,
242             context));
243   }
244
245   private Map<String,List<String>> getComponentImages(Map<String, NodeTemplate>
246                                                           computeNodeTemplates,
247                                                       ToscaServiceModel toscaServiceModel) {
248     return getComponentProperty(ToscaConstants.COMPUTE_IMAGE, computeNodeTemplates, toscaServiceModel);
249   }
250
251   private Map<String,List<String>> getComponentComputeFlavor(Map<String, NodeTemplate>
252                                                                  computeNodeTemplates,
253                                                              ToscaServiceModel toscaServiceModel) {
254     return getComponentProperty(ToscaConstants.COMPUTE_FLAVOR, computeNodeTemplates, toscaServiceModel);
255   }
256
257   private Map<String, List<String>> getComponentProperty(String propertyName,
258                                                          Map<String, NodeTemplate> computeNodeTemplates,
259                                                          ToscaServiceModel toscaServiceModel) {
260     Map<String,List<String>> componentPropertyValues = new HashMap<>();
261     for (String component : computeNodeTemplates.keySet()) {
262       List<String> computes = new ArrayList<>();
263       Map<String,Object> properties =  computeNodeTemplates.get(component).getProperties();
264
265       if(MapUtils.isEmpty(properties)){
266         continue;
267       }
268
269       List<Object> computesList = properties.entrySet()
270           .stream()
271           .filter(map -> map.getKey().equals(propertyName))
272           .map(Map.Entry::getValue)
273           .collect(Collectors.toList());
274       for (Object obj : computesList) {
275         if (obj instanceof String) {
276           computes.add((String) obj);
277         } else {
278           Map<String, String> objMap = new ObjectMapper().convertValue(obj, Map.class);
279           computes.add(getInputs(toscaServiceModel, objMap.get("get_input")));
280         }
281       }
282       componentPropertyValues.put(component,computes);
283     }
284     return componentPropertyValues;
285   }
286
287   private String  getInputs(ToscaServiceModel toscaServiceModel, String inputValue) {
288     String mainTemplate = toscaServiceModel.getEntryDefinitionServiceTemplate();
289     List<ServiceTemplate> toscaServiceTemplates = toscaServiceModel.getServiceTemplates().entrySet()
290         .stream()
291         .filter(map -> map.getKey().equals(mainTemplate))
292         .map(Map.Entry::getValue)
293         .collect(Collectors.toList());
294     ServiceTemplate serviceTemplate = toscaServiceTemplates.get(0);
295
296     if (Objects.nonNull(serviceTemplate.getTopology_template())
297         && MapUtils.isNotEmpty(serviceTemplate.getTopology_template().getInputs())) {
298       for (Map.Entry<String, ParameterDefinition> inputEntry : serviceTemplate
299           .getTopology_template().getInputs().entrySet()) {
300         if (inputEntry.getKey().equals(inputValue)) {
301           String value;
302           try {
303             value= (String) inputEntry.getValue().get_default();
304           } catch (Exception e) {
305             logger.debug(e.getMessage(), e);
306             value = inputEntry.getValue().get_default().toString();
307           }
308         return value;
309         }
310       }
311     }
312     return inputValue;
313   }
314
315   private void extractComponent(ServiceTemplate serviceTemplate,
316                                        Map<String, List<String>> computeToPortsConnection,
317                                        Map<String, List<String>> computesGroupedByType,
318                                        Map<String, List<String>> imageList,
319                                        Map<String, List<String>> computeFlavorNodeTemplates,
320                                        String computeNodeType,
321                                        ExtractCompositionDataContext context) {
322     ComponentData component = new ComponentData();
323     component.setName(computeNodeType);
324     component.setDisplayName(getComponentDisplayName(component.getName()));
325     Component componentModel = new Component();
326     componentModel.setData(component);
327
328     String computeId = computesGroupedByType.get(computeNodeType).get(0);
329     List<String> connectedPortIds = computeToPortsConnection.get(computeId);
330     List<String> images = imageList.get(computeId);
331     List<String> computeFlavors = computeFlavorNodeTemplates.get(computeId);
332
333     if (CollectionUtils.isNotEmpty(connectedPortIds)) {
334       componentModel.setNics(new ArrayList<>());
335       componentModel.setImages(new ArrayList<>());
336       componentModel.setCompute(new ArrayList<>());
337
338       connectedPortIds.forEach(portId -> {
339         Nic port = extractPort(serviceTemplate, portId);
340         componentModel.getNics().add(port);
341         context.addNic(portId, port);
342       });
343
344       if (CollectionUtils.isNotEmpty(images)) {
345         images.forEach(image -> {
346           Image img = new Image(image);
347           componentModel.getImages().add(img);
348           context.addImage(image, img);
349         });
350       }
351
352       if (CollectionUtils.isNotEmpty(computeFlavors)) {
353         computeFlavors.forEach(flavor -> {
354           ComputeData computeFlavor = new ComputeData(flavor);
355           componentModel.getCompute().add(computeFlavor);
356           context.addCompute(flavor, computeFlavor);
357         });
358       }
359     }
360     context.addComponent(componentModel);
361     context.getCreatedComponents().add(computeNodeType);
362   }
363
364   private Nic extractPort(ServiceTemplate serviceTemplate, String portNodeTemplateId) {
365     Optional<NodeTemplate> portNodeTemplate =
366         toscaAnalyzerService.getNodeTemplateById(serviceTemplate, portNodeTemplateId);
367     if (portNodeTemplate.isPresent()) {
368       Nic port = new Nic();
369       port.setName(portNodeTemplateId);
370       connectPortToNetwork(port, portNodeTemplate.get());
371       return port;
372     } else {
373       throw new CoreException(
374           new ToscaInvalidEntryNotFoundErrorBuilder("Node Template", portNodeTemplateId).build());
375     }
376   }
377
378   private Map<String, List<String>> getNodeTemplatesGroupedByType(
379       Map<String, NodeTemplate> nodeTemplates) {
380     Map<String, List<String>> nodeTemplatesGrouped =
381         new HashMap<>();   //key - node type, value - list of node ids with this type
382     for (String nodeId : nodeTemplates.keySet()) {
383       String nodeType = nodeTemplates.get(nodeId).getType();
384       nodeTemplatesGrouped.putIfAbsent(nodeType, new ArrayList<>());
385       nodeTemplatesGrouped.get(nodeType).add(nodeId);
386     }
387     return nodeTemplatesGrouped;
388   }
389
390   private List<Network> extractNetworks(ServiceTemplate serviceTemplate,
391                                                ToscaServiceModel toscaServiceModel) {
392     List<Network> networks = new ArrayList<>();
393     Map<String, NodeTemplate> networkNodeTemplates = toscaAnalyzerService
394         .getNodeTemplatesByType(serviceTemplate, ToscaNodeType.NATIVE_NETWORK,
395             toscaServiceModel);
396     if (MapUtils.isEmpty(networkNodeTemplates)) {
397       return networks;
398     }
399     for (String networkId : networkNodeTemplates.keySet()) {
400       Network network = new Network();
401       network.setName(networkId);
402       Optional<Boolean> networkDhcpValue =
403           getNetworkDhcpValue(serviceTemplate, networkNodeTemplates.get(networkId));
404       network.setDhcp(networkDhcpValue.orElse(true));
405       networks.add(network);
406     }
407     return networks;
408   }
409
410   //dhcp default value is true
411   private Optional<Boolean> getNetworkDhcpValue(ServiceTemplate serviceTemplate,
412                                                        NodeTemplate networkNodeTemplate) {
413     if (networkNodeTemplate == null) {
414       return Optional.empty();
415     }
416     if (networkNodeTemplate.getProperties() == null
417         || networkNodeTemplate.getProperties().get(ToscaConstants.DHCP_ENABLED_PROPERTY_NAME)
418         == null) {
419       return Optional.of(true);
420     }
421
422     Object dhcp =
423         networkNodeTemplate.getProperties().get(ToscaConstants.DHCP_ENABLED_PROPERTY_NAME);
424     if (dhcp instanceof String) {
425       return Optional.of(Boolean.valueOf((String) dhcp));
426     } else if (dhcp instanceof Boolean) {
427       return Optional.of((Boolean) dhcp);
428     } else if (dhcp instanceof Map) {
429       String inputParameterName =
430           (String) ((Map) dhcp).get(ToscaFunctions.GET_INPUT.getFunctionName());
431       if (inputParameterName != null) {
432         ParameterDefinition inputParameterDefinition =
433             serviceTemplate.getTopology_template().getInputs().get(inputParameterName);
434         if (inputParameterDefinition != null) {
435           if (inputParameterDefinition.get_default() != null) {
436             return Optional.of(Boolean.valueOf(inputParameterDefinition.get_default().toString()));
437           }
438         } else {
439           throw new CoreException(
440               new ToscaInvalidEntryNotFoundErrorBuilder("Input Parameter", inputParameterName)
441                   .build());
442         }
443       }
444     }
445     return Optional.of(true);
446   }
447
448   @Override
449   public String getComponentDisplayName(String componentName) {
450     if (componentName == null) {
451       return null;
452     }
453     String delimiterChar = ".";
454     if (componentName.contains(delimiterChar)) {
455       return componentName.substring(componentName.lastIndexOf(delimiterChar) + 1);
456     }
457     return componentName;
458
459   }
460
461 }