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