/*- * ============LICENSE_START======================================================= * m2/test * ================================================================================ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END========================================================= */ package org.onap.policy.controlloop.p_${unique}; import org.drools.core.spi.KnowledgeHelper; import org.onap.policy.appclcm.AppcLcmDmaapWrapper; import org.onap.policy.controlloop.VirtualControlLoopEvent; import org.onap.policy.controlloop.ControlLoopEventStatus; import org.onap.policy.controlloop.ControlLoopException; import org.onap.policy.controlloop.ControlLoopNotification; import org.onap.policy.controlloop.ControlLoopNotificationType; import org.onap.policy.controlloop.VirtualControlLoopNotification; import org.onap.policy.controlloop.compiler.ControlLoopCompiler; import org.onap.policy.controlloop.policy.ControlLoop; import org.onap.policy.controlloop.policy.ControlLoopPolicy; import org.onap.policy.drools.core.PolicySession; import org.onap.policy.drools.droolsinit.DroolsInitFeature; import org.onap.policy.drools.system.PolicyController; import org.onap.policy.drools.system.PolicyEngineConstants; import org.onap.policy.guard.GuardContext; import org.onap.policy.guard.PolicyGuardResponse; import org.onap.policy.m2.base.GuardAdjunct; import org.onap.policy.m2.base.Transaction; import org.onap.policy.m2.base.Util; import org.onap.policy.m2.appclcm.AppcLcmOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.net.URLDecoder; import java.time.Instant; import java.util.LinkedList; import java.util.Properties; declare Params closedLoopControlName : String controlLoopYaml : String notificationTopic : String operationTopic : String end declare Context logger : Logger metricsLogger : Logger auditLogger : Logger policy : ControlLoopPolicy guardContext : GuardContext maxObjectCount : long closedLoopControlName : String controlLoopYaml : String notificationTopic : String operationTopic : String end // this object is to provide support for timeouts // due to a bug in drools' built in timers declare ControlLoopTimer closedLoopControlName : String requestId : String delay : String expired : boolean //timerType is the type of timer: either "ClosedLoop" or "Operation" timerType : String end function void sendNotification(Context context, KnowledgeHelper drools, ControlLoopNotification notification) { if (notification != null) { notification.setFrom("policy"); notification.setPolicyName(drools.getRule().getName()); notification.setPolicyScope("${policyScope}"); notification.setPolicyVersion("${policyVersion}"); Util.deliver(context.getNotificationTopic(), notification); } } function Context setParams(Context context) { context.setClosedLoopControlName("${closedLoopControlName}"); context.setControlLoopYaml("${controlLoopYaml}"); context.setNotificationTopic("${notificationTopic}"); context.setOperationTopic("${operationTopic}"); context.setPolicy(ControlLoopCompiler.compile (new ByteArrayInputStream (URLDecoder.decode(context.getControlLoopYaml(), "UTF-8").getBytes()), null)); return context; } rule "${policyName}.INIT" salience 100 when $init : DroolsInitFeature.Init() not(Context(closedLoopControlName == "${closedLoopControlName}")) then { Logger logger = LoggerFactory.getLogger("${policyName}.drl"); Logger metricsLogger = LoggerFactory.getLogger("com.att.eelf.metrics"); Logger auditLogger = LoggerFactory.getLogger("com.att.eelf.audit"); logger.info("This is ${policyName}.INIT"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); Context context = new Context(); context.setLogger(logger); // add metricsLogger to context context.setMetricsLogger(metricsLogger); context.setAuditLogger(auditLogger); context = setParams(context); PolicySession session = PolicySession.getCurrentSession(); context.setGuardContext(new GuardContext(session)); try { // initially, set a default maximum object count of 1000 context.setMaxObjectCount(1000); // 'IllegalArgumentException' if the properties can't be found PolicyController policyController = Util.getPolicyController(session); Properties properties = policyController.getProperties(); String maxObjectCount = properties.getProperty("overload.maxObjectCount","").trim(); if (!maxObjectCount.isEmpty()) { // A value has been specified in the properties file -- // 'NumberFormatException' if the value is bad context.setMaxObjectCount(Long.valueOf(maxObjectCount)); } } catch (IllegalArgumentException e) { logger.error("${policyName}.INIT: Can't locate properties", e); } catch (Exception e) { logger.error ("${policyName}.INIT: Can't decode 'overload.maxObjectCount'", e); } insert(context); } end /* * This rule fires when a drools update occurs. Its purpose is to * update the expandable parameters of the context object to reflect * the new state of the policy. */ rule "${policyName}.REINIT" salience 100 when $init : DroolsInitFeature.Init() $context : Context(closedLoopControlName == "${closedLoopControlName}" && (controlLoopYaml != "${controlLoopYaml}" || notificationTopic != "${notificationTopic}" || operationTopic != "${operationTopic}")) then { Logger logger = $context.getLogger(); // get metricsLogger from context Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.REINIT"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); $context = setParams($context); modify($context) { } } end /* * Fires when an incoming 'ONSET' event occurs, without an associated * 'Transaction' instance */ rule "${policyName}.EVENT.NO-TRANSACTION" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ONSET) not(Transaction(closedLoopControlName == $context.closedLoopControlName, requestId == $event.requestId)) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); logger.info("This is ${policyName}.EVENT.NO-TRANSACTION"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); // check for overload if (drools.getWorkingMemory().getFactCount() > $context.getMaxObjectCount()) { // send 'Overload' notification ControlLoopNotification notification = new VirtualControlLoopNotification($event); notification.setNotification(ControlLoopNotificationType.REJECTED); notification.setMessage("Overload in progress"); sendNotification($context, drools, notification); // discard event, and return auditLogger.info(Instant.now() + "Event End: FAIL: Overload in progress"); retract($event); return; } Transaction transaction = new Transaction(drools.getWorkingMemory(), $context.getClosedLoopControlName(), $event.getRequestId(), $context.getPolicy()); // // Setup the Overall Control Loop timer // ControlLoopTimer clTimer = new ControlLoopTimer($event.getClosedLoopControlName(), $event.getRequestId().toString(), transaction.getTimeout(), false, "ClosedLoop"); insert(clTimer); // check that the event and a&ai is valid if (!transaction.isControlLoopEventValid($event) || !AppcLcmOperation.isAaiValid(transaction, $event)) { ControlLoopNotification notification = new VirtualControlLoopNotification($event); notification.setNotification(ControlLoopNotificationType.REJECTED); notification.setMessage(transaction.getNotificationMessage()); sendNotification($context, drools, notification); // discard event, and return auditLogger.info(Instant.now() + "Event End: FAIL: Invalid event/AAI"); retract($event); return; } // this adjunct needs to be in place before the first 'Operation' // is created GuardAdjunct.create(transaction, $context.getGuardContext()); insert(transaction); // this creates the initial 'Operation' transaction.setControlLoopEvent($event); retract($event); // send out an active notification ControlLoopNotification notification = transaction.getNotification(null); notification.setNotification(ControlLoopNotificationType.ACTIVE); sendNotification($context, drools, notification); } end /* * Fires when 'ONSET' and 'ABATED' events have occured before an appc request was processed */ rule "${policyName}.TRANSACTION.ABATED.NO-REQUEST" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state != AppcLcmOperation.LCM_PENDING && state != "COMPLETE" && state != AppcLcmOperation.LCM_COMPLETE) $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("${policyName}.TRANSACTION.ABATED.NO-REQUEST"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); $transaction.incomingMessage($event); modify($transaction) { } } end rule "${policyName}.TRANSACTION.LCM.GUARD_PENDING.RESPONSE" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_GUARD_PENDING) $response : PolicyGuardResponse(requestId == $transaction.getRequestId()) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.TRANSACTION.LCM.GUARD_PENDING.RESPONSE"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); sendNotification($context, drools, $transaction.incomingMessage($response)); // // insert operation timeout object // ControlLoopTimer opTimer = new ControlLoopTimer($transaction.getClosedLoopControlName(), $transaction.getRequestId().toString(), $transaction.getOperationTimeout(), false, "Operation"); insert(opTimer); modify($transaction) { } retract($response); } end /* * Initial state for LCM operations (Restart/Rebuild/Migrate) */ rule "${policyName}.TRANSACTION.LCM.BEGIN" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction : Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_BEGIN) ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), !expired, timerType == "Operation") not(VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId)) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.TRANSACTION.LCM.BEGIN"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); Object request = null; try { request = $transaction.getCurrentOperation().getRequest(); } catch (ControlLoopException e) { logger.error("request could not be formed due to: "+e.getMessage()); return; } Util.deliver($context.getOperationTopic(), request); // send notification sendNotification($context, drools, $transaction.initialOperationNotification()); modify($transaction) { } } end /* * We are waiting for an LCM response, and one has occurred * (it may or may not be the one we were expecting) */ rule "${policyName}.TRANSACTION.LCM.PENDING.RESPONSE" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_PENDING) $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), !expired, timerType == "Operation") $response : AppcLcmDmaapWrapper(body.output.commonHeader.requestId == $transaction.getRequestId()) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.TRANSACTION.LCM.PENDING.RESPONSE"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); ControlLoopNotification notification = $transaction.incomingMessage($response); sendNotification($context, drools, notification); if (notification != null) { retract($opTimer); } modify($transaction) { } retract($response); } end /* * * This is the timer that manages the timeout for an individual operation. * Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause * */ rule "${policyName}.LCM.OPERATION.TIMER.FIRED" timer (expr: $timeout) when $context : Context(closedLoopControlName == "${closedLoopControlName}") $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, $timeout : delay, !expired, timerType == "Operation") then Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); modify($opTimer){setExpired(true)}; end /* * We are waiting for an LCM response, but the timer expired before * receiving one. */ rule "${policyName}.TRANSACTION.LCM.PENDING.TIMEOUT" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_PENDING, $timeout : getOperationTimeout()) $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), expired, timerType == "Operation") then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.TRANSACTION.LCM.PENDING.TIMEOUT: " + $transaction.getOperationTimeout()); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); sendNotification($context, drools, $transaction.timeout()); retract($opTimer); modify($transaction) { } } end rule "${policyName}.TRANSACTION.LCM.PENDING.ERROR" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_ERROR) $timers : LinkedList() from collect (ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "Operation")) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.TRANSACTION.LCM.ERROR"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); $transaction.processError(); for (Object timer : $timers) { retract(timer); } modify($transaction) { } } end /* * We are in the 'LCM_COMPLETE' state, meaning no operations are in progress, * and no 'ABATED' message is expected so the transaction can complete. */ rule "${policyName}.TRANSACTION.COMPLETE.NOT-ABATED" when $context : Context(closedLoopControlName == "${closedLoopControlName}", getPolicy().getControlLoop().getAbatement() == false) $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE") $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop") then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); logger.info("This is ${policyName}.TRANSACTION.COMPLETE.NOT-ABATED"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); auditLogger.info(Instant.now() + " Event End: SUCCESS: NOT-ABATED"); retract($transaction); retract($clTimer); $transaction.cleanup(); sendNotification($context, drools, $transaction.finalNotification()); } end /* * We are in the 'COMPLETE' state, meaning no operations are in progress, * and we have received an 'ABATED' message. */ rule "${policyName}.TRANSACTION.COMPLETE.ABATED" when $context : Context(closedLoopControlName == "${closedLoopControlName}", getPolicy().getControlLoop().getAbatement() == true) $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE") $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId) $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop") then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); logger.info("This is ${policyName}.TRANSACTION.COMPLETE.ABATED"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); auditLogger.info(Instant.now() + "Event End: SUCCESS: ABATED"); retract($event); retract($transaction); retract($clTimer); $transaction.cleanup(); sendNotification($context, drools, $transaction.finalNotification()); } end /* * We are in the 'COMPLETE' state, meaning no operations are in progress, * and the overall transaction failed, so no ABATED message is expected. */ rule "${policyName}.TRANSACTION.COMPLETE.FAILED" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE", finalResultFailure) $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop") then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); logger.info("This is ${policyName}.TRANSACTION.COMPLETE.FAILED"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); auditLogger.info(Instant.now() + "Transaction End: FAIL: Final Result Failure"); retract($transaction); retract($clTimer); $transaction.cleanup(); sendNotification($context, drools, $transaction.finalNotification()); } end /* * * This is the timer that manages the overall control loop timeout. * Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause * */ rule "${policyName}.CLOSED_LOOP.TIMER.FIRED" timer (expr: $timeout) when $context : Context(closedLoopControlName == "${closedLoopControlName}") $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, $timeout : delay, !expired, timerType == "ClosedLoop") then Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); logger.info("This is ${policyName}.CLOSED_LOOP.TIMER.FIRED"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); modify($clTimer){setExpired(true)}; end /* * The overall transaction has timed out. */ rule "${policyName}.TRANSACTION.TIMEOUT" when $context : Context(closedLoopControlName == "${closedLoopControlName}") $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName) $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), expired, timerType == "ClosedLoop") $opTimers : LinkedList() from collect (ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "Operation")) then { Logger logger = $context.getLogger(); Logger metricsLogger = $context.getMetricsLogger(); Logger auditLogger = $context.getAuditLogger(); logger.info("This is ${policyName}.TRANSACTION.TIMEOUT"); metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage()); auditLogger.info(Instant.now() + "Transaction End: FAIL: Transaction Timeout"); retract($transaction); retract($clTimer); // Drools does not support generics, so the time of the objects in opTimers // can't be set to OperationTimer. Therefore, timer must be an Object. for (Object timer : $opTimers) { retract(timer); } $transaction.cleanup(); $transaction.clTimeout(); sendNotification($context, drools, $transaction.finalNotification()); } end /* ============================================================ */ /* * This rule is an audit runs at a low priority, and cleans up any unclaimed * 'ControlLoopEvent' instances. The assumption is that all other rules are * structured so that incoming messages will be processed, and retracted * from Drools memory (whether or not they are retained within the * transaction). */ rule "${policyName}.UNCLAIMED-EVENT" salience -1000 when $context : Context() $event : VirtualControlLoopEvent() then { Logger logger = $context.getLogger(); Logger auditLogger = $context.getAuditLogger(); logger.debug("${policyName}.UNCLAIMED-EVENT: " + $event); auditLogger.info(Instant.now() + "Event End: FAIL: UNCLAIMED-EVENT - See error log"); retract($event); } end /* * This rule is an audit runs at a low priority, and cleans up any unclaimed * 'PolicyGuardResponse' instances. The assumption is that all other rules are * structured so that incoming messages will be processed, and retracted * from Drools memory (whether or not they are retained within the * transaction). */ rule "${policyName}.UNCLAIMED-POLICY-GUARD-RESPONSE" salience -1000 when $context : Context() $response : PolicyGuardResponse() then { Logger logger = $context.getLogger(); logger.error("${policyName}.UNCLAIMED-POLICY-GUARD-RESPONSE: " + $response); retract($response); } end /* * This rule is an audit runs at a low priority, and cleans up any unclaimed * 'Response' instances. The assumption is that all other rules are * structured so that incoming messages will be processed, and retracted * from Drools memory (whether or not they are retained within the * transaction). */ rule "${policyName}.UNCLAIMED-LCM-RESPONSE" salience -1000 when $context : Context() $response : AppcLcmDmaapWrapper() then { Logger logger = $context.getLogger(); logger.error("${policyName}.UNCLAIMED-LCM-RESPONSE: " + $response); retract($response); } end