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