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