diff options
author | Joshua Reich <jreich@research.att.com> | 2018-09-14 11:38:01 -0700 |
---|---|---|
committer | Joshua Reich <jreich@research.att.com> | 2018-09-17 22:35:09 -0700 |
commit | faf283066f186838665ed5c38c1ba8319041bc1c (patch) | |
tree | baecbf025374a4e120de5b9ddf405b26cb6e5d75 /controlloop/templates/template.demo.clc | |
parent | e3357d24078195756c8e16fc0da8aa8d1e507290 (diff) |
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 <jreich@research.att.com>
Diffstat (limited to 'controlloop/templates/template.demo.clc')
12 files changed, 3362 insertions, 0 deletions
diff --git a/controlloop/templates/template.demo.clc/README.md b/controlloop/templates/template.demo.clc/README.md new file mode 100644 index 000000000..b20161931 --- /dev/null +++ b/controlloop/templates/template.demo.clc/README.md @@ -0,0 +1,6 @@ +Copyright 2018 AT&T Intellectual Property. All rights reserved. +This file is licensed under the CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL LICENSE +Full license text at https://creativecommons.org/licenses/by/4.0/legalcode + +This is the ongoing implementation of template demonstrating use of control loop coordination facility. + diff --git a/controlloop/templates/template.demo.clc/pom.xml b/controlloop/templates/template.demo.clc/pom.xml new file mode 100644 index 000000000..9295d78f7 --- /dev/null +++ b/controlloop/templates/template.demo.clc/pom.xml @@ -0,0 +1,216 @@ +<!-- + ============LICENSE_START======================================================= + drools-applications Control Loop Drools Templates + ================================================================================ + 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========================================================= + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.policy.drools-applications.controlloop.templates</groupId> + <artifactId>templates</artifactId> + <version>1.3.0-SNAPSHOT</version> + </parent> + + <artifactId>template.demo.clc</artifactId> + + <dependencies> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.eclipse.persistence</groupId> + <artifactId>org.eclipse.persistence.jpa</artifactId> + <version>2.7.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.5</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.drools</groupId> + <artifactId>drools-core</artifactId> + <version>6.5.0.Final</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.drools</groupId> + <artifactId>drools-compiler</artifactId> + <version>6.5.0.Final</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>appc</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>sdnr</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>appclcm</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>so</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>trafficgenerator</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>vfc</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>events</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>guard</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>aai</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.model-impl</groupId> + <artifactId>sdc</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>policy-yaml</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>eventmanager</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.att.research.xacml</groupId> + <artifactId>xacml</artifactId> + <version>1.0.1</version> + </dependency> + <dependency> + <groupId>com.att.research.xacml</groupId> + <artifactId>xacml-pdp</artifactId> + <version>1.0.1</version> + </dependency> + <dependency> + <groupId>javax.persistence</groupId> + <artifactId>persistence-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actorServiceProvider</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actor.appc</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actor.sdnr</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actor.appclcm</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actor.so</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common.actors</groupId> + <artifactId>actor.vfc</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.common</groupId> + <artifactId>policy-endpoints</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>simulators</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-pdp</groupId> + <artifactId>policy-management</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> +</project> + 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 "<actor>:<recipe>". + */ + private static final Set<String> 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 + * "<actor>:<recipe>". + */ + private static final Set<String> 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<Integer> 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<Result> itRes = xacmlResponse.getResults().iterator(); + + Result res = itRes.next(); + String decisionFromXacmlResponse = res.getDecision().toString(); + Iterator<AttributeCategory> itAttrCat = res.getAttributes().iterator(); + UUID reqIdFromXacmlResponse = null; + String operationFromXacmlResponse = ""; + + while (itAttrCat.hasNext()) { + Iterator<Attribute> 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<AttributeValue<?>> 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<AttributeValue<?>> 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<UrlEntry> 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<UrlEntry> initEntries(Properties properties, StringBuilder sb) { + // now, see which numeric entries (1-9) exist + ArrayList<UrlEntry> 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<GuardResult, TargetLock> 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 diff --git a/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/ControlLoopCoordinationTest.java b/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/ControlLoopCoordinationTest.java new file mode 100644 index 000000000..2d6279f32 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/ControlLoopCoordinationTest.java @@ -0,0 +1,497 @@ +/*- + * ============LICENSE_START======================================================= + * demo + * ================================================================================ + * 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.template.demo.clc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.att.research.xacml.util.XACMLProperties; + +import com.google.gson.Gson; + +import java.io.IOException; +import java.lang.StringBuilder; +import java.net.URLEncoder; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.kie.api.runtime.KieSession; +import org.kie.api.runtime.rule.FactHandle; + +import org.onap.policy.appclcm.LcmRequest; +import org.onap.policy.appclcm.LcmRequestWrapper; +import org.onap.policy.appclcm.LcmResponse; +import org.onap.policy.appclcm.LcmResponseWrapper; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.event.comm.TopicEndpoint; +import org.onap.policy.common.endpoints.event.comm.TopicListener; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties; +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopTargetType; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.policy.ControlLoopPolicy; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyEngine; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ControlLoopCoordinationTest implements TopicListener { + + private static final Logger logger = LoggerFactory.getLogger(ControlLoopCoordinationTest.class); + + private static List<? extends TopicSink> noopTopics; + + private static KieSession kieSession1; + private static KieSession kieSession2; + private static StringBuilder controlLoopOneName = new StringBuilder(); + private static StringBuilder controlLoopTwoName = new StringBuilder(); + private static String expectedDecision; + + static { + /* Set environment properties */ + Util.setAaiProps(); + Util.setGuardPropsEmbedded(); + Util.setPuProp(); + } + + /** + * Setup simulator. + */ + @BeforeClass + public static void setUpSimulator() { + PolicyEngine.manager.configure(new Properties()); + assertTrue(PolicyEngine.manager.start()); + Properties noopSinkProperties = new Properties(); + noopSinkProperties.put(PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS, "APPC-LCM-READ,POLICY-CL-MGT"); + noopSinkProperties.put("noop.sink.topics.APPC-LCM-READ.events", "org.onap.policy.appclcm.LcmRequestWrapper"); + noopSinkProperties.put("noop.sink.topics.APPC-LCM-READ.events.custom.gson", + "org.onap.policy.appclcm.util.Serialization,gson"); + noopSinkProperties.put("noop.sink.topics.POLICY-CL-MGT.events", + "org.onap.policy.controlloop.VirtualControlLoopNotification"); + noopSinkProperties.put("noop.sink.topics.POLICY-CL-MGT.events.custom.gson", + "org.onap.policy.controlloop.util.Serialization,gsonPretty"); + noopTopics = TopicEndpoint.manager.addTopicSinks(noopSinkProperties); + + EventProtocolCoder.manager.addEncoder("junit.groupId", "junit.artifactId", "POLICY-CL-MGT", + "org.onap.policy.controlloop.VirtualControlLoopNotification", new JsonProtocolFilter(), null, null, + 1111); + EventProtocolCoder.manager.addEncoder("junit.groupId", "junit.artifactId", "APPC-LCM-READ", + "org.onap.policy.appclcm.LcmRequestWrapper", new JsonProtocolFilter(), null, null, 1111); + try { + Util.buildAaiSim(); + } catch (Exception e) { + fail(e.getMessage()); + } + + /* + * Start the kie sessions + */ + try { + kieSession1 = startSession( + controlLoopOneName, + "src/main/resources/__closedLoopControlName__.drl", + "src/test/resources/yaml/policy_ControlLoop_SyntheticOne.yaml", + "service=ServiceDemo;resource=Res1Demo;type=operational", + "SyntheticControlLoopOnePolicy", + "org.onap.closed_loop.ServiceDemo:VNFS:1.0.0"); + kieSession2 = startSession( + controlLoopTwoName, + "src/main/resources/__closedLoopControlName__.drl", + "src/test/resources/yaml/policy_ControlLoop_SyntheticTwo.yaml", + "service=ServiceDemo;resource=Res1Demo;type=operational", + "SyntheticControlLoopTwoPolicy", + "org.onap.closed_loop.ServiceDemo:VNFS:1.0.0"); + } catch (IOException e) { + logger.debug("Could not create kieSession, exception {}", e.getMessage()); + fail("Could not create kieSession"); + } + } + + /** + * Tear down simulator. + */ + @AfterClass + public static void tearDownSimulator() { + /* + * Gracefully shut down the kie session + */ + kieSession1.dispose(); + kieSession2.dispose(); + + PolicyEngine.manager.stop(); + HttpServletServer.factory.destroy(); + PolicyController.factory.shutdown(); + TopicEndpoint.manager.shutdown(); + } + + /** + * Set expected decision. + * + * @param ed the expected decision ("PERMIT" or "DENY") + */ + public void expectedDecisionIs(String ed) { + expectedDecision = ed; + logger.info("Expected decision is {}", ed); + } + + /** + * This method is used to simulate event messages from DCAE + * that start the control loop (onset message) or end the + * control loop (abatement message). + * + * @param controlLoopName the control loop name + * @param requestID the requestId for this event + * @param status could be onset or abated + * @param target the target name + * @param kieSession the kieSession to which this event is being sent + */ + protected void sendEvent(String controlLoopName, + UUID requestId, + ControlLoopEventStatus status, + String target, + KieSession kieSession) { + logger.debug("sendEvent controlLoopName={}", controlLoopName); + VirtualControlLoopEvent event = new VirtualControlLoopEvent(); + event.setClosedLoopControlName(controlLoopName); + event.setRequestId(requestId); + event.setTarget("generic-vnf.vnf-name"); + event.setTargetType(ControlLoopTargetType.VNF); + event.setClosedLoopAlarmStart(Instant.now()); + event.setAai(new HashMap<>()); + event.getAai().put("generic-vnf.vnf-name", target); + event.setClosedLoopEventStatus(status); + + Gson gson = new Gson(); + String json = gson.toJson(event); + logger.debug("sendEvent {}", json); + + kieSession.insert(event); + } + + + /** + * Simulate an event by inserting into kieSession and firing rules as needed. + * + * @param cles the ControlLoopEventStatus + * @param rid the request ID + * @param controlLoopName the control loop name + * @param kieSession the kieSession to which this event is being sent + * @param expectedDecision the expected decision + */ + protected void simulateEvent(ControlLoopEventStatus cles, + UUID rid, + String controlLoopName, + String target, + KieSession kieSession, + String expectedDecision) { + int waitMillis = 5000; + // + // if onset, set expected decision + // + if (cles == ControlLoopEventStatus.ONSET) { + expectedDecisionIs(expectedDecision); + } + // + // simulate sending event + // + sendEvent(controlLoopName, rid, cles, target, kieSession); + kieSession.fireUntilHalt(); + // + // get dump of database entries and log + // + List entries = Util.dumpDb(); + assertNotNull(entries); + logger.debug("dumpDB, {} entries", entries.size()); + for (Object entry : entries) { + logger.debug("{}", entry); + } + // + // we are done + // + logger.info("simulateEvent: done"); + } + + /** + * Simulate an onset event. + * + * @param rid the request ID + * @param controlLoopName the control loop name + * @param kieSession the kieSession to which this event is being sent + * @param expectedDecision the expected decision + */ + public void simulateOnset(UUID rid, + String controlLoopName, + String target, + KieSession kieSession, + String expectedDecision) { + simulateEvent(ControlLoopEventStatus.ONSET, rid, controlLoopName, target, kieSession, expectedDecision); + } + + /** + * Simulate an abated event. + * + * @param rid the request ID + * @param controlLoopName the control loop name + * @param kieSession the kieSession to which this event is being sent + */ + public void simulateAbatement(UUID rid, + String controlLoopName, + String target, + KieSession kieSession) { + simulateEvent(ControlLoopEventStatus.ABATED, rid, controlLoopName, target, kieSession, null); + } + + /** + * This method will start a kie session and instantiate the Policy Engine. + * + * @param droolsTemplate the DRL rules file + * @param yamlFile the yaml file containing the policies + * @param policyScope scope for policy + * @param policyName name of the policy + * @param policyVersion version of the policy + * @return the kieSession to be used to insert facts + * @throws IOException throws IO exception + */ + private static KieSession startSession(StringBuilder controlLoopName, + String droolsTemplate, + String yamlFile, + String policyScope, + String policyName, + String policyVersion) throws IOException { + + /* + * Load policies from yaml + */ + Util.Pair<ControlLoopPolicy, String> pair = Util.loadYaml(yamlFile); + assertNotNull(pair); + assertNotNull(pair.first); + assertNotNull(pair.first.getControlLoop()); + assertNotNull(pair.first.getControlLoop().getControlLoopName()); + assertTrue(!pair.first.getControlLoop().getControlLoopName().isEmpty()); + + controlLoopName.append(pair.first.getControlLoop().getControlLoopName()); + String yamlContents = pair.second; + + /* + * Construct a kie session + */ + final KieSession kieSession = Util.buildContainer(droolsTemplate, + controlLoopName.toString(), + policyScope, + policyName, + policyVersion, + URLEncoder.encode(yamlContents, "UTF-8")); + + /* + * Retrieve the Policy Engine + */ + + logger.debug("============"); + logger.debug(URLEncoder.encode(yamlContents, "UTF-8")); + logger.debug("============"); + + return kieSession; + } + + /* + * (non-Javadoc) + * + * @see org.onap.policy.drools.PolicyEngineListener#newEventNotification(java.lang.String) + */ + @Override + public void onTopicEvent(CommInfrastructure commType, String topic, String event) { + /* + * Pull the object that was sent out to DMAAP and make sure it is a ControlLoopNoticiation + * of type active + */ + Object obj = null; + if ("POLICY-CL-MGT".equals(topic)) { + obj = org.onap.policy.controlloop.util.Serialization.gsonJunit.fromJson(event, + org.onap.policy.controlloop.VirtualControlLoopNotification.class); + } else if ("APPC-LCM-READ".equals(topic)) { + obj = org.onap.policy.appclcm.util.Serialization.gsonJunit.fromJson(event, + org.onap.policy.appclcm.LcmRequestWrapper.class); + } + assertNotNull(obj); + if (obj instanceof VirtualControlLoopNotification) { + VirtualControlLoopNotification notification = (VirtualControlLoopNotification) obj; + String policyName = notification.getPolicyName(); + if (policyName.endsWith("EVENT")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + assertTrue(ControlLoopNotificationType.ACTIVE.equals(notification.getNotification())); + } else if (policyName.endsWith("GUARD_NOT_YET_QUERIED")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + assertTrue(ControlLoopNotificationType.OPERATION.equals(notification.getNotification())); + assertNotNull(notification.getMessage()); + assertTrue(notification.getMessage().startsWith("Sending guard query")); + } else if (policyName.endsWith("GUARD.RESPONSE")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + assertTrue(ControlLoopNotificationType.OPERATION.equals(notification.getNotification())); + assertNotNull(notification.getMessage()); + // THESE ARE THE MOST CRITICAL ASSERTS + // TEST IF GUARD.RESPONSE IS CORRECT + logger.debug("Testing whether decision was {} as expected", expectedDecision); + assertTrue(notification.getMessage().toUpperCase().endsWith(expectedDecision)); + } else if (policyName.endsWith("GUARD_PERMITTED")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + assertEquals(ControlLoopNotificationType.OPERATION,notification.getNotification()); + assertNotNull(notification.getMessage()); + assertTrue(notification.getMessage().startsWith("actor=APPC")); + } else if (policyName.endsWith("OPERATION.TIMEOUT")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + kieSession1.halt(); + kieSession2.halt(); + logger.debug("The operation timed out"); + fail("Operation Timed Out"); + } else if (policyName.endsWith("APPC.LCM.RESPONSE")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + assertTrue(ControlLoopNotificationType.OPERATION_SUCCESS.equals(notification.getNotification())); + assertNotNull(notification.getMessage()); + assertTrue(notification.getMessage().startsWith("actor=APPC")); + } else if (policyName.endsWith("EVENT.MANAGER")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + if (notification.getMessage().endsWith("Closing the control loop.") + || notification.getMessage().equals("Waiting for abatement")) { + if (policyName.startsWith(controlLoopOneName.toString())) { + logger.debug("Halting kieSession1"); + kieSession1.halt(); + } else if (policyName.startsWith(controlLoopTwoName.toString())) { + logger.debug("Halting kieSession2"); + kieSession2.halt(); + } else { + fail("Unknown ControlLoop"); + } + } + } else if (policyName.endsWith("EVENT.MANAGER.TIMEOUT")) { + logger.debug("Rule Fired: " + notification.getPolicyName()); + kieSession1.halt(); + kieSession2.halt(); + logger.debug("The control loop timed out"); + fail("Control Loop Timed Out"); + } + } else if (obj instanceof LcmRequestWrapper) { + /* + * The request should be of type LCMRequestWrapper and the subrequestid should be 1 + */ + LcmRequestWrapper dmaapRequest = (LcmRequestWrapper) obj; + LcmRequest appcRequest = dmaapRequest.getBody(); + assertEquals(appcRequest.getCommonHeader().getSubRequestId(),"1"); + + logger.debug("\n============ APPC received the request!!! ===========\n"); + + /* + * Simulate a success response from APPC and insert the response into the working memory + */ + LcmResponseWrapper dmaapResponse = new LcmResponseWrapper(); + LcmResponse appcResponse = new LcmResponse(appcRequest); + appcResponse.getStatus().setCode(400); + appcResponse.getStatus().setMessage("AppC success"); + dmaapResponse.setBody(appcResponse); + kieSession1.insert(dmaapResponse); + kieSession2.insert(dmaapResponse); + } + } + + /** + * This method will dump all the facts in the working memory. + * + * @param kieSession the session containing the facts + */ + public void dumpFacts(KieSession kieSession) { + logger.debug("Fact Count: {}", kieSession.getFactCount()); + for (FactHandle handle : kieSession.getFactHandles()) { + logger.debug("FACT: {}", handle); + } + } + + /** + * Test that SyntheticControlLoopOne blocks SyntheticControlLoopTwo + * is enforced correctly. + */ + @Test + public void testSyntheticControlLoopOneBlocksSyntheticControlLoopTwo() throws InterruptedException { + logger.info("Beginning testSyntheticControlLoopOneBlocksSyntheticControlLoopTwo"); + /* + * Allows the PolicyEngine to callback to this object to + * notify that there is an event ready to be pulled + * from the queue + */ + for (TopicSink sink : noopTopics) { + assertTrue(sink.start()); + sink.register(this); + } + + /* + * Create unique requestIds + */ + final UUID requestId1 = UUID.randomUUID(); + final UUID requestId2 = UUID.randomUUID(); + final UUID requestId3 = UUID.randomUUID(); + final UUID requestId4 = UUID.randomUUID(); + final UUID requestId5 = UUID.randomUUID(); + final String cl1 = controlLoopOneName.toString(); + final String cl2 = controlLoopTwoName.toString(); + final String t1 = "TARGET_1"; + final String t2 = "TARGET_2"; + + logger.info("@@@@@@@@@@ cl2 ONSET t1 (Success) @@@@@@@@@@"); + simulateOnset(requestId1, cl2, t1, kieSession2,"PERMIT"); + logger.info("@@@@@@@@@@ cl1 ONSET t1 @@@@@@@@@@"); + simulateOnset(requestId2, cl1, t1, kieSession1,"PERMIT"); + logger.info("@@@@@@@@@@ cl2 ABATED t1 @@@@@@@@@@"); + simulateAbatement(requestId1, cl2, t1, kieSession2); + logger.info("@@@@@@@@@@ cl2 ONSET t1 (Fail) @@@@@@@@@@"); + simulateOnset(requestId3, cl2, t1, kieSession2,"DENY"); + logger.info("@@@@@@@@@@ cl2 ONSET t2 (Success) @@@@@@@@@@"); + simulateOnset(requestId4, cl2, t2, kieSession2,"PERMIT"); + logger.info("@@@@@@@@@@ cl2 ABATED t2 @@@@@@@@@@"); + simulateAbatement(requestId4, cl2, t2, kieSession2); + logger.info("@@@@@@@@@@ cl1 ABATED t1 @@@@@@@@@@"); + simulateAbatement(requestId2, cl1, t1, kieSession1); + logger.info("@@@@@@@@@@ cl2 ONSET t1 (Success) @@@@@@@@@@"); + simulateOnset(requestId5, cl2, t1, kieSession2,"PERMIT"); + logger.info("@@@@@@@@@@ cl2 ABATED t1 @@@@@@@@@@"); + simulateAbatement(requestId5, cl2, t1, kieSession2); + + /* + * Print what's left in memory + */ + dumpFacts(kieSession1); + dumpFacts(kieSession2); + } +} + diff --git a/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/Util.java b/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/Util.java new file mode 100644 index 000000000..5b5aa2d9c --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/Util.java @@ -0,0 +1,297 @@ +/*- + * ============LICENSE_START======================================================= + * demo + * ================================================================================ + * 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.template.demo.clc; + +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import javax.persistence.Query; + +import org.apache.commons.io.IOUtils; +import org.kie.api.KieServices; +import org.kie.api.builder.KieBuilder; +import org.kie.api.builder.KieFileSystem; +import org.kie.api.builder.Message; +import org.kie.api.builder.ReleaseId; +import org.kie.api.builder.Results; +import org.kie.api.builder.model.KieModuleModel; +import org.kie.api.runtime.KieContainer; +import org.kie.api.runtime.KieSession; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.controlloop.policy.ControlLoopPolicy; +import org.onap.policy.controlloop.policy.guard.ControlLoopGuard; +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.guard.PolicyGuardYamlToXacml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + + +public final class Util { + + private static final String OPSHISTPUPROP = "OperationsHistoryPU"; + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + public static class Pair<A, B> { + public final A first; + public final B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + } + + /** + * Load YAML. + * + * @param testFile test file to load + * @return the Pair of a policy and the yaml contents + */ + public static Pair<ControlLoopPolicy, String> loadYaml(String testFile) { + try (InputStream is = new FileInputStream(new File(testFile))) { + String contents = IOUtils.toString(is, StandardCharsets.UTF_8); + // + // Read the yaml into our Java Object + // + Yaml yaml = new Yaml(new Constructor(ControlLoopPolicy.class)); + Object obj = yaml.load(contents); + + logger.debug(contents); + + return new Pair<ControlLoopPolicy, String>((ControlLoopPolicy) obj, contents); + } catch (IOException e) { + fail(e.getLocalizedMessage()); + } + return null; + } + + /** + * Load the YAML guard policy. + * + * @param testFile the test file to load + * @return return the guard object + */ + public static ControlLoopGuard loadYamlGuard(String testFile) { + try (InputStream is = new FileInputStream(new File(testFile))) { + String contents = IOUtils.toString(is, StandardCharsets.UTF_8); + // + // Read the yaml into our Java Object + // + Yaml yaml = new Yaml(new Constructor(ControlLoopGuard.class)); + Object obj = yaml.load(contents); + return (ControlLoopGuard) obj; + } catch (IOException e) { + fail(e.getLocalizedMessage()); + } + return null; + } + + public static HttpServletServer buildAaiSim() throws InterruptedException, IOException { + return org.onap.policy.simulators.Util.buildAaiSim(); + } + + private static String generatePolicy(String ruleContents, + String closedLoopControlName, + String policyScope, + String policyName, + String policyVersion, + String controlLoopYaml) { + + Pattern pattern = Pattern.compile("\\$\\{closedLoopControlName\\}"); + Matcher matcher = pattern.matcher(ruleContents); + ruleContents = matcher.replaceAll(closedLoopControlName); + + pattern = Pattern.compile("\\$\\{policyScope\\}"); + matcher = pattern.matcher(ruleContents); + ruleContents = matcher.replaceAll(policyScope); + + pattern = Pattern.compile("\\$\\{policyName\\}"); + matcher = pattern.matcher(ruleContents); + ruleContents = matcher.replaceAll(policyName); + + pattern = Pattern.compile("\\$\\{policyVersion\\}"); + matcher = pattern.matcher(ruleContents); + ruleContents = matcher.replaceAll(policyVersion); + + pattern = Pattern.compile("\\$\\{controlLoopYaml\\}"); + matcher = pattern.matcher(ruleContents); + ruleContents = matcher.replaceAll(controlLoopYaml); + + return ruleContents; + } + + /** + * Build the container. + * + * @param droolsTemplate template + * @param closedLoopControlName control loop id + * @param policyScope policy scope + * @param policyName policy name + * @param policyVersion policy version + * @param yamlSpecification incoming yaml specification + * @return the Kie session + * @throws IOException if the container cannot be built + */ + public static KieSession buildContainer(String droolsTemplate, String closedLoopControlName, + String policyScope, String policyName, String policyVersion, + String yamlSpecification) throws IOException { + // + // Get our Drools Kie factory + // + KieServices ks = KieServices.Factory.get(); + + KieModuleModel kieModule = ks.newKieModuleModel(); + + logger.debug("KMODULE:" + System.lineSeparator() + kieModule.toXML()); + + // + // Generate our drools rule from our template + // + KieFileSystem kfs = ks.newKieFileSystem(); + + kfs.writeKModuleXML(kieModule.toXML()); + { + Path rule = Paths.get(droolsTemplate); + String ruleTemplate = new String(Files.readAllBytes(rule)); + String drlContents = generatePolicy(ruleTemplate, + closedLoopControlName, + policyScope, + policyName, + policyVersion, + yamlSpecification); + + kfs.write("src/main/resources/" + policyName + ".drl", + ks.getResources().newByteArrayResource(drlContents.getBytes())); + } + // + // Compile the rule + // + KieBuilder builder = ks.newKieBuilder(kfs).buildAll(); + Results results = builder.getResults(); + if (results.hasMessages(Message.Level.ERROR)) { + for (Message msg : results.getMessages()) { + logger.error(msg.toString()); + } + throw new RuntimeException("Drools Rule has Errors"); + } + for (Message msg : results.getMessages()) { + logger.debug(msg.toString()); + } + // + // Create our kie Session and container + // + ReleaseId releaseId = ks.getRepository().getDefaultReleaseId(); + logger.debug(releaseId.toString()); + KieContainer keyContainer = ks.newKieContainer(releaseId); + + return keyContainer.newKieSession(); + } + + /** + * Set the A&AI properties. + */ + public static void setAaiProps() { + PolicyEngine.manager.setEnvironmentProperty("aai.url", "http://localhost:6666"); + PolicyEngine.manager.setEnvironmentProperty("aai.username", "AAI"); + PolicyEngine.manager.setEnvironmentProperty("aai.password", "AAI"); + } + + /** + * Set the Guard properties to use embedded XACML PDPEngine. + */ + public static void setGuardPropsEmbedded() { + /* + * Guard PDP-x connection Properties. No URL specified -> use embedded PDPEngine. + */ + PolicyEngine.manager.setEnvironmentProperty("prop.guard.propfile", + "src/test/resources/xacml/xacml_guard_clc.properties"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_USER, "python"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_PASS, "test"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_CLIENT_USER, "python"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_CLIENT_PASS, "test"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_ENV, "TEST"); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_DISABLED, "false"); + } + + /** + * Set the operation history properties. + */ + public static void setPuProp() { + System.setProperty(OPSHISTPUPROP, "TestOperationsHistoryPU"); + } + + /** + * Dump the contents of the History database. + * + * @return a list of the database entries + */ + public static List dumpDb() { + // + // Connect to in-mem db + // + EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestOperationsHistoryPU"); + EntityManager em = emf.createEntityManager(); + // + // Create query + // + String sql = "select * from operationshistory10"; + Query nq = em.createNativeQuery(sql); + List results = null; + // + // Execute query + // + try { + results = nq.getResultList(); + } catch (Exception ex) { + logger.error("getStatusFromDB threw: ", ex); + // + // Clean up and return null + // + em.close(); + emf.close(); + return null; + } + // + // Clean up and return results + // + em.close(); + emf.close(); + return results; + } +} diff --git a/controlloop/templates/template.demo.clc/src/test/resources/META-INF/persistence.xml b/controlloop/templates/template.demo.clc/src/test/resources/META-INF/persistence.xml new file mode 100644 index 000000000..808cef9b6 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ drools-applications
+ ================================================================================
+ 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=========================================================
+ -->
+<persistence version="2.1"
+ xmlns="http://xmlns.jcp.org/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+ <!-- In-mem DB for junit -->
+ <persistence-unit name="TestOperationsHistoryPU"
+ transaction-type="RESOURCE_LOCAL">
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <class>org.onap.policy.controlloop.eventmanager.OperationsHistoryDbEntry</class>
+ <properties>
+ <property name="eclipselink.ddl-generation"
+ value="create-tables" />
+ <property name="javax.persistence.jdbc.driver"
+ value="org.h2.Driver" />
+ <property name="javax.persistence.jdbc.url"
+ value="jdbc:h2:mem:test" />
+ <property name="javax.persistence.jdbc.user"
+ value="sa" />
+ <property name="javax.persistence.jdbc.password"
+ value="" />
+ <property name="eclipselink.logging.level"
+ value="CONFIG" />
+ </properties>
+ </persistence-unit>
+
+
+</persistence>
diff --git a/controlloop/templates/template.demo.clc/src/test/resources/xacml/synthetic_control_loop_one_blocks_synthetic_control_loop_two.xml b/controlloop/templates/template.demo.clc/src/test/resources/xacml/synthetic_control_loop_one_blocks_synthetic_control_loop_two.xml new file mode 100644 index 000000000..f503a2b11 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/resources/xacml/synthetic_control_loop_one_blocks_synthetic_control_loop_two.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ============LICENSE_START======================================================= + drools-applications + ================================================================================ + 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========================================================= + --> +<Policy xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" PolicyId="urn:com:att:xacml:policy:id:son:guard:5" Version="1" RuleCombiningAlgId="urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:permit-unless-deny"> +<Description>Policy for first_blocks_second coordination (if first running and second requests to run, deny second).</Description> +<Target> + <AnyOf> + <AllOf> + <Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-regexp-match"> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">SyntheticControlLoopTwo</AttributeValue> + <!-- value should be autofilled by yaml from a xacml template --> + <AttributeDesignator Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" AttributeId="urn:oasis:names:tc:xacml:1.0:clname:clname-id" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false"/> + </Match> + </AllOf> + </AnyOf> +</Target> + +<Rule RuleId="urn:com:att:xacml:rule:id:1" Effect="Deny"> + <Description>First Is Running</Description> + <Condition> + <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-equal"> + <VariableReference VariableId="clc_status"/> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">Success</AttributeValue> + </Apply> + </Condition> +</Rule> + +<!-- 'action_one' should be autofilled by yaml from a xacml template --> +<VariableDefinition VariableId="clc_status"> + <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-one-and-only"> + <AttributeDesignator Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" AttributeId="com:att:research:xacml:test:sql:resource:operations:status" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer="org:onap:policy:guard:getstatus:clname:SyntheticControlLoopOne" MustBePresent="false"/> + </Apply> +</VariableDefinition> +</Policy> diff --git a/controlloop/templates/template.demo.clc/src/test/resources/xacml/xacml_guard_clc.properties b/controlloop/templates/template.demo.clc/src/test/resources/xacml/xacml_guard_clc.properties new file mode 100644 index 000000000..5f5e0c8b2 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/resources/xacml/xacml_guard_clc.properties @@ -0,0 +1,65 @@ +### +# ============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========================================================= +### +# +# +# This files defines PIPs that will be used by XACML Guard Policies. One PIP per time window (5 min, 10min,...,1 month). +# +# +# + +# +# Default XACML Properties File +# Standard API Factories +# +xacml.dataTypeFactory=com.att.research.xacml.std.StdDataTypeFactory +xacml.pdpEngineFactory=com.att.research.xacmlatt.pdp.ATTPDPEngineFactory +xacml.pepEngineFactory=com.att.research.xacml.std.pep.StdEngineFactory +xacml.pipFinderFactory=com.att.research.xacml.std.pip.StdPIPFinderFactory +xacml.traceEngineFactory=com.att.research.xacml.std.trace.LoggingTraceEngineFactory +# +# AT&T PDP Implementation Factories +# +xacml.att.evaluationContextFactory=com.att.research.xacmlatt.pdp.std.StdEvaluationContextFactory +xacml.att.combiningAlgorithmFactory=com.att.research.xacmlatt.pdp.std.StdCombiningAlgorithmFactory +xacml.att.functionDefinitionFactory=com.att.research.xacmlatt.pdp.std.StdFunctionDefinitionFactory +xacml.att.policyFinderFactory=com.att.research.xacmlatt.pdp.std.StdPolicyFinderFactory + + +# +# NOTE: If you are testing against a RESTful PDP, then the PDP must be configured with the +# policies and PIP configuration as defined below. Otherwise, this is the configuration that +# the embedded PDP uses. +# + +# In case we have multiple applicable Guard policies, we will deny if any of them denies. +#xacml.att.policyFinderFactory.combineRootPolicies=urn:com:att:xacml:3.0:policy-combining-algorithm:combined-deny-overrides +xacml.att.policyFinderFactory.combineRootPolicies=urn:oasis:names:tc:xacml:3.0:policy-combining-algorithm:permit-unless-deny + + +# Policies to load +# +xacml.rootPolicies=p1 +p1.file=src/test/resources/xacml/synthetic_control_loop_one_blocks_synthetic_control_loop_two.xml + +# PIP Engine Definition +# +xacml.pip.engines=getstatus +getstatus.classname=org.onap.policy.guard.PipEngineGetStatus +getstatus.issuer=org:onap:policy:guard:getstatus diff --git a/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticOne.yaml b/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticOne.yaml new file mode 100644 index 000000000..2a74843a0 --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticOne.yaml @@ -0,0 +1,43 @@ +# Copyright 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. +controlLoop: + version: 2.0.0 + controlLoopName: SyntheticControlLoopOne + services: + - serviceName: ServiceSimple + resources: + - resourceName: res1 + resourceType: VFC + - resourceName: res2 + resourceType: VFC + trigger_policy: unique-policy-id-1 + timeout: 2500 + abatement: true + +policies: + - id: unique-policy-id-1 + name: SyntheticControlLoopOnePolicy + description: + actor: APPC + recipe: action_one + target: + type: VNF + retry: 3 + timeout: 200 + success: final_success + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_guard: final_failure_guard + failure_exception: final_failure_exception diff --git a/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticTwo.yaml b/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticTwo.yaml new file mode 100644 index 000000000..12e1bdf2b --- /dev/null +++ b/controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticTwo.yaml @@ -0,0 +1,43 @@ +# Copyright 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. +controlLoop: + version: 2.0.0 + controlLoopName: SyntheticControlLoopTwo + services: + - serviceName: ServiceSimple + resources: + - resourceName: res1 + resourceType: VFC + - resourceName: res2 + resourceType: VFC + trigger_policy: unique-policy-id-1 + timeout: 2500 + abatement: true + +policies: + - id: unique-policy-id-1 + name: SyntheticControlLoopTwoPolicy + description: + actor: APPC + recipe: action_two + target: + type: VNF + retry: 3 + timeout: 200 + success: final_success + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_guard: final_failure_guard + failure_exception: final_failure_exception |