9dc4c5ad37849eed4633b1d884018f78175aa36f
[policy/drools-applications.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019 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.onap.policy.controlloop.ControlLoopOperation;
35 import org.onap.policy.controlloop.VirtualControlLoopNotification;
36 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
37 import org.onap.policy.drools.system.PolicyController;
38 import org.onap.policy.drools.utils.logging.MdcTransaction;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Control Loop Metrics Tracker Implementation.
44  */
45 class CacheBasedControlLoopMetricsManager implements ControlLoopMetrics {
46
47     private static final String UNEXPECTED_NOTIFICATION_TYPE = "unexpected notification type {} in notification {}";
48
49     private static final Logger logger = LoggerFactory.getLogger(CacheBasedControlLoopMetricsManager.class);
50
51     private LoadingCache<UUID, VirtualControlLoopNotification> cache;
52     private long cacheSize = ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT;
53
54     private long transactionTimeout = ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT;
55
56     public CacheBasedControlLoopMetricsManager() {
57
58         Properties properties = SystemPersistenceConstants.getManager()
59                         .getProperties(ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME);
60
61         /* cache size */
62
63         try {
64             this.cacheSize =
65                     Long.parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY,
66                             "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT));
67         } catch (Exception e) {
68             logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
69                     ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY, e);
70         }
71
72         /* transaction timeout */
73
74         try {
75             this.transactionTimeout = Long
76                     .parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY,
77                             "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT));
78         } catch (Exception e) {
79             logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
80                     ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY, e);
81         }
82
83         resetCache(this.cacheSize, this.transactionTimeout);
84     }
85
86     @Override
87     public void resetCache(long cacheSize, long transactionTimeout) {
88         this.cacheSize = cacheSize;
89         this.transactionTimeout = transactionTimeout;
90
91         CacheLoader<UUID, VirtualControlLoopNotification> loader =
92                 new CacheLoader<UUID, VirtualControlLoopNotification>() {
93
94             @Override
95             public VirtualControlLoopNotification load(UUID key) throws Exception {
96                 return null;
97             }
98         };
99
100         RemovalListener<UUID, VirtualControlLoopNotification> listener = notification -> {
101             if (notification.wasEvicted()) {
102                 evicted(notification.getValue());
103             } else {
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     }
181
182     @Override
183     public VirtualControlLoopNotification getTransaction(UUID requestId) {
184         return cache.getIfPresent(requestId);
185     }
186
187     @Override
188     public void removeTransaction(UUID requestId) {
189         cache.invalidate(requestId);
190     }
191
192     /**
193      * Tracks an in progress control loop transaction.
194      *
195      * @param notification control loop notification
196      */
197     protected void inProgressTransaction(VirtualControlLoopNotification notification) {
198         if (cache.getIfPresent(notification.getRequestId()) == null) {
199             cache.put(notification.getRequestId(), notification);
200         }
201
202         this.metric(notification);
203     }
204
205     /**
206      * End of a control loop transaction.
207      *
208      * @param notification control loop notification
209      */
210     protected void endTransaction(VirtualControlLoopNotification notification) {
211         ZonedDateTime startTime;
212         VirtualControlLoopNotification startNotification = cache.getIfPresent(notification.getRequestId());
213         if (startNotification != null) {
214             startTime = startNotification.getNotificationTime();
215         } else {
216             startTime = notification.getNotificationTime();
217         }
218
219         this.transaction(notification, startTime);
220         if (startNotification != null) {
221             cache.invalidate(startNotification);
222         }
223     }
224
225     protected void evicted(VirtualControlLoopNotification notification) {
226         MdcTransaction
227                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
228                 .setServiceName(notification.getClosedLoopControlName()).setTargetEntity(notification.getTarget())
229                 .setStartTime(notification.getNotificationTime().toInstant()).setEndTime(Instant.now())
230                 .setResponseDescription("EVICTED").setStatusCode(false).metric().resetTransaction();
231     }
232
233     @Override
234     public long getCacheSize() {
235         return this.cacheSize;
236     }
237
238     @Override
239     public void setMaxCacheSize(long cacheSize) {
240         this.cacheSize = cacheSize;
241     }
242
243     @Override
244     public long getTransactionTimeout() {
245         return this.transactionTimeout;
246     }
247
248     @Override
249     public void setTransactionTimeout(long transactionTimeout) {
250         this.transactionTimeout = transactionTimeout;
251     }
252
253     @Override
254     public long getCacheOccupancy() {
255         return this.cache.size();
256     }
257
258     protected void metric(VirtualControlLoopNotification notification) {
259         MdcTransaction trans = MdcTransaction
260                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
261                 .setServiceName(notification.getClosedLoopControlName()).setTargetEntity(notification.getTarget());
262
263         List<ControlLoopOperation> operations = notification.getHistory();
264         switch (notification.getNotification()) {
265             case ACTIVE:
266                 trans.setStatusCode(true);
267                 trans.metric().resetTransaction();
268                 break;
269             case OPERATION:
270                 metricOperation(trans, operations);
271                 break;
272             case OPERATION_SUCCESS:
273                 trans.setStatusCode(true);
274                 operation(trans, operations);
275                 trans.transaction().resetTransaction();
276                 break;
277             case OPERATION_FAILURE:
278                 trans.setStatusCode(false);
279                 operation(trans, operations);
280                 trans.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 void metricOperation(MdcTransaction trans, List<ControlLoopOperation> operations) {
291         trans.setStatusCode(true);
292         if (!operations.isEmpty()) {
293             ControlLoopOperation operation = operations.get(operations.size() - 1);
294             trans.setTargetEntity(operation.getTarget());
295             trans.setTargetServiceName(operation.getActor());
296         }
297         trans.metric().resetTransaction();
298     }
299
300     protected void operation(MdcTransaction trans, List<ControlLoopOperation> operations) {
301         if (!operations.isEmpty()) {
302             ControlLoopOperation operation = operations.get(operations.size() - 1);
303
304             if (operation.getTarget() != null) {
305                 trans.setTargetEntity(operation.getTarget());
306             }
307
308             if (operation.getActor() != null) {
309                 trans.setTargetServiceName(operation.getActor());
310             }
311
312             if (operation.getMessage() != null) {
313                 trans.setResponseDescription(operation.getMessage());
314             }
315
316             trans.setInvocationId(operation.getSubRequestId());
317
318             if (operation.getOutcome() != null) {
319                 trans.setResponseCode(operation.getOutcome());
320             }
321
322             if (operation.getStart() != null) {
323                 trans.setStartTime(operation.getStart());
324             }
325
326             if (operation.getEnd() != null) {
327                 trans.setEndTime(operation.getEnd());
328             }
329         }
330     }
331
332     protected void transaction(VirtualControlLoopNotification notification, ZonedDateTime startTime) {
333         MdcTransaction trans = MdcTransaction
334                 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
335                 .setServiceName(notification.getClosedLoopControlName()).setTargetEntity(notification.getTarget())
336                 .setStartTime(startTime.toInstant()).setEndTime(notification.getNotificationTime().toInstant())
337                 .setResponseDescription(notification.getMessage());
338
339         switch (notification.getNotification()) {
340             case FINAL_OPENLOOP:
341                 trans.setStatusCode(true);
342                 break;
343             case FINAL_SUCCESS:
344                 trans.setStatusCode(true);
345                 break;
346             case FINAL_FAILURE:
347                 trans.setStatusCode(false);
348                 break;
349             case REJECTED:
350                 trans.setStatusCode(false);
351                 break;
352             default:
353                 /* unexpected */
354                 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
355                         notification.getNotification(), notification);
356                 break;
357         }
358
359         trans.transaction().resetTransaction();
360     }
361
362     @Override
363     public String toString() {
364         final StringBuilder sb = new StringBuilder();
365         sb.append("CacheBasedControlLoopMetricsManager{");
366         sb.append("cacheSize=").append(cacheSize);
367         sb.append(", transactionTimeout=").append(transactionTimeout);
368         sb.append('}');
369         return sb.toString();
370     }
371 }