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