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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.clamp.acm.runtime.instantiation;
24 import jakarta.validation.Valid;
25 import jakarta.ws.rs.core.Response.Status;
26 import java.util.List;
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.supervision.SupervisionAcHandler;
33 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
34 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
35 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
36 import org.onap.policy.clamp.models.acm.concepts.DeployState;
37 import org.onap.policy.clamp.models.acm.concepts.LockState;
38 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
39 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
40 import org.onap.policy.clamp.models.acm.concepts.SubState;
41 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
42 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
43 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
44 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
45 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
46 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
47 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
48 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
49 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
50 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
51 import org.onap.policy.common.parameters.BeanValidationResult;
52 import org.onap.policy.common.parameters.ObjectValidationResult;
53 import org.onap.policy.common.parameters.ValidationStatus;
54 import org.onap.policy.models.base.PfConceptKey;
55 import org.onap.policy.models.base.PfKey;
56 import org.onap.policy.models.base.PfModelRuntimeException;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.stereotype.Service;
60 import org.springframework.transaction.annotation.Transactional;
63 * This class is dedicated to the Instantiation of Commissioned automation composition.
67 @RequiredArgsConstructor
68 public class AutomationCompositionInstantiationProvider {
69 private static final String DO_NOT_MATCH = " do not match with ";
70 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
71 private static final String NOT_VALID_ORDER =
72 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
74 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
76 private final AutomationCompositionProvider automationCompositionProvider;
77 private final AcDefinitionProvider acDefinitionProvider;
78 private final AcInstanceStateResolver acInstanceStateResolver;
79 private final SupervisionAcHandler supervisionAcHandler;
80 private final ParticipantProvider participantProvider;
81 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
84 * Create automation composition.
86 * @param compositionId The UUID of the automation composition definition
87 * @param automationComposition the automation composition
88 * @return the result of the instantiation operation
90 public InstantiationResponse createAutomationComposition(UUID compositionId,
91 AutomationComposition automationComposition) {
92 if (!compositionId.equals(automationComposition.getCompositionId())) {
93 throw new PfModelRuntimeException(Status.BAD_REQUEST,
94 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
96 var checkAutomationCompositionOpt =
97 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
98 if (checkAutomationCompositionOpt.isPresent()) {
99 throw new PfModelRuntimeException(Status.BAD_REQUEST,
100 automationComposition.getKey().asIdentifier() + " already defined");
103 var validationResult = validateAutomationComposition(automationComposition);
104 if (!validationResult.isValid()) {
105 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
107 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
109 return createInstantiationResponse(automationComposition);
112 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
113 var response = new InstantiationResponse();
114 response.setInstanceId(automationComposition.getInstanceId());
115 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
120 * Update automation composition.
122 * @param compositionId The UUID of the automation composition definition
123 * @param automationComposition the automation composition
124 * @return the result of the update
126 public InstantiationResponse updateAutomationComposition(UUID compositionId,
127 AutomationComposition automationComposition) {
128 var instanceId = automationComposition.getInstanceId();
129 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
130 if (!compositionId.equals(acToUpdate.getCompositionId())) {
131 throw new PfModelRuntimeException(Status.BAD_REQUEST,
132 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
134 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
135 acToUpdate.setElements(automationComposition.getElements());
136 acToUpdate.setName(automationComposition.getName());
137 acToUpdate.setVersion(automationComposition.getVersion());
138 acToUpdate.setDescription(automationComposition.getDescription());
139 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
140 var validationResult = validateAutomationComposition(acToUpdate);
141 if (!validationResult.isValid()) {
142 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
144 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
145 return createInstantiationResponse(automationComposition);
149 var deployOrder = DeployOrder.UPDATE;
150 var subOrder = SubOrder.NONE;
152 if (automationComposition.getCompositionTargetId() != null) {
154 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
155 subOrder = SubOrder.MIGRATE_PRECHECK;
156 deployOrder = DeployOrder.NONE;
158 deployOrder = DeployOrder.MIGRATE;
161 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
162 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
163 acToUpdate.getStateChangeResult());
164 return switch (result) {
165 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
167 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
169 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
171 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
172 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
177 * Update deployed AC Element properties.
179 * @param automationComposition the automation composition
180 * @param acToBeUpdated the composition to be updated
181 * @return the result of the update
183 private InstantiationResponse updateDeployedAutomationComposition(
184 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
186 // Iterate and update the element property values
187 for (var element : automationComposition.getElements().entrySet()) {
188 var elementId = element.getKey();
189 var dbAcElement = acToBeUpdated.getElements().get(elementId);
190 if (dbAcElement == null) {
191 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
193 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
196 var validationResult = validateAutomationComposition(acToBeUpdated);
197 if (!validationResult.isValid()) {
198 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
200 // Publish property update event to the participants
201 supervisionAcHandler.update(acToBeUpdated);
203 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
204 return createInstantiationResponse(automationComposition);
207 private InstantiationResponse migrateAutomationComposition(
208 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
210 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
211 throw new PfModelRuntimeException(Status.BAD_REQUEST,
212 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
215 // Iterate and update the element property values
216 for (var element : automationComposition.getElements().entrySet()) {
217 var elementId = element.getKey();
218 var dbAcElement = acToBeUpdated.getElements().get(elementId);
219 // Add additional elements if present for migration
220 if (dbAcElement == null) {
221 LOGGER.info("New Ac element {} added in Migration", elementId);
222 acToBeUpdated.getElements().put(elementId, element.getValue());
224 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
225 var newDefinition = element.getValue().getDefinition().asConceptKey();
226 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
227 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
228 dbAcElement.setDefinition(element.getValue().getDefinition());
231 // Remove element which is not present in the new Ac instance
232 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
233 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
235 var validationResult =
236 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
237 if (!validationResult.isValid()) {
238 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
240 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
241 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
242 // Publish migrate event to the participants
243 supervisionAcHandler.migrate(acToBeUpdated, acDefinition.getServiceTemplate());
245 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
246 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
247 return createInstantiationResponse(ac);
250 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
251 return acFromDb.getElements().keySet().stream()
252 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
255 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
257 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
258 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
259 throw new PfModelRuntimeException(Status.BAD_REQUEST,
260 dbElementDefinition + " is not compatible with " + newDefinition);
262 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
263 .equals(compatibility)) {
264 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
265 compatibility, dbElementDefinition);
269 private InstantiationResponse migratePrecheckAc(
270 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
272 acToBeUpdated.setPrecheck(true);
273 var copyAc = new AutomationComposition(acToBeUpdated);
274 // Iterate and update the element property values
275 for (var element : automationComposition.getElements().entrySet()) {
276 var elementId = element.getKey();
277 var copyElement = copyAc.getElements().get(elementId);
278 // Add additional elements if present for migration
279 if (copyElement == null) {
280 LOGGER.info("New Ac element {} added in Migration", elementId);
281 copyAc.getElements().put(elementId, element.getValue());
283 AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
284 var newDefinition = element.getValue().getDefinition().asConceptKey();
285 var copyElementDefinition = copyElement.getDefinition().asConceptKey();
286 checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
287 copyElement.setDefinition(element.getValue().getDefinition());
290 // Remove element which is not present in the new Ac instance
291 var elementsRemoved = getElementRemoved(copyAc, automationComposition);
292 elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
294 var validationResult =
295 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
296 if (!validationResult.isValid()) {
297 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
299 copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
301 // Publish migrate event to the participants
302 supervisionAcHandler.migratePrecheck(copyAc);
304 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
305 SubState.MIGRATION_PRECHECKING);
306 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
308 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
311 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
312 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
316 * Validate AutomationComposition.
318 * @param automationComposition AutomationComposition to validate
319 * @param compositionId the composition id
320 * @return the result of validation
322 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
323 UUID compositionId) {
325 var result = new BeanValidationResult("AutomationComposition", automationComposition);
326 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
327 if (acDefinitionOpt.isEmpty()) {
328 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
329 "Commissioned automation composition definition not found"));
332 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
333 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
334 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
337 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
338 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
340 participantProvider.verifyParticipantState(participantIds);
342 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
343 acDefinitionOpt.get().getServiceTemplate(),
344 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
346 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
348 if (result.isValid()) {
349 for (var element : automationComposition.getElements().values()) {
350 var name = element.getDefinition().getName();
351 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
352 element.setParticipantId(participantId);
360 * Get Automation Composition.
362 * @param compositionId The UUID of the automation composition definition
363 * @param instanceId The UUID of the automation composition instance
364 * @return the Automation Composition
366 @Transactional(readOnly = true)
367 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
368 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
369 if (!compositionId.equals(automationComposition.getCompositionId())
370 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
371 throw new PfModelRuntimeException(Status.BAD_REQUEST,
372 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
374 return automationComposition;
378 * Delete the automation composition with the given name and version.
380 * @param compositionId The UUID of the automation composition definition
381 * @param instanceId The UUID of the automation composition instance
382 * @return the result of the deletion
384 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
385 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
386 if (!compositionId.equals(automationComposition.getCompositionId())) {
387 throw new PfModelRuntimeException(Status.BAD_REQUEST,
388 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
390 if (!DeployState.UNDEPLOYED.equals(automationComposition.getDeployState())
391 && !DeployState.DELETING.equals(automationComposition.getDeployState())) {
392 throw new PfModelRuntimeException(Status.BAD_REQUEST,
393 "Automation composition state is still " + automationComposition.getDeployState());
395 if (DeployState.DELETING.equals(automationComposition.getDeployState())
396 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
397 throw new PfModelRuntimeException(Status.BAD_REQUEST,
398 "Automation composition state is still " + automationComposition.getDeployState());
400 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
401 var participantIds = acDefinition.getElementStateMap().values().stream()
402 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
403 participantProvider.verifyParticipantState(participantIds);
404 supervisionAcHandler.delete(automationComposition, acDefinition);
405 var response = new InstantiationResponse();
406 response.setInstanceId(automationComposition.getInstanceId());
407 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
412 * Get the requested automation compositions.
414 * @param name the name of the automation composition to get, null for all automation compositions
415 * @param version the version of the automation composition to get, null for all automation compositions
416 * @return the automation compositions
418 @Transactional(readOnly = true)
419 public AutomationCompositions getAutomationCompositions(UUID compositionId, String name, String version) {
420 var automationCompositions = new AutomationCompositions();
421 automationCompositions.setAutomationCompositionList(
422 automationCompositionProvider.getAutomationCompositions(compositionId, name, version));
424 return automationCompositions;
428 * Handle Composition Instance State.
430 * @param compositionId the compositionId
431 * @param instanceId the instanceId
432 * @param acInstanceStateUpdate the AcInstanceStateUpdate
434 public void compositionInstanceState(UUID compositionId, UUID instanceId,
435 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
436 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
437 if (!compositionId.equals(automationComposition.getCompositionId())) {
438 throw new PfModelRuntimeException(Status.BAD_REQUEST,
439 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
441 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
443 var participantIds = acDefinition.getElementStateMap().values().stream()
444 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
446 participantProvider.verifyParticipantState(participantIds);
447 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
448 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
449 automationComposition.getDeployState(), automationComposition.getLockState(),
450 automationComposition.getSubState(), automationComposition.getStateChangeResult());
453 supervisionAcHandler.deploy(automationComposition, acDefinition);
457 supervisionAcHandler.undeploy(automationComposition, acDefinition);
461 supervisionAcHandler.lock(automationComposition, acDefinition);
465 supervisionAcHandler.unlock(automationComposition, acDefinition);
469 supervisionAcHandler.prepare(automationComposition);
473 supervisionAcHandler.review(automationComposition);
477 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
478 automationComposition.getDeployState(), automationComposition.getLockState(),
479 automationComposition.getSubState(), automationComposition.getStateChangeResult());
480 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);