diff --git a/README.md b/README.md index 7ef5b0aa..4e5e5309 100644 --- a/README.md +++ b/README.md @@ -8,70 +8,147 @@ There is a [Jenkins-CI Wiki page](https://wiki.jenkins-ci.org/display/JENKINS/Da ## Features Currently, the plugin is tracking the following data. -List of events: -* Started build -* Finished build -* SCM Checkout - - -| Metric Name | Description | -|--------------------------|---------------------------------------------------------------| -| jenkins.executor.count | Executor count | -| jenkins.executor.free | Number of unused executor | -| jenkins.executor.in_use | Number of idle executor | -| jenkins.job.completed | Rate of completed jobs | -| jenkins.job.cycletime | Build Cycle Time | -| jenkins.job.duration | Build duration (in seconds) | -| jenkins.job.feedbacktime | Feedback time from code commit to job failure | -| jenkins.job.leadtime | Build Lead Time | -| jenkins.job.mtbf | MTBF, time between last successful job and current failed job | -| jenkins.job.mttr | MTTR: time between last failed job and current successful job | -| jenkins.job.started | Rate of started jobs | -| jenkins.job.waiting | Time spent waiting for job to run (in milliseconds) | -| jenkins.node.count | Total number of node | -| jenkins.node.offline | Offline nodes count | -| jenkins.node.online | Online nodes count | -| jenkins.plugin.count | Plugins count | -| jenkins.project.count | Project count | -| jenkins.queue.size | Queue Size | -| jenkins.queue.buildable | Number of Buildable item in Queue | -| jenkins.queue.pending | Number of Pending item in Queue | -| jenkins.queue.stuck | Number of Stuck item in Queue | -| jenkins.queue.blocked | Number of Blocked item in Queue | -| jenkins.scm.checkout | Rate of SCM checkouts | - - -List of service checks: -* Build status (jenkins.job.status) - -All events, metrics, and service checks include the following tags, if they are available: -* `job` -* `result` -* (Git Branch, SVN revision or CVS branch) `branch` - * Git Branch available when using the [Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin) -* `node` - -`jenkins.executor.*` metrics have the following additional tags: -* `node_hostname` -* `node_name` -* `node_label` - +### Events +#### Default Events Type +* Build Started + - Triggered on `RunListener#onStarted` + - Default tags: `job`, `node`, `branch` + - Associated rate metric: `jenkins.job.started` +* Build Aborted + - Triggered on `RunListener#onDeleted` + - Default tags: `job`, `node`, `branch` + - Associated rate metric: `jenkins.job.aborted` +* Build Completed + - Triggered on `RunListener#onCompleted` + - Default tags: `job`, `node`, `branch`, `result` (Git Branch, SVN revision or CVS branch) + - Associated rate metric: `jenkins.job.completed` + +#### Source Control Management Events Type +* SCM Checkout + - Triggered on `SCMListener#onCheckout` + - Default tags: `job`, `node`, `branch` + - Associated rate metric: `jenkins.scm.checkout` + +#### Systems Events Type +* Computer Online + - Triggered on `ComputerListener#onOnline` + - Associated rate metric: `jenkins.computer.online` +* Computer Offline + - Triggered on `ComputerListener#onOffline` + - Associated rate metric: `jenkins.computer.online` +* Computer TemporarilyOnline + - Triggered on `ComputerListener#onTemporarilyOnline` + - Associated rate metric: `jenkins.computer.temporarily_online` +* Computer TemporarilyOffline + - Triggered on `ComputerListener#onTemporarilyOffline` + - Associated rate metric: `jenkins.computer.temporarily_offline` +* Computer LaunchFailure + - Triggered on `ComputerListener#onLaunchFailure` + - Associated rate metric: `jenkins.computer.launch_failure` +* Item Created + - Triggered on `ItemListener#onCreated` + - Associated rate metric: `jenkins.item.created` +* Item Deleted + - Triggered on `ItemListener#onDeleted` + - Associated rate metric: `jenkins.item.deleted` +* Item Updated + - Triggered on `ItemListener#onUpdated` + - Default tags: + - Associated rate metric: `jenkins.item.updated` +* Item Copied + - Triggered on `ItemListener#onCopied` + - Associated rate metric: `jenkins.item.copied` +* ItemListener LocationChanged + - Triggered on `ItemListener#onLocationChanged` + - Associated rate metric: `jenkins.item.location_changed` +* Config Changed + - Triggered on `SaveableListener#onChange` + - Associated rate metric: `jenkins.config.changed` + +#### Security Events Type +* User Authenticated + - Triggered on `SecurityListener#authenticated` + - Default tags: + - Associated rate metric: `jenkins.user.authenticated` +* User failed To Authenticate + - Triggered on `SecurityListener#failedToAuthenticate` + - Associated rate metric: `jenkins.user.access_denied` +* User loggedOut + - Triggered on `SecurityListener#loggedOut` + - Associated rate metric: `jenkins.user.logout` + +### Metrics + +| Metric Name | Description | Default Tags | +|----------------------------------------|----------------------------------------------------------------|--------------------------------------------| +| `jenkins.computer.launch_failure` | Rate of computer launch failures. | | +| `jenkins.computer.offline` | Rate of computer going offline. | | +| `jenkins.computer.online` | Rate of computer going online. | | +| `jenkins.computer.temporarily_offline` | Rate of computer going temporarily offline. | | +| `jenkins.computer.temporarily_online` | Rate of computer going temporarily online. | | +| `jenkins.config.changed` | Rate of configs being changed. | | +| `jenkins.executor.count` | Executor count. | `node_hostname`, `node_name`, `node_label` | +| `jenkins.executor.free` | Number of unused executor. | `node_hostname`, `node_name`, `node_label` | +| `jenkins.executor.in_use` | Number of idle executor. | `node_hostname`, `node_name`, `node_label` | +| `jenkins.item.copied` | Rate of items being copied. | | +| `jenkins.item.created` | Rate of items being created. | | +| `jenkins.item.deleted` | Rate of items being deleted. | | +| `jenkins.item.location_changed` | Rate of items being moved. | | +| `jenkins.item.updated` | Rate of items being updated. | | +| `jenkins.job.aborted` | Rate of aborted jobs. | `branch`, `job`, `node` | +| `jenkins.job.completed` | Rate of completed jobs. | `branch`, `job`, `node`, `result` | +| `jenkins.job.cycletime` | Build Cycle Time. | `branch`, `job`, `node`, `result` | +| `jenkins.job.duration` | Build duration (in seconds). | `branch`, `job`, `node`, `result` | +| `jenkins.job.feedbacktime` | Feedback time from code commit to job failure. | `branch`, `job`, `node`, `result` | +| `jenkins.job.leadtime` | Build Lead Time. | `branch`, `job`, `node`, `result` | +| `jenkins.job.mtbf` | MTBF, time between last successful job and current failed job. | `branch`, `job`, `node`, `result` | +| `jenkins.job.mttr` | MTTR: time between last failed job and current successful job. | `branch`, `job`, `node`, `result` | +| `jenkins.job.started` | Rate of started jobs. | `branch`, `job`, `node` | +| `jenkins.job.waiting` | Time spent waiting for job to run (in milliseconds). | `branch`, `job`, `node` | +| `jenkins.node.count` | Total number of node. | | +| `jenkins.node.offline` | Offline nodes count. | | +| `jenkins.node.online` | Online nodes count. | | +| `jenkins.plugin.count` | Plugins count. | | +| `jenkins.project.count` | Project count. | | +| `jenkins.queue.size` | Queue Size. | | +| `jenkins.queue.buildable` | Number of Buildable item in Queue. | | +| `jenkins.queue.pending` | Number of Pending item in Queue. | | +| `jenkins.queue.stuck` | Number of Stuck item in Queue. | | +| `jenkins.queue.blocked` | Number of Blocked item in Queue. | | +| `jenkins.scm.checkout` | Rate of SCM checkouts. | `branch`, `job`, `node` | +| `jenkins.user.access_denied` | Rate of users failing to authenticate. | | +| `jenkins.user.authenticated` | Rate of users authenticating. | | +| `jenkins.user.logout` | Rate of users logging out. | | + + +### Service checks +* Build status `jenkins.job.status` + - Default tags: : `job`, `node`, `branch`, `result` (Git Branch, SVN revision or CVS branch) + - NOTE: Git Branch available when using the [Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin) ## Customization From the global configuration page, at `Manage Jenkins -> Configure System`. +* Global Tags + * A comma-separated list of tags to apply to all metrics, events, service checks. * Blacklisted Jobs * A comma-separated list of regex to match job names that should not be monitored. (eg: susans-job,johns-.*,prod_folder/prod_release). * Whitelisted Jobs * A comma-separated list of regex to match job names that should be monitored. (eg: susans-job,johns-.*,prod_folder/prod_release). -* Global Tags +* Global Job Tags * A regex to match a job, and a list of tags to apply to that job, all separated by a comma. * tags can reference match groups in the regex using the $ symbol * eg: `(.*?)_job_(*?)_release, owner:$1, release_env:$2, optional:Tag3` +* Send Security audit events + * Enabled by default, it submits `Security Events Type` of events and metrics. +* Send System events + * Enabled by default, it submits `System Events Type` of events and metrics From a job specific configuration page * Custom tags * From a file in the job workspace (not compatible with Pipeline jobs). * As text properties directly from the configuration page. +* Send Source Control Management events + * Enabled by default, it submits `Source Control Management Events Type` of events and metrics. ## Installation _This plugin requires [Jenkins 1.580.1](http://updates.jenkins-ci.org/download/war/1.580.1/jenkins.war) or newer._ diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java index 41ad98a8..e1c0e26b 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java @@ -1,11 +1,12 @@ package org.datadog.jenkins.plugins.datadog; import hudson.util.Secret; -import net.sf.json.JSONArray; import net.sf.json.JSONObject; import javax.servlet.ServletException; import java.io.IOException; +import java.util.Map; +import java.util.Set; public interface DatadogClient { @@ -34,7 +35,7 @@ public interface DatadogClient { * @param hostname - metric hostname * @param tags - metric tags */ - public void incrementCounter(String name, String hostname, JSONArray tags); + public void incrementCounter(String name, String hostname, Map> tags); /** * Submit all your counters as rate with 10 seconds intervals. @@ -47,10 +48,10 @@ public interface DatadogClient { * @param name - A String with the name of the metric to record. * @param value - A long containing the value to submit. * @param hostname - A String with the hostname to submit. - * @param tags - A Object containing the tags to submit. + * @param tags - A Map containing the tags to submit. * @return a boolean to signify the success or failure of the HTTP POST request. */ - public boolean gauge(String name, long value, String hostname, JSONArray tags); + public boolean gauge(String name, long value, String hostname, Map> tags); /** * Sends a service check to the Datadog API, including the check name, and status. @@ -58,10 +59,10 @@ public interface DatadogClient { * @param name - A String with the name of the service check to record. * @param code - An int with the status code to record for this service check. * @param hostname - A String with the hostname to submit. - * @param tags - A Object containing the tags to submit. + * @param tags - A Map containing the tags to submit. * @return a boolean to signify the success or failure of the HTTP POST request. */ - public boolean serviceCheck(String name, int code, String hostname, JSONArray tags); + public boolean serviceCheck(String name, int code, String hostname, Map> tags); /** * Tests the apiKey is valid. diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java index ea45cd19..67358f05 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java @@ -15,21 +15,26 @@ import javax.servlet.ServletException; import java.io.IOException; +import java.util.*; import java.util.logging.Logger; import static hudson.Util.fixEmptyAndTrim; @Extension public class DatadogGlobalConfiguration extends GlobalConfiguration { - private static final String DISPLAY_NAME = "Datadog Plugin"; + private static final Logger logger = Logger.getLogger(DatadogGlobalConfiguration.class.getName()); + private static final String DISPLAY_NAME = "Datadog Plugin"; private Secret apiKey = null; private String hostname = null; private String blacklist = null; private String whitelist = null; + private String globalTags = null; private String globalJobTags = null; private String targetMetricURL = "https://api.datadoghq.com/api/"; + private boolean emitSecurityEvents = true; + private boolean emitSystemEvents = true; @DataBoundConstructor public DatadogGlobalConfiguration() { @@ -146,8 +151,11 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th this.setHostname(formData.getString("hostname")); this.setBlacklist(formData.getString("blacklist")); this.setWhitelist(formData.getString("whitelist")); + this.setGlobalTags(formData.getString("globalTags")); this.setGlobalJobTags(formData.getString("globalJobTags")); this.setTargetMetricURL(formData.getString("targetMetricURL")); + this.setEmitSecurityEvents(formData.getBoolean("emitSecurityEvents")); + this.setEmitSystemEvents(formData.getBoolean("emitSystemEvents")); //When form is saved...reinitialize the DatadogClient. DatadogHttpClient.getInstance(this.getTargetMetricURL(), this.getApiKey()); @@ -242,6 +250,27 @@ public void setWhitelist(final String jobs) { this.whitelist = jobs; } + /** + * Getter function for the globalTags global configuration, containing + * a comma-separated list of tags that should be applied everywhere. + * + * @return a String array containing the globalTags global configuration + */ + public String getGlobalTags() { + return globalTags; + } + + /** + * Setter function for the globalTags global configuration, + * accepting a comma-separated string of tags. + * + * @param globalTags - a comma-separated list of tags. + */ + @DataBoundSetter + public void setGlobalTags(String globalTags) { + this.globalTags = globalTags; + } + /** * Getter function for the globalJobTags global configuration, containing * a comma-separated list of jobs and tags that should be applied to them @@ -278,4 +307,38 @@ public void setTargetMetricURL(String targetMetricURL) { this.targetMetricURL = targetMetricURL; } + /** + * @return - A {@link Boolean} indicating if the user has configured Datadog to emit Security related events. + */ + public boolean isEmitSecurityEvents() { + return emitSecurityEvents; + } + + /** + * Set the checkbox in the UI, used for Jenkins data binding + * + * @param emitSecurityEvents - The checkbox status (checked/unchecked) + */ + @DataBoundSetter + public void setEmitSecurityEvents(boolean emitSecurityEvents) { + this.emitSecurityEvents = emitSecurityEvents; + } + + /** + * @return - A {@link Boolean} indicating if the user has configured Datadog to emit System related events. + */ + public boolean isEmitSystemEvents() { + return emitSystemEvents; + } + + /** + * Set the checkbox in the UI, used for Jenkins data binding + * + * @param emitSystemEvents - The checkbox status (checked/unchecked) + */ + @DataBoundSetter + public void setEmitSystemEvents(boolean emitSystemEvents) { + this.emitSystemEvents = emitSystemEvents; + } + } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogJobProperty.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogJobProperty.java index 6b602907..8241fd5e 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogJobProperty.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogJobProperty.java @@ -3,15 +3,9 @@ import hudson.Extension; import hudson.FilePath; import hudson.model.*; -import net.sf.json.JSONObject; -import org.apache.commons.lang.StringUtils; -import org.datadog.jenkins.plugins.datadog.listeners.DatadogBuildListener; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.StaplerRequest; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; import java.io.IOException; import java.util.logging.Logger; @@ -19,28 +13,21 @@ * Create a job property for use with Datadog plugin. */ public class DatadogJobProperty> extends JobProperty { - private static final Logger LOGGER = Logger.getLogger(DatadogBuildListener.class.getName()); + + private static final Logger LOGGER = Logger.getLogger(DatadogJobProperty.class.getName()); private static final String DISPLAY_NAME = "Datadog Job Tagging"; - private String tagProperties = null; + private boolean enableFile = false; private String tagFile = null; - private boolean emitOnCheckout = false; - - /** - * @param r - Current build. - * @return - The configured {@link DatadogJobProperty}. Null if not there - */ - @CheckForNull - public static DatadogJobProperty retrieveProperty(Run r) { - return (DatadogJobProperty) r.getParent().getProperty(DatadogJobProperty.class); - } + private boolean enableProperty = false; + private String tagProperties = null; + private boolean emitSCMEvents = true; /** * Runs when the {@link DatadogJobProperty} class is created. */ @DataBoundConstructor - public DatadogJobProperty() { - } + public DatadogJobProperty() { } /** * Gets a list of tag properties to be submitted with the Build to Datadog. @@ -48,7 +35,7 @@ public DatadogJobProperty() { * @return a String representing a list of tag properties. */ public String getTagProperties() { - return tagProperties; + return isEnableProperty() ? tagProperties : null; } /** @@ -65,11 +52,11 @@ public void setTagProperties(final String tagProperties) { * @return a String representing the relative path to a tagFile */ public String getTagFile() { - return tagFile; + return isEnableFile() ? tagFile : null; } /** - * Sets the tagFile set in the job configration. + * Sets the tagFile set in the job configuration. * * @param tagFile - a String representing the relative path to a tagFile */ @@ -79,68 +66,58 @@ public void setTagFile(String tagFile) { } /** - * This method is called whenever the Job form is saved. We use the 'on' property - * to determine if the controls are selected. + * Gets the enableFile set in the job configuration. * - * @param req - The request - * @param form - A JSONObject containing the submitted form data from the job configuration - * @return a {@link JobProperty} object representing the tagging added to the job - * @throws hudson.model.Descriptor.FormException if querying of form throws an error + * @return a boolean representing the enableFile checkbox */ - @Override - public JobProperty reconfigure(StaplerRequest req, @Nonnull JSONObject form) - throws Descriptor.FormException { - - DatadogJobProperty prop = (DatadogJobProperty) super.reconfigure(req, form); - boolean isEnableFile = form.getBoolean("enableFile"); - boolean isEnableTagProperties = form.getBoolean("enableProperty"); - - if (!isEnableFile) { - prop.tagFile = null; - prop.emitOnCheckout = false; - } - if (!isEnableTagProperties) { - prop.tagProperties = null; - } - + public boolean isEnableFile() { + return enableFile; + } - return prop; + /** + * Sets the enableFile set in the job configuration. + * + * @param enableFile - a boolean representing the enableFile checkbox + */ + @DataBoundSetter + public void setEnableFile(boolean enableFile) { + this.enableFile = enableFile; } /** - * Checks if tagFile was set in the job configuration. + * Gets the enableProperty set in the job configuration. * - * @return a boolean representing the state of the tagFile job configuration + * @return a boolean representing the enableProperty checkbox */ - public boolean isTagFileEmpty() { - return StringUtils.isBlank(this.tagFile); + public boolean isEnableProperty() { + return enableProperty; } /** - * Checks if the contents of the properties in the job tagging configuration section is empty + * Sets the enableProperty set in the job configuration. * - * @return a boolean representing the state of the properties job configuration + * @param enableProperty - a boolean representing the enableProperty checkbox */ - public boolean isTagPropertiesEmpty() { - return StringUtils.isBlank(this.tagProperties); + @DataBoundSetter + public void setEnableProperty(boolean enableProperty) { + this.enableProperty = enableProperty; } /** - * @return - A {@link Boolean} indicating if the user has configured Datadog to emit the - * - an event after checkout. + * @return - A {@link Boolean} indicating if the user has configured Datadog to emit SCM related events. */ - public boolean isEmitOnCheckout() { - return emitOnCheckout; + public boolean isEmitSCMEvents() { + return emitSCMEvents; } /** - * Set the checkbox in the UI, used for Jenkins databbinding + * Set the checkbox in the UI, used for Jenkins data binding * - * @param emitOnCheckout - The checkbox status (checked/unchecked) + * @param emitSCMEvents - The checkbox status (checked/unchecked) */ @DataBoundSetter - public void setEmitOnCheckout(boolean emitOnCheckout) { - this.emitOnCheckout = emitOnCheckout; + public void setEmitSCMEvents(boolean emitSCMEvents) { + this.emitSCMEvents = emitSCMEvents; } /** @@ -156,7 +133,7 @@ public String readTagFile(Run r) { //We need to make sure that the workspace has been created. When 'onStarted' is //invoked, the workspace has not yet been established, so this check is necessary. FilePath workspace = r.getExecutor().getCurrentWorkspace(); - if (workspace != null) { + if (workspace != null && getTagFile() != null) { FilePath path = new FilePath(workspace, getTagFile()); if (path.exists()) { s = path.readToString(); @@ -169,7 +146,7 @@ public String readTagFile(Run r) { } @Extension - public static final class DatadogJobPropertyDescriptorImpl extends JobPropertyDescriptor { + public static final class DatadogJobPropertyDescriptor extends JobPropertyDescriptor { /** * Getter function for a human readable class display name. diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogUtilities.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogUtilities.java index bd129030..71b88dc6 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogUtilities.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogUtilities.java @@ -2,11 +2,13 @@ import hudson.EnvVars; import hudson.ExtensionList; -import hudson.model.Run; -import hudson.model.TaskListener; +import hudson.model.*; +import hudson.model.labels.LabelAtom; import jenkins.model.Jenkins; import org.datadog.jenkins.plugins.datadog.clients.DatadogHttpClient; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -35,6 +37,14 @@ public static DatadogGlobalConfiguration getDatadogGlobalDescriptor() { return ExtensionList.lookup(DatadogGlobalConfiguration.class).get(DatadogGlobalConfiguration.class); } + /** + * @param r - Current build. + * @return - The configured {@link DatadogJobProperty}. Null if not there + */ + public static DatadogJobProperty retrieveProperty(@Nonnull Run r) { + return (DatadogJobProperty) r.getParent().getProperty(DatadogJobProperty.class); + } + /** * @return - The descriptor for the Datadog plugin. In this case the global configuration. */ @@ -50,29 +60,25 @@ public static DatadogClient getDatadogClient() { * @param listener - Current listener * @return A {@link HashMap} containing the key,value pairs of tags if any. */ - public static Map> buildExtraTags(Run run, TaskListener listener) { + public static Map> getBuildTags(Run run, @Nonnull TaskListener listener) { Map> result = new HashMap<>(); String jobName = run.getParent().getFullName(); final String globalJobTags = getDatadogGlobalDescriptor().getGlobalJobTags(); - final DatadogJobProperty property = DatadogJobProperty.retrieveProperty(run); + final DatadogJobProperty property = DatadogUtilities.retrieveProperty(run); final String workspaceTagFile = property.readTagFile(run); try { final EnvVars envVars = run.getEnvironment(listener); - if (!property.isTagFileEmpty()) { - if (workspaceTagFile != null) { - result = merge(result, computeTagListFromVarList(envVars, workspaceTagFile)); - } + if (workspaceTagFile != null) { + result = TagsUtil.merge(result, computeTagListFromVarList(envVars, workspaceTagFile)); } String prop = property.getTagProperties(); - if (!property.isTagPropertiesEmpty()) { - result = merge(result, computeTagListFromVarList(envVars, prop)); - } + result = TagsUtil.merge(result, computeTagListFromVarList(envVars, prop)); } catch (IOException | InterruptedException ex) { logger.severe(ex.getMessage()); } - result = merge(result, getTagsFromGlobalJobTags(jobName, globalJobTags)); + result = TagsUtil.merge(result, getTagsFromGlobalJobTags(jobName, globalJobTags)); return result; } @@ -119,20 +125,54 @@ private static Map> getTagsFromGlobalJobTags(String jobName, Matcher jobNameMatcher = jobNamePattern.matcher(jobName); if (jobNameMatcher.matches()) { for (int i = 1; i < jobInfo.size(); i++) { - String[] tagItem = jobInfo.get(i).split(":"); - String tagName = tagItem[0]; - String tagValue = tagItem[1]; - // Fills regex group values from the regex job name to tag values - // eg: (.*?)-job, owner:$1 - if (Character.toString(tagValue.charAt(0)).equals("$")) { - try { - tagValue = jobNameMatcher.group(Character.getNumericValue(tagValue.charAt(1))); - } catch (IndexOutOfBoundsException e) { - logger.fine(String.format( - "Specified a capture group that doesn't exist, not applying tag: %s Exception: %s", - Arrays.toString(tagItem), e)); + String[] tagItem = jobInfo.get(i).replaceAll(" ", "").split(":"); + if (tagItem.length == 2) { + String tagName = tagItem[0]; + String tagValue = tagItem[1]; + // Fills regex group values from the regex job name to tag values + // eg: (.*?)-job, owner:$1 + if (Character.toString(tagValue.charAt(0)).equals("$")) { + try { + tagValue = jobNameMatcher.group(Character.getNumericValue(tagValue.charAt(1))); + } catch (IndexOutOfBoundsException e) { + logger.fine(String.format( + "Specified a capture group that doesn't exist, not applying tag: %s Exception: %s", + Arrays.toString(tagItem), e)); + } } + Set tagValues = tags.containsKey(tagName) ? tags.get(tagName) : new HashSet(); + tagValues.add(tagValue.toLowerCase()); + tags.put(tagName, tagValues); } + } + } + } + + return tags; + } + + /** + * Getter function for the globalTags global configuration, containing + * a comma-separated list of tags that should be applied everywhere. + * + * @return a map containing the globalTags global configuration. + */ + public static Map> getTagsFromGlobalTags() { + final String globalTags = getDatadogGlobalDescriptor().getGlobalTags(); + Map> tags = new HashMap<>(); + List globalTagsLines = DatadogUtilities.linesToList(globalTags); + + for (String globalTagsLine : globalTagsLines) { + List tagList = DatadogUtilities.cstrToList(globalTagsLine); + if (tagList.isEmpty()) { + continue; + } + + for (int i = 0; i < tagList.size(); i++) { + String[] tagItem = tagList.get(i).replaceAll(" ", "").split(":"); + if(tagItem.length == 2) { + String tagName = tagItem[0]; + String tagValue = tagItem[1]; Set tagValues = tags.containsKey(tagName) ? tags.get(tagName) : new HashSet(); tagValues.add(tagValue.toLowerCase()); tags.put(tagName, tagValues); @@ -189,7 +229,7 @@ private static boolean isJobWhitelisted(final String jobName) { * @return a String List with all items transform with trim and lower case */ public static List cstrToList(final String str) { - return convertRegexStringtoList(str, ","); + return convertRegexStringToList(str, ","); } /** @@ -199,7 +239,7 @@ public static List cstrToList(final String str) { * @return a String List with all items */ public static List linesToList(final String str) { - return convertRegexStringtoList(str, "\\r?\\n"); + return convertRegexStringToList(str, "\\r?\\n"); } /** @@ -209,7 +249,7 @@ public static List linesToList(final String str) { * @param regex - Regex to use to split the string list * @return a String List with all items */ - private static List convertRegexStringtoList(final String str, String regex) { + private static List convertRegexStringToList(final String str, String regex) { List result = new ArrayList<>(); if (str != null && str.length() != 0) { for (String item : str.trim().split(regex)) { @@ -221,40 +261,27 @@ private static List convertRegexStringtoList(final String str, String re return result; } - public static Map> merge(Map> dest, Map> orig) { - if (dest == null) { - dest = new HashMap<>(); - } - if (orig == null) { - orig = new HashMap<>(); - } - for (String o: orig.keySet()){ - Set values = dest.containsKey(o) ? dest.get(o) : new HashSet(); - if (values == null) { - values = new HashSet<>(); - } - if (orig.get(o) != null) { - values.addAll(orig.get(o)); - } - dest.put(o, values); - } - return dest; - } - public static Map> computeTagListFromVarList(EnvVars envVars, final String varList) { HashMap> result = new HashMap<>(); List rawTagList = linesToList(varList); - for (String tag : rawTagList) { - String[] expanded = envVars.expand(tag).split("="); - if (expanded.length > 1) { - String name = expanded[0]; - String value = expanded[1]; - Set values = result.containsKey(name) ? result.get(name) : new HashSet(); - values.add(value); - result.put(name, values); - logger.fine(String.format("Emitted tag %s:%s", expanded[0], expanded[1])); - } else { - logger.fine(String.format("Ignoring the tag %s. It is empty.", tag)); + for (String tagLine : rawTagList) { + List tagList = DatadogUtilities.cstrToList(tagLine); + if (tagList.isEmpty()) { + continue; + } + for (int i = 0; i < tagList.size(); i++) { + String tag = tagList.get(i).replaceAll(" ", ""); + String[]expanded = envVars.expand(tag).split("="); + if (expanded.length > 1) { + String name = expanded[0]; + String value = expanded[1]; + Set values = result.containsKey(name) ? result.get(name) : new HashSet(); + values.add(value); + result.put(name, values); + logger.fine(String.format("Emitted tag %s:%s", expanded[0], expanded[1])); + } else { + logger.fine(String.format("Ignoring the tag %s. It is empty.", tag)); + } } } return result; @@ -390,4 +417,71 @@ public static Boolean isValidHostname(String hostname) { return m.find(); } + public static Map> getComputerTags(Computer computer) { + Set labels = null; + try { + labels = computer.getNode().getAssignedLabels(); + } catch (NullPointerException e){ + logger.fine("Could not retrieve labels"); + } + String nodeHostname = null; + try { + nodeHostname = computer.getHostName(); + } catch (IOException | InterruptedException e) { + logger.fine("Could not retrieve hostname"); + } + String nodeName = getNodeName(computer); + Map> result = new HashMap<>(); + Set nodeNameValues = new HashSet<>(); + nodeNameValues.add(nodeName); + result.put("node_name", nodeNameValues); + if(nodeHostname != null){ + Set nodeHostnameValues = new HashSet<>(); + nodeHostnameValues.add(nodeHostname); + result.put("node_hostname", nodeHostnameValues); + } + if(labels != null){ + Set nodeLabelsValues = new HashSet<>(); + for (LabelAtom label: labels){ + nodeLabelsValues.add(label.getName()); + } + result.put("node_label", nodeLabelsValues); + } + + return result; + } + + public static String getNodeName(Computer computer){ + if (computer instanceof Jenkins.MasterComputer) { + return "master"; + } else { + return computer.getName(); + } + } + + public static String getUserId() { + User user = User.current(); + if (user == null) { + return "anonymous"; + } else { + return user.getId(); + } + } + + public static String getItemName(Item item) { + if (item == null) { + return "unknown"; + } + return item.getName(); + } + + public static Long getRunStartTimeInMillis(Run run) { + // getStartTimeInMillis wrapper in order to mock it in unit tests + return run.getStartTimeInMillis(); + } + + public static long currentTimeMillis(){ + // This method exist so we can mock System.currentTimeMillis in unit tests + return System.currentTimeMillis(); + } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java index 00bee332..8365927a 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ConcurrentMetricCounters.java @@ -1,7 +1,7 @@ package org.datadog.jenkins.plugins.datadog.clients; -import net.sf.json.JSONArray; - +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Logger; @@ -42,7 +42,7 @@ private ConcurrentMap getCounters(){ return counters; } - public synchronized void increment(String name, String hostname, JSONArray tags) { + public synchronized void increment(String name, String hostname, Map> tags) { ConcurrentMap counters = ConcurrentMetricCounters.get(); CounterMetric counterMetric = new CounterMetric(tags, name, hostname); Integer previousValue = counters.putIfAbsent(counterMetric, 1); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java index 21465335..6d638e8f 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/CounterMetric.java @@ -1,23 +1,25 @@ package org.datadog.jenkins.plugins.datadog.clients; -import net.sf.json.JSONArray; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; public class CounterMetric { - private JSONArray tags = new JSONArray(); + private Map> tags = new HashMap<>(); private String metricName; private String hostname; - public CounterMetric(JSONArray tags, String metricName, String hostname) { + public CounterMetric(Map> tags, String metricName, String hostname) { this.tags = tags; this.metricName = metricName; this.hostname = hostname; } - public JSONArray getTags() { + public Map> getTags() { return tags; } - public void setTags(JSONArray tags) { + public void setTags(Map> tags) { this.tags = tags; } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogHttpClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogHttpClient.java index fcfde5e1..317d2aa6 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogHttpClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogHttpClient.java @@ -7,6 +7,8 @@ import net.sf.json.JSONObject; import net.sf.json.JSONSerializer; import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; import javax.servlet.ServletException; import java.io.BufferedReader; @@ -16,6 +18,8 @@ import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.logging.Logger; @@ -110,7 +114,7 @@ public boolean sendEvent(JSONObject payload) { } @Override - public void incrementCounter(String name, String hostname, JSONArray tags) { + public void incrementCounter(String name, String hostname, Map> tags) { ConcurrentMetricCounters.getInstance().increment(name, hostname, tags); } @@ -131,11 +135,11 @@ public void flushCounters() { } @Override - public boolean gauge(String name, long value, String hostname, JSONArray tags) { + public boolean gauge(String name, long value, String hostname, Map> tags) { return postMetric(name, value, hostname, tags, "gauge"); } - private boolean postMetric(String name, float value, String hostname, JSONArray tags, String type) { + private boolean postMetric(String name, float value, String hostname, Map> tags, String type) { int INTERVAL = 10; logger.fine(String.format("Sending metric '%s' with value %s", name, String.valueOf(value))); @@ -162,7 +166,7 @@ private boolean postMetric(String name, float value, String hostname, JSONArray } if (tags != null) { logger.fine(tags.toString()); - metric.put("tags", tags); + metric.put("tags", TagsUtil.convertTagsToJSONArray(tags)); } // Place metric as item of series list JSONArray series = new JSONArray(); @@ -185,7 +189,7 @@ private boolean postMetric(String name, float value, String hostname, JSONArray } @Override - public boolean serviceCheck(String name, int code, String hostname, JSONArray tags) { + public boolean serviceCheck(String name, int code, String hostname, Map> tags) { logger.fine(String.format("Sending service check '%s' with status %s", name, code)); // Build payload @@ -314,7 +318,7 @@ public boolean validate() throws IOException, ServletException { private HttpURLConnection getHttpURLConnection(final URL url) throws IOException { HttpURLConnection conn = null; Jenkins jenkins = Jenkins.getInstance(); - if (jenkins == null){ + if (jenkins == null) { return null; } ProxyConfiguration proxyConfig = jenkins.proxy; diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogEvent.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogBuildEvent.java similarity index 63% rename from src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogEvent.java rename to src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogBuildEvent.java index c165f17f..2af3191d 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogEvent.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogBuildEvent.java @@ -3,36 +3,32 @@ import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.model.BuildData; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; -import java.util.Map; -import java.util.Set; +public abstract class AbstractDatadogBuildEvent implements DatadogEvent { -public abstract class AbstractDatadogEvent implements DatadogEvent { - - protected BuildData builddata; - protected Map> tags; + protected BuildData buildData; private static final float MINUTE = 60; private static final float HOUR = 3600; - public AbstractDatadogEvent(BuildData buildData, Map> buildTags) { - this.builddata = buildData; - this.tags = buildTags; + public AbstractDatadogBuildEvent(BuildData buildData) { + this.buildData = buildData; } public JSONObject createPayload() { JSONObject payload = new JSONObject(); - payload.put("host", builddata.getHostname(null)); - payload.put("aggregation_key", builddata.getJobName(null)); - payload.put("date_happened", builddata.getEndTime(System.currentTimeMillis()) / 1000); - payload.put("tags", builddata.getAssembledTags(tags)); + payload.put("host", buildData.getHostname(null)); + payload.put("aggregation_key", buildData.getJobName("unknown")); + payload.put("date_happened", buildData.getEndTime(System.currentTimeMillis()) / 1000); + payload.put("tags", TagsUtil.convertTagsToJSONArray(buildData.getTags())); payload.put("source_type_name", "jenkins"); return payload; } protected String getFormattedDuration() { - Long duration = builddata.getDuration(null); + Long duration = buildData.getDuration(null); if (duration != null) { String output = "("; String format = "%.2f"; diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogSimpleEvent.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogSimpleEvent.java new file mode 100644 index 00000000..6307d2a2 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/AbstractDatadogSimpleEvent.java @@ -0,0 +1,31 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.model.BuildData; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public abstract class AbstractDatadogSimpleEvent implements DatadogEvent { + + protected Map> tags; + + public AbstractDatadogSimpleEvent(Map> tags) { + this.tags = tags; + } + + public JSONObject createPayload(String aggregation_key) { + JSONObject payload = new JSONObject(); + payload.put("host", DatadogUtilities.getHostname(null)); + payload.put("aggregation_key", aggregation_key); + payload.put("date_happened", System.currentTimeMillis() / 1000); + payload.put("tags", TagsUtil.convertTagsToJSONArray(tags)); + payload.put("source_type_name", "jenkins"); + + return payload; + } + +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildAbortedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildAbortedEventImpl.java new file mode 100644 index 00000000..f56000e7 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildAbortedEventImpl.java @@ -0,0 +1,37 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.model.BuildData; + +public class BuildAbortedEventImpl extends AbstractDatadogBuildEvent { + + public BuildAbortedEventImpl(BuildData buildData) { + super(buildData); + } + + @Override + public JSONObject createPayload() { + JSONObject payload = super.createPayload(); + String number = buildData.getBuildNumber("unknown"); + String userId = buildData.getUserId(); + String jobName = buildData.getJobName("unknown"); + String buildUrl = buildData.getBuildUrl("unknown"); + String hostname = buildData.getHostname("unknown"); + + // Build title + // eg: `job_name build #1 aborted on hostname` + String title = jobName + " build #" + number + " aborted on " + hostname; + payload.put("title", title); + + // Build Text + // eg: `User aborted the [job with build number #] (1sec)` + String message = "%%% \nUser " + userId + " aborted the [job " + jobName + " with build number #" + number + + "](" + buildUrl + ") " + getFormattedDuration() + " \n%%%"; + payload.put("text", message); + + payload.put("priority", "low"); + payload.put("alert_type", "info"); + + return payload; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventImpl.java index e067f520..71168dbe 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventImpl.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventImpl.java @@ -5,42 +5,34 @@ import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.model.BuildData; -import java.util.Map; -import java.util.Set; - /** * Class that implements the {@link DatadogEvent}. This event produces an event payload * with a proper description for a finished build. */ -public class BuildFinishedEventImpl extends AbstractDatadogEvent { +public class BuildFinishedEventImpl extends AbstractDatadogBuildEvent { - public BuildFinishedEventImpl(BuildData buildData, Map> buildTags) { - super(buildData, buildTags); + public BuildFinishedEventImpl(BuildData buildData) { + super(buildData); } @Override public JSONObject createPayload() { JSONObject payload = super.createPayload(); - String buildNumber = builddata.getBuildNumber("unknown"); - String buildResult = builddata.getResult("UNKNOWN"); + String buildNumber = buildData.getBuildNumber("unknown"); + String buildResult = buildData.getResult("UNKNOWN"); + String jobName = buildData.getJobName("unknown"); + String buildUrl = buildData.getBuildUrl("unknown"); + String hostname = buildData.getHostname("unknown"); // Build title - String title = builddata.getJobName("unknown") + - " build #" + - buildNumber + - " " + - buildResult.toLowerCase() + - " on " + - builddata.getHostname("unknown"); + // eg: `job_name build #1 success on hostname` + String title = jobName + " build #" + buildNumber + " " + buildResult.toLowerCase() + " on " + hostname; payload.put("title", title); - String message = "%%% \n [See results for build #" + - buildNumber + - "](" + - builddata.getBuildUrl("unknown") + - ") " + - getFormattedDuration() + - " \n %%%"; + // Build Text + // eg: `[Job with build number #] finished with status (1sec)` + String message = "%%% \n[Job " + jobName + " build #" + buildNumber + "](" + buildUrl + + ") finished with status " + buildResult.toLowerCase() + " " + getFormattedDuration() + " \n%%%"; payload.put("text", message); if (Result.SUCCESS.toString().equals(buildResult)) { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventImpl.java index 294d59a9..b4a0a7e7 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventImpl.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventImpl.java @@ -4,17 +4,14 @@ import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.model.BuildData; -import java.util.Map; -import java.util.Set; - /** * This event should contain all the data to construct a build started event. With * the right message for Datadog. */ -public class BuildStartedEventImpl extends AbstractDatadogEvent { +public class BuildStartedEventImpl extends AbstractDatadogBuildEvent { - public BuildStartedEventImpl(BuildData buildData, Map> buildTags) { - super(buildData, buildTags); + public BuildStartedEventImpl(BuildData buildData) { + super(buildData); } /** @@ -23,29 +20,25 @@ public BuildStartedEventImpl(BuildData buildData, Map> build @Override public JSONObject createPayload() { JSONObject payload = super.createPayload(); - String number = builddata.getBuildNumber("unknown"); + String buildNumber = buildData.getBuildNumber("unknown"); + String userId = buildData.getUserId(); + String jobName = buildData.getJobName("unknown"); + String buildUrl = buildData.getBuildUrl("unknown"); + String hostname = buildData.getHostname("unknown"); // Build title - String title = builddata.getJobName("unknown") + - " build #" + - number + - " started" + - " on " + - builddata.getHostname("unknown"); + // eg: `job_name build #1 started on hostname` + String title = jobName + " build #" + buildNumber + " started on " + hostname; payload.put("title", title); // Build Text - String message = "%%% \n [Follow build #" + - number + - " progress](" + - builddata.getBuildUrl("unknown") + - ") " + - getFormattedDuration() + - " \n %%%"; + // eg: User started the [job with build number #] (1sec)" + String message = "%%% \nUser " + userId + " started the [job " + jobName + " build #" + + buildNumber + "](" + buildUrl + ") " + getFormattedDuration() + " \n%%%"; payload.put("text", message); - payload.put("alert_type", "info"); payload.put("priority", "low"); + payload.put("alert_type", "info"); return payload; } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventImpl.java deleted file mode 100644 index 7c305f1c..00000000 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.datadog.jenkins.plugins.datadog.events; - -import net.sf.json.JSONObject; -import org.datadog.jenkins.plugins.datadog.DatadogEvent; -import org.datadog.jenkins.plugins.datadog.model.BuildData; - -import java.util.Map; -import java.util.Set; - -/** - * Class that implements the {@link DatadogEvent}. This event produces an event payload with a - * with a proper description for a completed checkout. - */ -public class CheckoutCompletedEventImpl extends AbstractDatadogEvent { - - public CheckoutCompletedEventImpl(BuildData buildData, Map> buildTags) { - super(buildData, buildTags); - } - - /** - * @return - A JSON payload. See {@link DatadogEvent#createPayload()} - */ - @Override - public JSONObject createPayload() { - JSONObject payload = super.createPayload(); - String number = builddata.getBuildNumber("unknown"); - - // Build title - String title = builddata.getJobName("unknown") + - " build #" + - number + - " checkout finished" + - " on " + - builddata.getHostname("unknown"); - payload.put("title", title); - - // Build Text - String message = "%%% \n [Follow build #" + - number + - " progress](" + - builddata.getBuildUrl("unknown") + - ") " + - getFormattedDuration() + - " \n %%%"; - payload.put("text", message); - - payload.put("alert_type", "info"); - payload.put("priority", "low"); - - return payload; - } -} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerLaunchFailedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerLaunchFailedEventImpl.java new file mode 100644 index 00000000..c5d6f2c8 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerLaunchFailedEventImpl.java @@ -0,0 +1,39 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Computer; +import hudson.model.TaskListener; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ComputerLaunchFailedEventImpl extends AbstractDatadogSimpleEvent { + + private Computer computer; + private TaskListener listener; + + public ComputerLaunchFailedEventImpl(Computer computer, TaskListener listener, Map> tags) { + super(tags); + this.computer = computer; + this.listener = listener; + } + + @Override + public JSONObject createPayload() { + String nodeName = DatadogUtilities.getNodeName(computer); + JSONObject payload = super.createPayload(nodeName); + + String title = "Jenkins node " + nodeName + " failed to launch"; + payload.put("title", title); + + String message = "%%% \nJenkins node " + nodeName + " failed to launch \n%%%"; + payload.put("text", message); + + payload.put("priority", "normal"); + payload.put("alert_type", "error"); + + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOfflineEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOfflineEventImpl.java new file mode 100644 index 00000000..7dcfde03 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOfflineEventImpl.java @@ -0,0 +1,43 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Computer; +import hudson.slaves.OfflineCause; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ComputerOfflineEventImpl extends AbstractDatadogSimpleEvent { + + private Computer computer; + private OfflineCause cause; + private boolean isTemporarily; + + public ComputerOfflineEventImpl(Computer computer, OfflineCause cause, Map> tags, boolean isTemporarily) { + super(tags); + this.computer = computer; + this.cause = cause; + this.isTemporarily = isTemporarily; + } + + @Override + public JSONObject createPayload() { + String nodeName = DatadogUtilities.getNodeName(computer); + JSONObject payload = super.createPayload(nodeName); + + String title = "Jenkins node " + nodeName + " is" + (isTemporarily? " temporarily ": " ") + "Offline"; + payload.put("title", title); + + // TODO: Add more info about the case in the event in message. + String message = "%%% \nJenkins node " + nodeName + " is" + (isTemporarily? " temporarily ": " ") + + "Offline \n%%%"; + payload.put("text", message); + + payload.put("priority", "normal"); + payload.put("alert_type", "warning"); + + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOnlineEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOnlineEventImpl.java new file mode 100644 index 00000000..4898bf00 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ComputerOnlineEventImpl.java @@ -0,0 +1,42 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Computer; +import hudson.model.TaskListener; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ComputerOnlineEventImpl extends AbstractDatadogSimpleEvent { + + private Computer computer; + private TaskListener listener; + private boolean isTemporarily; + + public ComputerOnlineEventImpl(Computer computer, TaskListener listener, Map> tags, boolean isTemporarily) { + super(tags); + this.computer = computer; + this.listener = listener; + this.isTemporarily = isTemporarily; + } + + @Override + public JSONObject createPayload() { + String nodeName = DatadogUtilities.getNodeName(computer); + JSONObject payload = super.createPayload(nodeName); + + String title = "Jenkins node " + nodeName + " is" + (isTemporarily ? " temporarily " : " ") + "Online"; + payload.put("title", title); + + String message = "%%% \nJenkins node " + nodeName + " is" + (isTemporarily ? " temporarily " : " ") + + "Online \n%%%"; + payload.put("text", message); + + payload.put("priority", "low"); + payload.put("alert_type", "success"); + + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ConfigChangedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ConfigChangedEventImpl.java new file mode 100644 index 00000000..fe24d80d --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ConfigChangedEventImpl.java @@ -0,0 +1,45 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.XmlFile; +import hudson.model.Saveable; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ConfigChangedEventImpl extends AbstractDatadogSimpleEvent { + + private Saveable config; + private XmlFile file; + + public ConfigChangedEventImpl(Saveable config, XmlFile file, Map> tags) { + super(tags); + this.config = config; + this.file = file; + } + + @Override + public JSONObject createPayload() { + String fileName = file.getFile().getName(); + String userId = DatadogUtilities.getUserId(); + JSONObject payload = super.createPayload(fileName); + + String title = userId + " changed file " + fileName; + payload.put("title", title); + + String message = "%%% \nUser " + userId + " changed " + fileName + " \n%%%"; + payload.put("text", message); + + if ("system".equals(userId.toLowerCase())){ + payload.put("priority", "low"); + payload.put("alert_type", "info"); + }else{ + payload.put("priority", "normal"); + payload.put("alert_type", "warning"); + } + + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCRUDEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCRUDEventImpl.java new file mode 100644 index 00000000..6d4631e4 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCRUDEventImpl.java @@ -0,0 +1,43 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Item; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ItemCRUDEventImpl extends AbstractDatadogSimpleEvent { + + public final static String CREATED = "Created"; + public final static String UPDATED = "Updated"; + public final static String DELETED = "Deleted"; + + private Item item; + private String action; + + public ItemCRUDEventImpl(Item item, String action, Map> tags) { + super(tags); + this.item = item; + this.action = action; + } + + @Override + public JSONObject createPayload() { + String itemName = DatadogUtilities.getItemName(item); + String userId = DatadogUtilities.getUserId(); + JSONObject payload = super.createPayload(itemName); + + String title = userId + " " + action.toLowerCase() + " the item " + itemName; + payload.put("title", title); + + String message = "%%% \nUser " + userId + " " + action.toLowerCase() + " the item " + itemName + " \n%%%"; + payload.put("text", message); + + payload.put("priority", "normal"); + payload.put("alert_type", "info"); + + return payload; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCopiedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCopiedEventImpl.java new file mode 100644 index 00000000..a4294eaa --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemCopiedEventImpl.java @@ -0,0 +1,40 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Item; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class ItemCopiedEventImpl extends AbstractDatadogSimpleEvent { + + private Item src; + private Item item; + + public ItemCopiedEventImpl(Item src, Item item, Map> tags) { + super(tags); + this.src = src; + this.item = item; + } + + @Override + public JSONObject createPayload() { + String srcName = DatadogUtilities.getItemName(src); + String itemName = DatadogUtilities.getItemName(item); + String userId = DatadogUtilities.getUserId(); + JSONObject payload = super.createPayload(itemName); + + String title = userId + " copied the item " + itemName + " from " + srcName; + payload.put("title", title); + + String message = "%%% \nUser " + userId + " copied the item " + itemName + " from " + srcName + " \n%%%"; + payload.put("text", message); + + payload.put("priority", "normal"); + payload.put("alert_type", "low"); + + return payload; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemLocationChangedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemLocationChangedEventImpl.java new file mode 100644 index 00000000..69fbc7bc --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/ItemLocationChangedEventImpl.java @@ -0,0 +1,41 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import hudson.model.Item; +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; + +import java.util.Map; +import java.util.Set; + +public class ItemLocationChangedEventImpl extends AbstractDatadogSimpleEvent { + + private Item item; + private String oldFullName; + private String newFullName; + + public ItemLocationChangedEventImpl(Item item, String oldFullName, String newFullName, Map> tags) { + super(tags); + this.item = item; + this.oldFullName = oldFullName; + this.newFullName = newFullName; + } + + @Override + public JSONObject createPayload() { + String itemName = DatadogUtilities.getItemName(item); + String userId = DatadogUtilities.getUserId(); + JSONObject payload = super.createPayload(itemName); + + String title = userId + " changed the location of the item " + itemName; + payload.put("title", title); + + String message = "%%% \nUser " + userId + " changed the location of the item " + itemName + " from " + + oldFullName + " to " + newFullName + " \n%%%"; + payload.put("text", message); + + payload.put("priority", "normal"); + payload.put("alert_type", "info"); + + return payload; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventImpl.java new file mode 100644 index 00000000..82b70659 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventImpl.java @@ -0,0 +1,44 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.model.BuildData; + +/** + * Class that implements the {@link DatadogEvent}. This event produces an event payload with a + * with a proper description for a completed checkout. + */ +public class SCMCheckoutCompletedEventImpl extends AbstractDatadogBuildEvent { + + public SCMCheckoutCompletedEventImpl(BuildData buildData) { + super(buildData); + } + + /** + * @return - A JSON payload. See {@link DatadogEvent#createPayload()} + */ + @Override + public JSONObject createPayload() { + JSONObject payload = super.createPayload(); + String buildNumber = buildData.getBuildNumber("unknown"); + String jobName = buildData.getJobName("unknown"); + String buildUrl = buildData.getBuildUrl("unknown"); + String hostname = buildData.getHostname("unknown"); + + // Build title + // eg: `job_name build #1 checkout finished hostname` + String title = jobName + " build #" + buildNumber + " checkout finished on " + hostname; + payload.put("title", title); + + // Build Text + // eg: `[Job with build number #] checkout successfully (1sec)` + String message = "%%% \n[Job " + jobName + " build #" + buildNumber + "](" + buildUrl + + ") checkout finished successfully on " + hostname + " " + getFormattedDuration() + " \n%%%"; + payload.put("text", message); + + payload.put("priority", "low"); + payload.put("alert_type", "success"); + + return payload; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/events/UserAuthenticationEventImpl.java b/src/main/java/org/datadog/jenkins/plugins/datadog/events/UserAuthenticationEventImpl.java new file mode 100644 index 00000000..546b091f --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/events/UserAuthenticationEventImpl.java @@ -0,0 +1,45 @@ +package org.datadog.jenkins.plugins.datadog.events; + +import net.sf.json.JSONObject; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import java.util.Map; +import java.util.Set; + +public class UserAuthenticationEventImpl extends AbstractDatadogSimpleEvent { + + public final static String LOGIN = "authenticated"; + public final static String ACCESS_DENIED = "failed to authenticate"; + public final static String LOGOUT = "logout"; + + private String username; + private String action; + + public UserAuthenticationEventImpl(String username, String action, Map> tags) { + super(tags); + this.username = username; + this.action = action; + } + + @Override + public JSONObject createPayload() { + JSONObject payload = super.createPayload(username); + + String title = username + " " + action.toLowerCase(); + payload.put("title", title); + + String message = "%%% \nUser " + username + " " + action.toLowerCase() +" \n%%%"; + payload.put("text", message); + + if (LOGIN.equals(action) || LOGOUT.equals(action)){ + payload.put("priority", "low"); + payload.put("alert_type", "success"); + } else { + payload.put("priority", "normal"); + payload.put("alert_type", "error"); + } + + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java index 77ff9a01..32f5bbd4 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java @@ -3,11 +3,10 @@ import hudson.Extension; import hudson.model.*; import hudson.model.listeners.RunListener; -import net.sf.json.JSONArray; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogEvent; -import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.events.BuildAbortedEventImpl; import org.datadog.jenkins.plugins.datadog.events.BuildFinishedEventImpl; import org.datadog.jenkins.plugins.datadog.events.BuildStartedEventImpl; import org.datadog.jenkins.plugins.datadog.model.BuildData; @@ -20,23 +19,14 @@ /** - * DatadogBuildListener {@link RunListener}. - * When a build starts, the {@link #onStarted(Run, TaskListener)} method will be invoked. And - * when a build finishes, the {@link #onCompleted(Run, TaskListener)} method will be invoked. + * This class registers an {@link RunListener} to trigger events and calculate metrics: + * - When a build starts, the {@link #onStarted(Run, TaskListener)} method will be invoked. + * - When a build finishes, the {@link #onCompleted(Run, TaskListener)} method will be invoked. */ @Extension public class DatadogBuildListener extends RunListener { - /** - * Static variables describing consistent plugin names, Datadog API endpoints/codes, and magic - * numbers. - */ - private static final Logger logger = Logger.getLogger(DatadogBuildListener.class.getName()); - /** - * Runs when the {@link DatadogGlobalConfiguration} class is created. - */ - public DatadogBuildListener() { - } + private static final Logger logger = Logger.getLogger(DatadogBuildListener.class.getName()); /** * Called when a build is first started. @@ -46,14 +36,13 @@ public DatadogBuildListener() { * operation. */ @Override - public final void onStarted(final Run run, final TaskListener listener) { + public void onStarted(Run run, TaskListener listener) { try { // Process only if job is NOT in blacklist and is in whitelist if (!DatadogUtilities.isJobTracked(run.getParent().getFullName())) { return; } - - logger.fine("Started build!"); + logger.fine("End DatadogBuildListener#onStarted"); // Get Datadog Client Instance DatadogClient client = DatadogUtilities.getDatadogClient(); @@ -67,12 +56,8 @@ public final void onStarted(final Run run, final TaskListener listener) { return; } - // Get the list of global tags to apply - Map> extraTags = DatadogUtilities.buildExtraTags(run, listener); - String hostname = buildData.getHostname("null"); - // Send an event - BuildStartedEventImpl event = new BuildStartedEventImpl(buildData, extraTags); + DatadogEvent event = new BuildStartedEventImpl(buildData); client.sendEvent(event.createPayload()); // Send an metric @@ -81,9 +66,10 @@ public final void onStarted(final Run run, final TaskListener listener) { // queue times if the plugin is spinning up an instance/container for one/first job. Queue queue = Queue.getInstance(); Queue.Item item = queue.getItem(run.getQueueId()); + Map> tags = buildData.getTags(); + String hostname = buildData.getHostname("null"); try { - long waiting = (currentTimeMillis() - item.getInQueueSince()) / 1000; - JSONArray tags = buildData.getAssembledTags(extraTags); + long waiting = (DatadogUtilities.currentTimeMillis() - item.getInQueueSince()) / 1000; client.gauge("jenkins.job.waiting", waiting, hostname, tags); } catch (NullPointerException e) { logger.warning("Unable to compute 'waiting' metric. " + @@ -91,9 +77,9 @@ public final void onStarted(final Run run, final TaskListener listener) { } // Submit counter - JSONArray tags = buildData.getAssembledTags(extraTags); client.incrementCounter("jenkins.job.started", hostname, tags); - logger.fine("Finished onStarted()"); + + logger.fine("End DatadogBuildListener#onStarted"); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); } @@ -108,14 +94,13 @@ public final void onStarted(final Run run, final TaskListener listener) { */ @Override - public final void onCompleted(final Run run, @Nonnull final TaskListener listener) { + public void onCompleted(Run run, @Nonnull TaskListener listener) { try { // Process only if job in NOT in blacklist and is in whitelist if (!DatadogUtilities.isJobTracked(run.getParent().getFullName())) { return; } - - logger.fine("Completed build!"); + logger.fine("Start DatadogBuildListener#onCompleted"); // Get Datadog Client Instance DatadogClient client = DatadogUtilities.getDatadogClient(); @@ -128,17 +113,14 @@ public final void onCompleted(final Run run, @Nonnull final TaskListener listene logger.severe(e.getMessage()); return; } - String hostname = buildData.getHostname("null"); - - // Get the list of global tags to apply - Map> extraTags = DatadogUtilities.buildExtraTags(run, listener); // Send an event - DatadogEvent event = new BuildFinishedEventImpl(buildData, extraTags); + DatadogEvent event = new BuildFinishedEventImpl(buildData); client.sendEvent(event.createPayload()); // Send a metric - JSONArray tags = buildData.getAssembledTags(extraTags); + Map> tags = buildData.getTags(); + String hostname = buildData.getHostname("null"); client.gauge("jenkins.job.duration", buildData.getDuration(0L) / 1000, hostname, tags); // Submit counter @@ -179,21 +161,56 @@ public final void onCompleted(final Run run, @Nonnull final TaskListener listene client.gauge("jenkins.job.mtbf", mtbf / 1000, hostname, tags); } } - logger.fine("Finished onCompleted()"); + + logger.fine("End DatadogBuildListener#onCompleted"); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); } } - public long currentTimeMillis(){ - // This method exist so we can mock System.currentTimeMillis in unit tests - return System.currentTimeMillis(); + @Override + public void onDeleted(Run run) { + try { + // Process only if job is NOT in blacklist and is in whitelist + if (!DatadogUtilities.isJobTracked(run.getParent().getFullName())) { + return; + } + logger.fine("Start DatadogBuildListener#onDeleted"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Collect Build Data + BuildData buildData; + try { + buildData = new BuildData(run, null); + } catch (IOException | InterruptedException e) { + logger.severe(e.getMessage()); + return; + } + + // Get the list of global tags to apply + String hostname = buildData.getHostname("null"); + + // Send an event + DatadogEvent event = new BuildAbortedEventImpl(buildData); + client.sendEvent(event.createPayload()); + + // Submit counter + Map> tags = buildData.getTags(); + client.incrementCounter("jenkins.job.aborted", hostname, tags); + + logger.fine("End DatadogBuildListener#onDeleted"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } } private long getMeanTimeBetweenFailure(Run run) { Run lastGreenRun = run.getPreviousNotFailedBuild(); if (lastGreenRun != null) { - return getStartTimeInMillis(run) - getStartTimeInMillis(lastGreenRun); + return DatadogUtilities.getRunStartTimeInMillis(run) - + DatadogUtilities.getRunStartTimeInMillis(lastGreenRun); } return 0; } @@ -201,8 +218,9 @@ private long getMeanTimeBetweenFailure(Run run) { private long getCycleTime(Run run) { Run previousSuccessfulBuild = run.getPreviousSuccessfulBuild(); if (previousSuccessfulBuild != null) { - return (getStartTimeInMillis(run) + run.getDuration()) - - (getStartTimeInMillis(previousSuccessfulBuild) + previousSuccessfulBuild.getDuration()); + return (DatadogUtilities.getRunStartTimeInMillis(run) + run.getDuration()) - + (DatadogUtilities.getRunStartTimeInMillis(previousSuccessfulBuild) + + previousSuccessfulBuild.getDuration()); } return 0; } @@ -215,16 +233,13 @@ private long getMeanTimeToRecovery(Run run) { firstFailedRun = firstFailedRun.getPreviousBuiltBuild(); } if (firstFailedRun != null) { - return getStartTimeInMillis(run) - getStartTimeInMillis(firstFailedRun); + return DatadogUtilities.getRunStartTimeInMillis(run) - + DatadogUtilities.getRunStartTimeInMillis(firstFailedRun); } } return 0; } - public long getStartTimeInMillis(Run run) { - // getStartTimeInMillis wrapper in order to mock it in unit tests - return run.getStartTimeInMillis(); - } private boolean isFailedBuild(Run run) { return run != null && run.getResult() != Result.SUCCESS; diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java new file mode 100644 index 00000000..0d6d5b89 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogComputerListener.java @@ -0,0 +1,189 @@ +package org.datadog.jenkins.plugins.datadog.listeners; + +import hudson.Extension; +import hudson.model.Computer; +import hudson.model.TaskListener; +import hudson.slaves.ComputerListener; +import hudson.slaves.OfflineCause; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.events.*; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * This class registers an {@link ComputerListener} to trigger events and calculate metrics: + * - When a computer gets online, the {@link #onOnline(Computer, TaskListener)} method will be invoked. + * - When a computer gets offline, the {@link #onOffline(Computer, OfflineCause)} method will be invoked. + * - When a computer gets temporarily online, the {@link #onTemporarilyOnline(Computer)} method will be invoked. + * - When a computer gets temporarily offline, the {@link #onTemporarilyOffline(Computer, OfflineCause)} method will be invoked. + * - When a computer failed to launch, the {@link #onLaunchFailure(Computer, TaskListener)} method will be invoked. + */ +@Extension +public class DatadogComputerListener extends ComputerListener { + + private static final Logger logger = Logger.getLogger(DatadogComputerListener.class.getName()); + + @Override + public void onOnline(Computer computer, TaskListener listener) throws IOException, InterruptedException { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogComputerListener#onOnline"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of tags to apply + Map> tags = TagsUtil.merge( + DatadogUtilities.getTagsFromGlobalTags(), + DatadogUtilities.getComputerTags(computer)); + + // Send event + DatadogEvent event = new ComputerOnlineEventImpl(computer, listener, tags, false); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.computer.online", hostname, tags); + + logger.fine("End DatadogComputerListener#onOnline"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onOffline(@Nonnull Computer computer, @CheckForNull OfflineCause cause) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogComputerListener#onOffline"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of tags to apply + Map> tags = TagsUtil.merge( + DatadogUtilities.getTagsFromGlobalTags(), + DatadogUtilities.getComputerTags(computer)); + + // Send event + DatadogEvent event = new ComputerOfflineEventImpl(computer, cause, tags, false); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.computer.offline", hostname, tags); + + logger.fine("End DatadogComputerListener#onOffline"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onTemporarilyOnline(Computer computer) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogComputerListener#onTemporarilyOnline"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of tags to apply + Map> tags = TagsUtil.merge( + DatadogUtilities.getTagsFromGlobalTags(), + DatadogUtilities.getComputerTags(computer)); + + // Send event + DatadogEvent event = new ComputerOnlineEventImpl(computer, null, tags, true); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.computer.temporarily_online", hostname, tags); + + logger.fine("End DatadogComputerListener#onTemporarilyOnline"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onTemporarilyOffline(Computer computer, OfflineCause cause) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogComputerListener#onTemporarilyOffline"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of tags to apply + Map> tags = TagsUtil.merge( + DatadogUtilities.getTagsFromGlobalTags(), + DatadogUtilities.getComputerTags(computer)); + + // Send event + DatadogEvent event = new ComputerOfflineEventImpl(computer, cause, tags, true); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.computer.temporarily_offline", hostname, tags); + + logger.fine("End DatadogComputerListener#onTemporarilyOffline"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onLaunchFailure(Computer computer, TaskListener taskListener) throws IOException, InterruptedException { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogComputerListener#onLaunchFailure"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of tags to apply + Map> tags = TagsUtil.merge( + DatadogUtilities.getTagsFromGlobalTags(), + DatadogUtilities.getComputerTags(computer)); + + // Send event + DatadogEvent event = new ComputerLaunchFailedEventImpl(computer, taskListener, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.computer.launch_failure", hostname, tags); + + logger.fine("End DatadogComputerListener#onLaunchFailure"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java new file mode 100644 index 00000000..99617a89 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogItemListener.java @@ -0,0 +1,131 @@ +package org.datadog.jenkins.plugins.datadog.listeners; + +import hudson.Extension; +import hudson.model.Item; +import hudson.model.listeners.ItemListener; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.events.ItemCRUDEventImpl; +import org.datadog.jenkins.plugins.datadog.events.ItemCopiedEventImpl; +import org.datadog.jenkins.plugins.datadog.events.ItemLocationChangedEventImpl; + +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * This class registers an {@link ItemListener} to trigger events and calculate metrics: + * - When an item gets created, the {@link #onCreated(Item)} method will be invoked. + * - When an item gets copied, the {@link #onCopied(Item, Item)} method will be invoked. + * - When an item gets deleted, the {@link #onDeleted(Item)} method will be invoked. + * - When an item gets updated, the {@link #onUpdated(Item)} method will be invoked. + * - When an item gets their location changed, the {@link #onLocationChanged(Item, String, String)} method will be invoked. + */ +@Extension +public class DatadogItemListener extends ItemListener { + + private static final Logger logger = Logger.getLogger(DatadogItemListener.class.getName()); + + @Override + public void onCreated(Item item) { + onCRUD(item, ItemCRUDEventImpl.CREATED); + } + + @Override + public void onDeleted(Item item) { + onCRUD(item, ItemCRUDEventImpl.DELETED); + } + + @Override + public void onUpdated(Item item) { + onCRUD(item, ItemCRUDEventImpl.UPDATED); + } + + private void onCRUD(Item item, String action) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogItemListener#on" + action); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new ItemCRUDEventImpl(item, action, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.item." + action.toLowerCase(), hostname, tags); + + logger.fine("End DatadogItemListener#on" + action); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onCopied(Item src, Item item) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogItemListener#onCopied"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new ItemCopiedEventImpl(src, item, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.item.copied", hostname, tags); + + logger.fine("End DatadogItemListener#onCopied"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + public void onLocationChanged(Item item, String oldFullName, String newFullName) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogItemListener#onLocationChanged"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new ItemLocationChangedEventImpl(item, oldFullName, newFullName, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.item.location_changed", hostname, tags); + + logger.fine("End DatadogItemListener#onLocationChanged"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java index a21f84bd..133c135c 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSCMListener.java @@ -12,7 +12,7 @@ import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogJobProperty; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; -import org.datadog.jenkins.plugins.datadog.events.CheckoutCompletedEventImpl; +import org.datadog.jenkins.plugins.datadog.events.SCMCheckoutCompletedEventImpl; import org.datadog.jenkins.plugins.datadog.model.BuildData; import java.io.File; @@ -46,14 +46,13 @@ public class DatadogSCMListener extends SCMListener { public void onCheckout(Run build, SCM scm, FilePath workspace, TaskListener listener, File changelogFile, SCMRevisionState pollingBaseline) throws Exception { try { - // Process only if job is NOT in blacklist and is in whitelist - DatadogJobProperty prop = DatadogJobProperty.retrieveProperty(build); + DatadogJobProperty prop = DatadogUtilities.retrieveProperty(build); if (!(DatadogUtilities.isJobTracked(build.getParent().getFullName()) - && prop != null && prop.isEmitOnCheckout())) { + && prop != null && prop.isEmitSCMEvents())) { return; } - logger.fine("Checkout! in onCheckout()"); + logger.fine("Start DatadogSCMListener#onCheckout"); // Get Datadog Client Instance DatadogClient client = DatadogUtilities.getDatadogClient(); @@ -67,17 +66,16 @@ public void onCheckout(Run build, SCM scm, FilePath workspace, TaskListene return; } - // Get the list of global tags to apply - Map> extraTags = DatadogUtilities.buildExtraTags(build, listener); - // Send event - DatadogEvent event = new CheckoutCompletedEventImpl(buildData, extraTags); + DatadogEvent event = new SCMCheckoutCompletedEventImpl(buildData); client.sendEvent(event.createPayload()); // Submit counter - JSONArray tags = buildData.getAssembledTags(extraTags); String hostname = DatadogUtilities.getHostname("null"); + Map> tags = buildData.getTags(); client.incrementCounter("jenkins.scm.checkout", hostname, tags); + + logger.fine("End DatadogSCMListener#onCheckout"); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java new file mode 100644 index 00000000..8fbdefe9 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSaveableListener.java @@ -0,0 +1,54 @@ +package org.datadog.jenkins.plugins.datadog.listeners; + +import hudson.Extension; +import hudson.XmlFile; +import hudson.model.Saveable; +import hudson.model.listeners.SaveableListener; +import net.sf.json.JSONArray; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.events.ConfigChangedEventImpl; + +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * This class registers an {@link SaveableListener} to trigger events and calculate metrics: + * - When an saveable gets changed, the {@link #onChange(Saveable, XmlFile)} method will be invoked. + */ +@Extension +public class DatadogSaveableListener extends SaveableListener { + + private static final Logger logger = Logger.getLogger(DatadogSaveableListener.class.getName()); + + @Override + public void onChange(Saveable config, XmlFile file) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSystemEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogSaveableListener#onChange"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new ConfigChangedEventImpl(config, file, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.config.changed", hostname, tags); + + logger.fine("End DatadogSaveableListener#onChange"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java new file mode 100644 index 00000000..ca797841 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogSecurityListener.java @@ -0,0 +1,124 @@ +package org.datadog.jenkins.plugins.datadog.listeners; + +import hudson.Extension; +import jenkins.security.SecurityListener; +import org.acegisecurity.userdetails.UserDetails; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogEvent; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.events.UserAuthenticationEventImpl; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * This class registers an {@link SecurityListener} to trigger events and calculate metrics: + * - When an user authenticates, the {@link #authenticated(UserDetails)} method will be invoked. + * - When an user fails to authenticate, the {@link #failedToAuthenticate(String)} method will be invoked. + * - When an user logout, the {@link #loggedOut(String)} method will be invoked. + */ +@Extension +public class DatadogSecurityListener extends SecurityListener { + + private static final Logger logger = Logger.getLogger(DatadogSecurityListener.class.getName()); + + @Override + protected void authenticated(@Nonnull UserDetails details) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSecurityEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogSecurityListener#authenticated"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new UserAuthenticationEventImpl(details.getUsername(), + UserAuthenticationEventImpl.LOGIN, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.user.authenticated", hostname, tags); + + logger.fine("End DatadogSecurityListener#authenticated"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + protected void failedToAuthenticate(@Nonnull String username) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSecurityEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogSecurityListener#failedToAuthenticate"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new UserAuthenticationEventImpl(username, UserAuthenticationEventImpl.ACCESS_DENIED, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.user.access_denied", hostname, tags); + + logger.fine("End DatadogSecurityListener#failedToAuthenticate"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } + + @Override + protected void loggedIn(@Nonnull String username) { + //Covered by Authenticated + } + + @Override + protected void failedToLogIn(@Nonnull String username) { + //Covered by failedToAuthenticate + } + + @Override + protected void loggedOut(@Nonnull String username) { + try { + final boolean emitSystemEvents = DatadogUtilities.getDatadogGlobalDescriptor().isEmitSecurityEvents(); + if (!emitSystemEvents) { + return; + } + logger.fine("Start DatadogSecurityListener#loggedOut"); + + // Get Datadog Client Instance + DatadogClient client = DatadogUtilities.getDatadogClient(); + + // Get the list of global tags to apply + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); + + // Send event + DatadogEvent event = new UserAuthenticationEventImpl(username, UserAuthenticationEventImpl.LOGOUT, tags); + client.sendEvent(event.createPayload()); + + // Submit counter + String hostname = DatadogUtilities.getHostname("null"); + client.incrementCounter("jenkins.user.logout", hostname, tags); + + logger.fine("End DatadogSecurityListener#loggedOut"); + } catch (Exception e) { + logger.warning("Unexpected exception occurred - " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/BuildData.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/BuildData.java index a827dff9..6b706117 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/BuildData.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/BuildData.java @@ -2,12 +2,14 @@ import hudson.EnvVars; import hudson.model.*; -import net.sf.json.JSONArray; -import net.sf.json.JSONObject; +import hudson.triggers.SCMTrigger; +import hudson.triggers.TimerTrigger; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -40,13 +42,23 @@ public class BuildData { private String result; private String hostname; + private String userId; + private Map> tags; private Long startTime; private Long endTime; private Long duration; public BuildData(Run run, TaskListener listener) throws IOException, InterruptedException { - EnvVars envVars = run.getEnvironment(listener); + if (run == null) { + return; + } + EnvVars envVars = null; + if(listener != null){ + envVars = run.getEnvironment(listener); + setTags(DatadogUtilities.getBuildTags(run, listener)); + } + // Populate instance using environment variables. populateEnvVariables(envVars); @@ -64,6 +76,8 @@ public BuildData(Run run, TaskListener listener) throws IOException, Interrupted setEndTime(endTimeInMs); } + // Set UserId + setUserId(getUserId(run)); // Set Result setResult(run.getResult() == null ? null : run.getResult().toString()); // Set Build Number @@ -71,9 +85,15 @@ public BuildData(Run run, TaskListener listener) throws IOException, Interrupted // Set Hostname setHostname(DatadogUtilities.getHostname(envVars == null ? null : envVars.get("HOSTNAME"))); // Set Job Name - String jobName = run.getParent().getFullName(); - setJobName(jobName == null ? - "" : jobName.replaceAll("»", "/").replaceAll(" ", "")); + String jobName = null; + try { + jobName = run.getParent().getFullName(); + } catch(NullPointerException e){ + //noop + } + setJobName(jobName == null ? null : jobName. + replaceAll("»", "/"). + replaceAll(" ", "")); } private void populateEnvVariables(EnvVars envVars){ @@ -108,38 +128,47 @@ private void populateEnvVariables(EnvVars envVars){ } /** - * Assembles a {@link JSONArray} from metadata available in the - * {@link JSONObject} builddata. Returns a {@link JSONArray} with the set - * of tags. + * Assembles a map of tags containing: + * - Build Tags + * - Global Job Tags set in Job Properties + * - Global Tag set in Jenkins Global configuration * - * @param extra - A list of tags. - * @return a JSONArray containing a specific subset of tags retrieved from a builds metadata. + * @return a map containing all tags values */ - public JSONArray getAssembledTags(Map> extra) { - if(extra == null){ - extra = new HashMap<>(); + public Map> getTags() { + Map> mergedTags = new HashMap<>(); + try { + mergedTags = DatadogUtilities.getTagsFromGlobalTags(); + } catch(NullPointerException e){ + //noop } - JSONArray tags = new JSONArray(); - tags.add("job:" + getJobName("null")); + mergedTags = TagsUtil.merge(mergedTags, tags); + Map> additionalTags = new HashMap<>(); + Set jobValues = new HashSet<>(); + jobValues.add(getJobName("unknown")); + additionalTags.put("job", jobValues); if (nodeName != null) { - tags.add("node:" + getNodeName("null")); + Set nodeValues = new HashSet<>(); + nodeValues.add(getNodeName("unknown")); + additionalTags.put("node", nodeValues); } if (result != null) { - tags.add("result:" + getResult("null")); + Set resultValues = new HashSet<>(); + resultValues.add(getResult("UNKNOWN")); + additionalTags.put("result", resultValues); } - if (branch != null && !extra.containsKey("branch")) { - tags.add("branch:" + getBranch("null")); + if (branch != null) { + Set branchValues = new HashSet<>(); + branchValues.add(getBranch("unknown")); + additionalTags.put("branch", branchValues); } + mergedTags = TagsUtil.merge(mergedTags, additionalTags); - //Add the extra tags here - for (String name : extra.keySet()) { - Set values = extra.get(name); - for (String value : values){ - tags.add(String.format("%s:%s", name, value)); - } - } + return mergedTags; + } - return tags; + public void setTags(Map> tags) { + this.tags = tags; } private A defaultIfNull(A value, A defaultValue) { @@ -357,4 +386,55 @@ public String getPromotedJobFullName(String value) { public void setPromotedJobFullName(String promotedJobFullName) { this.promotedJobFullName = promotedJobFullName; } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + private String getUserId(Run run) { + if (promotedUserId != null){ + return promotedUserId; + } + String userName; + for (CauseAction action : run.getActions(CauseAction.class)) { + if (action != null && action.getCauses() != null) { + for (Cause cause : action.getCauses()) { + userName = getUserId(cause); + if (userName != null) { + return userName; + } + } + } + } + if (run.getParent().getClass().getName().equals("hudson.maven.MavenModule")) { + return "maven"; + } + return "anonymous"; + } + + private String getUserId(Cause cause){ + if (cause instanceof TimerTrigger.TimerTriggerCause) { + return "timer"; + } else if (cause instanceof SCMTrigger.SCMTriggerCause) { + return "scm"; + } else if (cause instanceof Cause.UserIdCause) { + String userName = ((Cause.UserIdCause) cause).getUserId(); + if (userName != null) { + return userName; + } + } else if (cause instanceof Cause.UpstreamCause) { + for (Cause upstreamCause : ((Cause.UpstreamCause) cause).getUpstreamCauses()) { + String username = getUserId(upstreamCause); + if (username != null) { + return username; + } + } + } + return null; + } + } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogNodePublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java similarity index 66% rename from src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogNodePublisher.java rename to src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java index 046f231a..533f829f 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogNodePublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogComputerPublisher.java @@ -3,12 +3,12 @@ import hudson.Extension; import hudson.model.Computer; import hudson.model.PeriodicWork; -import hudson.model.labels.LabelAtom; import jenkins.model.Jenkins; -import net.sf.json.JSONArray; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.util.TagsUtil; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -18,9 +18,9 @@ * us to compute metrics related to nodes and executors. */ @Extension -public class DatadogNodePublisher extends PeriodicWork { +public class DatadogComputerPublisher extends PeriodicWork { - private static final Logger logger = Logger.getLogger(DatadogNodePublisher.class.getName()); + private static final Logger logger = Logger.getLogger(DatadogComputerPublisher.class.getName()); private static final long RECURRENCE_PERIOD = TimeUnit.MINUTES.toMillis(1); @@ -42,6 +42,7 @@ protected void doRun() throws Exception { long nodeOffline = 0; long nodeOnline = 0; Computer[] computers = Jenkins.getInstance().getComputers(); + final Map> globalTags = DatadogUtilities.getTagsFromGlobalTags(); for (Computer computer : computers) { nodeCount++; if (computer.isOffline()) { @@ -54,37 +55,16 @@ protected void doRun() throws Exception { int executorCount = computer.countExecutors(); int inUse = computer.countBusy(); int free = computer.countIdle(); - Set labels = null; - try { - labels = computer.getNode().getAssignedLabels(); - } catch (NullPointerException e){ - logger.fine("Could not retrieve labels"); - } - String nodeHostname = computer.getHostName(); - String nodeName; - if (computer instanceof Jenkins.MasterComputer) { - nodeName = "master"; - } else { - nodeName = computer.getName(); - } - JSONArray tags = new JSONArray(); - tags.add("node_name:" + nodeName); - if(nodeHostname != null){ - tags.add("node_hostname:" + nodeHostname); - } - if(labels != null){ - for (LabelAtom label: labels){ - tags.add("node_label:" + label.getName()); - } - } + + Map> tags = TagsUtil.merge( + DatadogUtilities.getComputerTags(computer), globalTags); client.gauge("jenkins.executor.count", executorCount, hostname, tags); client.gauge("jenkins.executor.in_use", inUse, hostname, tags); client.gauge("jenkins.executor.free", free, hostname, tags); } - - client.gauge("jenkins.node.count", nodeCount, hostname, null); - client.gauge("jenkins.node.offline", nodeOffline, hostname, null); - client.gauge("jenkins.node.online", nodeOnline, hostname, null); + client.gauge("jenkins.node.count", nodeCount, hostname, globalTags); + client.gauge("jenkins.node.offline", nodeOffline, hostname, globalTags); + client.gauge("jenkins.node.online", nodeOnline, hostname, globalTags); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java index be349897..c2577695 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogJenkinsPublisher.java @@ -7,6 +7,8 @@ import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -34,7 +36,7 @@ protected void doRun() throws Exception { // Get Datadog Client Instance DatadogClient client = DatadogUtilities.getDatadogClient(); String hostname = DatadogUtilities.getHostname("null"); - + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); long projectCount = 0; try { projectCount = Jenkins.getInstance().getAllItems(Project.class).size(); @@ -47,8 +49,8 @@ protected void doRun() throws Exception { } catch (NullPointerException e){ logger.fine("Could not retrieve plugins"); } - client.gauge("jenkins.project.count", projectCount, hostname, null); - client.gauge("jenkins.plugin.count", pluginCount, hostname, null); + client.gauge("jenkins.project.count", projectCount, hostname, tags); + client.gauge("jenkins.plugin.count", pluginCount, hostname, tags); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java index 035aead1..fc4aacea 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/publishers/DatadogQueuePublisher.java @@ -6,6 +6,8 @@ import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -33,6 +35,7 @@ protected void doRun() throws Exception { // Get Datadog Client Instance DatadogClient client = DatadogUtilities.getDatadogClient(); + Map> tags = DatadogUtilities.getTagsFromGlobalTags(); long size = 0; long buildable = queue.countBuildableItems(); @@ -50,11 +53,11 @@ protected void doRun() throws Exception { } } String hostname = DatadogUtilities.getHostname("null"); - client.gauge("jenkins.queue.size", size, hostname, null); - client.gauge("jenkins.queue.buildable", buildable, hostname, null); - client.gauge("jenkins.queue.pending", pending, hostname, null); - client.gauge("jenkins.queue.stuck", stuck, hostname, null); - client.gauge("jenkins.queue.blocked", blocked, hostname, null); + client.gauge("jenkins.queue.size", size, hostname, tags); + client.gauge("jenkins.queue.buildable", buildable, hostname, tags); + client.gauge("jenkins.queue.pending", pending, hostname, tags); + client.gauge("jenkins.queue.stuck", stuck, hostname, tags); + client.gauge("jenkins.queue.blocked", blocked, hostname, tags); } catch (Exception e) { logger.warning("Unexpected exception occurred - " + e.getMessage()); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/util/TagsUtil.java b/src/main/java/org/datadog/jenkins/plugins/datadog/util/TagsUtil.java new file mode 100644 index 00000000..8b31d38e --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/util/TagsUtil.java @@ -0,0 +1,43 @@ +package org.datadog.jenkins.plugins.datadog.util; + +import net.sf.json.JSONArray; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class TagsUtil { + + public static Map> merge(Map> dest, Map> orig) { + if (dest == null) { + dest = new HashMap<>(); + } + if (orig == null) { + orig = new HashMap<>(); + } + for (String oName: orig.keySet()){ + Set dValues = dest.containsKey(oName) ? dest.get(oName) : new HashSet(); + if (dValues == null) { + dValues = new HashSet<>(); + } + Set oValues = orig.get(oName); + if (oValues != null) { + dValues.addAll(oValues); + } + dest.put(oName, dValues); + } + return dest; + } + + public static JSONArray convertTagsToJSONArray(Map> tags){ + JSONArray result = new JSONArray(); + for (String name : tags.keySet()) { + Set values = tags.get(name); + for (String value : values){ + result.add(String.format("%s:%s", name, value)); + } + } + return result; + } +} diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly index 4591372b..92ff33ac 100644 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly @@ -13,30 +13,43 @@ so it should be straightforward to find them. --> - + - + + - + + - + + - + + + + + + - + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-apiKeyTitle.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-apiKeyEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-apiKeyTitle.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-apiKeyEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalJobTags.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalJobTagsEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalJobTags.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalJobTagsEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalTagsEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalTagsEntry.html new file mode 100644 index 00000000..f2bf67a3 --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-globalTagsEntry.html @@ -0,0 +1,6 @@ +
+ Enter a comma separated list of tags that should be applied to all metrics, events, logs, service checks submitted by this Datadog Plugin. E.g.: +
+        instance:east, language:french
+    
+
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-hostname.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-hostnameEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-hostname.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-hostnameEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURL.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURLEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURL.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURLEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/config.jelly index d2998e96..506df97c 100644 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/config.jelly +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/config.jelly @@ -1,21 +1,23 @@ - - - - - - + + + - - - + + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFile.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFileEntry.html similarity index 89% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFile.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFileEntry.html index 49a815a0..e7167cf0 100644 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFile.html +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagFileEntry.html @@ -1,5 +1,5 @@
-

Read a file from workspace as a newline seperated list of tags to push to Datadog. Location is relative to workspace.

+

Read a file from workspace as a newline separated list of tags to push to Datadog. Location is relative to workspace.

Example: os=${OS_PARAM}.

The value can either be something that is automatically expanded by Jenkins (environment and/or build variable), or a static value. The OS_PARAM in this case is expanded by Jenkins.

Note: Tags set this way, in the workspace, won't appear in started events. To provide visibility of a jobs lifespan, check the option for sending an extra event after each successful checkout.

diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagProperties.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagPropertiesEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagProperties.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogJobProperty/help-tagPropertiesEntry.html diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogUtilitiesTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogUtilitiesTest.java index 9ce4700e..eb0d943a 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogUtilitiesTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogUtilitiesTest.java @@ -46,93 +46,4 @@ public void testLinesToList(){ Assert.assertTrue(DatadogUtilities.linesToList(" \n item1 \n item2 \n ").equals(items)); } - @Test - public void testMerge(){ - Map> emptyTags = new HashMap<>(); - Assert.assertTrue(DatadogUtilities.merge(null, null).equals(new HashMap>())); - Assert.assertTrue(DatadogUtilities.merge(emptyTags, null).equals(new HashMap>())); - Assert.assertTrue(DatadogUtilities.merge(null, emptyTags).equals(new HashMap>())); - - Map> assertionTags = new HashMap<>(); - assertionTags.put("name1", new HashSet()); - Map> nullTagValue = new HashMap<>(); - nullTagValue.put("name1", null); - Assert.assertTrue(DatadogUtilities.merge(null, nullTagValue).equals(assertionTags)); - Assert.assertTrue(DatadogUtilities.merge(nullTagValue, nullTagValue).equals(assertionTags)); - Assert.assertTrue(DatadogUtilities.merge(nullTagValue, null).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(nullTagValue, emptyTags).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTags, nullTagValue).toString().equals(assertionTags.toString())); - Map> emptyTagValue = new HashMap<>(); - emptyTagValue.put("name1", new HashSet()); - Assert.assertTrue(DatadogUtilities.merge(emptyTagValue, null).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(null, emptyTagValue).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTagValue, emptyTags).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTags, emptyTagValue).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(nullTagValue, emptyTagValue).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTagValue, nullTagValue).toString().equals(assertionTags.toString())); - Map> n1v1Tag = new HashMap<>(); - Set v1 = new HashSet<>(); - v1.add("value1"); - n1v1Tag.put("name1", v1); - assertionTags = new HashMap<>(); - Set assertionV1 = new HashSet<>(); - assertionV1.add("value1"); - assertionTags.put("name1", assertionV1); - Assert.assertTrue(DatadogUtilities.merge(n1v1Tag, null).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(null, n1v1Tag).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(n1v1Tag, emptyTags).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTags, n1v1Tag).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(nullTagValue, n1v1Tag).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(n1v1Tag, nullTagValue).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(n1v1Tag, emptyTagValue).toString().equals(assertionTags.toString())); - Assert.assertTrue(DatadogUtilities.merge(emptyTagValue, n1v1Tag).toString().equals(assertionTags.toString())); - - Map> n1v1TagCopy = new HashMap<>(); - Set v1Copy = new HashSet<>(); - v1Copy.add("value1"); - n1v1TagCopy.put("name1", v1Copy); - Assert.assertTrue(DatadogUtilities.merge(n1v1TagCopy, n1v1Tag).toString().equals(assertionTags.toString())); - - Map> n1v2Tag = new HashMap<>(); - Set v2 = new HashSet<>(); - v2.add("value2"); - n1v2Tag.put("name1", v2); - assertionTags = new HashMap<>(); - Set assertionValues = new HashSet<>(); - assertionValues.add("value1"); - assertionValues.add("value2"); - assertionTags.put("name1", assertionValues); - Assert.assertTrue(DatadogUtilities.merge(n1v2Tag, n1v1Tag).toString().equals(assertionTags.toString())); - - Map> n2v1Tag = new HashMap<>(); - v1 = new HashSet<>(); - v1.add("value1"); - n2v1Tag.put("name2", v1); - assertionTags = new HashMap<>(); - assertionV1 = new HashSet<>(); - assertionV1.add("value1"); - assertionTags.put("name1", assertionV1); - assertionV1.add("value1"); - assertionTags.put("name2", assertionV1); - Assert.assertTrue(DatadogUtilities.merge(n2v1Tag, n1v1Tag).toString().equals(assertionTags.toString())); - - n2v1Tag = new HashMap<>(); - v1 = new HashSet<>(); - v1.add("value1"); - n2v1Tag.put("name2", v1); - Map> n2v1v2Tag = new HashMap<>(); - Set v1v2 = new HashSet<>(); - v1v2.add("value1"); - v1v2.add("value2"); - n2v1v2Tag.put("name2", v1v2); - assertionTags = new HashMap<>(); - assertionValues = new HashSet<>(); - assertionValues.add("value1"); - assertionValues.add("value2"); - assertionTags.put("name2", assertionValues); - Assert.assertTrue(DatadogUtilities.merge(n2v1Tag, n2v1v2Tag).toString() + " - "+ assertionTags.toString(), - DatadogUtilities.merge(n2v1Tag, n2v1v2Tag).toString().equals(assertionTags.toString())); - - } - } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java index 5446d9c7..8be85732 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientStub.java @@ -1,18 +1,13 @@ package org.datadog.jenkins.plugins.datadog.clients; -import hudson.Util; import hudson.util.Secret; -import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.junit.Assert; import javax.servlet.ServletException; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; public class DatadogClientStub implements DatadogClient { @@ -41,30 +36,16 @@ public boolean sendEvent(JSONObject payload) { } @Override - public void incrementCounter(String name, String hostname, JSONArray tags) { + public void incrementCounter(String name, String hostname, Map> tags) { for (DatadogMetric m : this.metrics) { - if(Objects.equals(m.getName(), name) && Objects.equals(m.getHostname(), hostname) && - equalsTags(m.getTags(),tags)) { + if(m.same(new DatadogMetric(name, 0, hostname, convertTagMapToList(tags)))) { double value = m.getValue() + 1; this.metrics.remove(m); - this.metrics.add(new DatadogMetric(name, value, hostname, tags)); + this.metrics.add(new DatadogMetric(name, value, hostname, convertTagMapToList(tags))); return; } } - this.metrics.add(new DatadogMetric(name, 1, hostname, tags)); - } - - public static boolean equalsTags(JSONArray j1, JSONArray j2){ - if(j1.size() != j2.size()){ - return false; - } - for (Object j: j1){ - String j1s = (String)j; - if(!j2.contains(j1s)){ - return false; - } - } - return true; + this.metrics.add(new DatadogMetric(name, 1, hostname, convertTagMapToList(tags))); } @Override @@ -73,14 +54,14 @@ public void flushCounters() { } @Override - public boolean gauge(String name, long value, String hostname, JSONArray tags) { - this.metrics.add(new DatadogMetric(name, value, hostname, tags)); + public boolean gauge(String name, long value, String hostname, Map> tags) { + this.metrics.add(new DatadogMetric(name, value, hostname, convertTagMapToList(tags))); return true; } @Override - public boolean serviceCheck(String name, int code, String hostname, JSONArray tags) { - this.serviceChecks.add(new DatadogMetric(name, code, hostname, tags)); + public boolean serviceCheck(String name, int code, String hostname, Map> tags) { + this.serviceChecks.add(new DatadogMetric(name, code, hostname, convertTagMapToList(tags))); return true; } @@ -90,11 +71,7 @@ public boolean validate() throws IOException, ServletException { } public boolean assertMetric(String name, double value, String hostname, String[] tags) { - JSONArray jtags = new JSONArray(); - if (tags != null) { - jtags.addAll(Arrays.asList(tags)); - } - DatadogMetric m = new DatadogMetric(name, value, hostname, jtags); + DatadogMetric m = new DatadogMetric(name, value, hostname, Arrays.asList(tags)); if (this.metrics.contains(m)) { this.metrics.remove(m); return true; @@ -105,11 +82,7 @@ public boolean assertMetric(String name, double value, String hostname, String[] } public boolean assertServiceCheck(String name, int code, String hostname, String[] tags) { - JSONArray jtags = new JSONArray(); - if (tags != null) { - jtags.addAll(Arrays.asList(tags)); - } - DatadogMetric m = new DatadogMetric(name, code, hostname, jtags); + DatadogMetric m = new DatadogMetric(name, code, hostname, Arrays.asList(tags)); if (this.serviceChecks.contains(m)) { this.serviceChecks.remove(m); return true; @@ -128,4 +101,23 @@ public boolean assertedAllMetricsAndServiceChecks() { this.serviceChecks.toString() + "}"); return false; } + + public static List convertTagMapToList(Map> tags){ + List result = new ArrayList<>(); + for (String name : tags.keySet()) { + Set values = tags.get(name); + for (String value : values){ + result.add(String.format("%s:%s", name, value)); + } + } + return result; + + } + + public static Map> addTagToMap(Map> tags, String name, String value){ + Set v = tags.containsKey(name) ? tags.get(name) : new HashSet(); + v.add(value); + tags.put(name, v); + return tags; + } } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java index f4a910a9..7c4aa175 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java @@ -1,12 +1,14 @@ package org.datadog.jenkins.plugins.datadog.clients; -import hudson.util.Secret; import net.sf.json.JSONArray; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.junit.Assert; import org.junit.Test; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.concurrent.*; public class DatadogClientTest { @@ -15,16 +17,16 @@ public class DatadogClientTest { public void testIncrementCountAndFlush() throws IOException, InterruptedException { DatadogHttpClient.enableValidations = false; DatadogClient client = DatadogHttpClient.getInstance("test", null); - JSONArray tags1 = new JSONArray(); - tags1.add("tag1"); - tags1.add("tag2"); + Map> tags1 = new HashMap<>(); + tags1 = DatadogClientStub.addTagToMap(tags1, "tag1", "value"); + tags1 = DatadogClientStub.addTagToMap(tags1, "tag2", "value"); client.incrementCounter("metric1", "host1", tags1); client.incrementCounter("metric1", "host1", tags1); - JSONArray tags2 = new JSONArray(); - tags2.add("tag1"); - tags2.add("tag2"); - tags2.add("tag3"); + Map> tags2 = new HashMap<>(); + tags2 = DatadogClientStub.addTagToMap(tags2, "tag1", "value"); + tags2 = DatadogClientStub.addTagToMap(tags2, "tag2", "value"); + tags2 = DatadogClientStub.addTagToMap(tags2, "tag3", "value"); client.incrementCounter("metric1", "host1", tags2); client.incrementCounter("metric1", "host2", tags2); @@ -76,9 +78,9 @@ public void run() { // We use a new instance of a client on every run. DatadogHttpClient.enableValidations = false; DatadogClient client = DatadogHttpClient.getInstance("test", null); - JSONArray tags = new JSONArray(); - tags.add("tag1"); - tags.add("tag2"); + Map> tags = new HashMap<>(); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); client.incrementCounter("metric1", "host1", tags); } }; @@ -105,9 +107,9 @@ public void run() { // We use a new instance of a client on every run. DatadogHttpClient.enableValidations = false; DatadogClient client = DatadogHttpClient.getInstance("test", null); - JSONArray tags = new JSONArray(); - tags.add("tag1"); - tags.add("tag2"); + Map> tags = new HashMap<>(); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); client.incrementCounter("metric1", "host1", tags); } }; @@ -148,9 +150,9 @@ public void testIncrementCountAndFlushThreadedEnvOneClient() throws IOException, Runnable increment = new Runnable() { @Override public void run() { - JSONArray tags = new JSONArray(); - tags.add("tag1"); - tags.add("tag2"); + Map> tags = new HashMap<>(); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value"); client.incrementCounter("metric1", "host1", tags); } }; diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogMetric.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogMetric.java index 7490fbb2..e55662ab 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogMetric.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogMetric.java @@ -1,14 +1,15 @@ package org.datadog.jenkins.plugins.datadog.clients; -import net.sf.json.JSONArray; +import java.util.Collections; +import java.util.List; public class DatadogMetric { private String name = null; private double value; private String hostname = null; - private JSONArray tags = null; + private List tags = null; - DatadogMetric(String name, double value, String hostname, JSONArray tags) { + DatadogMetric(String name, double value, String hostname, List tags) { this.name = name; this.value = value; this.hostname = hostname; @@ -23,9 +24,23 @@ public boolean equals(Object o) { DatadogMetric that = (DatadogMetric) o; if (Double.compare(that.value, value) != 0) return false; - if (name != null ? !name.equals(that.name) : that.name != null) return false; - if (hostname != null ? !hostname.equals(that.hostname) : that.hostname != null) return false; - return tags != null ? tags.equals(that.tags) : that.tags == null; + return this.same(that); + } + + /** + * This method check that this object and the one passed as parameter are equal to the exception of the value field. + * @param m DatadogMetric object to compare to. + * @return true is both DatadogMetric are the same otherwise false + */ + public boolean same(DatadogMetric m) { + if (this == m) return true; + if (m == null) return false; + if (name != null ? !name.equals(m.name) : m.name != null) return false; + if (hostname != null ? !hostname.equals(m.hostname) : m.hostname != null) return false; + if (tags != null && m.tags == null) return false; + Collections.sort(tags); + Collections.sort(m.tags); + return tags != null ? tags.toString().equals(m.tags.toString()) : m.tags == null; } @Override @@ -62,7 +77,7 @@ public String getHostname() { return hostname; } - public JSONArray getTags() { + public List getTags() { return tags; } } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventTest.java index ee1ddcd7..b4093d56 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildFinishedEventTest.java @@ -5,6 +5,7 @@ import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.junit.Assert; import org.junit.Test; @@ -30,7 +31,7 @@ public void testWithNothingSet() throws IOException, InterruptedException { when(DatadogUtilities.getHostname(any(String.class))).thenReturn(null); ItemGroup parent = mock(ItemGroup.class); - when(parent.getFullName()).thenReturn(""); + when(parent.getFullName()).thenReturn(null); Job job = mock(Job.class); when(job.getParent()).thenReturn(parent); @@ -43,7 +44,7 @@ public void testWithNothingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); try { @@ -52,13 +53,13 @@ public void testWithNothingSet() throws IOException, InterruptedException { } catch (JSONException e) { //continue } - Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "")); + Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "unknown")); Assert.assertTrue(o.getLong("date_happened") != 0); Assert.assertTrue(o.getJSONArray("tags").size() == 1); - Assert.assertTrue(Objects.equals(o.getJSONArray("tags").getString(0), "job:")); + Assert.assertTrue(Objects.equals(o.getJSONArray("tags").getString(0), "job:unknown")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); - Assert.assertTrue(Objects.equals(o.getString("title"), " build #0 unknown on unknown")); - Assert.assertTrue(o.getString("text").contains("[See results for build #0](unknown) (0.00 secs)")); + Assert.assertTrue(Objects.equals(o.getString("title"), "unknown build #0 unknown on unknown")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Job unknown build #0](unknown) finished with status unknown (0.00 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "warning")); Assert.assertTrue(Objects.equals(o.getString("priority"), "normal")); } @@ -82,7 +83,7 @@ public void testWithNothingSet_parentFullName() throws IOException, InterruptedE TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/null")); @@ -110,7 +111,7 @@ public void testWithNothingSet_parentFullName_2() throws IOException, Interrupte TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parent/FullName/null")); @@ -138,7 +139,7 @@ public void testWithNothingSet_jobName() throws IOException, InterruptedExceptio TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/jobName")); @@ -166,7 +167,7 @@ public void testWithNothingSet_result_failure() throws IOException, InterruptedE TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Object[] sortedTags = o.getJSONArray("tags").toArray(); @@ -175,6 +176,7 @@ public void testWithNothingSet_result_failure() throws IOException, InterruptedE Assert.assertTrue(Objects.equals(sortedTags[0], "job:parentFullName/jobName")); Assert.assertTrue(Objects.equals(sortedTags[1], "result:FAILURE")); Assert.assertTrue(Objects.equals(o.getString("title"), "parentFullName/jobName build #0 failure on unknown")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Job parentFullName/jobName build #0](unknown) finished with status failure (0.00 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "error")); } @@ -197,7 +199,7 @@ public void testWithNothingSet_result_unstable() throws IOException, Interrupted TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Object[] sortedTags = o.getJSONArray("tags").toArray(); @@ -206,6 +208,7 @@ public void testWithNothingSet_result_unstable() throws IOException, Interrupted Assert.assertTrue(Objects.equals(sortedTags[0], "job:parentFullName/jobName")); Assert.assertTrue(Objects.equals(sortedTags[1], "result:UNSTABLE")); Assert.assertTrue(Objects.equals(o.getString("title"), "parentFullName/jobName build #0 unstable on unknown")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Job parentFullName/jobName build #0](unknown) finished with status unstable (0.00 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "warning")); } @@ -237,7 +240,7 @@ public void testWithEverythingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, null); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -252,7 +255,7 @@ public void testWithEverythingSet() throws IOException, InterruptedException { Assert.assertTrue(Objects.equals(sortedTags[3], "result:SUCCESS")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 success on test-hostname-1")); - Assert.assertTrue(o.getString("text").contains("[See results for build #2](http://build_url.com) (0.01 secs)")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Job ParentFullName/JobName build #2](http://build_url.com) finished with status success (0.01 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } @@ -285,13 +288,10 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt BuildData bd = new BuildData(run, listener); Map> tags = new HashMap<>(); - Set v1 = new HashSet<>(); - v1.add("value1"); - tags.put("tag1", v1); - Set v2 = new HashSet<>(); - v2.add("value2"); - tags.put("tag2", v2); - BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd, tags); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value1"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value2"); + bd.setTags(tags); + BuildFinishedEventImpl event = new BuildFinishedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -307,7 +307,7 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt Assert.assertTrue(Objects.equals(sortedTags[4], "tag2:value2")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 success on test-hostname-1")); - Assert.assertTrue(o.getString("text").contains("[See results for build #2](http://build_url.com) (0.01 secs)")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Job ParentFullName/JobName build #2](http://build_url.com) finished with status success (0.01 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventTest.java index 84e865ae..ed7ef289 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/events/BuildStartedEventTest.java @@ -5,6 +5,7 @@ import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.junit.Assert; import org.junit.Test; @@ -31,7 +32,7 @@ public void testWithNothingSet() throws IOException, InterruptedException { when(DatadogUtilities.getHostname(any(String.class))).thenReturn(null); ItemGroup parent = mock(ItemGroup.class); - when(parent.getFullName()).thenReturn(""); + when(parent.getFullName()).thenReturn(null); Job job = mock(Job.class); when(job.getParent()).thenReturn(parent); @@ -44,7 +45,7 @@ public void testWithNothingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); try { @@ -53,13 +54,13 @@ public void testWithNothingSet() throws IOException, InterruptedException { } catch (JSONException e) { //continue } - Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "")); + Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "unknown")); Assert.assertTrue(o.getLong("date_happened") != 0); Assert.assertTrue(o.getJSONArray("tags").size() == 1); - Assert.assertTrue(Objects.equals(o.getJSONArray("tags").getString(0), "job:")); + Assert.assertTrue(Objects.equals(o.getJSONArray("tags").getString(0), "job:unknown")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); - Assert.assertTrue(Objects.equals(o.getString("title"), " build #0 started on unknown")); - Assert.assertTrue(o.getString("text").contains("[Follow build #0 progress](unknown) (0.00 secs)")); + Assert.assertTrue(Objects.equals(o.getString("title"), "unknown build #0 started on unknown")); + Assert.assertTrue(o.getString("text").contains("User anonymous started the [job unknown build #0](unknown) (0.00 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } @@ -83,7 +84,7 @@ public void testWithNothingSet_parentFullName() throws IOException, InterruptedE TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/null")); @@ -111,7 +112,7 @@ public void testWithNothingSet_parentFullName_2() throws IOException, Interrupte TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parent/FullName/null")); @@ -139,7 +140,7 @@ public void testWithNothingSet_jobName() throws IOException, InterruptedExceptio TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/jobName")); @@ -167,7 +168,7 @@ public void testWithNothingSet_result() throws IOException, InterruptedException TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Object[] sortedTags = o.getJSONArray("tags").toArray(); @@ -206,7 +207,7 @@ public void testWithEverythingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, null); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -220,7 +221,7 @@ public void testWithEverythingSet() throws IOException, InterruptedException { Assert.assertTrue(Objects.equals(sortedTags[2], "node:test-node")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 started on test-hostname-1")); - Assert.assertTrue(o.getString("text").contains("[Follow build #2 progress](http://build_url.com) (0.01 secs)")); + Assert.assertTrue(o.getString("text").contains("User anonymous started the [job ParentFullName/JobName build #2](http://build_url.com) (0.01 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } @@ -252,13 +253,10 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt BuildData bd = new BuildData(run, listener); Map> tags = new HashMap<>(); - Set v1 = new HashSet<>(); - v1.add("value1"); - tags.put("tag1", v1); - Set v2 = new HashSet<>(); - v2.add("value2"); - tags.put("tag2", v2); - BuildStartedEventImpl event = new BuildStartedEventImpl(bd, tags); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value1"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value2"); + bd.setTags(tags); + BuildStartedEventImpl event = new BuildStartedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -273,7 +271,7 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt Assert.assertTrue(Objects.equals(sortedTags[3], "tag2:value2")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 started on test-hostname-1")); - Assert.assertTrue(o.getString("text"), o.getString("text").contains("[Follow build #2 progress](http://build_url.com) (0.01 secs)")); + Assert.assertTrue(o.getString("text"), o.getString("text").contains("User anonymous started the [job ParentFullName/JobName build #2](http://build_url.com) (0.01 secs)")); Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventTest.java similarity index 86% rename from src/test/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventTest.java rename to src/test/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventTest.java index 434103ff..c23412b5 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/events/CheckoutCompletedEventTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/events/SCMCheckoutCompletedEventTest.java @@ -5,6 +5,7 @@ import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; import org.datadog.jenkins.plugins.datadog.model.BuildData; import org.junit.Assert; import org.junit.Test; @@ -23,7 +24,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({DatadogUtilities.class}) -public class CheckoutCompletedEventTest { +public class SCMCheckoutCompletedEventTest { @Test public void testWithNothingSet() throws IOException, InterruptedException { @@ -31,7 +32,7 @@ public void testWithNothingSet() throws IOException, InterruptedException { when(DatadogUtilities.getHostname(any(String.class))).thenReturn(null); ItemGroup parent = mock(ItemGroup.class); - when(parent.getFullName()).thenReturn(""); + when(parent.getFullName()).thenReturn(null); Job job = mock(Job.class); when(job.getParent()).thenReturn(parent); @@ -44,23 +45,22 @@ public void testWithNothingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); try { - o.getString("host"); Assert.fail(o.getString("host")); } catch (JSONException e) { //continue } - Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "")); + Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "unknown")); Assert.assertTrue(o.getLong("date_happened") != 0); Assert.assertTrue(o.getJSONArray("tags").size() == 1); - Assert.assertTrue(Objects.equals(o.getJSONArray("tags").getString(0), "job:")); + Assert.assertTrue(o.getJSONArray("tags").toString(), Objects.equals(o.getJSONArray("tags").getString(0), "job:unknown")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); - Assert.assertTrue(Objects.equals(o.getString("title"), " build #0 checkout finished on unknown")); - Assert.assertTrue(o.getString("text").contains("[Follow build #0 progress](unknown) (0.00 secs)")); - Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); + Assert.assertTrue(o.getString("title"), Objects.equals(o.getString("title"), "unknown build #0 checkout finished on unknown")); + Assert.assertTrue(o.getString("text").contains("[Job unknown build #0](unknown) checkout finished successfully on unknown (0.00 secs)")); + Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } @@ -83,7 +83,7 @@ public void testWithNothingSet_parentFullName() throws IOException, InterruptedE TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/null")); @@ -111,7 +111,7 @@ public void testWithNothingSet_parentFullName_2() throws IOException, Interrupte TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parent/FullName/null")); @@ -139,7 +139,7 @@ public void testWithNothingSet_jobName() throws IOException, InterruptedExceptio TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("aggregation_key"), "parentFullName/jobName")); @@ -167,7 +167,7 @@ public void testWithNothingSet_result() throws IOException, InterruptedException TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Object[] sortedTags = o.getJSONArray("tags").toArray(); @@ -176,7 +176,7 @@ public void testWithNothingSet_result() throws IOException, InterruptedException Assert.assertTrue(Objects.equals(sortedTags[0], "job:parentFullName/jobName")); Assert.assertTrue(Objects.equals(sortedTags[1], "result:FAILURE")); Assert.assertTrue(Objects.equals(o.getString("title"), "parentFullName/jobName build #0 checkout finished on unknown")); - Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); + Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); } @Test @@ -206,7 +206,7 @@ public void testWithEverythingSet() throws IOException, InterruptedException { TaskListener listener = mock(TaskListener.class); BuildData bd = new BuildData(run, listener); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, null); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -220,8 +220,8 @@ public void testWithEverythingSet() throws IOException, InterruptedException { Assert.assertTrue(Objects.equals(sortedTags[2], "node:test-node")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 checkout finished on test-hostname-1")); - Assert.assertTrue(o.getString("text").contains("[Follow build #2 progress](http://build_url.com) (0.01 secs)")); - Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); + Assert.assertTrue(o.getString("text").contains("[Job ParentFullName/JobName build #2](http://build_url.com) checkout finished successfully on test-hostname-1 (0.01 secs)")); + Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } @@ -252,13 +252,10 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt BuildData bd = new BuildData(run, listener); Map> tags = new HashMap<>(); - Set v1 = new HashSet<>(); - v1.add("value1"); - tags.put("tag1", v1); - Set v2 = new HashSet<>(); - v2.add("value2"); - tags.put("tag2", v2); - CheckoutCompletedEventImpl event = new CheckoutCompletedEventImpl(bd, tags); + tags = DatadogClientStub.addTagToMap(tags, "tag1", "value1"); + tags = DatadogClientStub.addTagToMap(tags, "tag2", "value2"); + bd.setTags(tags); + SCMCheckoutCompletedEventImpl event = new SCMCheckoutCompletedEventImpl(bd); JSONObject o = event.createPayload(); Assert.assertTrue(Objects.equals(o.getString("host"), "test-hostname-1")); @@ -273,8 +270,8 @@ public void testWithEverythingSet_envVarsAndTags() throws IOException, Interrupt Assert.assertTrue(Objects.equals(sortedTags[3], "tag2:value2")); Assert.assertTrue(Objects.equals(o.getString("source_type_name"), "jenkins")); Assert.assertTrue(Objects.equals(o.getString("title"), "ParentFullName/JobName build #2 checkout finished on test-hostname-1")); - Assert.assertTrue(o.getString("text").contains("[Follow build #2 progress](http://build_url.com) (0.01 secs)")); - Assert.assertTrue(Objects.equals(o.getString("alert_type"), "info")); + Assert.assertTrue(o.getString("text").contains("[Job ParentFullName/JobName build #2](http://build_url.com) checkout finished successfully on test-hostname-1 (0.01 secs)")); + Assert.assertTrue(Objects.equals(o.getString("alert_type"), "success")); Assert.assertTrue(Objects.equals(o.getString("priority"), "low")); } } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java index 50c1bf31..96083a3a 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListenerTest.java @@ -5,7 +5,6 @@ import jenkins.model.Jenkins; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; -import org.datadog.jenkins.plugins.datadog.listeners.DatadogBuildListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,6 +13,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.util.HashMap; +import java.util.Set; + import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -26,10 +28,6 @@ public class DatadogBuildListenerTest { @Mock private Queue queue; - private DatadogClientStub client; - - public DatadogBuildListener datadogBuildListener; - @Before public void setUp() throws Exception { PowerMockito.mockStatic(Jenkins.class); @@ -44,12 +42,15 @@ public void setUp() throws Exception { @Test public void testOnCompletedWithNothing() throws Exception { - client = new DatadogClientStub(); - datadogBuildListener = mock(DatadogBuildListener.class); + DatadogClientStub client = new DatadogClientStub(); + DatadogBuildListener datadogBuildListener = new DatadogBuildListener(); when(DatadogUtilities.getDatadogClient()).thenReturn(client); + when(DatadogUtilities.getBuildTags(any(Run.class), any(TaskListener.class))). + thenReturn(new HashMap>()); + when(DatadogUtilities.getTagsFromGlobalTags()).thenReturn(new HashMap>()); ItemGroup parent = mock(ItemGroup.class); - when(parent.getFullName()).thenReturn(""); + when(parent.getFullName()).thenReturn(null); Job job = mock(Job.class); when(job.getParent()).thenReturn(parent); @@ -69,9 +70,12 @@ public void testOnCompletedWithNothing() throws Exception { @Test public void testOnCompletedOnSuccessfulRun() throws Exception { - client = new DatadogClientStub(); - datadogBuildListener = mock(DatadogBuildListener.class); + DatadogClientStub client = new DatadogClientStub(); + DatadogBuildListener datadogBuildListener = new DatadogBuildListener(); when(DatadogUtilities.getDatadogClient()).thenReturn(client); + when(DatadogUtilities.getBuildTags(any(Run.class), any(TaskListener.class))). + thenReturn(new HashMap>()); + when(DatadogUtilities.getTagsFromGlobalTags()).thenReturn(new HashMap>()); ItemGroup parent = mock(ItemGroup.class); when(parent.getFullName()).thenReturn("ParentFullName"); @@ -92,7 +96,7 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { when(previousSuccessfulRun.getDuration()).thenReturn(121000L); when(previousSuccessfulRun.getNumber()).thenReturn(1); when(previousSuccessfulRun.getParent()).thenReturn(job); - when(datadogBuildListener.getStartTimeInMillis(previousSuccessfulRun)).thenReturn(1000000L); + when(DatadogUtilities.getRunStartTimeInMillis(previousSuccessfulRun)).thenReturn(1000000L); Run previousFailedRun1 = mock(Run.class); when(previousFailedRun1.getResult()).thenReturn(Result.FAILURE); @@ -102,7 +106,7 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { when(previousFailedRun1.getParent()).thenReturn(job); when(previousFailedRun1.getPreviousBuiltBuild()).thenReturn(previousSuccessfulRun); when(previousFailedRun1.getPreviousSuccessfulBuild()).thenReturn(previousSuccessfulRun); - when(datadogBuildListener.getStartTimeInMillis(previousFailedRun1)).thenReturn(2000000L); + when(DatadogUtilities.getRunStartTimeInMillis(previousFailedRun1)).thenReturn(2000000L); Run previousFailedRun2 = mock(Run.class); when(previousFailedRun2.getResult()).thenReturn(Result.FAILURE); @@ -112,7 +116,7 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { when(previousFailedRun2.getParent()).thenReturn(job); when(previousFailedRun2.getPreviousBuiltBuild()).thenReturn(previousFailedRun1); when(previousFailedRun2.getPreviousSuccessfulBuild()).thenReturn(previousSuccessfulRun); - when(datadogBuildListener.getStartTimeInMillis(previousFailedRun2)).thenReturn(3000000L); + when(DatadogUtilities.getRunStartTimeInMillis(previousFailedRun2)).thenReturn(3000000L); Run successRun = mock(Run.class); when(successRun.getResult()).thenReturn(Result.SUCCESS); @@ -123,7 +127,7 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { when(successRun.getParent()).thenReturn(job); when(successRun.getPreviousBuiltBuild()).thenReturn(previousFailedRun2); when(successRun.getPreviousSuccessfulBuild()).thenReturn(previousSuccessfulRun); - when(datadogBuildListener.getStartTimeInMillis(successRun)).thenReturn(4000000L); + when(DatadogUtilities.getRunStartTimeInMillis(successRun)).thenReturn(4000000L); datadogBuildListener.onCompleted(previousSuccessfulRun, mock(TaskListener.class)); String[] expectedTags1 = new String[4]; @@ -135,6 +139,7 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { client.assertMetric("jenkins.job.leadtime", 121, "null", expectedTags1); client.assertServiceCheck("jenkins.job.status", 0, "null", expectedTags1); + when(DatadogUtilities.getTagsFromGlobalTags()).thenReturn(new HashMap>()); datadogBuildListener.onCompleted(previousFailedRun1, mock(TaskListener.class)); String[] expectedTags2 = new String[4]; expectedTags2[0] = "job:ParentFullName/JobName"; @@ -145,12 +150,14 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { client.assertMetric("jenkins.job.feedbacktime", 122, "null", expectedTags2); client.assertServiceCheck("jenkins.job.status", 2, "null", expectedTags2); + when(DatadogUtilities.getTagsFromGlobalTags()).thenReturn(new HashMap>()); datadogBuildListener.onCompleted(previousFailedRun2, mock(TaskListener.class)); client.assertMetric("jenkins.job.duration", 123, "null", expectedTags2); client.assertMetric("jenkins.job.feedbacktime", 123, "null", expectedTags2); client.assertMetric("jenkins.job.completed", 2, "null", expectedTags2); client.assertServiceCheck("jenkins.job.status", 2, "null", expectedTags2); + when(DatadogUtilities.getTagsFromGlobalTags()).thenReturn(new HashMap>()); datadogBuildListener.onCompleted(successRun, mock(TaskListener.class)); client.assertMetric("jenkins.job.duration", 124, "null", expectedTags1); client.assertMetric("jenkins.job.leadtime", 2124, "null", expectedTags1); @@ -164,8 +171,8 @@ public void testOnCompletedOnSuccessfulRun() throws Exception { @Test public void testOnCompletedOnFailedRun() throws Exception { - client = new DatadogClientStub(); - datadogBuildListener = mock(DatadogBuildListener.class); + DatadogClientStub client = new DatadogClientStub(); + DatadogBuildListener datadogBuildListener = new DatadogBuildListener(); when(DatadogUtilities.getDatadogClient()).thenReturn(client); ItemGroup parent = mock(ItemGroup.class); @@ -187,7 +194,7 @@ public void testOnCompletedOnFailedRun() throws Exception { when(previousSuccessfulRun.getDuration()).thenReturn(123000L); when(previousSuccessfulRun.getNumber()).thenReturn(1); when(previousSuccessfulRun.getParent()).thenReturn(job); - when(datadogBuildListener.getStartTimeInMillis(previousSuccessfulRun)).thenReturn(1000000L); + when(DatadogUtilities.getRunStartTimeInMillis(previousSuccessfulRun)).thenReturn(1000000L); Run failedRun = mock(Run.class); when(failedRun.getResult()).thenReturn(Result.FAILURE); @@ -196,7 +203,7 @@ public void testOnCompletedOnFailedRun() throws Exception { when(failedRun.getNumber()).thenReturn(2); when(failedRun.getParent()).thenReturn(job); when(failedRun.getPreviousNotFailedBuild()).thenReturn(previousSuccessfulRun); - when(datadogBuildListener.getStartTimeInMillis(failedRun)).thenReturn(2000000L); + when(DatadogUtilities.getRunStartTimeInMillis(failedRun)).thenReturn(2000000L); datadogBuildListener.onCompleted(previousSuccessfulRun, mock(TaskListener.class)); String[] expectedTags1 = new String[4]; @@ -226,10 +233,10 @@ public void testOnCompletedOnFailedRun() throws Exception { @Test public void testOnStarted() throws Exception { - client = new DatadogClientStub(); - datadogBuildListener = mock(DatadogBuildListener.class); + DatadogClientStub client = new DatadogClientStub(); + DatadogBuildListener datadogBuildListener = new DatadogBuildListener(); when(DatadogUtilities.getDatadogClient()).thenReturn(client); - when(datadogBuildListener.currentTimeMillis()).thenReturn(3000000L); + when(DatadogUtilities.currentTimeMillis()).thenReturn(3000000L); ItemGroup parent = mock(ItemGroup.class); when(parent.getFullName()).thenReturn("ParentFullName"); @@ -263,7 +270,7 @@ public void testOnStarted() throws Exception { expectedTags[1] = "node:test-node"; expectedTags[2] = "result:SUCCESS"; expectedTags[3] = "branch:test-branch"; - client.assertMetric("jenkins.job.waiting", 1000, "null", expectedTags); + client.assertMetric("jenkins.job.waiting", 1000.0, "null", expectedTags); client.assertMetric("jenkins.job.started", 1, "null", expectedTags); client.assertedAllMetricsAndServiceChecks(); } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/util/TagsUtilTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/util/TagsUtilTest.java new file mode 100644 index 00000000..3a11933f --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/util/TagsUtilTest.java @@ -0,0 +1,80 @@ +package org.datadog.jenkins.plugins.datadog.util; + +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogClientStub; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; + +public class TagsUtilTest { + + @Test + public void testMerge(){ + Map> emptyTags = new HashMap<>(); + Assert.assertTrue(TagsUtil.merge(null, null).equals(new HashMap>())); + Assert.assertTrue(TagsUtil.merge(emptyTags, null).equals(new HashMap>())); + Assert.assertTrue(TagsUtil.merge(null, emptyTags).equals(new HashMap>())); + + Map> assertionTags = new HashMap<>(); + assertionTags.put("name1", new HashSet()); + Map> nullTagValue = new HashMap<>(); + nullTagValue.put("name1", null); + Assert.assertTrue(TagsUtil.merge(null, nullTagValue).equals(assertionTags)); + Assert.assertTrue(TagsUtil.merge(nullTagValue, nullTagValue).equals(assertionTags)); + Assert.assertTrue(TagsUtil.merge(nullTagValue, null).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(nullTagValue, emptyTags).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTags, nullTagValue).toString().equals(assertionTags.toString())); + Map> emptyTagValue = new HashMap<>(); + emptyTagValue.put("name1", new HashSet()); + Assert.assertTrue(TagsUtil.merge(emptyTagValue, null).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(null, emptyTagValue).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTagValue, emptyTags).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTags, emptyTagValue).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(nullTagValue, emptyTagValue).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTagValue, nullTagValue).toString().equals(assertionTags.toString())); + Map> n1v1Tag = new HashMap<>(); + n1v1Tag = DatadogClientStub.addTagToMap(n1v1Tag, "name1", "value1"); + assertionTags = new HashMap<>(); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name1", "value1"); + Assert.assertTrue(TagsUtil.merge(n1v1Tag, null).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(null, n1v1Tag).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(n1v1Tag, emptyTags).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTags, n1v1Tag).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(nullTagValue, n1v1Tag).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(n1v1Tag, nullTagValue).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(n1v1Tag, emptyTagValue).toString().equals(assertionTags.toString())); + Assert.assertTrue(TagsUtil.merge(emptyTagValue, n1v1Tag).toString().equals(assertionTags.toString())); + + Map> n1v1TagCopy = new HashMap<>(); + n1v1TagCopy = DatadogClientStub.addTagToMap(n1v1TagCopy, "name1", "value1"); + Assert.assertTrue(TagsUtil.merge(n1v1TagCopy, n1v1Tag).toString().equals(assertionTags.toString())); + + Map> n1v2Tag = new HashMap<>(); + n1v2Tag = DatadogClientStub.addTagToMap(n1v2Tag, "name1", "value2"); + assertionTags = new HashMap<>(); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name1", "value1"); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name1", "value2"); + Assert.assertTrue(TagsUtil.merge(n1v2Tag, n1v1Tag).toString().equals(assertionTags.toString())); + + Map> n2v1Tag = new HashMap<>(); + n2v1Tag = DatadogClientStub.addTagToMap(n2v1Tag, "name2", "value1"); + assertionTags = new HashMap<>(); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name1", "value1"); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name2", "value1"); + Assert.assertTrue(TagsUtil.merge(n2v1Tag, n1v1Tag).toString().equals(assertionTags.toString())); + + n2v1Tag = new HashMap<>(); + n2v1Tag = DatadogClientStub.addTagToMap(n2v1Tag, "name2", "value1"); + Map> n2v1v2Tag = new HashMap<>(); + n2v1v2Tag = DatadogClientStub.addTagToMap(n2v1v2Tag, "name2", "value1"); + n2v1v2Tag = DatadogClientStub.addTagToMap(n2v1v2Tag, "name2", "value2"); + assertionTags = new HashMap<>(); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name2", "value1"); + assertionTags = DatadogClientStub.addTagToMap(assertionTags, "name2", "value2"); + Assert.assertTrue(TagsUtil.merge(n2v1Tag, n2v1v2Tag).toString() + " - "+ assertionTags.toString(), + TagsUtil.merge(n2v1Tag, n2v1v2Tag).toString().equals(assertionTags.toString())); + + } + +}