From faf283066f186838665ed5c38c1ba8319041bc1c Mon Sep 17 00:00:00 2001 From: Joshua Reich Date: Fri, 14 Sep 2018 11:38:01 -0700 Subject: Add demo for Control Loop Coordination. New PipEngine and Junit test added to guard. Also bug in existing Junit test fixed. All other code added to new directory template.demo.clc Change-Id: Ida2267528bcb9404dc59ff391d45797b591814cc Issue-ID: POLICY-1109 Signed-off-by: Joshua Reich --- .../onap/policy/guard/CallGuardTaskEmbedded.java | 165 +++ .../guard/PolicyGuardXacmlHelperEmbedded.java | 518 +++++++ .../main/resources/__closedLoopControlName__.drl | 1413 ++++++++++++++++++++ 3 files changed, 2096 insertions(+) create mode 100644 controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/CallGuardTaskEmbedded.java create mode 100644 controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelperEmbedded.java create mode 100644 controlloop/templates/template.demo.clc/src/main/resources/__closedLoopControlName__.drl (limited to 'controlloop/templates/template.demo.clc/src/main') diff --git a/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/CallGuardTaskEmbedded.java b/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/CallGuardTaskEmbedded.java new file mode 100644 index 000000000..aaa2a0a83 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/CallGuardTaskEmbedded.java @@ -0,0 +1,165 @@ +/*- + * ============LICENSE_START======================================================= + * guard + * ================================================================================ + * Copyright (C) 2018 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.guard; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; +import org.drools.core.WorkingMemory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.att.research.xacml.api.DataTypeException; +import com.att.research.xacml.std.annotations.RequestParser; + +public class CallGuardTaskEmbedded implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(CallGuardTaskEmbedded.class); + + /** + * Actor/recipe pairs whose guard requests need a VF Module count. Each element is of + * the form ":". + */ + private static final Set NEEDS_VF_COUNT = new HashSet<>(); + + /** + * Actor/recipe pairs whose guard requests need the VF Module count to be incremented + * (i.e., because a module is being added). Each element is of the form + * ":". + */ + private static final Set INCR_VF_COUNT = new HashSet<>(); + + static { + INCR_VF_COUNT.add("SO:VF Module Create"); + NEEDS_VF_COUNT.addAll(INCR_VF_COUNT); + } + + private WorkingMemory workingMemory; + private String clname; + private String actor; + private String recipe; + private String target; + private String requestId; + private Integer vfCount; + + /** + * Populated once the response has been determined, which may happen during the + * constructor or later, during {@link #run()}. + */ + private PolicyGuardResponse guardResponse; + + /** + * Guard url is grabbed from PolicyEngine.manager properties + */ + public CallGuardTaskEmbedded(WorkingMemory wm, String cl, String act, String rec, String tar, String reqId, Supplier vfcnt) { + workingMemory = wm; + clname = cl; + actor = act; + recipe = rec; + requestId = reqId; + target = tar; + + vfCount = null; + + String key = act + ":" + rec; + + if (NEEDS_VF_COUNT.contains(key)) { + // this actor/recipe needs the count - get it + if ((vfCount = vfcnt.get()) == null) { + /* + * The count is missing - create an artificial Deny, which will be + * inserted into working memory when "run()" is called. + */ + guardResponse = new PolicyGuardResponse(Util.DENY, UUID.fromString(requestId), recipe); + logger.error("CallEmbeddedGuardTask.run missing VF Module count; requestId={}", requestId); + return; + } + + if (INCR_VF_COUNT.contains(key)) { + // this actor/recipe needs the count to be incremented + ++vfCount; + } + } + } + + @Override + public void run() { + if (guardResponse != null) { + // already have a response - just insert it + workingMemory.insert(guardResponse); + return; + } + + final long startTime = System.nanoTime(); + com.att.research.xacml.api.Request request = null; + + PolicyGuardXacmlRequestAttributes xacmlReq = + new PolicyGuardXacmlRequestAttributes(clname, actor, recipe, target, requestId, vfCount); + + try { + request = RequestParser.parseRequest(xacmlReq); + } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) { + logger.error("CallEmbeddedGuardTask.run threw: {}", e); + } + + + logger.debug("\n********** XACML REQUEST START ********"); + logger.debug("{}", request); + logger.debug("********** XACML REQUEST END ********\n"); + + String guardDecision = null; + + // + // Make guard request + // + guardDecision = new PolicyGuardXacmlHelperEmbedded().callPdp(xacmlReq); + + logger.debug("\n********** XACML RESPONSE START ********"); + logger.debug("{}", guardDecision); + logger.debug("********** XACML RESPONSE END ********\n"); + + // + // Check if the restful call was unsuccessful or property doesn't exist + // + if (guardDecision == null) { + logger.error("********** XACML FAILED TO CONNECT ********"); + guardDecision = Util.INDETERMINATE; + } + + guardResponse = new PolicyGuardResponse(guardDecision, UUID.fromString(this.requestId), this.recipe); + + + // + // Create an artificial Guard response in case we didn't get a clear Permit or Deny + // + if (guardResponse.getResult().equals("Indeterminate")) { + guardResponse.setOperation(recipe); + guardResponse.setRequestID(UUID.fromString(requestId)); + } + + long estimatedTime = System.nanoTime() - startTime; + logger.debug("\n\n============ Guard inserted with decision {} !!! =========== time took: {} mili sec \n\n", + guardResponse.getResult(), (double) estimatedTime / 1000 / 1000); + workingMemory.insert(guardResponse); + + } + +} diff --git a/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelperEmbedded.java b/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelperEmbedded.java new file mode 100644 index 000000000..947b187e4 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelperEmbedded.java @@ -0,0 +1,518 @@ +/*- + * ============LICENSE_START======================================================= + * guard + * ================================================================================ + * Copyright (C) 2018 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.guard; + +import com.att.research.xacml.api.Attribute; +import com.att.research.xacml.api.AttributeCategory; +import com.att.research.xacml.api.AttributeValue; +import com.att.research.xacml.api.Result; +import com.att.research.xacml.api.pdp.PDPEngine; +import com.att.research.xacml.api.pdp.PDPException; +import com.att.research.xacml.api.pdp.PDPEngineFactory; +import com.att.research.xacmlatt.pdp.ATTPDPEngineFactory; +import com.att.research.xacml.std.annotations.RequestParser; + +import com.google.gson.Gson; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Iterator; +import java.util.Properties; +import java.util.UUID; + +import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; +import org.json.JSONObject; +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes; +import org.onap.policy.guard.PolicyGuardResponse; +import org.onap.policy.guard.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class PolicyGuardXacmlHelperEmbedded { + private static final Logger logger = LoggerFactory.getLogger(PolicyGuardXacmlHelperEmbedded.class); + private static final Logger netLogger = + LoggerFactory.getLogger(org.onap.policy.common.endpoints.event.comm.Topic.NETWORK_LOGGER); + + // Constant for the systme line separator + private static final String SYSTEM_LS = System.lineSeparator(); + private static String propfile; + + public PolicyGuardXacmlHelperEmbedded() { + init(PolicyEngine.manager.getEnvironment()); + } + + // initialized from 'pdpx.url' property -- + // Each entry in 'restUrls' contains a destination URL, and an optional + // 'Authorization' header entry. 'restUrlIndex' indicates the next + // entry to try -- after each failure, the index is advanced to the + // next entry (wrapping to the beginning, if needed). + private static class UrlEntry implements Serializable { + private static final long serialVersionUID = -8859237552195400518L; + + URL restUrl; + String authorization = null; + String clientAuth = null; + String environment = null; + } + + private UrlEntry[] restUrls = null; + private int restUrlIndex = 0; + + // REST timeout, initialized from 'pdpx.timeout' property + private int timeout = 20000; + + /** + * Call PDP. + * + * @param xacmlReq the XACML request + * @return the response + */ + public String callPdp(PolicyGuardXacmlRequestAttributes xacmlReq) { + // + // Send it to the PDP + // + String response = null; + + if ( propfile != null ) { + logger.debug("callEmbeddedPdp"); + return callEmbeddedPdp(xacmlReq); + } + // + // Build the json request + // + JSONObject attributes = new JSONObject(); + attributes.put("actor", xacmlReq.getActorID()); + attributes.put("recipe", xacmlReq.getOperationID()); + attributes.put("target", xacmlReq.getTargetID()); + if (xacmlReq.getClnameID() != null) { + attributes.put("clname", xacmlReq.getClnameID()); + } + if (xacmlReq.getVfCount() != null) { + attributes.put("vfCount", xacmlReq.getVfCount()); + } + JSONObject jsonReq = new JSONObject(); + jsonReq.put("decisionAttributes", attributes); + jsonReq.put("onapName", "PDPD"); + + + try { + // + // Call RESTful PDP + // + UrlEntry urlEntry = restUrls[restUrlIndex]; + String jsonRequestString = jsonReq.toString(); + netLogger.info("[OUT|{}|{}|]{}{}", "GUARD", urlEntry.restUrl, SYSTEM_LS, jsonRequestString); + response = callRestfulPdp(new ByteArrayInputStream(jsonReq.toString().getBytes()), urlEntry.restUrl, + urlEntry.authorization, urlEntry.clientAuth, urlEntry.environment); + netLogger.info("[IN|{}|{}|]{}{}", "GUARD", urlEntry.restUrl, SYSTEM_LS, response); + } catch (Exception e) { + logger.error("Error in sending RESTful request: ", e); + } + + return response; + } + + /** + * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision. + * + * @param is the InputStream + * @param authorization the Authorization + * @param clientauth the ClientAuth + * @param environment the Environment + * @return response from guard which contains "Permit" or "Deny" + */ + private String callRestfulPdp(InputStream is, URL restURL, String authorization, String clientauth, + String environment) { + HttpURLConnection connection = null; + + try { + // + // Open up the connection + // + connection = (HttpURLConnection) restURL.openConnection(); + connection.setRequestProperty("Content-Type", "application/json"); + // + // Setup our method and headers + // + connection.setRequestProperty("Accept", "application/json"); + if (authorization != null) { + connection.setRequestProperty("Authorization", authorization); + } + if (clientauth != null) { + connection.setRequestProperty("ClientAuth", clientauth); + } + if (environment != null) { + connection.setRequestProperty("Environment", environment); + } + connection.setConnectTimeout(timeout); + connection.setReadTimeout(timeout); + connection.setRequestMethod("POST"); + connection.setUseCaches(false); + // + // Adding this in. It seems the HttpUrlConnection class does NOT + // properly forward our headers for POST re-direction. It does so + // for a GET re-direction. + // + // So we need to handle this ourselves. + // + connection.setInstanceFollowRedirects(false); + connection.setDoOutput(true); + connection.setDoInput(true); + // + // Send the request + // + try (OutputStream os = connection.getOutputStream()) { + IOUtils.copy(is, os); + } + + // + // Do the connect + // + connection.connect(); + + if (connection.getResponseCode() != 200) { + logger.error(connection.getResponseCode() + " " + connection.getResponseMessage()); + return Util.INDETERMINATE; + } + } catch (Exception e) { + logger.error("Exception in 'PolicyGuardEmbeddedHelper.callRESTfulPDP'", e); + return Util.INDETERMINATE; + } + + // + // Read the response + // + try { + ContentType contentType = ContentType.parse(connection.getContentType()); + + if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) { + InputStream inputStream = connection.getInputStream(); + int contentLength = connection.getContentLength(); + + return readResponseFromStream(inputStream, contentLength); + } else { + logger.error("unknown content-type: {}", contentType); + return Util.INDETERMINATE; + } + + } catch (Exception e) { + String message = "Parsing Content-Type: " + connection.getContentType(); + logger.error(message, e); + return Util.INDETERMINATE; + } + } + + /** + * Call embedded PDP. + * + * @param xacmlReq the XACML request + * @return the response + */ + public static String callEmbeddedPdp(PolicyGuardXacmlRequestAttributes xacmlReq) { + com.att.research.xacml.api.Response response = null; + Properties props = new Properties(); + // + // Get properties + // + try ( InputStream is = new FileInputStream(propfile); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr) ) { + props.load(br); + } catch (Exception e) { + logger.error("Unable to load properties file {} {}", propfile, e.getMessage()); + } + // + // Create embedded PDPEngine + // + PDPEngine xacmlPdpEngine; + try { + xacmlPdpEngine = ATTPDPEngineFactory.newInstance().newEngine(props); + } catch (Exception e) { + logger.error("Failed to create new PDPEngine {}", e.getMessage()); + return null; + } + logger.debug("embedded Engine created"); + // + // Embedded call to PDP + // + long lTimeStart = System.currentTimeMillis(); + if (xacmlReq.getVfCount() == null ) { + xacmlReq.setVfCount(1); + } + try { + response = xacmlPdpEngine.decide(RequestParser.parseRequest(xacmlReq)); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + long lTimeEnd = System.currentTimeMillis(); + logger.debug("Elapsed Time: {} ms", (lTimeEnd - lTimeStart)); + // + // Convert response to string + // + logger.debug("converting response to string"); + PolicyGuardResponse pgr = parseXacmlPdpResponse(response); + logger.debug("parsed XacmlPdpResponse {}", pgr); + String decision = pgr.getResult(); + logger.debug("decision={}",decision); + return decision; + } + + /** + * Parse XACML PDP response. + * + * @param xacmlResponse the XACML response + * @return the PolicyGuardResponse + */ + public static PolicyGuardResponse parseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse) { + if (xacmlResponse == null) { + // + // In case the actual XACML response was null, create an empty + // response object with decision "Indeterminate" + // + return new PolicyGuardResponse("Indeterminate", null, ""); + } + + Iterator itRes = xacmlResponse.getResults().iterator(); + + Result res = itRes.next(); + String decisionFromXacmlResponse = res.getDecision().toString(); + Iterator itAttrCat = res.getAttributes().iterator(); + UUID reqIdFromXacmlResponse = null; + String operationFromXacmlResponse = ""; + + while (itAttrCat.hasNext()) { + Iterator itAttr = itAttrCat.next().getAttributes().iterator(); + while (itAttr.hasNext()) { + Attribute currentAttr = itAttr.next(); + String attributeId = currentAttr.getAttributeId().stringValue(); + if ("urn:oasis:names:tc:xacml:1.0:request:request-id".equals(attributeId)) { + Iterator> itValues = currentAttr.getValues().iterator(); + reqIdFromXacmlResponse = UUID.fromString(itValues.next().getValue().toString()); + } + if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id".equals(attributeId)) { + Iterator> itValues = currentAttr.getValues().iterator(); + operationFromXacmlResponse = itValues.next().getValue().toString(); + } + } + } + + return new PolicyGuardResponse(decisionFromXacmlResponse, reqIdFromXacmlResponse, operationFromXacmlResponse); + + } + + private void init(Properties properties) { + propfile = properties.getProperty("prop.guard.propfile"); + + // used to store error messages + StringBuilder sb = new StringBuilder(); + + // fetch these parameters, if they exist + String timeoutString = properties.getProperty("pdpx.timeout"); + String disabledString = properties.getProperty("guard.disabled"); + + if (disabledString != null && Boolean.parseBoolean(disabledString)) { + return; + } + + ArrayList entries = initEntries(properties, sb); + + if (entries.isEmpty()) { + sb.append("'pdpx.*' -- no URLs specified, "); + } else { + restUrls = entries.toArray(new UrlEntry[0]); + } + + if (timeoutString != null) { + try { + // decode optional 'pdpx.timeout' parameter + timeout = Integer.valueOf(timeoutString); + } catch (NumberFormatException e) { + sb.append("'pdpx.timeout': " + e + ", "); + logger.trace(e.getLocalizedMessage()); + } + } + + + // if there are any errors, update 'errorMessage' & disable guard + // queries + if (sb.length() != 0) { + // remove the terminating ", ", and extract resulting error message + sb.setLength(sb.length() - 2); + String errorMessage = sb.toString(); + logger.error("Initialization failure: {}", errorMessage); + } + } + + private ArrayList initEntries(Properties properties, StringBuilder sb) { + // now, see which numeric entries (1-9) exist + ArrayList entries = new ArrayList<>(); + + for (int index = 0; index < 10; index += 1) { + String urlPrefix = "guard."; + if (index != 0) { + urlPrefix = urlPrefix + index + "."; + } + + // see if the associated URL exists + String restUrllist = properties.getProperty(urlPrefix + "url"); + if (nullOrEmpty(restUrllist)) { + // no entry for this index + continue; + } + + // support a list of entries separated by semicolons. Each entry + // can be: + // URL + // URL,user + // URL,user,password + for (String restUrl : restUrllist.split("\\s*;\\s*")) { + UrlEntry entry = initRestUrl(properties, sb, restUrl); + // include this URLEntry in the list + if (entry != null) { + entries.add(entry); + } + } + } + + return entries; + } + + private UrlEntry initRestUrl(Properties properties, StringBuilder sb, String restUrl) { + String urlPrefix = "guard."; + String pdpxPrefix = "pdpx."; + + String[] segments = restUrl.split("\\s*,\\s*"); + String user = null; + String password = null; + + if (segments.length >= 2) { + // user id is provided + restUrl = segments[0]; + user = segments[1]; + if (segments.length >= 3) { + // password is also provided + password = segments[2]; + } + } + + // URL does exist -- create the entry + UrlEntry urlEntry = new UrlEntry(); + try { + urlEntry.restUrl = new URL(restUrl); + } catch (java.net.MalformedURLException e) { + // if we don't have a URL, + // don't bother with the rest on this one + sb.append("'").append(urlPrefix).append("url' '").append(restUrl).append("': ").append(e).append(","); + return null; + } + + if (nullOrEmpty(user)) { + // user id was not provided on '*.url' line -- + // extract it from a separate property + user = properties.getProperty(pdpxPrefix + "username", properties.getProperty("pdpx.username")); + } + if (nullOrEmpty(password)) { + // password was not provided on '*.url' line -- + // extract it from a separate property + password = properties.getProperty(pdpxPrefix + "password", properties.getProperty("pdpx.password")); + } + + // see if 'user' and 'password' entries both exist + if (!nullOrEmpty(user) && !nullOrEmpty(password)) { + urlEntry.authorization = "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes()); + } + + // see if 'client.user' and 'client.password' entries both exist + String clientUser = + properties.getProperty(pdpxPrefix + "client.username", properties.getProperty("pdpx.client.username")); + String clientPassword = + properties.getProperty(pdpxPrefix + "client.password", properties.getProperty("pdpx.client.password")); + if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) { + urlEntry.clientAuth = + "Basic " + Base64.getEncoder().encodeToString((clientUser + ":" + clientPassword).getBytes()); + } + + // see if there is an 'environment' entry + String environment = + properties.getProperty(pdpxPrefix + "environment", properties.getProperty("pdpx.environment")); + if (!nullOrEmpty(environment)) { + urlEntry.environment = environment; + } + + return urlEntry; + } + + /** + * Check if a string is null or an empty string. + * + * @param value the string to be tested + * @return 'true' if the string is 'null' or has a length of 0, 'false' otherwise + */ + private static boolean nullOrEmpty(String value) { + return (value == null || value.isEmpty()); + } + + private static String readResponseFromStream(InputStream inputStream, int contentLength) throws IOException { + // if content length is -1, response is chunked, and + // TCP connection will be dropped at the end + byte[] buf = new byte[contentLength < 0 ? 1024 : contentLength]; + + int offset = 0; + do { + int size = inputStream.read(buf, offset, buf.length - offset); + if (size < 0) { + // In a chunked response a dropped connection is expected, but not if the response + // is not chunked + if (contentLength > 0) { + logger.error("partial input stream"); + } + break; + } + offset += size; + } + while (offset != contentLength); + + String response = new String(buf, 0, offset); + + // + // Connection may have failed or not been 200 OK, return Indeterminate + // + if (response.isEmpty()) { + return Util.INDETERMINATE; + } + + return new JSONObject(response).getString("decision"); + + } +} diff --git a/controlloop/templates/template.demo.clc/src/main/resources/__closedLoopControlName__.drl b/controlloop/templates/template.demo.clc/src/main/resources/__closedLoopControlName__.drl new file mode 100644 index 000000000..3981f0def --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/main/resources/__closedLoopControlName__.drl @@ -0,0 +1,1413 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2018 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; + +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopLogger; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.controlloop.policy.ControlLoopPolicy; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager.NEW_EVENT_STATUS; +import org.onap.policy.controlloop.eventmanager.ControlLoopOperationManager; +import org.onap.policy.controlloop.actor.so.SOActorServiceProvider; +import org.onap.policy.aai.AaiNqResponseWrapper; +import org.onap.policy.appc.Request; +import org.onap.policy.appc.Response; +import org.onap.policy.appc.CommonHeader; +import org.onap.policy.appclcm.LcmRequestWrapper; +import org.onap.policy.appclcm.LcmResponseWrapper; +import org.onap.policy.appclcm.LcmRequest; +import org.onap.policy.appclcm.LcmResponse; +import org.onap.policy.appclcm.LcmCommonHeader; +import org.onap.policy.vfc.VFCRequest; +import org.onap.policy.vfc.VFCResponse; +import org.onap.policy.vfc.VFCManager; +import org.onap.policy.so.SOManager; +import org.onap.policy.so.SORequest; +import org.onap.policy.so.SORequestStatus; +import org.onap.policy.so.SORequestDetails; +import org.onap.policy.so.SOModelInfo; +import org.onap.policy.so.SOCloudConfiguration; +import org.onap.policy.so.SORequestInfo; +import org.onap.policy.so.SORequestParameters; +import org.onap.policy.so.SORelatedInstanceListElement; +import org.onap.policy.so.SORelatedInstance; +import org.onap.policy.so.SOResponse; +import org.onap.policy.so.SOResponseWrapper; +import org.onap.policy.guard.PolicyGuard; +import org.onap.policy.guard.PolicyGuard.LockResult; +import org.onap.policy.guard.TargetLock; +import org.onap.policy.guard.GuardResult; +import org.onap.policy.guard.PolicyGuardRequest; +import org.onap.policy.guard.PolicyGuardResponse; +import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes; +import org.onap.policy.guard.CallGuardTaskEmbedded; + +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import java.time.Instant; +import java.util.LinkedList; +import java.util.Iterator; + +import org.onap.policy.drools.system.PolicyEngine; + +/* + * This structure mimics the Params structure. + * Its only purpose is to allow management of + * rules by the PAP component.. + * It has no use at runtime since the rules go by + * Params for matching purposes. + */ +declare PapParams + closedLoopControlName : String + controlLoopYaml : String +end + +/* + * Control Loop Identity + */ +declare Params + closedLoopControlName : String + controlLoopYaml : String +end + +/* + * Used to clean up Params that no longer have associated rules. + */ +declare ParamsCleaner + closedLoopControlName : String + controlLoopYaml : String +end + + +/* + * Operation Timer + */ +declare OperationTimer + closedLoopControlName : String + requestID : String + delay : String +end + +/* + * Control Loop Timer + */ +declare ControlLoopTimer + closedLoopControlName : String + requestID : String + delay : String +end + +/* +* +* Called once and only once to insert the parameters into working memory for this Closed Loop policy. +* This has a higher salience so we can ensure that the Params is created before we have a chance to +* discard any events. +* +*/ +rule "${policyName}.SETUP" + salience 1 + when + not( Params( getClosedLoopControlName() == "${closedLoopControlName}", getControlLoopYaml() == "${controlLoopYaml}" ) ) + then + + Params params = new Params(); + params.setClosedLoopControlName("${closedLoopControlName}"); + params.setControlLoopYaml("${controlLoopYaml}"); + insert(params); + + // Note: globals have bad behavior when persistence is used, + // hence explicitly getting the logger vs using a global + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : YAML=[{}]", params.getClosedLoopControlName(), drools.getRule().getName(), params.getControlLoopYaml()); +end + +/* +* +* This rule responds to DCAE Events where there is no manager yet. Either it is +* the first ONSET, or a subsequent badly formed Event (i.e. Syntax error, or is-closed-loop-disabled) +* +*/ +rule "${policyName}.EVENT" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + not ( ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + + try { + + // + // Check the event, because we need it to not be null when + // we create the ControlLoopEventManager. The ControlLoopEventManager + // will do extra syntax checking as well check if the closed loop is disabled. + // + if ($event.getRequestId() == null) { + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setFrom("policy"); + notification.setMessage("Missing requestId"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + // + // Retract it from memory + // + retract($event); + } else if ($event.getClosedLoopEventStatus() != ControlLoopEventStatus.ONSET) { + throw new ControlLoopException($event.getClosedLoopEventStatus() + " received with no prior onset"); + } else { + // + // Create an EventManager + // + ControlLoopEventManager manager = new ControlLoopEventManager($params.getClosedLoopControlName(), $event.getRequestId()); + // + // Disable target locking + // + manager.setUseTargetLock(false); + // + // Determine if EventManager can actively process the event (i.e. syntax, is_closed_loop_disabled checks etc.) + // + VirtualControlLoopNotification notification = manager.activate($params.getControlLoopYaml(), $event); + notification.setFrom("pdp-0001-controller=controlloop"); // Engine.getInstanceName() + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + // + // Are we actively pursuing this event? + // + if (notification.getNotification() == ControlLoopNotificationType.ACTIVE) { + // + // Insert Event Manager into memory, this will now kick off processing. + // + insert(manager); + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Setup the Overall Control Loop timer + // + ControlLoopTimer clTimer = new ControlLoopTimer(); + clTimer.setClosedLoopControlName($event.getClosedLoopControlName()); + clTimer.setRequestID($event.getRequestId().toString()); + clTimer.setDelay(manager.getControlLoopTimeout(1500) + "s"); + // + // Insert it + // + insert(clTimer); + } else { + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Retract it from memory + // + retract($event); + } + + // + // Now that the manager is inserted into Drools working memory, we'll wait for + // another rule to fire in order to continue processing. This way we can also + // then screen for additional ONSET and ABATED events for this RequestId. + // + } + } catch (Exception e) { + logger.warn("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName(), e); + + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setMessage("Exception occurred: " + e.getMessage()); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + // + // + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Retract the event + // + retract($event); + } +end + +/* +* +* This rule happens when we got a valid ONSET, closed loop is enabled and an Event Manager +* is now created. We can start processing the yaml specification via the Event Manager. +* +*/ +rule "${policyName}.EVENT.MANAGER" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}: event={} manager={} clTimer={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $clTimer); + + try { + // + // Check which event this is. + // + ControlLoopEventManager.NEW_EVENT_STATUS eventStatus = $manager.onNewEvent($event); + // + // Check what kind of event this is + // + if (eventStatus == NEW_EVENT_STATUS.SUBSEQUENT_ONSET) { + // + // We don't care about subsequent onsets + // + logger.info("{}: {}: subsequent onset", + $params.getClosedLoopControlName(), drools.getRule().getName()); + retract($event); + return; + } + if (eventStatus == NEW_EVENT_STATUS.SYNTAX_ERROR) { + // + // Ignore any bad syntax events + // + logger.warn("{}: {}: syntax error", + $params.getClosedLoopControlName(), drools.getRule().getName()); + retract($event); + return; + } + // + // We only want the initial ONSET event in memory, + // all the other events need to be retracted to support + // cleanup and avoid the other rules being fired for this event. + // + if (eventStatus != NEW_EVENT_STATUS.FIRST_ONSET) { + logger.warn("{}: {}: not first onset", + $params.getClosedLoopControlName(), drools.getRule().getName()); + retract($event); + } + + logger.debug("{}: {}: target={}", $params.getClosedLoopControlName(), + drools.getRule().getName(), $event.getTarget()); + // + // Now start seeing if we need to process this event + // + + // + // Check if this is a Final Event + // + VirtualControlLoopNotification notification = $manager.isControlLoopFinal(); + + if (notification != null) { + // + // Set common notification fields + // + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + // + // Its final, but are we waiting for abatement? + // + if ($manager.getNumAbatements() > 0) { + // + // We have received an abatement and are done + // + logger.info("{}: {}: abatement received for {}. Closing the control loop", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event.getRequestId()); + // + // Set notification message + // + notification.setMessage("Abatement received. Closing the control loop."); + // + /// DB Write---end event processing for this RequestId() + // + $manager.commitAbatement("Event Abated","Closed"); + // + // Unlock the target + // + TargetLock lock = $manager.unlockCurrentOperation(); + if (lock != null) { + logger.debug("{}: {}: retracting lock=", $params.getClosedLoopControlName(), + drools.getRule().getName(), lock); + retract(lock); + } + // + // Retract everything from memory + // + logger.info("{}: {}: retracting onset, manager, and timer", + $params.getClosedLoopControlName(), drools.getRule().getName()); + + retract($manager.getOnsetEvent()); + retract($manager); + retract($clTimer); + // + // TODO - what if we get subsequent Events for this RequestId? + // By default, it will all start over again. May be confusing for Ruby. + // Or, we could track this and then subsequently ignore the events + // + } else { + // + // Check whether we need to wait for abatement + // + if ($manager.getProcessor().getControlLoop().getAbatement() == true && notification.getNotification() == ControlLoopNotificationType.FINAL_SUCCESS) { + // + // We will wait + // + logger.info("{}: {}: waiting for abatement ..", + $params.getClosedLoopControlName(), drools.getRule().getName()); + // + // Set notification message + // + notification.setMessage("Waiting for abatement"); + } else { + // + // We are done + // + logger.info("{}: {}: no abatement expected for {}. Closing the control loop", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event.getRequestId()); + // + // Set notification message + // + notification.setMessage("No abatement expected. Closing the control loop."); + // + // Unlock the target + // + TargetLock lock = $manager.unlockCurrentOperation(); + if (lock != null) { + logger.debug("{}: {}: retracting lock=", $params.getClosedLoopControlName(), + drools.getRule().getName(), lock); + retract(lock); + } + // + // Retract everything from memory + // + logger.info("{}: {}: retracting onset, manager, and timer", + $params.getClosedLoopControlName(), drools.getRule().getName()); + + retract($manager.getOnsetEvent()); + retract($manager); + retract($clTimer); + } + } + // + // Send the notification + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + } else { + // + // NOT final, so let's ask for the next operation + // + ControlLoopOperationManager operation = $manager.processControlLoop(); + if (operation != null) { + // + // Let's ask for a lock right away + // + LockResult result = $manager.lockCurrentOperation(); + logger.info("{}: {}: guard lock acquired={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + result.getB()); + if (result.getA().equals(GuardResult.LOCK_ACQUIRED)) { + // + // insert the operation into memory + // + insert(operation); + + // + // insert operation timeout object + // + OperationTimer opTimer = new OperationTimer(); + opTimer.setClosedLoopControlName($event.getClosedLoopControlName()); + opTimer.setRequestID($event.getRequestId().toString()); + opTimer.setDelay(operation.getOperationTimeout().toString() + "s"); + insert(opTimer); + + // + // Insert lock into memory + // + insert(result.getB()); + } + else { + logger.debug("The target resource {} is already processing", + $event.getAai().get($event.getTarget())); + notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setMessage("The target " + $event.getAai().get($event.getTarget()) + " is already locked"); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + retract($event); + retract($manager); + retract($clTimer); + + if(result.getB() != null) { + retract(result.getB()); + } + } + logger.info("{}: {}: starting operation={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + operation); + } else { + // + // Probably waiting for abatement + // + logger.info("{}: {}: no operation, probably waiting for abatement", + $params.getClosedLoopControlName(), drools.getRule().getName()); + } + } + } catch (Exception e) { + logger.warn("{}: {}: unexpected", + $params.getClosedLoopControlName(), + drools.getRule().getName(), e); + + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE); + notification.setMessage(e.getMessage()); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + retract($event); + retract($manager); + retract($clTimer); + } + +end + +/* +* +* Guard Permitted, let's send request to the actor. +* +*/ +rule "${policyName}.EVENT.MANAGER.OPERATION.LOCKED.GUARD_PERMITTED" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId(), "Permit".equalsIgnoreCase(getGuardApprovalStatus()) ) + $lock : TargetLock (requestID == $event.getRequestId()) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}: event={} manager={} operation={} lock={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock); + + Object request = null; + boolean caughtException = false; + + try { + request = $operation.startOperation($event); + + if (request != null) { + logger.debug("{}: {}: starting operation ..", + $params.getClosedLoopControlName(), drools.getRule().getName()); + // + // Tell interested parties we are performing this Operation + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage($operation.getOperationMessage()); + notification.setHistory($operation.getHistory()); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + switch ($operation.policy.getActor()){ + + case "APPC": + + if (request instanceof Request) { + PolicyEngine.manager.deliver("APPC-CL", request); + } + else if (request instanceof LcmRequestWrapper) { + PolicyEngine.manager.deliver("APPC-LCM-READ", request); + } + break; + case "SO": + // at this point the AAI named query request should have already been made, the response recieved and used + // in the construction of the SO Request which is stored in operationRequest + + if(request instanceof SORequest) { + // Call SO. The response will be inserted into memory once it's received + SOActorServiceProvider.sendRequest($event.getRequestId().toString(), drools.getWorkingMemory(), request); + } + break; + case "VFC": + if (request instanceof VFCRequest) { + // Start VFC thread + Thread t = new Thread(new VFCManager(drools.getWorkingMemory(), (VFCRequest)request)); + t.start(); + } + break; + } + } else { + // + // What happens if its null? + // + logger.warn("{}: {}: unexpected null operation request", + $params.getClosedLoopControlName(), + drools.getRule().getName()); + if ("SO".equals($operation.policy.getActor())) { + retract($opTimer); + retract($operation); + modify($manager) {finishOperation($operation)}; + } + else if ("vfc".equalsIgnoreCase($operation.policy.getActor())) { + retract($opTimer); + retract($operation); + modify($manager) {finishOperation($operation)}; + } + } + + } catch (Exception e) { + String msg = e.getMessage(); + logger.warn("{}: {}: operation={}: AAI failure: {}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $operation, msg, e); + $operation.setOperationHasException(msg); + + if(request != null) { + // + // Create a notification for it ("DB Write - end operation") + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + } + + retract($opTimer); + retract($operation); + caughtException = true; + } + + // Having the modify statement in the catch clause doesn't work for whatever reason + if (caughtException) { + modify($manager) {finishOperation($operation)}; + } +end + + +/* +* +* We were able to acquire a lock so now let's ask Xacml Guard whether +* we are allowed to proceed with the request to the actor. +* +*/ +rule "${policyName}.EVENT.MANAGER.OPERATION.LOCKED.GUARD_NOT_YET_QUERIED" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId(), getGuardApprovalStatus() == "NONE" ) + $lock : TargetLock (requestID == $event.getRequestId()) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}: event={} manager={} operation={} lock={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock); + + // + // Sending notification that we are about to query Guard ("DB write - start operation") + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage("Sending guard query for " + $operation.policy.getActor() + " " + $operation.policy.getRecipe()); + notification.setHistory($operation.getHistory()); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + // + // Now send Guard Request to XACML Guard. In order to bypass the call to Guard, + // just change guardEnabled to false. + // + // + + // NOTE: The environment properties uses "guard.disabled" but the boolean is guardEnabled + boolean guardEnabled = "false".equalsIgnoreCase(PolicyEngine.manager.getEnvironmentProperty("guard.disabled")); + + if(guardEnabled){ + + Thread t = new Thread(new CallGuardTaskEmbedded( + drools.getWorkingMemory(), + $event.getClosedLoopControlName(), + $operation.policy.getActor().toString(), + $operation.policy.getRecipe(), + $operation.getTargetEntity(), + $event.getRequestId().toString(), + () -> { + AaiNqResponseWrapper resp = $manager.getNqVserverFromAai(); + return(resp == null ? null : resp.countVfModules()); + })); + t.start(); + } + else{ + insert(new PolicyGuardResponse("Permit", $event.getRequestId(), $operation.policy.getRecipe())); + } + +end + +// +// This rule will be triggered when a thread talking to the XACML Guard inserts a +// guardResponse object into the working memory +// +rule "${policyName}.GUARD.RESPONSE" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $lock : TargetLock (requestID == $event.getRequestId()) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + $guardResponse : PolicyGuardResponse(requestID == $event.getRequestId(), $operation.policy.recipe == operation) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}: event={} manager={} operation={} lock={} opTimer={} guardResponse={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $opTimer, $guardResponse); + + + //we will permit the operation if there was no Guard for it + if("Indeterminate".equalsIgnoreCase($guardResponse.getResult())){ + $guardResponse.setResult("Permit"); + } + + // + // This notification has Guard result in "message". ("DB write - end operation in case of Guard Deny") + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage("Guard result for " + $operation.policy.getActor() + " " + $operation.policy.getRecipe() + " is " + $guardResponse.getResult()); + notification.setHistory($operation.getHistory()); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + + if("Permit".equalsIgnoreCase($guardResponse.getResult())){ + + modify($operation){setGuardApprovalStatus($guardResponse.getResult())}; + } + else { + //This is the Deny case + $operation.startOperation($event); + $operation.setOperationHasGuardDeny(); + retract($opTimer); + retract($operation); + modify($manager) {finishOperation($operation)}; + } + + retract($guardResponse); + +end + +/* +* +* This rule responds to APPC Response Events +* +* I would have like to be consistent and write the Response like this: +* $response : Response( CommonHeader.RequestId == $onset.getRequestId() ) +* +* However, no compile error was given. But a runtime error was given. I think +* because drools is confused between the classname CommonHeader vs the property CommonHeader. +* +*/ +rule "${policyName}.APPC.RESPONSE" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + $lock : TargetLock (requestID == $event.getRequestId()) + $response : Response( getCommonHeader().RequestId == $event.getRequestId() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: event={} manager={} operation={} lock={} opTimer={} response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $opTimer, $response); + // + // Get the result of the operation + // + PolicyResult policyResult = $operation.onResponse($response); + if (policyResult != null) { + logger.debug("{}: {}: operation finished - result={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + policyResult); + // + // This Operation has completed, construct a notification showing our results. (DB write - end operation) + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + if (policyResult.equals(PolicyResult.SUCCESS)) { + notification.setNotification(ControlLoopNotificationType.OPERATION_SUCCESS); + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + } else { + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + } + // + // Ensure the operation is complete + // + if ($operation.isOperationComplete() == true) { + // + // It is complete, remove it from memory + // + retract($operation); + // + // We must also retract the timer object + // NOTE: We could write a Rule to do this + // + retract($opTimer); + // + // Complete the operation + // + modify($manager) {finishOperation($operation)}; + } else { + // + // Just doing this will kick off the LOCKED rule again + // + modify($operation) {}; + } + } else { + // + // Its not finished yet (i.e. expecting more Response objects) + // + // Or possibly it is a leftover response that we timed the request out previously + // + } + // + // We are going to retract these objects from memory + // + retract($response); +end + +/* +* +* The problem with Responses is that they don't have a controlLoopControlName +* field in them, so the only way to attach them is via RequestId. If we have multiple +* control loop .drl's loaded in the same container, we need to be sure the cleanup +* rules don't remove Responses for other control loops. +* +*/ +rule "${policyName}.APPC.RESPONSE.CLEANUP" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $response : Response($id : getCommonHeader().RequestId ) + not ( VirtualControlLoopEvent( requestId == $id, closedLoopEventStatus == ControlLoopEventStatus.ONSET ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: orphan appc response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), $id); + + // + // Retract it + // + retract($response); +end + +/* +* +* This rule responds to APPC Response Events using the new LCM interface provided by appc +* +*/ +rule "${policyName}.APPC.LCM.RESPONSE" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + $lock : TargetLock (requestID == $event.getRequestId()) + $response : LcmResponseWrapper( getBody().getCommonHeader().getRequestId() == $event.getRequestId() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: event={} manager={} operation={} lock={} opTimer={} response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $operation, $opTimer, $response); + + // + // Get the result of the operation + // + PolicyResult policyResult = $operation.onResponse($response); + if (policyResult != null) { + logger.debug("{}: {}: operation finished - result={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + policyResult); + + // + // This Operation has completed, construct a notification showing our results. (DB write - end operation) + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + if (policyResult.equals(PolicyResult.SUCCESS)) { + notification.setNotification(ControlLoopNotificationType.OPERATION_SUCCESS); + } else { + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + } + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Ensure the operation is complete + // + if ($operation.isOperationComplete() == true) { + // + // It is complete, remove it from memory + // + retract($operation); + // + // We must also retract the timer object + // NOTE: We could write a Rule to do this + // + retract($opTimer); + // + // Complete the operation + // + modify($manager) {finishOperation($operation)}; + } else { + // + // Just doing this will kick off the LOCKED rule again + // + modify($operation) {}; + } + } else { + // + // Its not finished yet (i.e. expecting more Response objects) + // + // Or possibly it is a leftover response that we timed the request out previously + // + } + // + // We are going to retract these objects from memory + // + retract($response); +end + +/* +* +* Clean Up any lingering LCM reponses +* +*/ +rule "${policyName}.APPC.LCM.RESPONSE.CLEANUP" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $response : LcmResponseWrapper($id : getBody().getCommonHeader().getRequestId ) + not ( VirtualControlLoopEvent( requestId == $id, closedLoopEventStatus == ControlLoopEventStatus.ONSET ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: orphan appc response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), $id); + // + // Retract it + // + retract($response); +end + +/* +* +* This rule responds to SO Response Events +* +*/ +rule "${policyName}.SO.RESPONSE" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + $lock : TargetLock (requestID == $event.getRequestId()) + $response : SOResponseWrapper(requestID.toString() == $event.getRequestId().toString() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: event={} manager={} operation={} lock={} opTimer={} response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $operation, $opTimer, $response); + + // Get the result of the operation + // + PolicyResult policyResult = $operation.onResponse($response); + if (policyResult != null) { + logger.debug("{}: {}: operation finished - result={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + policyResult); + + // + // This Operation has completed, construct a notification showing our results + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + if (policyResult.equals(PolicyResult.SUCCESS)) { + notification.setNotification(ControlLoopNotificationType.OPERATION_SUCCESS); + } else { + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + + } + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Ensure the operation is complete + // + if ($operation.isOperationComplete() == true) { + // + // It is complete, remove it from memory + // + retract($operation); + // + // We must also retract the timer object + // NOTE: We could write a Rule to do this + // + retract($opTimer); + // + // Complete the operation + // + modify($manager) {finishOperation($operation)}; + } else { + // + // Just doing this will kick off the LOCKED rule again + // + modify($operation) {}; + } + } else { + // + // Its not finished yet (i.e. expecting more Response objects) + // + // Or possibly it is a leftover response that we timed the request out previously + // + } + // + // We are going to retract these objects from memory + // + retract($response); + +end + +/* +* +* This rule responds to VFC Response Events +* +*/ +rule "${policyName}.VFC.RESPONSE" + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() ) + $lock : TargetLock (requestID == $event.getRequestId()) + $response : VFCResponse( requestId.toString() == $event.getRequestId().toString() ) + then + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: event={} manager={} operation={} lock={} opTimer={} response={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $operation, $opTimer, $response); + + // Get the result of the operation + // + PolicyResult policyResult = $operation.onResponse($response); + if (policyResult != null) { + // + // This Operation has completed, construct a notification showing our results + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + // + // Ensure the operation is complete + // + if ($operation.isOperationComplete() == true) { + // + // It is complete, remove it from memory + // + retract($operation); + // + // We must also retract the timer object + // NOTE: We could write a Rule to do this + // + retract($opTimer); + // + // Complete the operation + // + modify($manager) {finishOperation($operation)}; + } else { + // + // Just doing this will kick off the LOCKED rule again + // + modify($operation) {}; + } + } else { + // + // Its not finished yet (i.e. expecting more Response objects) + // + // Or possibly it is a leftover response that we timed the request out previously + // + } + // + // We are going to retract these objects from memory + // + retract($response); + +end + +/* +* +* This is the timer that manages the timeout for an individual operation. +* +*/ +rule "${policyName}.EVENT.MANAGER.OPERATION.TIMEOUT" + timer (expr: $to ) + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() ) + $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() ) + $lock : TargetLock (requestID == $event.getRequestId()) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + logger.debug("{}: {}: event={} manager={} operation={} lock={} opTimer={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event, $manager, $operation, $lock, $operation, $opTimer); + + // + // Tell it its timed out + // + $operation.setOperationHasTimedOut(); + // + // Create a notification for it ("DB Write - end operation") + // + VirtualControlLoopNotification notification = new VirtualControlLoopNotification($event); + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + notification.setMessage($operation.getOperationHistory()); + notification.setHistory($operation.getHistory()); + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + // + // Get rid of the timer + // + retract($opTimer); + // + // Ensure the operation is complete + // + if ($operation.isOperationComplete() == true) { + // + // It is complete, remove it from memory + // + retract($operation); + // + // Complete the operation + // + modify($manager) {finishOperation($operation)}; + } else { + // + // Just doing this will kick off the LOCKED rule again + // + modify($operation) {}; + } +end + +/* +* +* This is the timer that manages the overall control loop timeout. +* +*/ +rule "${policyName}.EVENT.MANAGER.TIMEOUT" + timer (expr: $to ) + when + $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" ) + $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() ) + $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) + $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $params.getClosedLoopControlName(), drools.getRule().getName()); + + logger.debug("{}: {}: event={}", + $params.getClosedLoopControlName(), drools.getRule().getName(), + $event); + // + // Tell the Event Manager it has timed out + // + VirtualControlLoopNotification notification = $manager.setControlLoopTimedOut(); + if (notification != null) { + notification.setFrom("policy"); + notification.setPolicyName(drools.getRule().getName()); + notification.setPolicyScope("${policyScope}"); + notification.setPolicyVersion("${policyVersion}"); + // + // Let interested parties know + // + PolicyEngine.manager.deliver("POLICY-CL-MGT", notification); + } + // + // Retract the event + // + retract($event); +end + +/* +* +* This rule cleans up the manager and other objects after an event has +* been retracted. +* +*/ +rule "${policyName}.EVENT.MANAGER.CLEANUP" + when + $manager : ControlLoopEventManager( $clName : getClosedLoopControlName(), $requestId : getRequestID() ) + $clTimer : ControlLoopTimer ( closedLoopControlName == $clName, requestID == $requestId.toString() ) + $operations : LinkedList() + from collect( ControlLoopOperationManager( onset.closedLoopControlName == $clName, onset.getRequestId() == $requestId ) ) + $opTimers : LinkedList() + from collect( OperationTimer( closedLoopControlName == $clName, requestID == $requestId.toString() ) ) + $locks : LinkedList() + from collect( TargetLock (requestID == $requestId) ) + not( VirtualControlLoopEvent( closedLoopControlName == $clName, requestId == $requestId ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $clName, drools.getRule().getName()); + + logger.debug("{}: {}: manager={} clTimer={} operations={}", + $clName, drools.getRule().getName(), + $manager, $clTimer, $operations.size()); + + // + // Retract EVERYTHING + // + retract($manager); + retract($clTimer); + + for(Object manager: $operations) { + retract((ControlLoopOperationManager) manager); + } + for(Object opTimer: $opTimers) { + retract((OperationTimer) opTimer); + } + for(Object lock: $locks) { + TargetLock tgt = (TargetLock) lock; + // + // Ensure we release the lock + // + PolicyGuard.unlockTarget(tgt); + retract(tgt); + } +end + +/* +* +* This rule will clean up any rogue onsets where there is no +* ControlLoopParams object corresponding to the onset event. +* +*/ +rule "${policyName}.EVENT.CLEANUP" + when + $event : VirtualControlLoopEvent( $clName: closedLoopControlName ) + not ( Params( getClosedLoopControlName() == $clName) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $clName, drools.getRule().getName()); + logger.debug("{}: {}: orphan onset event={}", + $clName, drools.getRule().getName(), $event); + + retract($event); +end + +/* +* +* When rules are deleted, the associated Params (and its subordinate objects) +* remain in working memory, because there are no longer any rules to clean +* them up. However, ANY time new rules are loaded, this rule will trigger +* a clean-up of ALL Params, regardless of their name & yaml, thus removing +* any that no longer have associated rules. +* This has a higher salience so that we immediately check Params when the +* rules change, before processing any events. +* +*/ +rule "${policyName}.PARAMS.CHECKUP" + salience 2 + when + Params( $clName: closedLoopControlName, $yaml: controlLoopYaml ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : YAML=[{}]", $clName, drools.getRule().getName(), $yaml); + + ParamsCleaner cleaner = new ParamsCleaner(); + cleaner.setClosedLoopControlName($clName); + cleaner.setControlLoopYaml($yaml); + + insert(cleaner); +end + +/* +* +* This rule removes "cleaner" objects for rules that are still active, thus +* preventing the associated Params objects from being removed. Any cleaners +* that are left after this rule has fired will cause their corresponding Params +* to be removed. +* This has a higher salience so that we discard the cleaner before it has +* a chance to force the removal of the associated Params. +* +*/ +rule "${policyName}.CLEANER.ACTIVE" + salience 2 + when + $cleaner: ParamsCleaner( getClosedLoopControlName() == "${closedLoopControlName}", getControlLoopYaml() == "${controlLoopYaml}" ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : YAML=[{}]", $cleaner.getClosedLoopControlName(), drools.getRule().getName(), $cleaner.getControlLoopYaml()); + + retract($cleaner); +end + +/* +* +* This rule removes Params objects that no longer have associated rules; if a +* Params still had associated rules, then the cleaner would have been removed +* by those rules and thus this rule would not fire. +* This has a higher salience so that we remove old Params before it causes any +* events to be processed. +* +*/ +rule "${policyName}.PARAMS.CLEANUP" + salience 1 + when + $params: Params( $clName: closedLoopControlName, $yaml: controlLoopYaml ) + ParamsCleaner( getClosedLoopControlName() == $clName, getControlLoopYaml() == $yaml ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : YAML=[{}]", $params.getClosedLoopControlName(), drools.getRule().getName(), $params.getControlLoopYaml()); + + retract($params); + + // Note: the cleaner may be needed for cleaning additional params, thus + // we do not retract it here - we'll leave that to another rule +end + +/* +* +* This rule removes "cleaner" objects when they're no longer needed. +* +*/ +rule "${policyName}.CLEANER.CLEANUP" + when + $cleaner: ParamsCleaner( ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : YAML=[{}]", $cleaner.getClosedLoopControlName(), drools.getRule().getName(), $cleaner.getControlLoopYaml()); + + retract($cleaner); +end -- cgit 1.2.3-korg