aboutsummaryrefslogtreecommitdiffstats
path: root/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
blob: 1155bd4f487ff5e0141997d71c8a7853ab71b9dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/*-
 * ============LICENSE_START=======================================================
 *  Copyright (C) 2021-2024 Nordix Foundation.
 * ================================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * ============LICENSE_END=========================================================
 */

package org.onap.policy.clamp.models.acm.utils;

import jakarta.ws.rs.core.Response;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.onap.policy.clamp.models.acm.concepts.AcElementDeploy;
import org.onap.policy.clamp.models.acm.concepts.AcElementRestart;
import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementDefinition;
import org.onap.policy.clamp.models.acm.concepts.DeployState;
import org.onap.policy.clamp.models.acm.concepts.LockState;
import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
import org.onap.policy.clamp.models.acm.concepts.ParticipantDefinition;
import org.onap.policy.clamp.models.acm.concepts.ParticipantDeploy;
import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
import org.onap.policy.common.parameters.BeanValidationResult;
import org.onap.policy.common.parameters.ObjectValidationResult;
import org.onap.policy.common.parameters.ValidationResult;
import org.onap.policy.common.parameters.ValidationStatus;
import org.onap.policy.models.base.PfModelRuntimeException;
import org.onap.policy.models.base.PfUtils;
import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
import org.onap.policy.models.tosca.authorative.concepts.ToscaTopologyTemplate;

