2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019-2022 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023-2024 Nordix Foundation.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.drools.apps.controlloop.feature.trans;
24 import com.google.common.cache.CacheBuilder;
25 import com.google.common.cache.CacheLoader;
26 import com.google.common.cache.LoadingCache;
27 import com.google.common.cache.RemovalListener;
28 import java.time.Instant;
29 import java.time.ZonedDateTime;
30 import java.util.ArrayList;
31 import java.util.List;
33 import java.util.Objects;
34 import java.util.UUID;
35 import java.util.concurrent.TimeUnit;
38 import org.apache.commons.collections4.CollectionUtils;
39 import org.onap.policy.controlloop.ControlLoopNotificationType;
40 import org.onap.policy.controlloop.ControlLoopOperation;
41 import org.onap.policy.controlloop.VirtualControlLoopNotification;
42 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
43 import org.onap.policy.drools.system.PolicyController;
44 import org.onap.policy.drools.system.PolicyEngineConstants;
45 import org.onap.policy.drools.utils.logging.MdcTransaction;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Control Loop Metrics Tracker Implementation.
52 class CacheBasedControlLoopMetricsManager implements ControlLoopMetrics {
54 private static final String UNEXPECTED_NOTIFICATION_TYPE = "unexpected notification type {} in notification {}";
56 private static final Logger logger = LoggerFactory.getLogger(CacheBasedControlLoopMetricsManager.class);
58 private LoadingCache<UUID, VirtualControlLoopNotification> cache;
61 private long cacheSize = ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT;
65 private long transactionTimeout = ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT;
68 * Numeric response code.
70 private static final Map<String, String> note2code = Map.of(
71 ControlLoopNotificationType.ACTIVE.name(), "100",
72 ControlLoopNotificationType.REJECTED.name(), "200",
73 ControlLoopNotificationType.OPERATION.name(), "300",
74 ControlLoopNotificationType.OPERATION_SUCCESS.name(), "301",
75 ControlLoopNotificationType.OPERATION_FAILURE.name(), "302",
76 ControlLoopNotificationType.FINAL_FAILURE.name(), "400",
77 ControlLoopNotificationType.FINAL_SUCCESS.name(), "401",
78 ControlLoopNotificationType.FINAL_OPENLOOP.name(), "402"
81 private static final String UNKNOWN_RESPONSE_CODE = "900";
83 public CacheBasedControlLoopMetricsManager() {
85 var properties = SystemPersistenceConstants.getManager()
86 .getProperties(ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME);
92 Long.parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY,
93 "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_DEFAULT));
94 } catch (Exception e) {
95 logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
96 ControlLoopMetricsFeature.CL_CACHE_TRANS_SIZE_PROPERTY, e);
99 /* transaction timeout */
102 this.transactionTimeout = Long
103 .parseLong(properties.getProperty(ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY,
104 "" + ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_DEFAULT));
105 } catch (Exception e) {
106 logger.warn("{}:{} property cannot be accessed", ControlLoopMetricsFeature.CONFIGURATION_PROPERTIES_NAME,
107 ControlLoopMetricsFeature.CL_CACHE_TRANS_TIMEOUT_SECONDS_PROPERTY, e);
110 resetCache(this.cacheSize, this.transactionTimeout);
114 public void resetCache(long cacheSize, long transactionTimeout) {
115 this.cacheSize = cacheSize;
116 this.transactionTimeout = transactionTimeout;
118 CacheLoader<UUID, VirtualControlLoopNotification> loader = new CacheLoader<>() {
121 public VirtualControlLoopNotification load(UUID key) {
126 RemovalListener<UUID, VirtualControlLoopNotification> listener = notification -> {
127 if (notification.wasEvicted()) {
128 evicted(notification.getValue());
129 } else if (logger.isInfoEnabled()) {
130 logger.info("REMOVAL: {} because of {}", notification.getValue().getRequestId(),
131 notification.getCause().name());
135 synchronized (this) {
136 if (this.cache != null) {
137 this.cache.cleanUp();
138 this.cache.invalidateAll();
141 this.cache = CacheBuilder.newBuilder().maximumSize(this.cacheSize)
142 .expireAfterWrite(transactionTimeout, TimeUnit.SECONDS).removalListener(listener).build(loader);
147 public void refresh() {
148 this.cache.cleanUp();
152 public List<UUID> getTransactionIds() {
153 return new ArrayList<>(this.cache.asMap().keySet());
157 public List<VirtualControlLoopNotification> getTransactions() {
158 return new ArrayList<>(this.cache.asMap().values());
162 public void transactionEvent(PolicyController controller, VirtualControlLoopNotification notification) {
163 if (!isNotificationValid(notification)) {
167 setNotificationValues(controller, notification);
169 switch (notification.getNotification()) {
174 endTransaction(controller, notification);
178 case OPERATION_SUCCESS:
179 case OPERATION_FAILURE:
180 /* any other value is an in progress transaction */
181 inProgressTransaction(notification);
185 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
186 notification.getNotification(), notification);
191 private boolean isNotificationValid(VirtualControlLoopNotification notification) {
192 if (notification == null || notification.getRequestId() == null || notification.getNotification() == null) {
193 logger.warn("Invalid notification: {}", notification);
200 private void setNotificationValues(PolicyController controller, VirtualControlLoopNotification notification) {
201 if (notification.getNotificationTime() == null) {
202 notification.setNotificationTime(ZonedDateTime.now());
205 notification.setFrom(notification.getFrom() + ":" + controller.getName()
206 + ":" + controller.getDrools().getCanonicalSessionNames());
210 public VirtualControlLoopNotification getTransaction(UUID requestId) {
211 return cache.getIfPresent(requestId);
215 public void removeTransaction(UUID requestId) {
216 cache.invalidate(requestId);
220 * Tracks an in progress control loop transaction.
222 * @param notification control loop notification
224 protected void inProgressTransaction(VirtualControlLoopNotification notification) {
225 if (cache.getIfPresent(notification.getRequestId()) == null) {
226 cache.put(notification.getRequestId(), notification);
229 this.metric(notification);
233 * End of a control loop transaction.
235 * @param controller controller
236 * @param notification control loop notification
238 protected void endTransaction(PolicyController controller, VirtualControlLoopNotification notification) {
239 ZonedDateTime startTime;
240 VirtualControlLoopNotification startNotification = cache.getIfPresent(notification.getRequestId());
241 startTime = Objects.requireNonNullElse(startNotification, notification).getNotificationTime();
243 this.transaction(controller, notification, startTime);
244 if (startNotification != null) {
245 removeTransaction(startNotification.getRequestId());
249 protected void evicted(VirtualControlLoopNotification notification) {
251 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
252 .setServiceName(notification.getClosedLoopControlName())
253 .setServiceInstanceId(notification.getPolicyName() + ":" + notification.getPolicyVersion())
254 .setProcessKey("" + notification.getAai())
255 .setTargetEntity(notification.getTargetType() + "." + notification.getTarget())
256 .setResponseCode(UNKNOWN_RESPONSE_CODE)
257 .setStartTime(notification.getNotificationTime().toInstant())
258 .setEndTime(Instant.now())
259 .setResponseDescription("EVICTED")
260 .setStatusCode(false)
261 .setCustomField1((notification.getNotification() != null)
262 ? notification.getNotification().name() : "")
263 .setCustomField2(notification.getPolicyScope())
264 .setClientIpAddress(notification.getClosedLoopEventClient())
271 public void setMaxCacheSize(long cacheSize) {
272 this.cacheSize = cacheSize;
276 public long getCacheOccupancy() {
277 return this.cache.size();
280 protected void metric(VirtualControlLoopNotification notification) {
281 var trans = getMdcTransaction(notification);
282 List<ControlLoopOperation> operations = notification.getHistory();
283 switch (notification.getNotification()) {
285 trans.setStatusCode(true).metric().resetTransaction();
288 operation(trans.setStatusCode(true), operations).metric().resetTransaction();
290 case OPERATION_SUCCESS:
291 operation(trans.setStatusCode(true), operations).metric().transaction().resetTransaction();
293 case OPERATION_FAILURE:
294 operation(trans.setStatusCode(false), operations).metric().transaction().resetTransaction();
298 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
299 notification.getNotification(), notification);
304 private MdcTransaction getMdcTransaction(VirtualControlLoopNotification notification) {
305 return MdcTransaction
306 .newTransaction(notification.getRequestId().toString(), notification.getFrom())
307 .setServiceName(notification.getClosedLoopControlName())
308 .setServiceInstanceId(notification.getPolicyName() + ":" + notification.getPolicyVersion())
309 .setProcessKey("" + notification.getAai())
310 .setTargetEntity(notification.getTargetType() + "." + notification.getTarget())
311 .setResponseCode((notification.getNotification() != null)
312 ? notificationTypeToResponseCode(notification.getNotification().name())
313 : UNKNOWN_RESPONSE_CODE)
314 .setCustomField1((notification.getNotification() != null)
315 ? notification.getNotification().name() : "")
316 .setCustomField2(notification.getPolicyScope())
317 .setResponseDescription(notification.getMessage())
318 .setClientIpAddress(notification.getClosedLoopEventClient());
321 protected MdcTransaction operation(MdcTransaction trans, List<ControlLoopOperation> operations) {
322 if (CollectionUtils.isEmpty(operations)) {
326 ControlLoopOperation operation = operations.get(operations.size() - 1);
328 if (operation.getActor() != null) {
329 trans.setTargetServiceName(operation.getActor() + "." + operation.getOperation());
332 if (operation.getTarget() != null) {
333 trans.setTargetVirtualEntity(operation.getTarget());
336 if (operation.getSubRequestId() != null) {
337 trans.setInvocationId(operation.getSubRequestId());
340 if (operation.getOutcome() != null) {
341 trans.setResponseDescription(operation.getOutcome() + ":" + operation.getMessage());
344 if (operation.getStart() != null) {
345 trans.setStartTime(operation.getStart());
348 if (operation.getEnd() != null) {
349 trans.setEndTime(operation.getEnd());
355 protected void transaction(PolicyController controller,
356 VirtualControlLoopNotification notification, ZonedDateTime startTime) {
357 MdcTransaction trans = getMdcTransaction(notification)
358 .setStartTime(startTime.toInstant())
359 .setEndTime(notification.getNotificationTime().toInstant());
361 switch (notification.getNotification()) {
365 trans.setStatusCode(true);
370 trans.setStatusCode(false);
374 logger.warn(UNEXPECTED_NOTIFICATION_TYPE,
375 notification.getNotification(), notification);
380 PolicyEngineConstants.getManager().transaction(controller.getName(),
381 notification.getClosedLoopControlName(), trans.getMetric());
382 } catch (RuntimeException rex) {
383 logger.info("error pegging control loop transaction: {}", trans.getMetric(), rex);
386 trans.transaction().resetTransaction();
390 public String toString() {
391 return "CacheBasedControlLoopMetricsManager{" + "cacheSize=" + cacheSize
392 + ",transactionTimeout="
395 + getCacheOccupancy()
399 private String notificationTypeToResponseCode(String notificationType) {
400 String code = note2code.get(notificationType);
401 return Objects.requireNonNullElse(code, UNKNOWN_RESPONSE_CODE);