Merge "Cm Subscription: Predicates optional now"
[cps.git] / cps-ncmp-service / src / main / java / org / onap / cps / ncmp / api / impl / utils / data / operation / ResourceDataOperationRequestUtils.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2023-2024 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.api.impl.utils.data.operation;
22
23 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND;
24 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_READY;
25
26 import io.cloudevents.CloudEvent;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.TimeoutException;
35 import java.util.stream.Collectors;
36 import lombok.AccessLevel;
37 import lombok.NoArgsConstructor;
38 import lombok.extern.slf4j.Slf4j;
39 import org.onap.cps.events.EventsPublisher;
40 import org.onap.cps.ncmp.api.NcmpResponseStatus;
41 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
42 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation;
43 import org.onap.cps.ncmp.api.impl.operations.DmiOperationCmHandle;
44 import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
45 import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext;
46 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
47 import org.onap.cps.ncmp.api.models.DataOperationDefinition;
48 import org.onap.cps.ncmp.api.models.DataOperationRequest;
49 import org.springframework.util.LinkedMultiValueMap;
50 import org.springframework.util.MultiValueMap;
51
52 @Slf4j
53 @NoArgsConstructor(access = AccessLevel.PRIVATE)
54 public class ResourceDataOperationRequestUtils {
55
56     private static final String UNKNOWN_SERVICE_NAME = null;
57
58     /**
59      * Create a list of DMI data operation per DMI service (name).
60      *
61      * @param topicParamInQuery      client given topic
62      * @param requestId              unique identifier per request
63      * @param dataOperationRequestIn incoming data operation request details
64      * @param yangModelCmHandles     involved cm handles represented as YangModelCmHandle (incl. metadata)
65      * @return {@code Map<String, List<DmiBatchOperation>>} Create a list of DMI batch operation per DMI service (name).
66      */
67     public static Map<String, List<DmiDataOperation>> processPerDefinitionInDataOperationsRequest(
68             final String topicParamInQuery,
69             final String requestId,
70             final DataOperationRequest dataOperationRequestIn,
71             final Collection<YangModelCmHandle> yangModelCmHandles) {
72
73         final Map<String, List<DmiDataOperation>> dmiDataOperationsOutPerDmiServiceName = new HashMap<>();
74         final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus,
75                 List<String>>> cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
76         final Set<String> nonReadyCmHandleIdsLookup = filterAndGetNonReadyCmHandleIds(yangModelCmHandles);
77
78         final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName =
79                 DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles);
80
81         final Map<String, String> dmiServiceNamesPerCmHandleId =
82                 getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName);
83
84         final Map<String, String> moduleSetTagPerCmHandle = getModuleSetTagPerCmHandleId(yangModelCmHandles);
85
86         for (final DataOperationDefinition dataOperationDefinitionIn :
87                 dataOperationRequestIn.getDataOperationDefinitions()) {
88             final List<String> nonExistingCmHandleIds = new ArrayList<>();
89             final List<String> nonReadyCmHandleIds = new ArrayList<>();
90             for (final String cmHandleId : dataOperationDefinitionIn.getCmHandleIds()) {
91                 if (nonReadyCmHandleIdsLookup.contains(cmHandleId)) {
92                     nonReadyCmHandleIds.add(cmHandleId);
93                 } else {
94                     final String dmiServiceName = dmiServiceNamesPerCmHandleId.get(cmHandleId);
95                     final Map<String, String> cmHandleIdProperties
96                             = dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).get(cmHandleId);
97                     if (cmHandleIdProperties == null) {
98                         nonExistingCmHandleIds.add(cmHandleId);
99                     } else {
100                         final DmiDataOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName,
101                                 dataOperationDefinitionIn, dmiDataOperationsOutPerDmiServiceName);
102                         final DmiOperationCmHandle dmiOperationCmHandle = DmiOperationCmHandle
103                                 .buildDmiOperationCmHandle(cmHandleId, cmHandleIdProperties,
104                                         moduleSetTagPerCmHandle.get(cmHandleId));
105                         dmiBatchOperationOut.getCmHandles().add(dmiOperationCmHandle);
106                     }
107                 }
108             }
109             populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation,
110                     DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
111                     CM_HANDLES_NOT_FOUND, nonExistingCmHandleIds);
112             populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation,
113                     DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
114                     CM_HANDLES_NOT_READY, nonReadyCmHandleIds);
115         }
116         publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation);
117         return dmiDataOperationsOutPerDmiServiceName;
118     }
119
120     private static Map<String, String> getModuleSetTagPerCmHandleId(
121                                                        final Collection<YangModelCmHandle> yangModelCmHandles) {
122         final Map<String, String> moduleSetTagPerCmHandle = new HashMap<>(yangModelCmHandles.size());
123         yangModelCmHandles.forEach(yangModelCmHandle ->
124                 moduleSetTagPerCmHandle.put(yangModelCmHandle.getId(), yangModelCmHandle.getModuleSetTag()));
125         return moduleSetTagPerCmHandle;
126     }
127
128     /**
129      * Handles the async task completion for an entire data, publishing errors to client topic on task failure.
130      *
131      * @param topicParamInQuery      client given topic
132      * @param requestId              unique identifier per request
133      * @param dataOperationRequest   incoming data operation request details
134      * @param throwable              error cause, or null if task completed with no exception
135      */
136     public static void handleAsyncTaskCompletionForDataOperationsRequest(
137             final String topicParamInQuery,
138             final String requestId,
139             final DataOperationRequest dataOperationRequest,
140             final Throwable throwable) {
141         if (throwable == null) {
142             log.info("Data operations request {} completed.", requestId);
143         } else if (throwable instanceof TimeoutException) {
144             log.error("Data operations request {} timed out.", requestId);
145             ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery,
146                     requestId, dataOperationRequest, NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING);
147         } else {
148             log.error("Data operations request {} failed.", requestId, throwable);
149             ResourceDataOperationRequestUtils.publishErrorMessageToClientTopicForEntireOperation(topicParamInQuery,
150                     requestId, dataOperationRequest, NcmpResponseStatus.UNKNOWN_ERROR);
151         }
152     }
153
154     /**
155      * Creates data operation cloud event for when the entire data operation fails and publishes it to client topic.
156      *
157      * @param topicParamInQuery      client given topic
158      * @param requestId              unique identifier per request
159      * @param dataOperationRequestIn incoming data operation request details
160      * @param ncmpResponseStatus     response code to be sent for all cm handle ids in all operations
161      */
162     private static void publishErrorMessageToClientTopicForEntireOperation(
163             final String topicParamInQuery,
164             final String requestId,
165             final DataOperationRequest dataOperationRequestIn,
166             final NcmpResponseStatus ncmpResponseStatus) {
167
168         final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
169                 cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
170
171         for (final DataOperationDefinition dataOperationDefinitionIn :
172                 dataOperationRequestIn.getDataOperationDefinitions()) {
173             cmHandleIdsPerResponseCodesPerOperation.add(
174                     DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
175                     Map.of(ncmpResponseStatus, dataOperationDefinitionIn.getCmHandleIds()));
176         }
177         publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation);
178     }
179
180     /**
181      * Creates data operation cloud event and publish it to client topic.
182      *
183      * @param clientTopic                              client given topic
184      * @param requestId                                unique identifier per request
185      * @param cmHandleIdsPerResponseCodesPerOperation  list of cm handle ids per operation with response code
186      */
187     public static void publishErrorMessageToClientTopic(final String clientTopic,
188                                                          final String requestId,
189                                                          final MultiValueMap<DmiDataOperation,
190                                                                  Map<NcmpResponseStatus, List<String>>>
191                                                                     cmHandleIdsPerResponseCodesPerOperation) {
192         if (!cmHandleIdsPerResponseCodesPerOperation.isEmpty()) {
193             final CloudEvent dataOperationCloudEvent = DataOperationEventCreator.createDataOperationEvent(clientTopic,
194                     requestId, cmHandleIdsPerResponseCodesPerOperation);
195             final EventsPublisher<CloudEvent> eventsPublisher = CpsApplicationContext.getCpsBean(EventsPublisher.class);
196             eventsPublisher.publishCloudEvent(clientTopic, requestId, dataOperationCloudEvent);
197         }
198     }
199
200     private static Map<String, String> getDmiServiceNamesPerCmHandleId(
201             final Map<String, Map<String, Map<String, String>>> dmiDmiPropertiesPerCmHandleIdPerServiceName) {
202         final Map<String, String> dmiServiceNamesPerCmHandleId = new HashMap<>();
203         for (final Map.Entry<String, Map<String, Map<String, String>>> dmiDmiPropertiesEntry
204                 : dmiDmiPropertiesPerCmHandleIdPerServiceName.entrySet()) {
205             final String dmiServiceName = dmiDmiPropertiesEntry.getKey();
206             final Set<String> cmHandleIds = dmiDmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).keySet();
207             for (final String cmHandleId : cmHandleIds) {
208                 dmiServiceNamesPerCmHandleId.put(cmHandleId, dmiServiceName);
209             }
210         }
211         dmiDmiPropertiesPerCmHandleIdPerServiceName.put(UNKNOWN_SERVICE_NAME, Collections.emptyMap());
212         return dmiServiceNamesPerCmHandleId;
213     }
214
215     private static DmiDataOperation getOrAddDmiBatchOperation(final String dmiServiceName,
216                                                                final DataOperationDefinition
217                                                                        dataOperationDefinitionIn,
218                                                                final Map<String, List<DmiDataOperation>>
219                                                                        dmiBatchOperationsOutPerDmiServiceName) {
220         dmiBatchOperationsOutPerDmiServiceName
221                 .computeIfAbsent(dmiServiceName, dmiServiceNameAsKey -> new ArrayList<>());
222         final List<DmiDataOperation> dmiBatchOperationsOut
223                 = dmiBatchOperationsOutPerDmiServiceName.get(dmiServiceName);
224         final boolean isNewOperation = dmiBatchOperationsOut.isEmpty()
225                 || !dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1).getOperationId()
226                 .equals(dataOperationDefinitionIn.getOperationId());
227         if (isNewOperation) {
228             final DmiDataOperation newDmiBatchOperationOut =
229                     DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn);
230             dmiBatchOperationsOut.add(newDmiBatchOperationOut);
231             return newDmiBatchOperationOut;
232         }
233         return dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1);
234     }
235
236     private static Set<String> filterAndGetNonReadyCmHandleIds(final Collection<YangModelCmHandle> yangModelCmHandles) {
237         return yangModelCmHandles.stream()
238                 .filter(yangModelCmHandle -> yangModelCmHandle.getCompositeState().getCmHandleState()
239                         != CmHandleState.READY).map(YangModelCmHandle::getId).collect(Collectors.toSet());
240     }
241
242     private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<DmiDataOperation,
243             Map<NcmpResponseStatus, List<String>>> cmHandleIdsPerResponseCodesPerOperation,
244                                                                         final DmiDataOperation dmiDataOperation,
245                                                                         final NcmpResponseStatus
246                                                                                  ncmpResponseStatus,
247                                                                         final List<String> cmHandleIds) {
248         if (!cmHandleIds.isEmpty()) {
249             cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(ncmpResponseStatus, cmHandleIds));
250         }
251     }
252 }