/**
 * Utility functions used in acm-runtime and participants.
 *
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AcmUtils {
    public static final String ENTRY = "entry ";

    /**
     * Get the Policy information in the service template for the deploy message to participants.
     *
     * @param toscaServiceTemplate ToscaServiceTemplate
     */
    public static ToscaServiceTemplate getToscaServiceTemplateFragment(ToscaServiceTemplate toscaServiceTemplate) {
        // Pass respective PolicyTypes or Policies as part of toscaServiceTemplateFragment
        if (toscaServiceTemplate.getPolicyTypes() == null
                && toscaServiceTemplate.getToscaTopologyTemplate().getPolicies() == null) {
            return new ToscaServiceTemplate();
        }
        var toscaServiceTemplateFragment = new ToscaServiceTemplate();
        toscaServiceTemplateFragment.setPolicyTypes(toscaServiceTemplate.getPolicyTypes());
        var toscaTopologyTemplate = new ToscaTopologyTemplate();
        toscaTopologyTemplate.setPolicies(toscaServiceTemplate.getToscaTopologyTemplate().getPolicies());
        toscaServiceTemplateFragment.setToscaTopologyTemplate(toscaTopologyTemplate);
        toscaServiceTemplateFragment.setDataTypes(toscaServiceTemplate.getDataTypes());
        return toscaServiceTemplateFragment;
    }

    /**
     * Checks if a NodeTemplate is an AutomationCompositionElement.
     *
     * @param nodeTemplate the ToscaNodeTemplate
     * @param toscaServiceTemplate the ToscaServiceTemplate
     * @return true if the NodeTemplate is an AutomationCompositionElement
     */
    public static boolean checkIfNodeTemplateIsAutomationCompositionElement(ToscaNodeTemplate nodeTemplate,
            ToscaServiceTemplate toscaServiceTemplate, String toscaElementName) {
        if (nodeTemplate.getType().contains(toscaElementName)) {
            return true;
        } else {
            var nodeType = toscaServiceTemplate.getNodeTypes().get(nodeTemplate.getType());
            if (nodeType != null) {
                var derivedFrom = nodeType.getDerivedFrom();
                if (derivedFrom != null) {
                    return derivedFrom.contains(toscaElementName);
                }
            }
        }
        return false;
    }

    /**
     * Prepare list of ParticipantDefinition for the Priming message.
     *
     * @param acElements the extracted AcElements from ServiceTemplate
     * @param supportedElementMap supported Element Map
     */
    public static List<ParticipantDefinition> prepareParticipantPriming(
            List<Entry<String, ToscaNodeTemplate>> acElements, Map<ToscaConceptIdentifier, UUID> supportedElementMap) {

        Map<UUID, List<AutomationCompositionElementDefinition>> map = new HashMap<>();
        for (var elementEntry : acElements) {
            var type = new ToscaConceptIdentifier(elementEntry.getValue().getType(),
                    elementEntry.getValue().getTypeVersion());
            var participantId = supportedElementMap.get(type);
            if (participantId == null) {
                throw new PfModelRuntimeException(Response.Status.BAD_REQUEST,
                        "Element Type " + type + " not supported");
            }
            var acElementDefinition = new AutomationCompositionElementDefinition();
            acElementDefinition.setAcElementDefinitionId(
                    new ToscaConceptIdentifier(elementEntry.getKey(), elementEntry.getValue().getVersion()));
            acElementDefinition.setAutomationCompositionElementToscaNodeTemplate(elementEntry.getValue());
            map.putIfAbsent(participantId, new ArrayList<>());
            map.get(participantId).add(acElementDefinition);
        }
        return prepareParticipantPriming(map);
    }

    /**
     * Prepare ParticipantPriming.
     *
     * @param map of AutomationCompositionElementDefinition with participantId as key
     * @return list of ParticipantDefinition
     */
    public static List<ParticipantDefinition> prepareParticipantPriming(
            Map<UUID, List<AutomationCompositionElementDefinition>> map) {
        List<ParticipantDefinition> result = new ArrayList<>();
        for (var entry : map.entrySet()) {
            var participantDefinition = new ParticipantDefinition();
            participantDefinition.setParticipantId(entry.getKey());
            participantDefinition.setAutomationCompositionElementDefinitionList(entry.getValue());
            result.add(participantDefinition);
        }
        return result;
    }

    /**
     * Extract AcElements from ServiceTemplate.
     *
     * @param serviceTemplate the ToscaServiceTemplate
     * @return the list of Entry of AutomationCompositionElement
     */
    public static List<Entry<String, ToscaNodeTemplate>> extractAcElementsFromServiceTemplate(
            ToscaServiceTemplate serviceTemplate, String toscaElementName) {
        return serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().entrySet().stream().filter(
                nodeTemplateEntry -> checkIfNodeTemplateIsAutomationCompositionElement(nodeTemplateEntry.getValue(),
                        serviceTemplate, toscaElementName))
                .toList();
    }

    /**
     * Create NodeTemplateState Map.
     *
     * @param acElements extracted AcElements from ServiceTemplate.
     * @param state the AcTypeState
     * @return the NodeTemplateState Map
     */
    public static Map<String, NodeTemplateState> createElementStateMap(
            List<Entry<String, ToscaNodeTemplate>> acElements, AcTypeState state) {
        Map<String, NodeTemplateState> result = new HashMap<>(acElements.size());
        for (var entry : acElements) {
            var nodeTemplateState = new NodeTemplateState();
            nodeTemplateState.setNodeTemplateStateId(UUID.randomUUID());
            nodeTemplateState.setState(state);
            nodeTemplateState
                    .setNodeTemplateId(new ToscaConceptIdentifier(entry.getKey(), entry.getValue().getVersion()));
            result.put(entry.getKey(), nodeTemplateState);
        }
        return result;
    }

    /**
     * Validate AutomationComposition.
     *
     * @param automationComposition AutomationComposition to validate
     * @param serviceTemplate the service template
     * @return the result of validation
     */
    public static BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
            ToscaServiceTemplate serviceTemplate, String toscaCompositionName) {
        var result = new BeanValidationResult(ENTRY + automationComposition.getName(), automationComposition);

        var map = getMapToscaNodeTemplates(serviceTemplate);

        var nodeTemplateGet = map.values().stream()
                .filter(nodeTemplate -> toscaCompositionName.equals(nodeTemplate.getType())).findFirst();

        if (nodeTemplateGet.isEmpty()) {
            result.addResult(new ObjectValidationResult("ToscaServiceTemplate", serviceTemplate.getName(),
                    ValidationStatus.INVALID, "Commissioned automation composition definition not consistent"));
        } else {

            var toscaNodeTemplate = nodeTemplateGet.get();
            var acElementDefinitions = getAutomationCompositionElementDefinitions(map, toscaNodeTemplate);

            // @formatter:off
            var definitions = acElementDefinitions
                    .stream()
                    .map(nodeTemplate -> nodeTemplate.getKey().asIdentifier())
                    .collect(Collectors.toMap(ToscaConceptIdentifier::getName, UnaryOperator.identity()));
            // @formatter:on

            for (var element : automationComposition.getElements().values()) {
                result.addResult(validateDefinition(definitions, element.getDefinition()));
            }
        }

        return result;

    }

    private static ValidationResult validateDefinition(Map<String, ToscaConceptIdentifier> definitions,
            ToscaConceptIdentifier definition) {
        var result = new BeanValidationResult(ENTRY + definition.getName(), definition);
        var identifier = definitions.get(definition.getName());
        if (identifier == null) {
            result.setResult(ValidationStatus.INVALID, "Not found");
        } else if (!identifier.equals(definition)) {
            result.setResult(ValidationStatus.INVALID, "Version not matching");
        }
        return (result.isClean() ? null : result);
    }

    private static Map<ToscaConceptIdentifier, ToscaNodeTemplate> getMapToscaNodeTemplates(
            ToscaServiceTemplate serviceTemplate) {
        if (serviceTemplate.getToscaTopologyTemplate() == null
                || MapUtils.isEmpty(serviceTemplate.getToscaTopologyTemplate().getNodeTemplates())) {
            return Map.of();
        }
        var list = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().values();
        return list.stream().collect(Collectors
                .toMap(node -> new ToscaConceptIdentifier(node.getName(), node.getVersion()), Function.identity()));
    }

    private static List<ToscaNodeTemplate> getAutomationCompositionElementDefinitions(
            Map<ToscaConceptIdentifier, ToscaNodeTemplate> map, ToscaNodeTemplate automationCompositionNodeTemplate) {

        if (MapUtils.isEmpty(automationCompositionNodeTemplate.getProperties())) {
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        var automationCompositionElements =
                (List<Map<String, String>>) automationCompositionNodeTemplate.getProperties().get("elements");

        if (CollectionUtils.isEmpty(automationCompositionElements)) {
            return Collections.emptyList();
        }

        // @formatter:off
        return automationCompositionElements
                .stream()
                .map(elementMap ->
                    map.get(new ToscaConceptIdentifier(elementMap.get("name"), elementMap.get("version"))))
            .toList();
        // @formatter:on
    }

    /**
     * Return true if DeployState and LockState are in a Transitional State.
     *
     * @return true if DeployState and LockState are in a Transitional State
     */
    public static boolean isInTransitionalState(DeployState deployState, LockState lockState) {
        return DeployState.DEPLOYING.equals(deployState) || DeployState.UNDEPLOYING.equals(deployState)
                || LockState.LOCKING.equals(lockState) || LockState.UNLOCKING.equals(lockState)
                || DeployState.DELETING.equals(deployState) || DeployState.UPDATING.equals(deployState)
                || DeployState.MIGRATING.equals(deployState);
    }

    /**
     * Get DeployOrder from transitional DeployState.
     *
     * @param deployState the Deploy State
     * @return the DeployOrder
     */
    public static DeployOrder stateDeployToOrder(DeployState deployState) {
        return switch (deployState) {
            case DEPLOYING -> DeployOrder.DEPLOY;
            case UNDEPLOYING -> DeployOrder.UNDEPLOY;
            case DELETING -> DeployOrder.DELETE;
            default -> DeployOrder.NONE;
        };
    }

    /**
     * Get LockOrder from transitional LockState.
     *
     * @param lockState the Lock State
     * @return the LockOrder
     */
    public static LockOrder stateLockToOrder(LockState lockState) {
        if (LockState.LOCKING.equals(lockState)) {
            return LockOrder.LOCK;
        } else if (LockState.UNLOCKING.equals(lockState)) {
            return LockOrder.UNLOCK;
        }
        return LockOrder.NONE;
    }

    /**
     * Get final DeployState from transitional DeployState.
     *
     * @param deployState the DeployState
     * @return the DeployState
     */
    public static DeployState deployCompleted(DeployState deployState) {
        return switch (deployState) {
            case MIGRATING, UPDATING, DEPLOYING -> DeployState.DEPLOYED;
            case UNDEPLOYING -> DeployState.UNDEPLOYED;
            case DELETING -> DeployState.DELETED;
            default -> deployState;
        };
    }

    /**
     * Get final LockState from transitional LockState.
     *
     * @param lockState the LockState
     * @return the LockState
     */
    public static LockState lockCompleted(DeployState deployState, LockState lockState) {
        if (LockState.LOCKING.equals(lockState) || DeployState.DEPLOYING.equals(deployState)) {
            return LockState.LOCKED;
        } else if (LockState.UNLOCKING.equals(lockState)) {
            return LockState.UNLOCKED;
        } else if (DeployState.UNDEPLOYING.equals(deployState)) {
            return LockState.NONE;
        }
        return lockState;
    }

    /**
     * Return true if transition states is Forward.
     *
     * @param deployState the DeployState
     * @param lockState the LockState
     * @return true if transition if Forward
     */
    public static boolean isForward(DeployState deployState, LockState lockState) {
        return DeployState.DEPLOYING.equals(deployState) || LockState.UNLOCKING.equals(lockState);
    }

    /**
     * Set the states on the automation composition and on all its automation composition elements.
     *
     * @param deployState the DeployState we want the automation composition to transition to
     * @param lockState the LockState we want the automation composition to transition to
     */
    public static void setCascadedState(final AutomationComposition automationComposition,
            final DeployState deployState, final LockState lockState) {
        automationComposition.setDeployState(deployState);
        automationComposition.setLockState(lockState);

        if (MapUtils.isEmpty(automationComposition.getElements())) {
            return;
        }

        for (var element : automationComposition.getElements().values()) {
            element.setDeployState(deployState);
            element.setLockState(lockState);
            element.setMessage(null);
        }
    }

    /**
     * Create a new AcElementDeploy from an AutomationCompositionElement.
     *
     * @param element the AutomationCompositionElement
     * @param deployOrder the DeployOrder
     * @return the AcElementDeploy
     */
    public static AcElementDeploy createAcElementDeploy(AutomationCompositionElement element, DeployOrder deployOrder) {
        var acElementDeploy = new AcElementDeploy();
        acElementDeploy.setId(element.getId());
        acElementDeploy.setDefinition(new ToscaConceptIdentifier(element.getDefinition()));
        acElementDeploy.setOrderedState(deployOrder);
        acElementDeploy.setProperties(PfUtils.mapMap(element.getProperties(), UnaryOperator.identity()));
        return acElementDeploy;
    }

    /**
     * Create a list of AcElementDeploy for update/migrate message.
     *
     * @param automationComposition the AutomationComposition
     * @param deployOrder the DeployOrder
     */
    public static List<ParticipantDeploy> createParticipantDeployList(AutomationComposition automationComposition,
            DeployOrder deployOrder) {
        Map<UUID, List<AcElementDeploy>> map = new HashMap<>();
        for (var element : automationComposition.getElements().values()) {
            var acElementDeploy = createAcElementDeploy(element, deployOrder);
            map.putIfAbsent(element.getParticipantId(), new ArrayList<>());
            map.get(element.getParticipantId()).add(acElementDeploy);
        }
        List<ParticipantDeploy> participantDeploys = new ArrayList<>();
        for (var entry : map.entrySet()) {
            var participantDeploy = new ParticipantDeploy();
            participantDeploy.setParticipantId(entry.getKey());
            participantDeploy.setAcElementList(entry.getValue());
            participantDeploys.add(participantDeploy);
        }
        return participantDeploys;
    }

    /**
     * Create a new AcElementRestart from an AutomationCompositionElement.
     *
     * @param element the AutomationCompositionElement
     * @return the AcElementRestart
     */
    public static AcElementRestart createAcElementRestart(AutomationCompositionElement element) {
        var acElementRestart = new AcElementRestart();
        acElementRestart.setId(element.getId());
        acElementRestart.setDefinition(new ToscaConceptIdentifier(element.getDefinition()));
        acElementRestart.setDeployState(element.getDeployState());
        acElementRestart.setLockState(element.getLockState());
        acElementRestart.setOperationalState(element.getOperationalState());
        acElementRestart.setUseState(element.getUseState());
        acElementRestart.setProperties(PfUtils.mapMap(element.getProperties(), UnaryOperator.identity()));
        acElementRestart.setOutProperties(PfUtils.mapMap(element.getOutProperties(), UnaryOperator.identity()));
        return acElementRestart;
    }

    /**
     * Recursive Merge.
     *
     * @param map1 Map where to merge
     * @param map2 Map
     */
    public static void recursiveMerge(Map<String, Object> map1, Map<String, Object> map2) {
        Deque<Pair<Map<String, Object>, Map<String, Object>>> stack = new ArrayDeque<>();
        stack.push(Pair.of(map1, map2));
        while (!stack.isEmpty()) {
            var pair = stack.pop();
            var mapLeft = pair.getLeft();
            var mapRight = pair.getRight();
            for (var entryRight : mapRight.entrySet()) {
                var valueLeft = mapLeft.get(entryRight.getKey());
                var valueRight = entryRight.getValue();
                if (valueLeft instanceof Map subMapLeft && valueRight instanceof Map subMapRight) {
                    stack.push(Pair.of(subMapLeft, subMapRight));
                } else if ((valueLeft instanceof List subListLeft && valueRight instanceof List subListRight)
                        && (subListLeft.size() == subListRight.size())) {
                    recursiveMerge(subListLeft, subListRight);
                } else {
                    mapLeft.put(entryRight.getKey(), valueRight);
                }
            }
        }
    }

    private static void recursiveMerge(List<Object> list1, List<Object> list2) {
        for (var i = 0; i < list1.size(); i++) {
            var valueLeft = list1.get(i);
            var valueRight = list2.get(i);
            if (valueLeft instanceof Map subMapLeft && valueRight instanceof Map subMapRight) {
                recursiveMerge(subMapLeft, subMapRight);
            } else if ((valueLeft instanceof List subListLeft && valueRight instanceof List subListRight)
                    && (subListLeft.size() == subListRight.size())) {
                recursiveMerge(subListLeft, subListRight);
            } else {
                list1.set(i, valueRight);
            }
        }
    }
}