modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/AuditTask.java | 120 ++++++++++ modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml | 4 modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java | 45 +++ modules/common/ant-bundle/src/test/resources/test-audit-input.properties | 1 modules/common/ant-bundle/src/test/resources/test-audit.properties | 3 modules/common/ant-bundle/src/test/resources/test-bundle-audit.xml | 39 +++ modules/core/domain/src/main/java/org/rhq/core/domain/bundle/BundleResourceDeploymentHistory.java | 3 modules/plugins/ant-bundle/src/main/java/org/rhq/plugins/ant/DeploymentAuditorBuildListener.java | 37 +++ 8 files changed, 249 insertions(+), 3 deletions(-)
New commits: commit 65c741a60042218f7ab290b4795b563c9be2c77e Author: John Mazzitelli mazz@redhat.com Date: Thu Dec 23 06:03:04 2010 -0500
BZ 649318 add new rhq:audit task that lets the recipe author be able to send up custom audit trail entries
diff --git a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/AuditTask.java b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/AuditTask.java new file mode 100644 index 0000000..af0e60b --- /dev/null +++ b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/AuditTask.java @@ -0,0 +1,120 @@ +/* + * RHQ Management Platform + * Copyright (C) 2010 Red Hat, Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package org.rhq.bundle.ant.task; + +import java.util.Date; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * The rhq:audit task is a way recipe authors can add their own audit messages to the stream + * of audit messages that the server gets to see how the progress went with the provisioning of a bundle. + * + * <rhq:audit status='SUCCESS|WARN|FAILURE' action="My Step" info="summary info" message="intermediate details"> + * long details here + * </rhq:audit> + * + * @author John Mazzitelli + */ +public class AuditTask extends AbstractBundleTask { + private String status = "SUCCESS"; // must match one of SUCCESS, WARN, or FAILURE (see BundleResourceDeploymentHistory.Status) + private String action = null; + private String info = null; + private String message = ""; + private String details = ""; + + @Override + public void maybeConfigure() throws BuildException { + super.maybeConfigure(); // inits the attribute fields + validateAttributes(); + } + + @Override + public void execute() throws BuildException { + // this will log a message with a very specific format that is understood + // by the agent-side build listener's messageLogged method: + // org.rhq.plugins.ant.DeploymentAuditorBuildListener.messageLogged(BuildEvent) + // RHQ_AUDIT_MESSAGE___<status>___<action>___<info>___<message>___<details> + StringBuilder str = new StringBuilder("RHQ_AUDIT_MESSAGE___"); + str.append(this.status); + str.append("___"); + str.append((this.action != null) ? this.action : "Audit Message"); + str.append("___"); + str.append((this.info != null) ? this.info : "Timestamp: " + new Date().toString()); + str.append("___"); + str.append(this.message); + str.append("___"); + str.append(this.details); + getProject().log(str.toString(), Project.MSG_INFO); + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = (message != null) ? message : ""; + } + + /** + * Set a multiline message. + * @param msg the CDATA text to append to the message text + */ + public void addText(String msg) { + if (msg != null) { + this.details += getProject().replaceProperties(msg); + } + } + + protected void validateAttributes() throws BuildException { + if (this.status == null) { + this.status = "SUCCESS"; + } else if (!this.status.equalsIgnoreCase("SUCCESS") && !this.status.equalsIgnoreCase("FAILURE") + && !this.status.equalsIgnoreCase("WARN")) { + throw new BuildException("The 'result' attribute must be either 'SUCCESS', 'WARN' or 'FAILURE'"); + } + this.status = this.status.toUpperCase(); + } + +} \ No newline at end of file diff --git a/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml b/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml index fee46ef..010077e 100644 --- a/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml +++ b/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml @@ -17,5 +17,7 @@ <typedef name="ignore" classname="org.rhq.bundle.ant.type.IgnoreType"/> <typedef name="fileset" classname="org.rhq.bundle.ant.type.FileSet"/> <typedef name="system-service" classname="org.rhq.bundle.ant.type.SystemServiceType"/> - + + <!-- audit task used to send custom audit messages to server --> + <taskdef name="audit" classname="org.rhq.bundle.ant.task.AuditTask"/> </antlib> diff --git a/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java b/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java index 2c41874..772c3e5 100644 --- a/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java +++ b/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java @@ -140,6 +140,7 @@ public class AntLauncherTest { assert propDef.getDescription().equals("This is where the product will listen for incoming messages"); assert propDef.isRequired();
+ // make sure our test infrastruction setup the input properties correctly Configuration config = project.getConfiguration(); assert config.getProperties().size() == 1 : config.getProperties(); assert "10000".equals(config.getSimpleValue("listener.port", null)) : config.getProperties(); @@ -193,6 +194,7 @@ public class AntLauncherTest { assert propDef.getDescription().equals("This is where the product will listen for incoming messages"); assert propDef.isRequired();
+ // make sure our test infrastruction setup the input properties correctly Configuration config = project.getConfiguration(); assert config.getProperties().size() == 1; assert "20000".equals(config.getSimpleValue("listener.port", null)) : config.getProperties(); @@ -249,6 +251,7 @@ public class AntLauncherTest { assert propDef.getDescription().equals("This is where the product will listen for incoming messages"); assert propDef.isRequired();
+ // make sure our test infrastruction setup the input properties correctly Configuration config = project.getConfiguration(); assert config.getProperties().size() == 1; assert "20000".equals(config.getSimpleValue("listener.port", null)) : config.getProperties(); @@ -431,6 +434,48 @@ public class AntLauncherTest { } }
+ // this doesn't verify the audit messages getting emitted are correct + // but it does verify the audit tag getting processed correctly. + // you have to look at the test logs to see the audit messages + // TODO: write a ant build listener to listen for this messages, parse them and verify they are correct + // this test should then ask the listener at the end if everything was OK and assert false if not + public void testAuditMessages() throws Exception { + // We want to test a fresh install, so make sure the deploy dir doesn't pre-exist. + FileUtil.purge(DEPLOY_DIR, true); + + AntLauncher ant = new AntLauncher(); + Properties inputProps = createInputProperties("/test-audit-input.properties"); + List<BuildListener> buildListeners = createBuildListeners(); + + BundleAntProject project = ant.executeBundleDeployFile(getBuildXml("test-bundle-audit.xml"), inputProps, + buildListeners); + assert project != null; + Set<String> bundleFiles = project.getBundleFileNames(); + assert bundleFiles != null; + assert bundleFiles.size() == 1 : bundleFiles; + assert bundleFiles.contains("test-audit.properties") : bundleFiles; + + // sanity check - make sure our recipe defined this property + ConfigurationDefinition configDef = project.getConfigurationDefinition(); + assert configDef.getPropertyDefinitions().size() == 1 : configDef.getPropertyDefinitions(); + PropertyDefinitionSimple propDef = configDef.getPropertyDefinitionSimple("listener.port"); + assert propDef != null; + + // make sure our test infrastruction setup the input properties correctly + Configuration config = project.getConfiguration(); + assert config.getProperties().size() == 1 : config.getProperties(); + assert "777".equals(config.getSimpleValue("listener.port", null)) : config.getProperties(); + + String preinstallTargetExecuted = (String) project.getProperties().get("preinstallTargetExecuted"); + assert preinstallTargetExecuted.equals("1a"); + String postinstallTargetExecuted = (String) project.getProperties().get("postinstallTargetExecuted"); + assert postinstallTargetExecuted.equals("1b"); + + assert new File(DEPLOY_DIR, "test-audit.properties").exists() : "missing file"; + assert readPropsFile(new File(DEPLOY_DIR, "test-audit.properties")).getProperty("my.listener.port").equals( + "777"); + } + private List<BuildListener> createBuildListeners() { List<BuildListener> buildListeners = new ArrayList<BuildListener>(); DefaultLogger logger = new DefaultLogger(); diff --git a/modules/common/ant-bundle/src/test/resources/test-audit-input.properties b/modules/common/ant-bundle/src/test/resources/test-audit-input.properties new file mode 100644 index 0000000..aaa7499 --- /dev/null +++ b/modules/common/ant-bundle/src/test/resources/test-audit-input.properties @@ -0,0 +1 @@ +listener.port=777 diff --git a/modules/common/ant-bundle/src/test/resources/test-audit.properties b/modules/common/ant-bundle/src/test/resources/test-audit.properties new file mode 100644 index 0000000..4dc4374 --- /dev/null +++ b/modules/common/ant-bundle/src/test/resources/test-audit.properties @@ -0,0 +1,3 @@ +my.listener.port=@@listener.port@@ +one.property=wot +two.property=gorilla? diff --git a/modules/common/ant-bundle/src/test/resources/test-bundle-audit.xml b/modules/common/ant-bundle/src/test/resources/test-bundle-audit.xml new file mode 100644 index 0000000..687c4ba --- /dev/null +++ b/modules/common/ant-bundle/src/test/resources/test-bundle-audit.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> + +<project name="test-bundle" default="main" xmlns:rhq="antlib:org.rhq.bundle"> + + <rhq:bundle name="audit-test" version="1"> + + <rhq:input-property name="listener.port" type="integer"/> + + <rhq:deployment-unit name="test" preinstallTarget="preinstall" postinstallTarget="postinstall"> + <rhq:file name="test-audit.properties" destinationFile="test-audit.properties" replace="true"/> + </rhq:deployment-unit> + + </rhq:bundle> + + <target name="main"/> + + <target name="preinstall"> + <property name="preinstallTargetExecuted" value="1a"/> + <rhq:audit status="success" action="action1" info="info1" message="message1"> + preinstall successful message with port "${listener.port}" ! + </rhq:audit> + <rhq:audit status="failure" action="action2" info="info2" message="message2"> + preinstall failure message here! + </rhq:audit> + <rhq:audit status="warn" action="action3" info="info3" message="message3"> + preinstall warn message here! + </rhq:audit> + </target> + + <target name="postinstall"> + <property name="postinstallTargetExecuted" value="1b"/> + <rhq:audit /> + <rhq:audit status="SUCCESS" /> + <rhq:audit status="WARN" action="actionA" /> + <rhq:audit status="FAILURE" action="actionB" info="infoB" /> + <rhq:audit status="SUCCESS" action="actionC" info="infoC" message="messageC" /> + </target> + +</project> \ No newline at end of file diff --git a/modules/core/domain/src/main/java/org/rhq/core/domain/bundle/BundleResourceDeploymentHistory.java b/modules/core/domain/src/main/java/org/rhq/core/domain/bundle/BundleResourceDeploymentHistory.java index eb00b26..b7f28fc 100644 --- a/modules/core/domain/src/main/java/org/rhq/core/domain/bundle/BundleResourceDeploymentHistory.java +++ b/modules/core/domain/src/main/java/org/rhq/core/domain/bundle/BundleResourceDeploymentHistory.java @@ -228,7 +228,8 @@ public class BundleResourceDeploymentHistory implements Serializable { FILE_ADD("File Add"), // FILE_CHANGE("File Change"), // FILE_DOWNLOAD("File Download"), // - FILE_REMOVE("File Remove"); + FILE_REMOVE("File Remove"), // + AUDIT_MESSAGE("Audit Message");
private String displayName;
diff --git a/modules/plugins/ant-bundle/src/main/java/org/rhq/plugins/ant/DeploymentAuditorBuildListener.java b/modules/plugins/ant-bundle/src/main/java/org/rhq/plugins/ant/DeploymentAuditorBuildListener.java index 7f41de8..b8476b9 100644 --- a/modules/plugins/ant-bundle/src/main/java/org/rhq/plugins/ant/DeploymentAuditorBuildListener.java +++ b/modules/plugins/ant-bundle/src/main/java/org/rhq/plugins/ant/DeploymentAuditorBuildListener.java @@ -22,12 +22,16 @@ */ package org.rhq.plugins.ant;
+import java.util.Date; + import org.apache.tools.ant.BuildEvent; import org.apache.tools.ant.BuildListener; import org.apache.tools.ant.util.StringUtils;
import org.rhq.core.domain.bundle.BundleResourceDeployment; import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory; +import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory.Category; +import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory.Status; import org.rhq.core.pluginapi.bundle.BundleManagerProvider;
/** @@ -71,6 +75,38 @@ public class DeploymentAuditorBuildListener implements BuildListener { }
public void messageLogged(BuildEvent event) { + // this will see if this is an audit message (e.g. rhq:audit) and if so, send it up to the server + // see org.rhq.bundle.ant.task.AuditTask.execute() + // RHQ_AUDIT_MESSAGE___<status>___<action>___<info>___<message>___<details> + + try { + Status status = Status.SUCCESS; + String action = "Audit Message"; + String info = "Recipe Audit Message"; + String message = new Date().toString(); + String details = null; + BundleResourceDeployment deployment = this.bundleResourceDeployment; + Category category = Category.AUDIT_MESSAGE; + + String[] eventStrings = event.getMessage().split("___"); + int index = 0; + if (eventStrings[index++].equals("RHQ_AUDIT_MESSAGE")) { + try { + String statusStr = eventStrings[index++]; + status = Status.valueOf(statusStr.toUpperCase()); + action = eventStrings[index++]; + info = eventStrings[index++]; + message = eventStrings[index++]; + details = eventStrings[index++]; + } catch (ArrayIndexOutOfBoundsException e) { + // the message didn't have all the info, just skip looking for the rest and log what we have + } + } + + this.bundleManagerProvider.auditDeployment(deployment, action, info, category, status, message, details); + } catch (Exception e) { + throw new RuntimeException(e); + } return; }
@@ -101,7 +137,6 @@ public class DeploymentAuditorBuildListener implements BuildListener { return msg.toString(); }
- @SuppressWarnings( { "ThrowableResultOfMethodCallIgnored" }) private static String createMessage(String action, BuildEvent event) { StringBuilder msg = new StringBuilder(action);