d0b79125be475a5f33e635291419c88cfd6048f5
[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-2021 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()).setTargetEntity(notification.getTarget())
252                 .setStartTime(notification.getNotificationTime().toInstant()).setEndTime(Instant.now())
253                 .setResponseDescription("EVICTED").setStatusCode(false).metric().resetTransaction();
254     }
255
256     @Override
257     public void setMaxCacheSize(long cacheSize) {
258         this.cacheSize = cacheSize;
259     }
260
261     @Override
262     public long getCacheOccupancy() {
263         return this.cache.size();
264     }
265
266     protected void metric(VirtualControlLoopNotification notification) {
267         var trans = getMdcTransaction(notification);
268         List<ControlLoopOperation> operations = notification.getHistory();
269         switch (notification.getNotification()) {
270             case ACTIVE:
271                 trans.setStatusCode(true).metric().resetTransaction();
272                 break;
273             case OPERATION:
274                 operation(trans.setStatusCode(true), operations).metric().resetTransaction();
275                 break;
276             case OPERATION_SUCCESS:
277                 operation(trans.setStatusCode(true), operations).metric().transaction().resetTransaction();
278                 break;
279             case OPERATION_FAILURE:
280                 operation(trans.setStatusCode(false), operations).metric().transaction().resetTransaction();
281                 break;
282             default:
283                 /* unexpected */
284                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
285                         notification.getNotification(), notification);
286                 break;
287         }
288     }
289
290     private MdcTransaction getMdcTransaction(VirtualControlLoopNotification notification) {
291         return MdcTransaction
292                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
293                 .setServiceName(notification.getClosedLoopControlName())
294                 .setServiceInstanceId(notification.getPolicyScope()
295                     + ":" + notification.getPolicyName() + ":" + notification.getPolicyVersion())
296                 .setProcessKey("" + notification.getAai())
297                 .setTargetEntity(notification.getTargetType() + "." + notification.getTarget())
298                 .setResponseCode((notification.getNotification() != null)
299                     ? notificationTypeToResponseCode(notification.getNotification().name())
300                     : UNKNOWN_RESPONSE_CODE)
301                 .setCustomField1((notification.getNotification() != null)
302                     ? notification.getNotification().name() : "")
303                 .setResponseDescription(notification.getMessage())
304                 .setClientIpAddress(notification.getClosedLoopEventClient());
305     }
306
307     protected MdcTransaction operation(MdcTransaction trans, List<ControlLoopOperation> operations) {
308         if (CollectionUtils.isEmpty(operations)) {
309             return trans;
310         }
311
312         ControlLoopOperation operation = operations.get(operations.size() - 1);
313
314         if (operation.getActor() != null) {
315             trans.setTargetServiceName(operation.getActor() + "." + operation.getOperation());
316         }
317
318         if (operation.getTarget() != null) {
319             trans.setTargetVirtualEntity(operation.getTarget());
320         }
321
322         if (operation.getSubRequestId() != null) {
323             trans.setInvocationId(operation.getSubRequestId());
324         }
325
326         if (operation.getOutcome() != null) {
327             trans.setResponseDescription(operation.getOutcome() + ":" + operation.getMessage());
328         }
329
330         if (operation.getStart() != null) {
331             trans.setStartTime(operation.getStart());
332         }
333
334         if (operation.getEnd() != null) {
335             trans.setEndTime(operation.getEnd());
336         }
337
338         return trans;
339     }
340
341     protected void transaction(PolicyController controller,
342             VirtualControlLoopNotification notification, ZonedDateTime startTime) {
343         MdcTransaction trans = getMdcTransaction(notification)
344                 .setStartTime(startTime.toInstant())
345                 .setEndTime(notification.getNotificationTime().toInstant());
346
347         switch (notification.getNotification()) {
348             case FINAL_OPENLOOP:
349                 /* fall through */
350             case FINAL_SUCCESS:
351                 trans.setStatusCode(true);
352                 break;
353             case FINAL_FAILURE:
354                 /* fall through */
355             case REJECTED:
356                 trans.setStatusCode(false);
357                 break;
358             default:
359                 /* unexpected */
360                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
361                         notification.getNotification(), notification);
362                 break;
363         }
364
365         try {
366             PolicyEngineConstants.getManager().transaction(controller.getName(),
367                     notification.getClosedLoopControlName(), trans.getMetric());
368         } catch (RuntimeException rex) {
369             logger.info("error pegging control loop transaction: {}", trans.getMetric(), rex);
370         }
371
372         trans.transaction().resetTransaction();
373     }
374
375     @Override
376     public String toString() {
377         return "CacheBasedControlLoopMetricsManager{" + "cacheSize=" + cacheSize
378                        + ",transactionTimeout="
379                        + transactionTimeout
380                        + ",cacheOccupancy="
381                        + getCacheOccupancy()
382                        + "}";
383     }
384
385     private String notificationTypeToResponseCode(String notificationType) {
386         String code = note2code.get(notificationType);
387         return Objects.requireNonNullElse(code, UNKNOWN_RESPONSE_CODE);
388     }
389 }