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