c732654f2a88b72316eb752d78bcf295b168c364
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * Copyright (C) 2021-2024 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.acm.runtime.instantiation;
23
24 import jakarta.validation.Valid;
25 import jakarta.ws.rs.core.Response.Status;
26 import java.util.UUID;
27 import java.util.stream.Collectors;
28 import lombok.NonNull;
29 import lombok.RequiredArgsConstructor;
30 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
31 import org.onap.policy.clamp.acm.runtime.supervision.SupervisionAcHandler;
32 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
33 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
34 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
35 import org.onap.policy.clamp.models.acm.concepts.DeployState;
36 import org.onap.policy.clamp.models.acm.concepts.LockState;
37 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
38 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
39 import org.onap.policy.clamp.models.acm.concepts.SubState;
40 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
41 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
42 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
43 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
44 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
45 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
46 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
47 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
48 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
49 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
50 import org.onap.policy.common.parameters.BeanValidationResult;
51 import org.onap.policy.common.parameters.ObjectValidationResult;
52 import org.onap.policy.common.parameters.ValidationStatus;
53 import org.onap.policy.models.base.PfKey;
54 import org.onap.policy.models.base.PfModelRuntimeException;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.springframework.stereotype.Service;
58 import org.springframework.transaction.annotation.Transactional;
59
60 /**
61  * This class is dedicated to the Instantiation of Commissioned automation composition.
62  */
63 @Service
64 @Transactional
65 @RequiredArgsConstructor
66 public class AutomationCompositionInstantiationProvider {
67     private static final String DO_NOT_MATCH = " do not match with ";
68
69     private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
70
71     private final AutomationCompositionProvider automationCompositionProvider;
72     private final AcDefinitionProvider acDefinitionProvider;
73     private final AcInstanceStateResolver acInstanceStateResolver;
74     private final SupervisionAcHandler supervisionAcHandler;
75     private final ParticipantProvider participantProvider;
76     private final AcRuntimeParameterGroup acRuntimeParameterGroup;
77
78     /**
79      * Create automation composition.
80      *
81      * @param compositionId The UUID of the automation composition definition
82      * @param automationComposition the automation composition
83      * @return the result of the instantiation operation
84      */
85     public InstantiationResponse createAutomationComposition(UUID compositionId,
86             AutomationComposition automationComposition) {
87         if (!compositionId.equals(automationComposition.getCompositionId())) {
88             throw new PfModelRuntimeException(Status.BAD_REQUEST,
89                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
90         }
91         var checkAutomationCompositionOpt =
92                 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
93         if (checkAutomationCompositionOpt.isPresent()) {
94             throw new PfModelRuntimeException(Status.BAD_REQUEST,
95                     automationComposition.getKey().asIdentifier() + " already defined");
96         }
97
98         var validationResult = validateAutomationComposition(automationComposition);
99         if (!validationResult.isValid()) {
100             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
101         }
102         automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
103
104         return createInstantiationResponse(automationComposition);
105     }
106
107     private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
108         var response = new InstantiationResponse();
109         response.setInstanceId(automationComposition.getInstanceId());
110         response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
111         return response;
112     }
113
114     /**
115      * Update automation composition.
116      *
117      * @param compositionId The UUID of the automation composition definition
118      * @param automationComposition the automation composition
119      * @return the result of the update
120      */
121     public InstantiationResponse updateAutomationComposition(UUID compositionId,
122             AutomationComposition automationComposition) {
123         var instanceId = automationComposition.getInstanceId();
124         var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
125         if (!compositionId.equals(acToUpdate.getCompositionId())) {
126             throw new PfModelRuntimeException(Status.BAD_REQUEST,
127                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
128         }
129         if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
130             acToUpdate.setElements(automationComposition.getElements());
131             acToUpdate.setName(automationComposition.getName());
132             acToUpdate.setVersion(automationComposition.getVersion());
133             acToUpdate.setDescription(automationComposition.getDescription());
134             acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
135             var validationResult = validateAutomationComposition(acToUpdate);
136             if (!validationResult.isValid()) {
137                 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
138             }
139             automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
140             return createInstantiationResponse(automationComposition);
141
142         }
143
144         if (automationComposition.getRestarting() != null) {
145             throw new PfModelRuntimeException(Status.BAD_REQUEST, "There is a restarting process, Update not allowed");
146         }
147
148         var deployOrder = DeployOrder.UPDATE;
149         var subOrder = SubOrder.NONE;
150
151         if (automationComposition.getCompositionTargetId() != null) {
152
153             if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
154                 subOrder = SubOrder.MIGRATE_PRECHECK;
155                 deployOrder = DeployOrder.NONE;
156             } else {
157                 deployOrder = DeployOrder.MIGRATE;
158             }
159         }
160         var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
161                 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
162                 acToUpdate.getStateChangeResult());
163         return switch (result) {
164             case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
165
166             case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
167
168             case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
169
170             default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
171                     "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
172         };
173     }
174
175     /**
176      * Update deployed AC Element properties.
177      *
178      * @param automationComposition the automation composition
179      * @param acToBeUpdated the composition to be updated
180      * @return the result of the update
181      */
182     private InstantiationResponse updateDeployedAutomationComposition(
183             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
184
185         // Iterate and update the element property values
186         for (var element : automationComposition.getElements().entrySet()) {
187             var elementId = element.getKey();
188             var dbAcElement = acToBeUpdated.getElements().get(elementId);
189             if (dbAcElement == null) {
190                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
191             }
192             AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
193         }
194
195         var validationResult = validateAutomationComposition(acToBeUpdated);
196         if (!validationResult.isValid()) {
197             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
198         }
199         // Publish property update event to the participants
200         supervisionAcHandler.update(acToBeUpdated);
201
202         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
203         return createInstantiationResponse(automationComposition);
204     }
205
206     private InstantiationResponse migrateAutomationComposition(
207         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
208
209         if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
210             throw new PfModelRuntimeException(Status.BAD_REQUEST,
211                 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
212         }
213
214         // Iterate and update the element property values
215         for (var element : automationComposition.getElements().entrySet()) {
216             var elementId = element.getKey();
217             var dbAcElement = acToBeUpdated.getElements().get(elementId);
218             if (dbAcElement == null) {
219                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
220             }
221             AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
222             var newDefinition = element.getValue().getDefinition();
223             var compatibility =
224                 newDefinition.asConceptKey().getCompatibility(dbAcElement.getDefinition().asConceptKey());
225             if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
226                 throw new PfModelRuntimeException(Status.BAD_REQUEST,
227                     dbAcElement.getDefinition() + " is not compatible with " + newDefinition);
228             }
229             if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR.equals(compatibility)) {
230                 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ",
231                     automationComposition.getInstanceId(), newDefinition, compatibility, dbAcElement.getDefinition());
232             }
233             dbAcElement.setDefinition(element.getValue().getDefinition());
234         }
235
236         var validationResult =
237             validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
238         if (!validationResult.isValid()) {
239             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
240         }
241         acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
242
243         // Publish migrate event to the participants
244         supervisionAcHandler.migrate(acToBeUpdated);
245
246         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
247         return createInstantiationResponse(automationComposition);
248     }
249
250     private InstantiationResponse migratePrecheckAc(
251             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
252
253         acToBeUpdated.setPrecheck(true);
254         var copyAc = new AutomationComposition(acToBeUpdated);
255         // Iterate and update the element property values
256         for (var element : automationComposition.getElements().entrySet()) {
257             var elementId = element.getKey();
258             var copyElement = copyAc.getElements().get(elementId);
259             if (copyElement == null) {
260                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
261             }
262             AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
263             var newDefinition = element.getValue().getDefinition();
264             var compatibility =
265                     newDefinition.asConceptKey().getCompatibility(copyElement.getDefinition().asConceptKey());
266             if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
267                 throw new PfModelRuntimeException(Status.BAD_REQUEST,
268                         copyElement.getDefinition() + " is not compatible with " + newDefinition);
269             }
270             copyElement.setDefinition(element.getValue().getDefinition());
271         }
272
273         var validationResult =
274                 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
275         if (!validationResult.isValid()) {
276             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
277         }
278         copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
279
280         // Publish migrate event to the participants
281         supervisionAcHandler.migratePrecheck(copyAc);
282
283         AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
284             SubState.MIGRATION_PRECHECKING);
285         acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
286
287         return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
288     }
289
290     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
291         return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
292     }
293
294     /**
295      * Validate AutomationComposition.
296      *
297      * @param automationComposition AutomationComposition to validate
298      * @param compositionId the composition id
299      * @return the result of validation
300      */
301     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
302             UUID compositionId) {
303
304         var result = new BeanValidationResult("AutomationComposition", automationComposition);
305         var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
306         if (acDefinitionOpt.isEmpty()) {
307             result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
308                     "Commissioned automation composition definition not found"));
309             return result;
310         }
311         if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
312             result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
313                     ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
314             return result;
315         }
316         if (acDefinitionOpt.get().getRestarting() != null) {
317             result.addResult(
318                     new ObjectValidationResult("ServiceTemplate.restarting", acDefinitionOpt.get().getRestarting(),
319                             ValidationStatus.INVALID, "There is a restarting process in composition"));
320             return result;
321         }
322         var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
323                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
324
325         participantProvider.verifyParticipantState(participantIds);
326
327         result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
328                 acDefinitionOpt.get().getServiceTemplate(),
329                 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
330
331         result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
332
333         if (result.isValid()) {
334             for (var element : automationComposition.getElements().values()) {
335                 var name = element.getDefinition().getName();
336                 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
337                 element.setParticipantId(participantId);
338             }
339         }
340
341         return result;
342     }
343
344     /**
345      * Get Automation Composition.
346      *
347      * @param compositionId The UUID of the automation composition definition
348      * @param instanceId The UUID of the automation composition instance
349      * @return the Automation Composition
350      */
351     @Transactional(readOnly = true)
352     public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
353         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
354         if (!compositionId.equals(automationComposition.getCompositionId())
355                         && !compositionId.equals(automationComposition.getCompositionTargetId())) {
356             throw new PfModelRuntimeException(Status.BAD_REQUEST,
357                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
358         }
359         return automationComposition;
360     }
361
362     /**
363      * Delete the automation composition with the given name and version.
364      *
365      * @param compositionId The UUID of the automation composition definition
366      * @param instanceId The UUID of the automation composition instance
367      * @return the result of the deletion
368      */
369     public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
370         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
371         if (!compositionId.equals(automationComposition.getCompositionId())) {
372             throw new PfModelRuntimeException(Status.BAD_REQUEST,
373                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
374         }
375         if (!DeployState.UNDEPLOYED.equals(automationComposition.getDeployState())
376                 && !DeployState.DELETING.equals(automationComposition.getDeployState())) {
377             throw new PfModelRuntimeException(Status.BAD_REQUEST,
378                     "Automation composition state is still " + automationComposition.getDeployState());
379         }
380         if (DeployState.DELETING.equals(automationComposition.getDeployState())
381                 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
382             throw new PfModelRuntimeException(Status.BAD_REQUEST,
383                     "Automation composition state is still " + automationComposition.getDeployState());
384         }
385         if (automationComposition.getRestarting() != null) {
386             throw new PfModelRuntimeException(Status.BAD_REQUEST, "There is a restarting process, Delete not allowed");
387         }
388         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
389         var participantIds = acDefinition.getElementStateMap().values().stream()
390             .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
391         participantProvider.verifyParticipantState(participantIds);
392         supervisionAcHandler.delete(automationComposition, acDefinition);
393         var response = new InstantiationResponse();
394         response.setInstanceId(automationComposition.getInstanceId());
395         response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
396         return response;
397     }
398
399     /**
400      * Get the requested automation compositions.
401      *
402      * @param name the name of the automation composition to get, null for all automation compositions
403      * @param version the version of the automation composition to get, null for all automation compositions
404      * @return the automation compositions
405      */
406     @Transactional(readOnly = true)
407     public AutomationCompositions getAutomationCompositions(UUID compositionId, String name, String version) {
408         var automationCompositions = new AutomationCompositions();
409         automationCompositions.setAutomationCompositionList(
410                 automationCompositionProvider.getAutomationCompositions(compositionId, name, version));
411
412         return automationCompositions;
413     }
414
415     /**
416      * Handle Composition Instance State.
417      *
418      * @param compositionId the compositionId
419      * @param instanceId the instanceId
420      * @param acInstanceStateUpdate the AcInstanceStateUpdate
421      */
422     public void compositionInstanceState(UUID compositionId, UUID instanceId,
423             @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
424         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
425         if (!compositionId.equals(automationComposition.getCompositionId())) {
426             throw new PfModelRuntimeException(Status.BAD_REQUEST,
427                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
428         }
429         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
430
431         var participantIds = acDefinition.getElementStateMap().values().stream()
432                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
433
434         participantProvider.verifyParticipantState(participantIds);
435         var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
436                 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
437                 automationComposition.getDeployState(), automationComposition.getLockState(),
438                 automationComposition.getSubState(), automationComposition.getStateChangeResult());
439         switch (result) {
440             case "DEPLOY":
441                 supervisionAcHandler.deploy(automationComposition, acDefinition);
442                 break;
443
444             case "UNDEPLOY":
445                 supervisionAcHandler.undeploy(automationComposition, acDefinition);
446                 break;
447
448             case "LOCK":
449                 supervisionAcHandler.lock(automationComposition, acDefinition);
450                 break;
451
452             case "UNLOCK":
453                 supervisionAcHandler.unlock(automationComposition, acDefinition);
454                 break;
455
456             case "PREPARE":
457                 supervisionAcHandler.prepare(automationComposition);
458                 break;
459
460             case "REVIEW":
461                 supervisionAcHandler.review(automationComposition);
462                 break;
463
464             default:
465                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Not valid " + acInstanceStateUpdate);
466         }
467     }
468 }