Add demo for Control Loop Coordination. 73/66773/2
authorJoshua Reich <jreich@research.att.com>
Fri, 14 Sep 2018 18:38:01 +0000 (11:38 -0700)
committerJoshua Reich <jreich@research.att.com>
Tue, 18 Sep 2018 05:35:09 +0000 (22:35 -0700)
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>
18 files changed:
controlloop/common/guard/src/main/java/org/onap/policy/guard/PipEngineGetHistory.java [moved from controlloop/common/guard/src/main/java/org/onap/policy/guard/PIPEngineGetHistory.java with 99% similarity]
controlloop/common/guard/src/main/java/org/onap/policy/guard/PipEngineGetStatus.java [new file with mode: 0644]
controlloop/common/guard/src/test/java/org/onap/policy/guard/PipEngineGetHistoryTest.java
controlloop/common/guard/src/test/java/org/onap/policy/guard/PipEngineGetStatusTest.java [new file with mode: 0644]
controlloop/templates/pom.xml
controlloop/templates/template.demo.clc/README.md [new file with mode: 0644]
controlloop/templates/template.demo.clc/pom.xml [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/CallGuardTaskEmbedded.java [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelperEmbedded.java [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/main/resources/__closedLoopControlName__.drl [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/ControlLoopCoordinationTest.java [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/java/org/onap/policy/template/demo/clc/Util.java [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/resources/META-INF/persistence.xml [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/resources/xacml/synthetic_control_loop_one_blocks_synthetic_control_loop_two.xml [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/resources/xacml/xacml_guard_clc.properties [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticOne.yaml [new file with mode: 0644]
controlloop/templates/template.demo.clc/src/test/resources/yaml/policy_ControlLoop_SyntheticTwo.yaml [new file with mode: 0644]
controlloop/templates/template.demo/src/test/resources/xacml/xacml_guard.properties

@@ -55,8 +55,8 @@ import org.onap.policy.drools.system.PolicyEngine;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class PIPEngineGetHistory extends StdConfigurableEngine {
-    private static final Logger logger = LoggerFactory.getLogger(PIPEngineGetHistory.class);
+public class PipEngineGetHistory extends StdConfigurableEngine {
+    private static final Logger logger = LoggerFactory.getLogger(PipEngineGetHistory.class);
 
     //
     // Base issuer string. The issuer in the policy will also contain time window information
@@ -91,7 +91,7 @@ public class PIPEngineGetHistory extends StdConfigurableEngine {
             new StdPIPRequest(new IdentifierImpl(XACML_ATTRIBUTE_CATEGORY_RESOURCE),
                     new IdentifierImpl(XACML_TARGET_TARGET_ID), new IdentifierImpl(XML_SCHEMA_STRING));
 
-    public PIPEngineGetHistory() {
+    public PipEngineGetHistory() {
         super();
     }
 
diff --git a/controlloop/common/guard/src/main/java/org/onap/policy/guard/PipEngineGetStatus.java b/controlloop/common/guard/src/main/java/org/onap/policy/guard/PipEngineGetStatus.java
new file mode 100644 (file)
index 0000000..40b26c8
--- /dev/null
@@ -0,0 +1,311 @@
+/*-
+ * ============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.AttributeValue;
+import com.att.research.xacml.api.Identifier;
+import com.att.research.xacml.api.pip.PIPException;
+import com.att.research.xacml.api.pip.PIPFinder;
+import com.att.research.xacml.api.pip.PIPRequest;
+import com.att.research.xacml.api.pip.PIPResponse;
+import com.att.research.xacml.std.IdentifierImpl;
+import com.att.research.xacml.std.StdMutableAttribute;
+import com.att.research.xacml.std.datatypes.DataTypes;
+import com.att.research.xacml.std.pip.StdMutablePIPResponse;
+import com.att.research.xacml.std.pip.StdPIPRequest;
+import com.att.research.xacml.std.pip.StdPIPResponse;
+import com.att.research.xacml.std.pip.engines.StdConfigurableEngine;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.NoResultException;
+import javax.persistence.NonUniqueResultException;
+import javax.persistence.Persistence;
+import javax.persistence.Query;
+
+import org.onap.policy.drools.system.PolicyEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PipEngineGetStatus extends StdConfigurableEngine {
+    private static final Logger logger = LoggerFactory.getLogger(PipEngineGetStatus.class);
+
+    //
+    // Base issuer string. The issuer in the policy will contain the operations
+    // E.g., "org:onap:policy:guard:status:clname:testcl"
+    //
+    public static final String DEFAULT_ISSUER = "org:onap:policy:guard:status";
+    public static final String DEFAULT_DESCRIPTION = "PIP for retrieving Operation Status from DB";
+
+    private static final String XML_SCHEMA_STRING = "http://www.w3.org/2001/XMLSchema#string";
+
+    private static final String XACML_SUBJECT_CATEGORY_ACCESS_SUBJECT =
+            "urn:oasis:names:tc:xacml:1.0:subject-category:access-subject";
+    private static final String XACML_ACTOR_ACTOR_ID = "urn:oasis:names:tc:xacml:1.0:actor:actor-id";
+    private static final String XACML_ATTRIBUTE_CATEGORY_ACTION =
+            "urn:oasis:names:tc:xacml:3.0:attribute-category:action";
+    private static final String XACML_OPERATION_OPERATION_ID = "urn:oasis:names:tc:xacml:1.0:operation:operation-id";
+    private static final String XACML_ATTRIBUTE_CATEGORY_RESOURCE =
+            "urn:oasis:names:tc:xacml:3.0:attribute-category:resource";
+    private static final String XACML_TARGET_TARGET_ID = "urn:oasis:names:tc:xacml:1.0:target:target-id";
+    private static final String XACML_TEST_SQL_RESOURCE_OPERATIONS_STATUS =
+            "com:att:research:xacml:test:sql:resource:operations:status";
+
+    private static final PIPRequest PIP_REQUEST_ACTOR =
+            new StdPIPRequest(new IdentifierImpl(XACML_SUBJECT_CATEGORY_ACCESS_SUBJECT),
+                    new IdentifierImpl(XACML_ACTOR_ACTOR_ID), new IdentifierImpl(XML_SCHEMA_STRING));
+
+    private static final PIPRequest PIP_REQUEST_RECIPE =
+            new StdPIPRequest(new IdentifierImpl(XACML_ATTRIBUTE_CATEGORY_ACTION),
+                    new IdentifierImpl(XACML_OPERATION_OPERATION_ID), new IdentifierImpl(XML_SCHEMA_STRING));
+
+    private static final PIPRequest PIP_REQUEST_TARGET =
+            new StdPIPRequest(new IdentifierImpl(XACML_ATTRIBUTE_CATEGORY_RESOURCE),
+                    new IdentifierImpl(XACML_TARGET_TARGET_ID), new IdentifierImpl(XML_SCHEMA_STRING));
+
+    public PipEngineGetStatus() {
+        super();
+    }
+
+    @Override
+    public Collection<PIPRequest> attributesRequired() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Collection<PIPRequest> attributesProvided() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public PIPResponse getAttributes(PIPRequest pipRequest, PIPFinder pipFinder) throws PIPException {
+        logger.debug("Entering Status PIP");
+
+        /*
+         * First check to see if the issuer is set and then match it
+         */
+        String issuer;
+        if ((issuer = pipRequest.getIssuer()) == null) {
+
+            logger.debug("No issuer in the request...");
+            logger.debug("Status PIP - No issuer in the request!");
+            return StdPIPResponse.PIP_RESPONSE_EMPTY;
+         
+        } else if (!issuer.contains(this.getIssuer())) {
+            // Notice, we are checking here for the base issuer prefix.
+            logger.debug("Requested issuer '{}' does not match {}", issuer, getIssuer());
+            logger.debug("Status PIP - Issuer {}  does not match with: ", issuer, this.getIssuer());
+            return StdPIPResponse.PIP_RESPONSE_EMPTY;
+        }
+
+        String[] s1 = issuer.split("clname:");
+        String clname = s1[1];
+        String target = null;
+        try {
+            target = getTarget(pipFinder).iterator().next();
+        } catch (Exception e) {
+            logger.debug("could not retrieve target from PIP finder", e);
+            return StdPIPResponse.PIP_RESPONSE_EMPTY;
+        }
+
+        logger.debug("Going to query DB about: clname={}, target={}", clname, target);
+        String statusFromDb = getStatusFromDb(clname, target);
+
+        StdMutablePIPResponse stdPipResponse = new StdMutablePIPResponse();
+
+        this.addStringAttribute(stdPipResponse, new IdentifierImpl(XACML_ATTRIBUTE_CATEGORY_RESOURCE),
+                new IdentifierImpl(XACML_TEST_SQL_RESOURCE_OPERATIONS_STATUS), statusFromDb, pipRequest);
+
+        return new StdPIPResponse(stdPipResponse);
+    }
+
+    @Override
+    public void configure(String id, Properties properties) throws PIPException {
+        super.configure(id, properties);
+
+        if (this.getDescription() == null) {
+            this.setDescription(DEFAULT_DESCRIPTION);
+        }
+        if (this.getIssuer() == null) {
+            this.setIssuer(DEFAULT_ISSUER);
+        }
+    }
+
+    private PIPResponse getAttribute(PIPRequest pipRequest, PIPFinder pipFinder) {
+        PIPResponse pipResponse = null;
+        
+        try {
+            pipResponse = pipFinder.getMatchingAttributes(pipRequest, this);
+        } catch (PIPException ex) {
+            logger.error("getAttribute threw:", ex);
+            return null;
+        }
+        if (pipResponse == null) {
+            return null;
+        }
+        if (pipResponse.getStatus() != null && !pipResponse.getStatus().isOk()) {
+            logger.warn("PIP response error {}: {}", pipRequest.getAttributeId().stringValue(),
+                        pipResponse.getStatus());
+            return null;
+        }
+        if (pipResponse.getAttributes() != null && pipResponse.getAttributes().isEmpty()) {
+            logger.warn("No attributes in POP response {}: {}", pipRequest.getAttributeId().stringValue(),
+                        pipResponse.getStatus());
+            return null;
+        }
+        return pipResponse;
+    }
+
+    private void addStringAttribute(StdMutablePIPResponse stdPipResponse, Identifier category, Identifier attributeId,
+            String value, PIPRequest pipRequest) {
+        AttributeValue<String> attributeValue = null;
+        try {
+            attributeValue = DataTypes.DT_STRING.createAttributeValue(value);
+        } catch (Exception ex) {
+            logger.error("Failed to convert {} to an AttributeValue<String>", value, ex);
+        }
+        if (attributeValue != null) {
+            stdPipResponse.addAttribute(new StdMutableAttribute(category, attributeId, attributeValue,
+                    pipRequest.getIssuer()/* this.getIssuer() */, false));
+        }
+    }
+
+    private Set<String> getTarget(PIPFinder pipFinder) {
+        /*
+         * Get the UID from either the subject id or the uid property
+         */
+        PIPResponse pipResponseUid = this.getAttribute(PIP_REQUEST_TARGET, pipFinder);
+        if (pipResponseUid == null) {
+            return new HashSet<>();
+        }
+
+        /*
+         * Iterate over all of the returned results and do the LDAP requests
+         */
+        Collection<Attribute> listUids = pipResponseUid.getAttributes();
+        Set<String> setUids = new HashSet<>();
+        for (Attribute attributeUid : listUids) {
+            Iterator<AttributeValue<String>> iterAttributeValues = attributeUid.findValues(DataTypes.DT_STRING);
+            if (iterAttributeValues != null) {
+                while (iterAttributeValues.hasNext()) {
+                    String uid = iterAttributeValues.next().getValue();
+                    if (uid != null) {
+                        setUids.add(uid);
+                    }
+                }
+            }
+        }
+
+        return setUids;
+    }
+
+    private static String getStatusFromDb(String clname, String target) {
+        //
+        // DB Properties
+        //
+        Properties props = new Properties();
+        try {
+            props.put(Util.ECLIPSE_LINK_KEY_URL, PolicyEngine.manager.getEnvironmentProperty(Util.ONAP_KEY_URL));
+            props.put(Util.ECLIPSE_LINK_KEY_USER, PolicyEngine.manager.getEnvironmentProperty(Util.ONAP_KEY_USER));
+            props.put(Util.ECLIPSE_LINK_KEY_PASS, PolicyEngine.manager.getEnvironmentProperty(Util.ONAP_KEY_PASS));
+        } catch (NullPointerException e) {
+            logger.error("getStatusFromDB: {} when setting properties", e.getMessage());
+        }
+        //
+        // Set opsHistPu to the correct value and clear properties if necessary.
+        //
+        String opsHistPu = System.getProperty("OperationsHistoryPU");
+        if (opsHistPu == null || !opsHistPu.equals("TestOperationsHistoryPU")) {
+            opsHistPu = "OperationsHistoryPU";
+        } else {
+            props.clear();
+        }
+        //
+        // Set up the EntityManager
+        //
+        EntityManagerFactory emf = null;
+        EntityManager em = null;
+        try {
+            emf = Persistence.createEntityManagerFactory(opsHistPu, props);
+        } catch (Exception ex) {
+            logger.error("PIP thread got Exception. Can't connect to Operations History DB -- {}", opsHistPu);
+            logger.error("getStatusFromDb threw: ", ex);
+            return null;
+        }
+        try {
+            em = emf.createEntityManager();
+        } catch (Exception ex) {
+            logger.error("PIP thread got Exception. Problem creating EntityManager");
+            logger.error("getStatusFromDb threw: ", ex);
+            emf.close();
+            return null;
+        } 
+        //
+        // Create the query
+        //
+        String sql = "select outcome from operationshistory10 where"
+            + " clname= ?"
+            + " and target= ?"
+            + " order by endtime desc limit 1";
+        Query nq = em.createNativeQuery(sql);
+        nq.setParameter(1, clname);
+        nq.setParameter(2, target);
+        logger.debug("SQL query: {}, {}, {}", sql, clname, target);
+        //
+        // Run the query
+        //
+        String ret = null;
+        try{
+            ret = ((String)nq.getSingleResult());
+        } catch(NoResultException ex) {
+            logger.debug("NoResultException for getSingleResult()");
+            ret = "NO_MATCHING_ENTRY";
+        } catch(Exception ex){
+            logger.error("getStatusFromDB threw: ", ex);
+        }
+        if (ret != null) {
+            logger.debug("SQL query result: {}", ret);
+        }
+        //
+        // Clean up and return the result
+        //
+        try {
+            em.close();
+        } catch(Exception ex){
+            logger.error("getStatusFromDB threw: ", ex);
+        }
+        try {
+            emf.close();
+        } catch(Exception ex){
+            logger.error("getStatusFromDB threw: ", ex);
+        }
+        return ret;
+    }
+}
index 5780cec..288c6ca 100644 (file)
@@ -67,7 +67,7 @@ import org.junit.Test;
 import org.onap.policy.drools.system.PolicyEngine;
 
 public class PipEngineGetHistoryTest {
-    static PIPEngineGetHistory pegh;
+    static PipEngineGetHistory pegh;
     private static final String ISSUER = "issuerIntw:mid:end";
     
     private static EntityManagerFactory emf;
@@ -80,9 +80,9 @@ public class PipEngineGetHistoryTest {
     public static void testPipEngineGetHistory() {
         pegh = null;
         try {
-            pegh = new PIPEngineGetHistory();
+            pegh = new PipEngineGetHistory();
         } catch (Exception e) {
-            fail("PIPEngineGetHistory constructor failed");
+            fail("PipEngineGetHistory constructor failed");
         }
 
         // Set PU
@@ -108,8 +108,17 @@ public class PipEngineGetHistoryTest {
         em.getTransaction().commit();
     }
     
+    /**
+     * Clean up test class.
+     */
     @AfterClass
     public static void tearDown() {
+        String sql = "DROP TABLE `operationshistory10`";
+        Query nq = em.createNativeQuery(sql);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+        em.close();
         emf.close();
     }
 
@@ -174,7 +183,7 @@ public class PipEngineGetHistoryTest {
         Method method = null;
         int count = -1;
         try {
-            method = PIPEngineGetHistory.class.getDeclaredMethod("getCountFromDb", String.class, String.class,
+            method = PipEngineGetHistory.class.getDeclaredMethod("getCountFromDb", String.class, String.class,
                     String.class, String.class);
             method.setAccessible(true);
             count = (int) method.invoke(null, "actor", "op", "target", "1 MINUTE");
@@ -204,7 +213,7 @@ public class PipEngineGetHistoryTest {
 
     @Test
     public void testConfigure() throws PIPException {
-        PIPEngineGetHistory pegh = new PIPEngineGetHistory();
+        PipEngineGetHistory pegh = new PipEngineGetHistory();
         pegh.configure("Dorothy", new Properties());
 
         pegh.setDescription(null);
@@ -214,7 +223,7 @@ public class PipEngineGetHistoryTest {
 
     @Test
     public void getAttributesTest() throws URISyntaxException, PIPException, FactoryException {
-        PIPEngineGetHistory pegh = new PIPEngineGetHistory();
+        PipEngineGetHistory pegh = new PipEngineGetHistory();
         pegh.setIssuer("Dorothy");
 
         Identifier identifierCategory = new IdentifierImpl(new URI("http://somewhere.over.the.rainbow/category"));;
@@ -233,7 +242,7 @@ public class PipEngineGetHistoryTest {
 
     @Test
     public void timeWindowTest() throws URISyntaxException, PIPException, FactoryException {
-        PIPEngineGetHistory pegh = new PIPEngineGetHistory();
+        PipEngineGetHistory pegh = new PipEngineGetHistory();
         pegh.setIssuer("Dorothy");
 
         Identifier identifierCategory = new IdentifierImpl(new URI("http://somewhere.over.the.rainbow/category"));;
diff --git a/controlloop/common/guard/src/test/java/org/onap/policy/guard/PipEngineGetStatusTest.java b/controlloop/common/guard/src/test/java/org/onap/policy/guard/PipEngineGetStatusTest.java
new file mode 100644 (file)
index 0000000..cf83c88
--- /dev/null
@@ -0,0 +1,411 @@
+/*-
+ * ============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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.att.research.xacml.api.Attribute;
+import com.att.research.xacml.api.AttributeValue;
+import com.att.research.xacml.api.Identifier;
+import com.att.research.xacml.api.Status;
+import com.att.research.xacml.api.pip.PIPEngine;
+import com.att.research.xacml.api.pip.PIPException;
+import com.att.research.xacml.api.pip.PIPFinder;
+import com.att.research.xacml.api.pip.PIPRequest;
+import com.att.research.xacml.api.pip.PIPResponse;
+import com.att.research.xacml.std.IdentifierImpl;
+import com.att.research.xacml.std.StdAttribute;
+import com.att.research.xacml.std.StdAttributeValue;
+import com.att.research.xacml.std.StdStatus;
+import com.att.research.xacml.std.StdStatusCode;
+import com.att.research.xacml.std.pip.StdPIPRequest;
+import com.att.research.xacml.std.pip.StdPIPResponse;
+import com.att.research.xacml.std.pip.finders.EngineFinder;
+import com.att.research.xacml.util.FactoryException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.persistence.Query;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.drools.system.PolicyEngine;
+
+public class PipEngineGetStatusTest {
+    static PipEngineGetStatus pegs;
+    private static final String ISSUER = "issuer:clname:testclname";
+    
+    private static EntityManagerFactory emf;
+    private static EntityManager em;
+
+    /**
+     * Set up test class.
+     */
+    @BeforeClass
+    public static void testPipEngineGetStatus() {
+        pegs = null;
+        try {
+            pegs = new PipEngineGetStatus();
+        } catch (Exception e) {
+            fail("PipEngineGetStatus constructor failed");
+        }
+
+        // Set PU
+        System.setProperty(Util.PU_KEY, Util.JUNITPU);
+
+        // Enter dummy props to avoid nullPointerException
+        PolicyEngine.manager.setEnvironmentProperty(Util.ONAP_KEY_URL, "a");
+        PolicyEngine.manager.setEnvironmentProperty(Util.ONAP_KEY_USER, "b");
+        PolicyEngine.manager.setEnvironmentProperty(Util.ONAP_KEY_PASS, "c");
+
+        // Connect to in-mem db
+        emf = Persistence.createEntityManagerFactory(Util.JUNITPU);
+        em = emf.createEntityManager();
+
+        // Create necessary table
+        String sql = "CREATE TABLE `operationshistory10` (" + "`CLNAME` varchar(255)," + "`requestID` varchar(100),"
+                + "`actor` varchar(50) ," + "`operation` varchar(50)," + "`target` varchar(50),"
+                + "`starttime` timestamp," + "`outcome` varchar(50)," + "`message` varchar(255),"
+                + "`subrequestId` varchar(100)," + "`endtime` timestamp" + ")";
+        Query nq = em.createNativeQuery(sql);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+    }
+
+    /**
+     * Clean up test class.
+     */
+    @AfterClass
+    public static void tearDown() {
+        String sql = "DROP TABLE `operationshistory10`";
+        Query nq = em.createNativeQuery(sql);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+        em.close();
+        emf.close();
+    }
+
+    /**
+     * Setup method.
+     */
+    @Before
+    public void setUp() {
+        // clear the table
+        String sql = "DELETE FROM `operationshistory10`";
+        Query nq = em.createNativeQuery(sql);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+    }
+
+    @Test
+    public void testAttributesRequired() {
+        assertTrue(pegs.attributesRequired().isEmpty());
+    }
+
+    @Test
+    public void testAttributesProvided() {
+        assertTrue(pegs.attributesProvided().isEmpty());
+    }
+
+    @Test
+    public void testGetAttributes() {
+        StdPIPRequest mockPipRequest = mock(StdPIPRequest.class);
+        EngineFinder mockPipFinder = mock(EngineFinder.class);
+
+        // Test issuer null
+        when(mockPipRequest.getIssuer()).thenReturn(null);
+        try {
+            assertEquals(StdPIPResponse.PIP_RESPONSE_EMPTY, pegs.getAttributes(mockPipRequest, mockPipFinder));
+        } catch (Exception e) {
+            fail("getAttributes failed");
+        }
+        
+        // Test issuer not equal to our issuer
+        pegs.setIssuer(ISSUER);
+        when(mockPipRequest.getIssuer()).thenReturn("something else");
+        try {
+            assertEquals(StdPIPResponse.PIP_RESPONSE_EMPTY, pegs.getAttributes(mockPipRequest, mockPipFinder));
+        } catch (Exception e) {
+            fail("getAttributes failed");
+        }
+        
+        // Test issuer equal to our issuer
+        when(mockPipRequest.getIssuer()).thenReturn(ISSUER);
+        try {
+            assertNotNull(pegs.getAttributes(mockPipRequest, mockPipFinder));
+        } catch (Exception e) {
+            // Normal to catch exception
+        }
+    }
+
+    @Test
+    public void testGetStatusFromDb() {
+
+        // Use reflection to run getStatsFromDB
+        Method method = null;
+        String status = null;
+        String addEntry;
+        Query nq;
+        
+        // Add an entry
+        addEntry = "insert into operationshistory10 (outcome, CLNAME, actor, operation, target, endtime)"
+            + "values('1','testcl', 'actor', 'op', 'testtarget', CURRENT_TIMESTAMP())";
+        nq = em.createNativeQuery(addEntry);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+
+        try {
+            method = PipEngineGetStatus.class.getDeclaredMethod("getStatusFromDb", String.class, String.class);
+            method.setAccessible(true);
+            status = (String) method.invoke(null, "testcl", "testtarget");
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+                | NoSuchMethodException e) {
+            fail(e.getLocalizedMessage());
+        }
+
+        // Status should be "success"
+        assertEquals("1", status);
+
+        // Add entries
+        addEntry = "insert into operationshistory10 (outcome, CLNAME, actor, operation, target, endtime)"
+            + "values('2','testcl', 'actor', 'op', 'testtarget', CURRENT_TIMESTAMP())";
+        nq = em.createNativeQuery(addEntry);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+
+        addEntry = "insert into operationshistory10 (outcome, CLNAME, actor, operation, target, endtime)"
+            + "values('3','testcl', 'actor', 'op', 'testtarget2', CURRENT_TIMESTAMP())";
+        nq = em.createNativeQuery(addEntry);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+
+        addEntry = "insert into operationshistory10 (outcome, CLNAME, actor, operation, target, endtime)"
+            + "values('4','testcl2', 'actor', 'op', 'testtarget2', CURRENT_TIMESTAMP())";
+        nq = em.createNativeQuery(addEntry);
+        em.getTransaction().begin();
+        nq.executeUpdate();
+        em.getTransaction().commit();
+        
+        try {
+            method = PipEngineGetStatus.class.getDeclaredMethod("getStatusFromDb", String.class, String.class);
+            method.setAccessible(true);
+            status = (String) method.invoke(null, "testcl", "testtarget");
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+                | NoSuchMethodException e) {
+            fail(e.getLocalizedMessage());
+        }
+        assertEquals("2", status);
+
+        try {
+            method = PipEngineGetStatus.class.getDeclaredMethod("getStatusFromDb", String.class, String.class);
+            method.setAccessible(true);
+            status = (String) method.invoke(null, "testcl", "testtarget2");
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+                | NoSuchMethodException e) {
+            fail(e.getLocalizedMessage());
+        }
+        assertEquals("3", status);
+
+        try {
+            method = PipEngineGetStatus.class.getDeclaredMethod("getStatusFromDb", String.class, String.class);
+            method.setAccessible(true);
+            status = (String) method.invoke(null, "testcl2", "testtarget2");
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+                | NoSuchMethodException e) {
+            fail(e.getLocalizedMessage());
+        }
+        assertEquals("4", status);
+    }
+
+    @Test
+    public void testConfigure() throws PIPException {
+        PipEngineGetStatus pegs = new PipEngineGetStatus();
+        pegs.configure("Dorothy", new Properties());
+
+        pegs.setDescription(null);
+        pegs.setIssuer(null);
+        pegs.configure("Dorothy", new Properties());
+    }
+
+    private class DummyPipFinder implements PIPFinder {
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            try {
+                List<Attribute> attributeList = new ArrayList<>();
+                Identifier categoryIdIn = new IdentifierImpl(new URI("http://somewhere.over.the.rainbow/category"));
+                Identifier dataTypeIdIn = new IdentifierImpl(new URI("http://www.w3.org/2001/XMLSchema#string"));
+
+                Identifier attributeIdIn0 = new IdentifierImpl(new URI(UUID.randomUUID().toString()));
+                AttributeValue<String> valueIn0 = new StdAttributeValue<String>(dataTypeIdIn, "ActorDorothy");
+                Attribute attribute0 = new StdAttribute(categoryIdIn, attributeIdIn0, valueIn0);
+                attributeList.add(attribute0);
+
+                Identifier attributeIdIn1 = new IdentifierImpl(new URI(UUID.randomUUID().toString()));
+                AttributeValue<String> valueIn1 = new StdAttributeValue<String>(dataTypeIdIn, "OperationHomeFromOZ");
+                Attribute attribute1 = new StdAttribute(categoryIdIn, attributeIdIn1, valueIn1);
+                attributeList.add(attribute1);
+
+                Identifier attributeIdIn2 = new IdentifierImpl(new URI(UUID.randomUUID().toString()));
+                AttributeValue<String> valueIn2 = new StdAttributeValue<String>(dataTypeIdIn, "TargetWickedWitch");
+                Attribute attribute2 = new StdAttribute(categoryIdIn, attributeIdIn2, valueIn2);
+                attributeList.add(attribute2);
+
+                return new StdPIPResponse(attributeList);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public Collection<PIPEngine> getPIPEngines() {
+            return null;
+        }
+    }
+
+    private class DummyPipFinderPipException implements PIPFinder {
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            throw new PIPException();
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public Collection<PIPEngine> getPIPEngines() {
+            return null;
+        }
+    }
+
+    private class DummyPipFinderResponseStatusNok implements PIPFinder {
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            Status status = new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, "Processing Error");
+            return new StdPIPResponse(status);
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public Collection<PIPEngine> getPIPEngines() {
+            return null;
+        }
+    }
+
+    private class DummyPipFinderResponseEmptyAttrs implements PIPFinder {
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude) throws PIPException {
+            List<Attribute> attributeList = new ArrayList<>();
+            return new StdPIPResponse(attributeList);
+        }
+
+        @Override
+        public PIPResponse getMatchingAttributes(PIPRequest pipRequest, PIPEngine exclude, PIPFinder pipFinderParent)
+                throws PIPException {
+            return null;
+        }
+
+        @Override
+        public Collection<PIPEngine> getPIPEngines() {
+            return null;
+        }
+    }
+}
index 10a4a24..779837f 100644 (file)
@@ -34,6 +34,7 @@
 
   <modules>
     <module>template.demo</module>
+    <module>template.demo.clc</module>
     <module>archetype-cl-amsterdam</module>
     <module>archetype-cl-casablanca</module>
   </modules>
diff --git a/controlloop/templates/template.demo.clc/README.md b/controlloop/templates/template.demo.clc/README.md
new file mode 100644 (file)
index 0000000..b201619
--- /dev/null
@@ -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 (file)
index 0000000..9295d78
--- /dev/null
@@ -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 (file)
index 0000000..aaa2a0a
--- /dev/null
@@ -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 (file)
index 0000000..947b187
--- /dev/null
@@ -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 (file)
index 0000000..3981f0d
--- /dev/null
@@ -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 (file)
index 0000000..2d6279f
--- /dev/null
@@ -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 (file)
index 0000000..5b5aa2d
--- /dev/null
@@ -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 (file)
index 0000000..808cef9
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+  ============LICENSE_START=======================================================\r
+  drools-applications\r
+  ================================================================================\r
+  Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.\r
+  ================================================================================\r
+  Licensed under the Apache License, Version 2.0 (the "License");\r
+  you may not use this file except in compliance with the License.\r
+  You may obtain a copy of the License at\r
+  \r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+  \r
+  Unless required by applicable law or agreed to in writing, software\r
+  distributed under the License is distributed on an "AS IS" BASIS,\r
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+  See the License for the specific language governing permissions and\r
+  limitations under the License.\r
+  ============LICENSE_END=========================================================\r
+  -->\r
+<persistence version="2.1"\r
+    xmlns="http://xmlns.jcp.org/xml/ns/persistence"\r
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">\r
+\r
+    <!-- In-mem DB for junit -->\r
+    <persistence-unit name="TestOperationsHistoryPU"\r
+        transaction-type="RESOURCE_LOCAL">\r
+        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>\r
+        <class>org.onap.policy.controlloop.eventmanager.OperationsHistoryDbEntry</class>\r
+        <properties>\r
+            <property name="eclipselink.ddl-generation"\r
+                value="create-tables" />\r
+            <property name="javax.persistence.jdbc.driver"\r
+                value="org.h2.Driver" />\r
+            <property name="javax.persistence.jdbc.url"\r
+                value="jdbc:h2:mem:test" />\r
+            <property name="javax.persistence.jdbc.user"\r
+                value="sa" />\r
+            <property name="javax.persistence.jdbc.password"\r
+                value="" />\r
+            <property name="eclipselink.logging.level"\r
+                value="CONFIG" />\r
+        </properties>\r
+    </persistence-unit>\r
+\r
+\r
+</persistence>\r
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 (file)
index 0000000..f503a2b
--- /dev/null
@@ -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 (file)
index 0000000..5f5e0c8
--- /dev/null
@@ -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 (file)
index 0000000..2a74843
--- /dev/null
@@ -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 (file)
index 0000000..12e1bdf
--- /dev/null
@@ -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
index d05648b..0486f95 100644 (file)
@@ -66,6 +66,6 @@ p5.file=src/test/resources/xacml/autogenerated_blacklist.xml
 # PIP Engine Definition
 #
 xacml.pip.engines=historydb
-historydb.classname=org.onap.policy.guard.PIPEngineGetHistory
+historydb.classname=org.onap.policy.guard.PipEngineGetHistory
 historydb.issuer=com:att:research:xacml:guard:historydb