From 3e05cb41202145e113853392e9837abf3f6ec12c Mon Sep 17 00:00:00 2001 From: "Straubs, Ralph (rs8887)" Date: Tue, 19 Nov 2019 04:11:23 -0600 Subject: Add m2 model, including the LCM application Issue-ID: POLICY-1948 Change-Id: I18a5231d3102073c928a591c9e91b241b7093680 Signed-off-by: Straubs, Ralph (rs8887) --- controlloop/m2/guard/pom.xml | 84 +++++ .../java/org/onap/policy/guard/GuardContext.java | 400 +++++++++++++++++++++ .../org/onap/policy/guard/GuardContextTest.java | 139 +++++++ 3 files changed, 623 insertions(+) create mode 100644 controlloop/m2/guard/pom.xml create mode 100644 controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java create mode 100644 controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java (limited to 'controlloop/m2/guard') diff --git a/controlloop/m2/guard/pom.xml b/controlloop/m2/guard/pom.xml new file mode 100644 index 000000000..501151a85 --- /dev/null +++ b/controlloop/m2/guard/pom.xml @@ -0,0 +1,84 @@ + + + + + 4.0.0 + + + org.onap.policy.drools-applications.controlloop.m2 + m2 + 1.6.0-SNAPSHOT + + + guard + + + + junit + junit + test + + + + org.mockito + mockito-core + 2.13.0 + test + + + + org.onap.policy.drools-applications.controlloop.m2 + util + ${project.version} + + + + org.onap.policy.drools-pdp + policy-core + ${version.policy.drools-pdp} + provided + + + + org.onap.policy.drools-pdp + policy-management + ${version.policy.drools-pdp} + provided + + + + org.onap.policy.drools-applications.controlloop.common + guard + ${project.version} + + + + org.onap.policy.drools-applications.controlloop.common + database + ${project.version} + + + + com.h2database + h2 + + + diff --git a/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java b/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java new file mode 100644 index 000000000..d0d1b831f --- /dev/null +++ b/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java @@ -0,0 +1,400 @@ +/*- + * ============LICENSE_START======================================================= + * guard + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.guard; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.HashMap; +import java.util.Properties; +import java.util.UUID; +import java.util.function.Supplier; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.drools.core.WorkingMemory; + +import org.onap.policy.database.operationshistory.Dbao; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.core.PolicySession; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyControllerConstants; +import org.onap.policy.drools.system.PolicyEngineConstants; +import org.onap.policy.guard.Util; +import org.onap.policy.util.DroolsSessionCommonSerializable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Each instance of this class is initialized from a 'Properties' object, + * which is most likely a '*-controller.properties' file. The expectation is + * that it will be initialized within a '.drl' file, and be referenced by + * the associated 'Context' object. + */ +public class GuardContext implements Serializable { + private static final long serialVersionUID = 1L; + + private static final String ECLIPSE_LINK_KEY_DRIVER = "javax.persistence.jdbc.driver"; + + private static Logger logger = LoggerFactory.getLogger(GuardContext.class); + + // object that should be serialized + private Object namedSerializable; + + /*==================================*/ + /* fields extracted from properties */ + /*==================================*/ + // contains the four database properties, 'javax.persistence.jdbc.*', + private Properties dbProperties = null; + + // initialized from 'guard.disabled', but may also be set to 'true' if + // there is an initialization error + private boolean disabled = false; + + // errors that forced 'disabled' to be set to 'true' + private String errorMessage = null; + + /*======================================================*/ + /* fields that shouldn't be included in serialized data */ + /*======================================================*/ + + // derived from DB properties + private transient EntityManagerFactory emf = null; + + /** + * Constructor - initialize the 'GuardContext' instance using the + * controller's properties file. The properties file is located using a + * 'PolicySession' instance, but the way this mapping is done isn't + * perfect -- it may use the wrong properties file if there is another + * 'PolicyContainer' instance using the same 'artifactId' and 'groupId'. + * + * @param session the 'PolicySession' instance used to locate the associated + * 'Properties' instance + */ + public GuardContext(PolicySession session) { + this(session, null); + } + + /** + * Constructor - initialize the 'GuardContext' instance using the + * controller's properties file. The properties file is located using a + * 'PolicySession' instance, but the way this mapping is done isn't + * perfect -- it may use the wrong properties file if there is another + * 'PolicyContainer' instance using the same 'artifactId' and 'groupId'. + * + * @param session the 'PolicySession' instance used to locate the associated + * 'Properties' instance + * @param serializableName a String name unique within the Drools session + * that can be used to locate the corresponding 'GuardContext' object + * on the remote host + */ + public GuardContext(PolicySession session, String serializableName) { + namedSerializable = + (serializableName == null ? this : + new DroolsSessionCommonSerializable(serializableName, this)); + + // At present, there is no simple way to get the properties based + // upon a 'PolicyContainer'. Instead, we search through all of the + // 'PolicyController' instances looking for one with a matching + // 'artifactId' and 'groupId'. Note that this may not work correctly + // if there is more than one controller using the same or different + // version of the same artifact. + + PolicyContainer container = session.getPolicyContainer(); + String artifactId = container.getArtifactId(); + String groupId = container.getGroupId(); + + Properties properties = + PolicyControllerConstants.getFactory().get(groupId, artifactId).getProperties(); + init(properties); + } + + /** + * Constructor - initialize the 'GuardContext' instance using the + * specified properties. + * + * @param properties configuration data used to initialize the 'GuardContext' instance + */ + public GuardContext(Properties properties) { + init(properties); + } + + /** + * Common initialization routine code used by both constructors. + * + * @param properties configuration data used to initialize the 'GuardContext' instance + */ + private void init(Properties properties) { + // used to store error messages + StringBuilder sb = new StringBuilder(); + + // fetch these parameters, if they exist + String disabledString = + PolicyEngineConstants.getManager().getEnvironmentProperty(Util.PROP_GUARD_DISABLED); + + if (disabledString != null) { + // decode optional 'guard.disabled' parameter + disabled = Boolean.valueOf(disabledString); + if (disabled) { + // skip everything else + return; + } + } + + // extract 'guard.java.persistence.jdbc.*' parameters, + // which are all mandatory + dbProperties = new Properties(); + setProperty(dbProperties, Util.ONAP_KEY_URL, Util.ECLIPSE_LINK_KEY_URL, sb); + setProperty(dbProperties, Util.ONAP_KEY_USER, Util.ECLIPSE_LINK_KEY_USER, sb); + setProperty(dbProperties, Util.ONAP_KEY_PASS, Util.ECLIPSE_LINK_KEY_PASS, sb); + String driver = properties.getProperty("guard." + ECLIPSE_LINK_KEY_DRIVER); + if (driver != null) { + dbProperties.setProperty(ECLIPSE_LINK_KEY_DRIVER, driver); + } + + // 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); + errorMessage = sb.toString(); + disabled = true; + logger.error("Initialization failure: " + errorMessage); + } + } + + /** + * Fetch a property from the PolicyEngine environment, and store it in + * a corresponding property in 'properties'. + * + * @param properties the location to store the properties + * @param srcName source environment property name + * @param destName destination property name + * @param log a 'StringBuilder' used to construct an error message, if needed + */ + private void setProperty(Properties properties, String srcName, String destName, StringBuilder log) { + String value = + PolicyEngineConstants.getManager().getEnvironmentProperty(srcName); + if (value == null) { + log.append("'").append(srcName).append("' is not defined, "); + } else { + properties.setProperty(destName, value); + } + } + + /** + * Do an asynchronous (non-blocking) HTTP REST query to see if this + * operation is permitted by 'guard'. The response is returned by + * inserting a 'PolicyGuardResponse' instance into Drools memory. + * + * @param workingMemory the Drools response is inserted here + * @param actor the processor being acted upon (e.g. "APPC") + * @param recipe otherwise known as "operation" (e.g. "Restart") + * @param target a further qualifier on 'actor'? (e.g. "VM") + * @param requestId the UUID string identifying the overall request + */ + public void asyncQuery( + WorkingMemory workingMemory, + String actor, String recipe, String target, + String requestId) { + + asyncQuery(workingMemory, actor, recipe, target, requestId, null); + } + + /** + * Do an asynchronous (non-blocking) HTTP REST query to see if this + * operation is permitted by 'guard'. The response is returned by + * inserting a 'PolicyGuardResponse' instance into Drools memory. + * + * @param workingMemory the Drools response is inserted here + * @param actor the processor being acted upon (e.g. "APPC") + * @param recipe otherwise known as "operation" (e.g. "Restart") + * @param target a further qualifier on 'actor'? (e.g. "VM") + * @param requestId the UUID string identifying the overall request + * @param controlLoopName the 'controlLoopName' value or 'null' + * (if 'null', it is ommitted from the query to 'guard') + */ + public void asyncQuery( + final WorkingMemory workingMemory, + final String actor, final String recipe, final String target, + final String requestId, final String controlLoopName) { + + if (disabled) { + logger.error("query skipped: {}", errorMessage); + workingMemory.insert( + new PolicyGuardResponse("Deny", UUID.fromString(requestId), recipe)); + return; + } + + CallGuardTask cgt = new CallGuardTask(workingMemory, controlLoopName, + actor, recipe, target, requestId, () -> null); + + PolicyEngineConstants.getManager().getExecutorService().execute(cgt); + } + + /** + * Create an 'EntityManagerFactory', if needed, and then create a new + * 'EntityManager' instance. + * + * @return a new 'EntityManager' instance + */ + private EntityManager createEntityManager() { + if (emf == null) { + // 'EntityManagerFactory' does not exist yet -- create one + + // copy database properties to a 'HashMap' + HashMap propertiesMap = new HashMap<>(dbProperties); + + // use 'ClassLoader' from Drools session + propertiesMap.put("eclipselink.classloader", + GuardContext.class.getClassLoader()); + + // create DB tables, if needed + propertiesMap.put("eclipselink.ddl-generation", "create-tables"); + + // create entity manager factory + emf = Persistence.createEntityManagerFactory("OperationsHistoryPU", propertiesMap); + } + + // create and return the 'EntityManager' + return emf.createEntityManager(); + } + + /** + * This is a synchronous (blocking) method, which creates a database entity + * for an in-progress request. + * + * @param starttime this is used as the 'starttime' timestamp in the record + * @param endtime this is used as the 'endtime' timestamp in the record + * @param closedLoopControlName uniquely identifies the Drools rules + * @param actor the processor being acted upon (e.g. "APPC") + * @param recipe otherwise known as "operation" (e.g. "Restart") + * @param target a further qualifier on 'actor'? (e.g. "VM") + * @param requestId the UUID string identifying the overall request + * @param subRequestId further qualifier on 'requestId' + * @param message indicates success status, or reason for failure + * @param outcome 'PolicyResult' enumeration string + * @return 'true' if the operation was successful, and 'false' if not + */ + public boolean createDbEntry( + Instant starttime, Instant endtime, String closedLoopControlName, + String actor, String recipe, String target, + String requestId, String subRequestId, String message, String outcome) { + + if (disabled) { + if (errorMessage != null) { + logger.error("Database update skipped: " + errorMessage); + } + return false; + } + + EntityManager em = null; + boolean rval = false; + + try { + em = createEntityManager(); + + // create the new DB table entry + Dbao newEntry = new Dbao(); + + // populate the new DB table entry + newEntry.setClosedLoopName(closedLoopControlName); + newEntry.setRequestId(requestId); + newEntry.setActor(actor); + newEntry.setOperation(recipe); + newEntry.setTarget(target); + newEntry.setStarttime(new Timestamp(starttime.toEpochMilli())); + newEntry.setSubrequestId(subRequestId); + + newEntry.setEndtime(new Timestamp(endtime.toEpochMilli())); + newEntry.setMessage(message); + newEntry.setOutcome(outcome); + + // store the new entry in the DB + em.getTransaction().begin(); + em.persist(newEntry); + em.getTransaction().commit(); + + rval = true; + } finally { + // free EntityManager + if (em != null) { + em.close(); + } + } + return rval; + } + + /** + * This is an asynchronous (non-blocking) method, which creates a database + * entity for an in-progress request. + * + * @param starttime this is used as the 'starttime' timestamp in the record + * @param endtime this is used as the 'endtime' timestamp in the record + * @param closedLoopControlName uniquely identifies the Drools rules + * @param actor the processor being acted upon (e.g. "APPC") + * @param recipe otherwise known as "operation" (e.g. "Restart") + * @param target a further qualifier on 'actor'? (e.g. "VM") + * @param requestId the UUID string identifying the overall request + * @param subRequestId further qualifier on 'requestId' + * @param message indicates success status, or reason for failure + * @param outcome 'PolicyResult' enumeration string + */ + public void asyncCreateDbEntry( + final Instant starttime, final Instant endtime, + final String closedLoopControlName, + final String actor, final String recipe, final String target, + final String requestId, final String subRequestId, + final String message, final String outcome) { + if (disabled) { + if (errorMessage != null) { + logger.error("Database update skipped: " + errorMessage); + } + return; + } + + PolicyEngineConstants.getManager().getExecutorService().execute(() -> { + try { + // using a separate thread, call the synchronous 'createDbEntry' + // method + createDbEntry(starttime, endtime, closedLoopControlName, + actor, recipe, target, requestId, subRequestId, + message, outcome); + } catch (Exception e) { + logger.error("GuardContext.asyncCreateDbEntry", e); + } + }); + } + + /** + * This method is used as part of serialization -- 'namedSerializable' + * is serialized instead of 'this'. + * + * @return the object to be serialized + */ + private Object writeReplace() throws ObjectStreamException { + return namedSerializable; + } +} diff --git a/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java b/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java new file mode 100644 index 000000000..1a61d9019 --- /dev/null +++ b/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java @@ -0,0 +1,139 @@ +/*- + * ============LICENSE_START======================================================= + * guard + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.guard; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.isNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.time.Instant; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.drools.core.WorkingMemory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.onap.policy.drools.system.PolicyEngineConstants; + +public class GuardContextTest { + + private static Properties prop; + private static GuardContext guardContext; + private static WorkingMemory workingMemory; + private static LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + + /** + * Class-level initialization. + */ + @BeforeClass + public static void setup() throws IOException { + PolicyEngineConstants.getManager().configure(new Properties()); + PolicyEngineConstants.getManager().start(); + + prop = new Properties(); + prop.setProperty("guard.pdp.rest.url", "http://www.google.com/"); + prop.setProperty("guard.pdp.rest.client.user", "testuser"); + prop.setProperty("guard.pdp.rest.client.password", "testpassword"); + prop.setProperty("guard.pdp.rest.timeout", "1000"); + prop.setProperty("guard.pdp.rest.environment", "dev"); + + workingMemory = mock(WorkingMemory.class); + when(workingMemory.insert(isNotNull())).thenAnswer( + invocation -> { + queue.add(invocation.getArgument(0)); + return null; + }); + } + + @AfterClass + public static void stop() { + PolicyEngineConstants.getManager().stop(); + } + + @Test + public void guardDbResponseTest() throws InterruptedException { + Properties props = new Properties(prop); + props.setProperty("guard.disabled", "false"); + props.setProperty("guard.javax.persistence.jdbc.user", "user"); + props.setProperty("guard.javax.persistence.jdbc.password", "secret"); + props.setProperty("guard.javax.persistence.jdbc.driver", "org.h2.Driver"); + props.setProperty("guard.javax.persistence.jdbc.url", "jdbc:h2:file:./H2DB"); + + guardContext = new GuardContext(props); + assertNotNull(guardContext); + + guardContext.asyncCreateDbEntry(Instant.now().minusSeconds(1), Instant.now(), + "testCLName", "testActor", "testRecipe", "testTarget", + UUID.randomUUID().toString(), "1", "testMessage", "testOutcome"); + + queue.clear(); + guardContext.asyncQuery(workingMemory, "testActor", "testRecipe", + "testTarget", UUID.randomUUID().toString(), "testCLName"); + Object response = queue.poll(10, TimeUnit.SECONDS); + assertNotNull(response); + } + + @Test + public void badValuesTest() throws InterruptedException { + Properties props = new Properties(prop); + props.setProperty("guard.disabled", "true"); + props.setProperty("guard.pdp.rest.client.user", ""); + props.setProperty("guard.pdp.rest.client.password", ""); + props.setProperty("guard.pdp.rest.url", "bad,testuser,testpassword"); + + guardContext = new GuardContext(props); + + guardContext.asyncCreateDbEntry(Instant.now().minusSeconds(1), Instant.now(), + "testCLName", "testActor", "testRecipe", "testTarget", + UUID.randomUUID().toString(), "1", "testMessage", "testOutcome"); + + queue.clear(); + guardContext.asyncQuery(workingMemory, "testActor", "testRecipe", + "testTarget", UUID.randomUUID().toString()); + Object response = queue.poll(10, TimeUnit.SECONDS); + assertNotNull(response); + } + + @Test + public void policyGuardResponseTest() { + UUID requestId = UUID.randomUUID(); + PolicyGuardResponse emptyResponse1 = new PolicyGuardResponse(null, null, null); + + assertNotNull(emptyResponse1); + + PolicyGuardResponse response = new PolicyGuardResponse("Some Result", requestId, "Some Details"); + + response.setRequestId(requestId); + assertEquals(requestId, response.getRequestId()); + + response.setResult("Some Result"); + assertEquals("Some Result", response.getResult()); + + assertEquals("PolicyGuardResponse [requestId=", response.toString().substring(0, 31)); + } +} -- cgit 1.2.3-korg