2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.drools.apps.controlloop.feature.trans;
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;
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;
47 * Control Loop Metrics Tracker Implementation.
49 class CacheBasedControlLoopMetricsManager implements ControlLoopMetrics {
51 private static final String UNEXPECTED_NOTIFICATION_TYPE = "unexpected notification type {} in notification {}";
53 private static final Logger logger = LoggerFactory.getLogger(CacheBasedControlLoopMetricsManager.class);
55 private LoadingCache<UUID, VirtualControlLoopNotification> cache;
56 private long cacheSize = ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT;
58 private long transactionTimeout = ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT;
61 * Numeric response code.
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"
74 private static final String UNKNOWN_RESPONSE_CODE = "900";
76 public CacheBasedControlLoopMetricsManager() {
78 var properties = SystemPersistenceConstants.getManager()
79 .getProperties(ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME);
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);
92 /* transaction timeout */
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);
103 resetCache(this.cacheSize, this.transactionTimeout);
107 public void resetCache(long cacheSize, long transactionTimeout) {
108 this.cacheSize = cacheSize;
109 this.transactionTimeout = transactionTimeout;
111 CacheLoader<UUID, VirtualControlLoopNotification> loader = new CacheLoader<>() {
114 public VirtualControlLoopNotification load(UUID key) {
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());
128 synchronized (this) {
129 if (this.cache != null) {
130 this.cache.cleanUp();
131 this.cache.invalidateAll();
134 this.cache = CacheBuilder.newBuilder().maximumSize(this.cacheSize)
135 .expireAfterWrite(transactionTimeout, TimeUnit.SECONDS).removalListener(listener).build(loader);
140 public void refresh() {
141 this.cache.cleanUp();
145 public List<UUID> getTransactionIds() {
146 return new ArrayList<>(this.cache.asMap().keySet());
150 public List<VirtualControlLoopNotification> getTransactions() {
151 return new ArrayList<>(this.cache.asMap().values());
155 public void transactionEvent(PolicyController controller, VirtualControlLoopNotification notification) {
156 if (!isNotificationValid(notification)) {
160 setNotificationValues(controller, notification);
162 switch (notification.getNotification()) {
167 endTransaction(controller, notification);
171 case OPERATION_SUCCESS:
172 case OPERATION_FAILURE:
173 /* any other value is an in progress transaction */
174 inProgressTransaction(notification);
178 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
179 notification.getNotification(), notification);
184 private boolean isNotificationValid(VirtualControlLoopNotification notification) {
185 if (notification == null || notification.getRequestId() == null || notification.getNotification() == null) {
186 logger.warn("Invalid notification: {}", notification);
193 private void setNotificationValues(PolicyController controller, VirtualControlLoopNotification notification) {
194 if (notification.getNotificationTime() == null) {
195 notification.setNotificationTime(ZonedDateTime.now());
198 notification.setFrom(notification.getFrom() + ":" + controller.getName()
199 + ":" + controller.getDrools().getCanonicalSessionNames());
203 public VirtualControlLoopNotification getTransaction(UUID requestId) {
204 return cache.getIfPresent(requestId);
208 public void removeTransaction(UUID requestId) {
209 cache.invalidate(requestId);
213 * Tracks an in progress control loop transaction.
215 * @param notification control loop notification
217 protected void inProgressTransaction(VirtualControlLoopNotification notification) {
218 if (cache.getIfPresent(notification.getRequestId()) == null) {
219 cache.put(notification.getRequestId(), notification);
222 this.metric(notification);
226 * End of a control loop transaction.
228 * @param controller controller
229 * @param notification control loop notification
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();
236 this.transaction(controller, notification, startTime);
237 if (startNotification != null) {
238 removeTransaction(startNotification.getRequestId());
242 protected void evicted(VirtualControlLoopNotification notification) {
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();
251 public long getCacheSize() {
252 return this.cacheSize;
256 public void setMaxCacheSize(long cacheSize) {
257 this.cacheSize = cacheSize;
261 public long getTransactionTimeout() {
262 return this.transactionTimeout;
266 public void setTransactionTimeout(long transactionTimeout) {
267 this.transactionTimeout = transactionTimeout;
271 public long getCacheOccupancy() {
272 return this.cache.size();
275 protected void metric(VirtualControlLoopNotification notification) {
276 var trans = getMdcTransaction(notification);
277 List<ControlLoopOperation> operations = notification.getHistory();
278 switch (notification.getNotification()) {
280 trans.setStatusCode(true).metric().resetTransaction();
283 operation(trans.setStatusCode(true), operations).metric().resetTransaction();
285 case OPERATION_SUCCESS:
286 operation(trans.setStatusCode(true), operations).metric().transaction().resetTransaction();
288 case OPERATION_FAILURE:
289 operation(trans.setStatusCode(false), operations).metric().transaction().resetTransaction();
293 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
294 notification.getNotification(), notification);
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());
316 protected MdcTransaction operation(MdcTransaction trans, List<ControlLoopOperation> operations) {
317 if (CollectionUtils.isEmpty(operations)) {
321 ControlLoopOperation operation = operations.get(operations.size() - 1);
323 if (operation.getActor() != null) {
324 trans.setTargetServiceName(operation.getActor() + "." + operation.getOperation());
327 if (operation.getTarget() != null) {
328 trans.setTargetVirtualEntity(operation.getTarget());
331 if (operation.getSubRequestId() != null) {
332 trans.setInvocationId(operation.getSubRequestId());
335 if (operation.getOutcome() != null) {
336 trans.setResponseDescription(operation.getOutcome() + ":" + operation.getMessage());
339 if (operation.getStart() != null) {
340 trans.setStartTime(operation.getStart());
343 if (operation.getEnd() != null) {
344 trans.setEndTime(operation.getEnd());
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());
356 switch (notification.getNotification()) {
360 trans.setStatusCode(true);
365 trans.setStatusCode(false);
369 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
370 notification.getNotification(), notification);
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);
381 trans.transaction().resetTransaction();
385 public String toString() {
386 return "CacheBasedControlLoopMetricsManager{" + "cacheSize=" + cacheSize
387 + ",transactionTimeout="
390 + getCacheOccupancy()
394 private String notificationTypeToResponseCode(String notificationType) {
395 String code = note2code.get(notificationType);
396 return Objects.requireNonNullElse(code, UNKNOWN_RESPONSE_CODE);