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