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