dc40cc2741ba57a857532b567322d9a1deaa165b
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * Copyright (C) 2021 Nordix Foundation.
4  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.clamp.controlloop.runtime.instantiation;
23
24 import com.google.gson.Gson;
25 import com.google.gson.internal.LinkedTreeMap;
26 import com.google.gson.reflect.TypeToken;
27 import java.lang.reflect.Type;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.UUID;
35 import java.util.function.UnaryOperator;
36 import java.util.stream.Collectors;
37 import java.util.stream.Stream;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.Status;
40 import lombok.AllArgsConstructor;
41 import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException;
42 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoop;
43 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElement;
44 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopOrderedState;
45 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopState;
46 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoops;
47 import org.onap.policy.clamp.controlloop.models.controlloop.persistence.provider.ControlLoopProvider;
48 import org.onap.policy.clamp.controlloop.models.messages.rest.GenericNameVersion;
49 import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.ControlLoopOrderStateResponse;
50 import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstancePropertiesResponse;
51 import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationCommand;
52 import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationResponse;
53 import org.onap.policy.clamp.controlloop.runtime.commissioning.CommissioningProvider;
54 import org.onap.policy.clamp.controlloop.runtime.supervision.SupervisionHandler;
55 import org.onap.policy.common.parameters.BeanValidationResult;
56 import org.onap.policy.common.parameters.ObjectValidationResult;
57 import org.onap.policy.common.parameters.ValidationResult;
58 import org.onap.policy.common.parameters.ValidationStatus;
59 import org.onap.policy.models.base.PfModelException;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaNameVersion;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
64 import org.springframework.stereotype.Component;
65
66 /**
67  * This class is dedicated to the Instantiation of Commissioned control loop.
68  */
69 @Component
70 @AllArgsConstructor
71 public class ControlLoopInstantiationProvider {
72     private static final String CONTROL_LOOP_NODE_TYPE = "org.onap.policy.clamp.controlloop.ControlLoop";
73     private static final String CONTROL_LOOP_NODE_ELEMENT_TYPE = "ControlLoopElement";
74     private static final String PARTICIPANT_ID_PROPERTY_KEY = "participant_id";
75     private static final String CL_ELEMENT_NAME = "name";
76     private static final String CL_ELEMENT_VERSION = "version";
77     private static final String INSTANCE_TEXT = "_Instance";
78
79     private static final Gson GSON = new Gson();
80
81     private final ControlLoopProvider controlLoopProvider;
82     private final CommissioningProvider commissioningProvider;
83     private final SupervisionHandler supervisionHandler;
84
85     private static final Object lockit = new Object();
86
87     /**
88      * Creates Instance Properties and Control Loop.
89      *
90      * @param serviceTemplate the service template
91      * @return the result of the instantiation operation
92      * @throws PfModelException on creation errors
93      */
94     public InstancePropertiesResponse createInstanceProperties(ToscaServiceTemplate serviceTemplate)
95         throws PfModelException {
96
97         String instanceName = generateSequentialInstanceName();
98         ControlLoop controlLoop = new ControlLoop();
99         Map<UUID, ControlLoopElement> controlLoopElements = new HashMap<>();
100
101         ToscaServiceTemplate toscaServiceTemplate = commissioningProvider
102             .getToscaServiceTemplate(null, null);
103
104         Map<String, ToscaNodeTemplate> persistedNodeTemplateMap = toscaServiceTemplate
105             .getToscaTopologyTemplate().getNodeTemplates();
106
107         Map<String, ToscaNodeTemplate> nodeTemplates =
108             deepCloneNodeTemplate(serviceTemplate);
109
110         nodeTemplates.forEach((key, template) -> {
111             ToscaNodeTemplate newNodeTemplate = new ToscaNodeTemplate();
112             String name = key + instanceName;
113             String version = template.getVersion();
114             String description = template.getDescription() + instanceName;
115             newNodeTemplate.setName(name);
116             newNodeTemplate.setVersion(version);
117             newNodeTemplate.setDescription(description);
118             newNodeTemplate.setProperties(new HashMap<>(template.getProperties()));
119             newNodeTemplate.setType(template.getType());
120             newNodeTemplate.setTypeVersion(template.getTypeVersion());
121             newNodeTemplate.setMetadata(template.getMetadata());
122
123             crateNewControlLoopInstance(instanceName, controlLoop, controlLoopElements, template, newNodeTemplate);
124
125             persistedNodeTemplateMap.put(name, newNodeTemplate);
126         });
127
128         ControlLoops controlLoops = new ControlLoops();
129
130         serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().putAll(persistedNodeTemplateMap);
131
132         controlLoop.setElements(controlLoopElements);
133         controlLoops.getControlLoopList().add(controlLoop);
134
135         return saveInstancePropertiesAndControlLoop(serviceTemplate, controlLoops);
136     }
137
138     /**
139      * Deletes Instance Properties.
140      *
141      * @param name the name of the control loop to delete
142      * @param version the version of the control loop to delete
143      * @return the result of the deletion
144      * @throws PfModelException on deletion errors
145      */
146     public InstantiationResponse deleteInstanceProperties(String name, String version) throws PfModelException {
147
148         String instanceName = getInstancePropertyName(name, version);
149
150         Map<String, ToscaNodeTemplate> filteredToscaNodeTemplateMap = new HashMap<>();
151
152         ToscaServiceTemplate toscaServiceTemplate = commissioningProvider.getToscaServiceTemplate(name, version);
153
154         toscaServiceTemplate.getToscaTopologyTemplate()
155             .getNodeTemplates().forEach((key, nodeTemplate) -> {
156                 if (!nodeTemplate.getName().contains(instanceName)) {
157                     filteredToscaNodeTemplateMap.put(key, nodeTemplate);
158                 }
159             });
160
161         List<ToscaNodeTemplate> filteredToscaNodeTemplateList =
162             toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates().values().stream()
163                 .filter(nodeTemplate -> nodeTemplate.getName().contains(instanceName)).collect(Collectors.toList());
164
165         InstantiationResponse response = this.deleteControlLoop(name, version);
166
167         controlLoopProvider.deleteInstanceProperties(filteredToscaNodeTemplateMap, filteredToscaNodeTemplateList);
168
169         return response;
170     }
171
172     /**
173      * Create control loops.
174      *
175      * @param controlLoops the control loop
176      * @return the result of the instantiation operation
177      * @throws PfModelException on creation errors
178      */
179     public InstantiationResponse createControlLoops(ControlLoops controlLoops) throws PfModelException {
180
181         synchronized (lockit) {
182             for (ControlLoop controlLoop : controlLoops.getControlLoopList()) {
183                 var checkControlLoop = controlLoopProvider
184                     .getControlLoop(controlLoop.getKey().asIdentifier());
185                 if (checkControlLoop != null) {
186                     throw new PfModelException(Response.Status.BAD_REQUEST,
187                             controlLoop.getKey().asIdentifier() + " already defined");
188                 }
189             }
190             BeanValidationResult validationResult = validateControlLoops(controlLoops);
191             if (!validationResult.isValid()) {
192                 throw new PfModelException(Response.Status.BAD_REQUEST, validationResult.getResult());
193             }
194             controlLoopProvider.createControlLoops(controlLoops.getControlLoopList());
195         }
196
197         var response = new InstantiationResponse();
198         response.setAffectedControlLoops(controlLoops.getControlLoopList().stream()
199                 .map(cl -> cl.getKey().asIdentifier()).collect(Collectors.toList()));
200
201         return response;
202     }
203
204     /**
205      * Update control loops.
206      *
207      * @param controlLoops the control loop
208      * @return the result of the instantiation operation
209      * @throws PfModelException on update errors
210      */
211     public InstantiationResponse updateControlLoops(ControlLoops controlLoops) throws PfModelException {
212         synchronized (lockit) {
213             BeanValidationResult validationResult = validateControlLoops(controlLoops);
214             if (!validationResult.isValid()) {
215                 throw new PfModelException(Response.Status.BAD_REQUEST, validationResult.getResult());
216             }
217             controlLoopProvider.updateControlLoops(controlLoops.getControlLoopList());
218         }
219
220         var response = new InstantiationResponse();
221         response.setAffectedControlLoops(controlLoops.getControlLoopList().stream()
222                 .map(cl -> cl.getKey().asIdentifier()).collect(Collectors.toList()));
223
224         return response;
225     }
226
227     /**
228      * Validate ControlLoops.
229      *
230      * @param controlLoops ControlLoops to validate
231      * @return the result of validation
232      * @throws PfModelException if controlLoops is not valid
233      */
234     private BeanValidationResult validateControlLoops(ControlLoops controlLoops) throws PfModelException {
235
236         var result = new BeanValidationResult("ControlLoops", controlLoops);
237
238         for (ControlLoop controlLoop : controlLoops.getControlLoopList()) {
239             var subResult = new BeanValidationResult("entry " + controlLoop.getDefinition().getName(), controlLoop);
240
241             List<ToscaNodeTemplate> toscaNodeTemplates = commissioningProvider.getControlLoopDefinitions(
242                     controlLoop.getDefinition().getName(), controlLoop.getDefinition().getVersion());
243
244             if (toscaNodeTemplates.isEmpty()) {
245                 subResult.addResult(new ObjectValidationResult("ControlLoop", controlLoop.getDefinition().getName(),
246                         ValidationStatus.INVALID, "Commissioned control loop definition not FOUND"));
247             } else if (toscaNodeTemplates.size() > 1) {
248                 subResult.addResult(new ObjectValidationResult("ControlLoop", controlLoop.getDefinition().getName(),
249                         ValidationStatus.INVALID, "Commissioned control loop definition not VALID"));
250             } else {
251
252                 List<ToscaNodeTemplate> clElementDefinitions =
253                         commissioningProvider.getControlLoopElementDefinitions(toscaNodeTemplates.get(0));
254
255                 // @formatter:off
256                 Map<String, ToscaConceptIdentifier> definitions = clElementDefinitions
257                         .stream()
258                         .map(nodeTemplate -> nodeTemplate.getKey().asIdentifier())
259                         .collect(Collectors.toMap(ToscaConceptIdentifier::getName, UnaryOperator.identity()));
260                 // @formatter:on
261
262                 for (ControlLoopElement element : controlLoop.getElements().values()) {
263                     subResult.addResult(validateDefinition(definitions, element.getDefinition()));
264                 }
265             }
266             result.addResult(subResult);
267         }
268         return result;
269     }
270
271     /**
272      * Validate ToscaConceptIdentifier, checking if exist in ToscaConceptIdentifiers map.
273      *
274      * @param definitions map of all ToscaConceptIdentifiers
275      * @param definition ToscaConceptIdentifier to validate
276      * @return the validation result
277      */
278     private ValidationResult validateDefinition(Map<String, ToscaConceptIdentifier> definitions,
279             ToscaConceptIdentifier definition) {
280         var result = new BeanValidationResult("entry " + definition.getName(), definition);
281         ToscaConceptIdentifier identifier = definitions.get(definition.getName());
282         if (identifier == null) {
283             result.setResult(ValidationStatus.INVALID, "Not FOUND");
284         } else if (!identifier.equals(definition)) {
285             result.setResult(ValidationStatus.INVALID, "Version not matching");
286         }
287         return (result.isClean() ? null : result);
288     }
289
290     /**
291      * Delete the control loop with the given name and version.
292      *
293      * @param name the name of the control loop to delete
294      * @param version the version of the control loop to delete
295      * @return the result of the deletion
296      * @throws PfModelException on deletion errors
297      */
298     public InstantiationResponse deleteControlLoop(String name, String version) throws PfModelException {
299         var response = new InstantiationResponse();
300         synchronized (lockit) {
301             List<ControlLoop> controlLoops = controlLoopProvider.getControlLoops(name, version);
302             if (controlLoops.isEmpty()) {
303                 throw new PfModelException(Response.Status.NOT_FOUND, "Control Loop not found");
304             }
305             for (ControlLoop controlLoop : controlLoops) {
306                 if (!ControlLoopState.UNINITIALISED.equals(controlLoop.getState())) {
307                     throw new PfModelException(Response.Status.BAD_REQUEST,
308                             "Control Loop State is still " + controlLoop.getState());
309                 }
310             }
311
312             response.setAffectedControlLoops(Collections
313                     .singletonList(controlLoopProvider.deleteControlLoop(name, version).getKey().asIdentifier()));
314         }
315         return response;
316     }
317
318     /**
319      * Get the requested control loops.
320      *
321      * @param name the name of the control loop to get, null for all control loops
322      * @param version the version of the control loop to get, null for all control loops
323      * @return the control loops
324      * @throws PfModelException on errors getting control loops
325      */
326     public ControlLoops getControlLoops(String name, String version) throws PfModelException {
327         var controlLoops = new ControlLoops();
328         controlLoops.setControlLoopList(controlLoopProvider.getControlLoops(name, version));
329
330         return controlLoops;
331     }
332
333     /**
334      * Issue a command to control loops, setting their ordered state.
335      *
336      * @param command the command to issue to control loops
337      * @return the result of the initiation command
338      * @throws PfModelException on errors setting the ordered state on the control loops
339      * @throws ControlLoopException on ordered state invalid
340      */
341     public InstantiationResponse issueControlLoopCommand(InstantiationCommand command)
342             throws ControlLoopException, PfModelException {
343
344         if (command.getOrderedState() == null) {
345             throw new ControlLoopException(Status.BAD_REQUEST, "ordered state invalid or not specified on command");
346         }
347
348         synchronized (lockit) {
349             List<ControlLoop> controlLoops = new ArrayList<>(command.getControlLoopIdentifierList().size());
350             for (ToscaConceptIdentifier id : command.getControlLoopIdentifierList()) {
351                 var controlLoop = controlLoopProvider.getControlLoop(id);
352                 controlLoop.setCascadedOrderedState(command.getOrderedState());
353                 controlLoops.add(controlLoop);
354             }
355             controlLoopProvider.updateControlLoops(controlLoops);
356         }
357
358         supervisionHandler.triggerControlLoopSupervision(command.getControlLoopIdentifierList());
359         var response = new InstantiationResponse();
360         response.setAffectedControlLoops(command.getControlLoopIdentifierList());
361
362         return response;
363     }
364
365     /**
366      * Gets a list of control loops with it's ordered state.
367      *
368      * @param name the name of the control loop to get, null for all control loops
369      * @param version the version of the control loop to get, null for all control loops
370      * @return a list of Instantiation Command
371      * @throws PfModelException on errors getting control loops
372      */
373     public ControlLoopOrderStateResponse getInstantiationOrderState(String name, String version)
374         throws PfModelException {
375
376         List<ControlLoop> controlLoops = controlLoopProvider.getControlLoops(name, version);
377
378         var response = new ControlLoopOrderStateResponse();
379
380         controlLoops.forEach(controlLoop -> {
381             var genericNameVersion = new GenericNameVersion();
382             genericNameVersion.setName(controlLoop.getName());
383             genericNameVersion.setVersion(controlLoop.getVersion());
384             response.getControlLoopIdentifierList().add(genericNameVersion);
385         });
386
387         return response;
388     }
389
390     /**
391      * Saves Instance Properties and Control Loop.
392      *
393      * @param serviceTemplate the service template
394      * @param controlLoops a list of control loops
395      * @return the result of the instance properties and instantiation operation
396      * @throws PfModelException on creation errors
397      */
398     private InstancePropertiesResponse saveInstancePropertiesAndControlLoop(
399         ToscaServiceTemplate serviceTemplate, ControlLoops controlLoops) throws PfModelException {
400
401         var response = new InstancePropertiesResponse();
402
403         Map<String, ToscaNodeTemplate> toscaSavedNodeTemplate;
404
405         synchronized (lockit) {
406             for (ControlLoop controlLoop : controlLoops.getControlLoopList()) {
407                 var checkControlLoop = controlLoopProvider.getControlLoop(controlLoop.getKey().asIdentifier());
408                 if (checkControlLoop != null) {
409                     throw new PfModelException(Response.Status.BAD_REQUEST,
410                         controlLoop.getKey().asIdentifier() + " already defined");
411                 }
412             }
413
414             toscaSavedNodeTemplate = controlLoopProvider.saveInstanceProperties(serviceTemplate);
415
416             controlLoopProvider.createControlLoops(controlLoops.getControlLoopList());
417
418         }
419
420         List<ToscaConceptIdentifier> affectedControlLoops = controlLoops.getControlLoopList().stream()
421             .map(cl -> cl.getKey().asIdentifier()).collect(Collectors.toList());
422
423         List<ToscaConceptIdentifier> toscaAffectedProperties = toscaSavedNodeTemplate.values().stream()
424             .map(template -> template.getKey().asIdentifier()).collect(Collectors.toList());
425
426         response.setAffectedInstanceProperties(Stream.of(affectedControlLoops, toscaAffectedProperties)
427             .flatMap(Collection::stream).collect(Collectors.toList()));
428
429         return response;
430     }
431
432     /**
433      * Crates a new Control Loop instance.
434      * @param instanceName Control Loop Instance name
435      * @param controlLoop empty Control Loop
436      * @param controlLoopElements new Control Loop Element map
437      * @param template original Cloned Tosca Node Template
438      * @param newNodeTemplate new Tosca Node Template
439      */
440     private void crateNewControlLoopInstance(String instanceName, ControlLoop controlLoop,
441                                              Map<UUID, ControlLoopElement> controlLoopElements,
442                                              ToscaNodeTemplate template,
443                                              ToscaNodeTemplate newNodeTemplate) {
444         if (template.getType().equals(CONTROL_LOOP_NODE_TYPE)) {
445             controlLoop.setDefinition(getControlLoopDefinition(newNodeTemplate));
446         }
447
448         if (template.getType().contains(CONTROL_LOOP_NODE_ELEMENT_TYPE)) {
449             ControlLoopElement controlLoopElement = getControlLoopElement(instanceName, newNodeTemplate);
450             controlLoopElements.put(controlLoopElement.getId(), controlLoopElement);
451         }
452
453         controlLoop.setName("PMSH" + instanceName);
454         controlLoop.setVersion(template.getVersion());
455         controlLoop.setDescription("PMSH control loop " + instanceName);
456         controlLoop.setState(ControlLoopState.UNINITIALISED);
457         controlLoop.setOrderedState(ControlLoopOrderedState.UNINITIALISED);
458     }
459
460
461     /**
462      * Get's the instance property name of the control loop.
463      *
464      * @param name the name of the control loop to get, null for all control loops
465      * @param version the version of the control loop to get, null for all control loops
466      * @return the instance name of the control loop instance properties
467      * @throws PfModelException on errors getting control loops
468      */
469     private String getInstancePropertyName(String name, String version) throws PfModelException {
470         List<String> toscaDefinitionsNames =
471             controlLoopProvider.getControlLoops(name, version).stream().map(ControlLoop::getDefinition)
472                 .map(ToscaNameVersion::getName).collect(Collectors.toList());
473
474         return toscaDefinitionsNames.stream().reduce("", (s1, s2) -> {
475
476             if (s2.contains(INSTANCE_TEXT)) {
477                 String[] instances = s2.split(INSTANCE_TEXT);
478
479                 return INSTANCE_TEXT + instances[1];
480             }
481
482             return s1;
483         });
484     }
485
486     /**
487      * Generates Instance Name in sequential order and return it to append to the Node Template Name.
488      *
489      * @return instanceName
490      */
491     private String generateSequentialInstanceName() {
492         List<ToscaNodeTemplate> nodeTemplates = controlLoopProvider.getNodeTemplates(null, null);
493
494         int instanceNumber =
495             nodeTemplates.stream().map(ToscaNodeTemplate::getName)
496                 .filter(name -> name.contains(INSTANCE_TEXT)).map(n -> {
497                     String[] defNameArr = n.split(INSTANCE_TEXT);
498
499                     return Integer.parseInt(defNameArr[1]);
500                 }).reduce(0, Math::max);
501
502         return INSTANCE_TEXT + (instanceNumber + 1);
503     }
504
505     /**
506      * Retrieves Control Loop Definition.
507      *
508      * @param template tosca node template
509      * @return control loop definition
510      */
511     private ToscaConceptIdentifier getControlLoopDefinition(ToscaNodeTemplate template) {
512         ToscaConceptIdentifier definition = new ToscaConceptIdentifier();
513         definition.setName(template.getName());
514         definition.setVersion(template.getVersion());
515
516         return definition;
517     }
518
519     /**
520      * Retrieves Control Loop Element.
521      *
522      * @param instanceName instance name to be appended to participant name
523      * @param template tosca node template
524      * @return a control loop element
525      */
526     @SuppressWarnings("unchecked")
527     private ControlLoopElement getControlLoopElement(String instanceName, ToscaNodeTemplate template) {
528         ControlLoopElement controlLoopElement = new ControlLoopElement();
529         ToscaConceptIdentifier definition = new ToscaConceptIdentifier();
530         definition.setName(template.getName());
531         definition.setVersion(template.getVersion());
532         controlLoopElement.setDefinition(definition);
533
534         LinkedTreeMap<String, Object> participantId = (LinkedTreeMap<String, Object>) template.getProperties()
535             .get(PARTICIPANT_ID_PROPERTY_KEY);
536
537         ToscaConceptIdentifier participantIdAndType = new ToscaConceptIdentifier();
538         participantIdAndType.setName(participantId.get(CL_ELEMENT_NAME) + instanceName);
539         participantIdAndType.setVersion(String.valueOf(participantId.get(CL_ELEMENT_VERSION)));
540
541         controlLoopElement.setParticipantType(participantIdAndType);
542         controlLoopElement.setParticipantId(participantIdAndType);
543
544         return controlLoopElement;
545     }
546
547     /**
548      * Deep clones ToscaNodeTemplate.
549      *
550      * @param serviceTemplate ToscaServiceTemplate
551      * @return a cloned Hash Map of ToscaNodeTemplate
552      */
553     private Map<String, ToscaNodeTemplate> deepCloneNodeTemplate(ToscaServiceTemplate serviceTemplate) {
554         String jsonString = GSON.toJson(serviceTemplate.getToscaTopologyTemplate().getNodeTemplates());
555
556         Type type = new TypeToken<HashMap<String, ToscaNodeTemplate>>() {}.getType();
557
558         return GSON.fromJson(jsonString, type);
559     }
560
561 }