Make drools-pdp work with drools and java versions compatible
[policy/drools-applications.git] / controlloop / common / feature-controlloop-trans / src / main / java / org / onap / policy / drools / apps / controlloop / feature / trans / CacheBasedControlLoopMetricsManager.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2022 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2023-2024 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.drools.apps.controlloop.feature.trans;
23
24 import com.google.common.cache.CacheBuilder;
25 import com.google.common.cache.CacheLoader;
26 import com.google.common.cache.LoadingCache;
27 import com.google.common.cache.RemovalListener;
28 import java.time.Instant;
29 import java.time.ZonedDateTime;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.UUID;
35 import java.util.concurrent.TimeUnit;
36 import lombok.Getter;
37 import lombok.Setter;
38 import org.apache.commons.collections4.CollectionUtils;
39 import org.onap.policy.controlloop.ControlLoopNotificationType;
40 import org.onap.policy.controlloop.ControlLoopOperation;
41 import org.onap.policy.controlloop.VirtualControlLoopNotification;
42 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
43 import org.onap.policy.drools.system.PolicyController;
44 import org.onap.policy.drools.system.PolicyEngineConstants;
45 import org.onap.policy.drools.utils.logging.MdcTransaction;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Control Loop Metrics Tracker Implementation.
51  */
52 class CacheBasedControlLoopMetricsManager implements ControlLoopMetrics {
53
54     private static final String UNEXPECTED_NOTIFICATION_TYPE = "unexpected notification type {} in notification {}";
55
56     private static final Logger logger = LoggerFactory.getLogger(CacheBasedControlLoopMetricsManager.class);
57
58     private LoadingCache<UUID, VirtualControlLoopNotification> cache;
59
60     @Getter
61     private long cacheSize = ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT;
62
63     @Getter
64     @Setter
65     private long transactionTimeout = ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT;
66
67     /**
68      * Numeric response code.
69      */
70     private static final Map<String, String> note2code = Map.of(
71         ControlLoopNotificationType.ACTIVE.name(), "100",
72         ControlLoopNotificationType.REJECTED.name(), "200",
73         ControlLoopNotificationType.OPERATION.name(), "300",
74         ControlLoopNotificationType.OPERATION_SUCCESS.name(), "301",
75         ControlLoopNotificationType.OPERATION_FAILURE.name(), "302",
76         ControlLoopNotificationType.FINAL_FAILURE.name(), "400",
77         ControlLoopNotificationType.FINAL_SUCCESS.name(), "401",
78         ControlLoopNotificationType.FINAL_OPENLOOP.name(), "402"
79     );
80
81     private static final String UNKNOWN_RESPONSE_CODE = "900";
82
83     public CacheBasedControlLoopMetricsManager() {
84
85         var properties = SystemPersistenceConstants.getManager()
86                         .getProperties(ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME);
87
88         /* cache size */
89
90         try {
91             this.cacheSize =
92                     Long.parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY,
93                             "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT));
94         } catch (Exception e) {
95             logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
96                     ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY, e);
97         }
98
99         /* transaction timeout */
100
101         try {
102             this.transactionTimeout = Long
103                     .parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY,
104                             "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT));
105         } catch (Exception e) {
106             logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
107                     ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY, e);
108         }
109
110         resetCache(this.cacheSize, this.transactionTimeout);
111     }
112
113     @Override
114     public void resetCache(long cacheSize, long transactionTimeout) {
115         this.cacheSize = cacheSize;
116         this.transactionTimeout = transactionTimeout;
117
118         CacheLoader<UUID, VirtualControlLoopNotification> loader = new CacheLoader<>() {
119
120             @Override
121             public VirtualControlLoopNotification load(UUID key) {
122                 return null;
123             }
124         };
125
126         RemovalListener<UUID, VirtualControlLoopNotification> listener = notification -> {
127             if (notification.wasEvicted()) {
128                 evicted(notification.getValue());
129             } else if (logger.isInfoEnabled()) {
130                 logger.info("REMOVAL: {} because of {}", notification.getValue().getRequestId(),
131                                 notification.getCause().name());
132             }
133         };
134
135         synchronized (this) {
136             if (this.cache != null) {
137                 this.cache.cleanUp();
138                 this.cache.invalidateAll();
139             }
140
141             this.cache = CacheBuilder.newBuilder().maximumSize(this.cacheSize)
142                     .expireAfterWrite(transactionTimeout, TimeUnit.SECONDS).removalListener(listener).build(loader);
143         }
144     }
145
146     @Override
147     public void refresh() {
148         this.cache.cleanUp();
149     }
150
151     @Override
152     public List<UUID> getTransactionIds() {
153         return new ArrayList<>(this.cache.asMap().keySet());
154     }
155
156     @Override
157     public List<VirtualControlLoopNotification> getTransactions() {
158         return new ArrayList<>(this.cache.asMap().values());
159     }
160
161     @Override
162     public void transactionEvent(PolicyController controller, VirtualControlLoopNotification notification) {
163         if (!isNotificationValid(notification)) {
164             return;
165         }
166
167         setNotificationValues(controller, notification);
168
169         switch (notification.getNotification()) {
170             case REJECTED:
171             case FINAL_FAILURE:
172             case FINAL_SUCCESS:
173             case FINAL_OPENLOOP:
174                 endTransaction(controller, notification);
175                 break;
176             case ACTIVE:
177             case OPERATION:
178             case OPERATION_SUCCESS:
179             case OPERATION_FAILURE:
180                 /* any other value is an in progress transaction */
181                 inProgressTransaction(notification);
182                 break;
183             default:
184                 /* unexpected */
185                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
186                         notification.getNotification(), notification);
187                 break;
188         }
189     }
190
191     private boolean isNotificationValid(VirtualControlLoopNotification notification) {
192         if (notification == null || notification.getRequestId() == null || notification.getNotification() == null) {
193             logger.warn("Invalid notification: {}", notification);
194             return false;
195         }
196
197         return true;
198     }
199
200     private void setNotificationValues(PolicyController controller, VirtualControlLoopNotification notification) {
201         if (notification.getNotificationTime() == null) {
202             notification.setNotificationTime(ZonedDateTime.now());
203         }
204
205         notification.setFrom(notification.getFrom() + ":" + controller.getName()
206             + ":" + controller.getDrools().getCanonicalSessionNames());
207     }
208
209     @Override
210     public VirtualControlLoopNotification getTransaction(UUID requestId) {
211         return cache.getIfPresent(requestId);
212     }
213
214     @Override
215     public void removeTransaction(UUID requestId) {
216         cache.invalidate(requestId);
217     }
218
219     /**
220      * Tracks an in progress control loop transaction.
221      *
222      * @param notification control loop notification
223      */
224     protected void inProgressTransaction(VirtualControlLoopNotification notification) {
225         if (cache.getIfPresent(notification.getRequestId()) == null) {
226             cache.put(notification.getRequestId(), notification);
227         }
228
229         this.metric(notification);
230     }
231
232     /**
233      * End of a control loop transaction.
234      *
235      * @param controller controller
236      * @param notification control loop notification
237      */
238     protected void endTransaction(PolicyController controller, VirtualControlLoopNotification notification) {
239         ZonedDateTime startTime;
240         VirtualControlLoopNotification startNotification = cache.getIfPresent(notification.getRequestId());
241         startTime = Objects.requireNonNullElse(startNotification, notification).getNotificationTime();
242
243         this.transaction(controller, notification, startTime);
244         if (startNotification != null) {
245             removeTransaction(startNotification.getRequestId());
246         }
247     }
248
249     protected void evicted(VirtualControlLoopNotification notification) {
250         MdcTransaction
251                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
252                 .setServiceName(notification.getClosedLoopControlName())
253                 .setServiceInstanceId(notification.getPolicyName() + ":" + notification.getPolicyVersion())
254                 .setProcessKey("" + notification.getAai())
255                 .setTargetEntity(notification.getTargetType() + "." + notification.getTarget())
256                 .setResponseCode(UNKNOWN_RESPONSE_CODE)
257                 .setStartTime(notification.getNotificationTime().toInstant())
258                 .setEndTime(Instant.now())
259                 .setResponseDescription("EVICTED")
260                 .setStatusCode(false)
261                 .setCustomField1((notification.getNotification() != null)
262                                          ? notification.getNotification().name() : "")
263                 .setCustomField2(notification.getPolicyScope())
264                 .setClientIpAddress(notification.getClosedLoopEventClient())
265                 .metric()
266                 .resetTransaction();
267
268     }
269
270     @Override
271     public void setMaxCacheSize(long cacheSize) {
272         this.cacheSize = cacheSize;
273     }
274
275     @Override
276     public long getCacheOccupancy() {
277         return this.cache.size();
278     }
279
280     protected void metric(VirtualControlLoopNotification notification) {
281         var trans = getMdcTransaction(notification);
282         List<ControlLoopOperation> operations = notification.getHistory();
283         switch (notification.getNotification()) {
284             case ACTIVE:
285                 trans.setStatusCode(true).metric().resetTransaction();
286                 break;
287             case OPERATION:
288                 operation(trans.setStatusCode(true), operations).metric().resetTransaction();
289                 break;
290             case OPERATION_SUCCESS:
291                 operation(trans.setStatusCode(true), operations).metric().transaction().resetTransaction();
292                 break;
293             case OPERATION_FAILURE:
294                 operation(trans.setStatusCode(false), operations).metric().transaction().resetTransaction();
295                 break;
296             default:
297                 /* unexpected */
298                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
299                         notification.getNotification(), notification);
300                 break;
301         }
302     }
303
304     private MdcTransaction getMdcTransaction(VirtualControlLoopNotification notification) {
305         return MdcTransaction
306                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
307                 .setServiceName(notification.getClosedLoopControlName())
308                 .setServiceInstanceId(notification.getPolicyName() + ":" + notification.getPolicyVersion())
309                 .setProcessKey("" + notification.getAai())
310                 .setTargetEntity(notification.getTargetType() + "." + notification.getTarget())
311                 .setResponseCode((notification.getNotification() != null)
312                     ? notificationTypeToResponseCode(notification.getNotification().name())
313                     : UNKNOWN_RESPONSE_CODE)
314                 .setCustomField1((notification.getNotification() != null)
315                     ? notification.getNotification().name() : "")
316                 .setCustomField2(notification.getPolicyScope())
317                 .setResponseDescription(notification.getMessage())
318                 .setClientIpAddress(notification.getClosedLoopEventClient());
319     }
320
321     protected MdcTransaction operation(MdcTransaction trans, List<ControlLoopOperation> operations) {
322         if (CollectionUtils.isEmpty(operations)) {
323             return trans;
324         }
325
326         ControlLoopOperation operation = operations.get(operations.size() - 1);
327
328         if (operation.getActor() != null) {
329             trans.setTargetServiceName(operation.getActor() + "." + operation.getOperation());
330         }
331
332         if (operation.getTarget() != null) {
333             trans.setTargetVirtualEntity(operation.getTarget());
334         }
335
336         if (operation.getSubRequestId() != null) {
337             trans.setInvocationId(operation.getSubRequestId());
338         }
339
340         if (operation.getOutcome() != null) {
341             trans.setResponseDescription(operation.getOutcome() + ":" + operation.getMessage());
342         }
343
344         if (operation.getStart() != null) {
345             trans.setStartTime(operation.getStart());
346         }
347
348         if (operation.getEnd() != null) {
349             trans.setEndTime(operation.getEnd());
350         }
351
352         return trans;
353     }
354
355     protected void transaction(PolicyController controller,
356             VirtualControlLoopNotification notification, ZonedDateTime startTime) {
357         MdcTransaction trans = getMdcTransaction(notification)
358                 .setStartTime(startTime.toInstant())
359                 .setEndTime(notification.getNotificationTime().toInstant());
360
361         switch (notification.getNotification()) {
362             case FINAL_OPENLOOP:
363                  /* fall through */
364             case FINAL_SUCCESS:
365                 trans.setStatusCode(true);
366                 break;
367             case FINAL_FAILURE:
368                  /* fall through */
369             case REJECTED:
370                 trans.setStatusCode(false);
371                 break;
372             default:
373                 /* unexpected */
374                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
375                         notification.getNotification(), notification);
376                 break;
377         }
378
379         try {
380             PolicyEngineConstants.getManager().transaction(controller.getName(),
381                     notification.getClosedLoopControlName(), trans.getMetric());
382         } catch (RuntimeException rex) {
383             logger.info("error pegging control loop transaction: {}", trans.getMetric(), rex);
384         }
385
386         trans.transaction().resetTransaction();
387     }
388
389     @Override
390     public String toString() {
391         return "CacheBasedControlLoopMetricsManager{" + "cacheSize=" + cacheSize
392                        + ",transactionTimeout="
393                        + transactionTimeout
394                        + ",cacheOccupancy="
395                        + getCacheOccupancy()
396                        + "}";
397     }
398
399     private String notificationTypeToResponseCode(String notificationType) {
400         String code = note2code.get(notificationType);
401         return Objects.requireNonNullElse(code, UNKNOWN_RESPONSE_CODE);
402     }
403 }