diff --git a/README.md b/README.md index 81317be9..9ac7bb30 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Contributing Adding support for new indexers ------------------------------- -* Create a new class in the package `jenkins.plugins.logstash.persistence` that extends `AbstractLogstashIndexerDao` -* Add a new entry to the enum `IndexerType` in `LogstashIndexerDao` -* Add a new mapping to the `INDEXER_MAP` in `IndexerDaoFactory` +* Implement the extension point `jenkins.plugins.logstash.configuration.LogstashIndexer` that will take your configuration. +Override the method `shouldRefreshInstance()` where you decide if a new dao instance must be created because the configuration has changed in the meantime. +* Create a `configure-advanced.jelly` for the UI part of your configuration. +* Create a new class that extends `jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao`. This class will do the actual work of pushing the logs to the indexer. diff --git a/pom.xml b/pom.xml index d34c0ab6..54a32713 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,9 @@ UTF-8 true + 2.23 7 - 1.608 + 2.7.4 @@ -54,7 +55,7 @@ 2.0.0-SNAPSHOT Logstash A Logstash agent to send Jenkins logs to a Logstash indexer. - https://wiki.jenkins-ci.org/display/JENKINS/Logstash+Plugin + https://plugins.jenkins.io/logstash @@ -88,7 +89,7 @@ org.mockito mockito-core - 2.10.0 + 2.13.0 test @@ -142,6 +143,16 @@ + + + org.jenkins-ci.tools + maven-hpi-plugin + true + + 2.0 + + + org.apache.maven.plugins diff --git a/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java b/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java new file mode 100644 index 00000000..bbd68a7c --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java @@ -0,0 +1,182 @@ +package jenkins.plugins.logstash; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.CheckForNull; + +import org.kohsuke.stapler.StaplerRequest; + +import com.cloudbees.syslog.MessageFormat; + +import org.apache.http.client.utils.URIBuilder; +import hudson.Extension; +import hudson.init.InitMilestone; +import hudson.init.Initializer; +import jenkins.model.GlobalConfiguration; +import jenkins.plugins.logstash.LogstashInstallation.Descriptor; +import jenkins.plugins.logstash.configuration.ElasticSearch; +import jenkins.plugins.logstash.configuration.LogstashIndexer; +import jenkins.plugins.logstash.configuration.RabbitMq; +import jenkins.plugins.logstash.configuration.Redis; +import jenkins.plugins.logstash.configuration.Syslog; +import jenkins.plugins.logstash.persistence.LogstashIndexerDao; +import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; +import net.sf.json.JSONObject; + +@Extension +public class LogstashConfiguration extends GlobalConfiguration +{ + private static final Logger LOGGER = Logger.getLogger(LogstashConfiguration.class.getName()); + private LogstashIndexer logstashIndexer; + private boolean dataMigrated = false; + private transient LogstashIndexer activeIndexer; + + public LogstashConfiguration() + { + load(); + activeIndexer = logstashIndexer; + } + + /** + * Returns the current logstash indexer configuration. + * + * @return configuration instance + */ + public LogstashIndexer getLogstashIndexer() + { + return logstashIndexer; + } + + public void setLogstashIndexer(LogstashIndexer logstashIndexer) + { + this.logstashIndexer = logstashIndexer; + } + + /** + * Returns the actual instance of the logstash dao. + * @return dao instance + */ + @CheckForNull + public LogstashIndexerDao getIndexerInstance() + { + if (activeIndexer != null) + { + return activeIndexer.getInstance(); + } + return null; + } + + public List getIndexerTypes() + { + return LogstashIndexer.all(); + } + + @SuppressWarnings("deprecation") + @Initializer(after = InitMilestone.JOB_LOADED) + public void migrateData() + { + if (!dataMigrated) + { + Descriptor descriptor = LogstashInstallation.getLogstashDescriptor(); + if (descriptor.getType() != null) + { + IndexerType type = descriptor.getType(); + switch (type) + { + case REDIS: + LOGGER.log(Level.INFO, "Migrating logstash configuration for Redis"); + Redis redis = new Redis(); + redis.setHost(descriptor.getHost()); + redis.setPort(descriptor.getPort()); + redis.setKey(descriptor.getKey()); + redis.setPassword(descriptor.getPassword()); + logstashIndexer = redis; + break; + case ELASTICSEARCH: + LOGGER.log(Level.INFO, "Migrating logstash configuration for Elastic Search"); + URI uri; + try + { + uri = (new URIBuilder(descriptor.getHost())) + .setPort(descriptor.getPort()) + .setPath("/" + descriptor.getKey()).build(); + ElasticSearch es = new ElasticSearch(); + es.setUri(uri); + es.setUsername(descriptor.getUsername()); + es.setPassword(descriptor.getPassword()); + logstashIndexer = es; + } + catch (URISyntaxException e) + { + LOGGER.log(Level.INFO, "Migrating logstash configuration for Elastic Search failed: " + e.toString()); + } + break; + case RABBIT_MQ: + LOGGER.log(Level.INFO, "Migrating logstash configuration for RabbitMQ"); + RabbitMq rabbitMq = new RabbitMq(); + rabbitMq.setHost(descriptor.getHost()); + rabbitMq.setPort(descriptor.getPort()); + rabbitMq.setQueue(descriptor.getKey()); + rabbitMq.setUsername(descriptor.getUsername()); + rabbitMq.setPassword(descriptor.getPassword()); + logstashIndexer = rabbitMq; + break; + case SYSLOG: + LOGGER.log(Level.INFO, "Migrating logstash configuration for SYSLOG"); + Syslog syslog = new Syslog(); + syslog.setHost(descriptor.getHost()); + syslog.setPort(descriptor.getPort()); + syslog.setSyslogProtocol(descriptor.getSyslogProtocol()); + switch (descriptor.getSyslogFormat()) + { + case RFC3164: + syslog.setMessageFormat(MessageFormat.RFC_3164); + break; + case RFC5424: + syslog.setMessageFormat(MessageFormat.RFC_5424); + break; + default: + syslog.setMessageFormat(MessageFormat.RFC_3164); + break; + } + logstashIndexer = syslog; + break; + default: + LOGGER.log(Level.INFO, "unknown logstash Indexer type: " + type); + break; + } + activeIndexer = logstashIndexer; + } + dataMigrated = true; + save(); + } + } + + @Override + public boolean configure(StaplerRequest staplerRequest, JSONObject json) throws FormException + { + // when we bind the stapler request we get a new instance of logstashIndexer. + // logstashIndexer is holder for the dao instance. + // To avoid that we get a new dao instance in case there was no change in configuration + // we compare it to the currently active configuration. + staplerRequest.bindJSON(this, json); + if (!Objects.equals(logstashIndexer, activeIndexer)) + { + activeIndexer = logstashIndexer; + } + save(); + return true; + } + + public static LogstashConfiguration getInstance() + { + return GlobalConfiguration.all().get(LogstashConfiguration.class); + } + +} diff --git a/src/main/java/jenkins/plugins/logstash/LogstashInstallation.java b/src/main/java/jenkins/plugins/logstash/LogstashInstallation.java index cdac10a7..98b6de92 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashInstallation.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashInstallation.java @@ -28,7 +28,6 @@ import hudson.tools.ToolDescriptor; import hudson.tools.ToolProperty; import hudson.tools.ToolInstallation; -import hudson.util.FormValidation; import java.util.List; @@ -36,12 +35,8 @@ import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogFormat; import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogProtocol; -import net.sf.json.JSONObject; -import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -59,156 +54,79 @@ public LogstashInstallation(String name, String home, List { - private IndexerType type; - private SyslogFormat syslogFormat; - @SuppressFBWarnings(value="UUF_UNUSED_FIELD") - private SyslogProtocol syslogProtocol; - private String host; - private Integer port = -1; - private String username; - private String password; - private String key; + private transient IndexerType type; + private transient SyslogFormat syslogFormat; + private transient SyslogProtocol syslogProtocol; + private transient String host; + private transient Integer port = -1; + private transient String username; + private transient String password; + private transient String key; public Descriptor() { super(); load(); } - @Override - public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { - req.bindJSON(this, formData.getJSONObject("logstash")); - save(); - return super.configure(req, formData); - } - - @Override - public ToolInstallation newInstance(StaplerRequest req, JSONObject formData) throws FormException { - req.bindJSON(this, formData.getJSONObject("logstash")); - save(); - return super.newInstance(req, formData); - } @Override public String getDisplayName() { return Messages.DisplayName(); } - /* - * Form validation methods - */ - public FormValidation doCheckInteger(@QueryParameter("value") String value) { - try { - Integer.parseInt(value); - } catch (NumberFormatException e) { - return FormValidation.error(Messages.ValueIsInt()); - } - - return FormValidation.ok(); - } - - public FormValidation doCheckHost(@QueryParameter("value") String value) { - if (StringUtils.isBlank(value)) { - return FormValidation.warning(Messages.PleaseProvideHost()); - } - - return FormValidation.ok(); - } - - public FormValidation doCheckString(@QueryParameter("value") String value) { - if (StringUtils.isBlank(value)) { - return FormValidation.error(Messages.ValueIsRequired()); - } - - return FormValidation.ok(); - } public IndexerType getType() { return type; } - public void setType(IndexerType type) - { - this.type = type; - } public SyslogFormat getSyslogFormat() { return syslogFormat; } - public void setSyslogFormat(SyslogFormat syslogFormat) - { - this.syslogFormat = syslogFormat; - } public SyslogProtocol getSyslogProtocol() { return syslogProtocol; } - public void setSyslogProtocol(SyslogProtocol syslogProtocol) - { - this.syslogProtocol = syslogProtocol; - } public String getHost() { return host; } - public void setHost(String host) - { - this.host = host; - } public Integer getPort() { return port; } - public void setPort(Integer port) - { - this.port = port; - } public String getUsername() { return username; } - public void setUsername(String username) - { - this.username = username; - } public String getPassword() { return password; } - public void setPassword(String password) - { - this.password = password; - } public String getKey() { return key; } - public void setKey(String key) - { - this.key = key; - } - } } diff --git a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java index 60599a10..c60652c2 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java @@ -30,7 +30,6 @@ import hudson.model.Run; import jenkins.model.Jenkins; import jenkins.plugins.logstash.persistence.BuildData; -import jenkins.plugins.logstash.persistence.IndexerDaoFactory; import jenkins.plugins.logstash.persistence.LogstashIndexerDao; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; @@ -65,6 +64,9 @@ public class LogstashWriter { private boolean connectionBroken; private Charset charset; + /* + * TODO: the charset must not be transfered to the dao. The dao is shared between different build. + */ public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset) { this.errorStream = error != null ? error : System.err; this.build = run; @@ -149,10 +151,8 @@ public boolean isConnectionBroken() { } // Method to encapsulate calls for unit-testing - LogstashIndexerDao createDao() throws InstantiationException { - LogstashInstallation.Descriptor descriptor = LogstashInstallation.getLogstashDescriptor(); - return IndexerDaoFactory.getInstance(descriptor.getType(), descriptor.getHost(), descriptor.getPort(), - descriptor.getKey(), descriptor.getUsername(), descriptor.getPassword()); + LogstashIndexerDao getIndexerDao() { + return LogstashConfiguration.getInstance().getIndexerInstance(); } BuildData getBuildData() { @@ -163,8 +163,6 @@ BuildData getBuildData() { } } - @SuppressFBWarnings(value="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", - justification="Jenkins 2.0 will never return null. So wait for upgrade.") String getJenkinsUrl() { return Jenkins.getInstance().getRootUrl(); } @@ -177,7 +175,7 @@ private void write(List lines) { try { dao.push(payload.toString()); } catch (IOException e) { - String msg = "[logstash-plugin]: Failed to send log data to " + dao.getIndexerType() + ":" + dao.getDescription() + ".\n" + + String msg = "[logstash-plugin]: Failed to send log data: " + dao.getDescription() + ".\n" + "[logstash-plugin]: No Further logs will be sent to " + dao.getDescription() + ".\n" + ExceptionUtils.getStackTrace(e); logErrorMessage(msg); @@ -192,8 +190,13 @@ private void write(List lines) { */ private LogstashIndexerDao getDaoOrNull() { try { - return createDao(); - } catch (InstantiationException e) { + LogstashIndexerDao dao = getIndexerDao(); + if (dao == null) + { + logErrorMessage("[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"); + } + return dao; + } catch (IllegalArgumentException e) { String msg = ExceptionUtils.getMessage(e) + "\n" + "[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"; @@ -215,4 +218,5 @@ private void logErrorMessage(String msg) { ex.printStackTrace(); } } + } diff --git a/src/main/java/jenkins/plugins/logstash/PluginImpl.java b/src/main/java/jenkins/plugins/logstash/PluginImpl.java index d11216af..e7c15a6e 100644 --- a/src/main/java/jenkins/plugins/logstash/PluginImpl.java +++ b/src/main/java/jenkins/plugins/logstash/PluginImpl.java @@ -1,18 +1,18 @@ /* * The MIT License - * + * * Copyright 2013 Hewlett-Packard Development Company, L.P. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -27,9 +27,14 @@ import java.util.logging.Logger; +/* + * TODO: do we really need this class? + * All it does is printing a message at startup of Jenkins. + */ public class PluginImpl extends Plugin { private final static Logger LOG = Logger.getLogger(PluginImpl.class.getName()); + @Override public void start() throws Exception { LOG.info("Logstash: a logstash agent to send jenkins logs to a logstash indexer."); } diff --git a/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java new file mode 100644 index 00000000..f5f1a7db --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java @@ -0,0 +1,167 @@ +package jenkins.plugins.logstash.configuration; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; + +import hudson.Extension; +import hudson.util.FormValidation; +import hudson.util.Secret; +import jenkins.plugins.logstash.Messages; +import jenkins.plugins.logstash.persistence.ElasticSearchDao; + +public class ElasticSearch extends LogstashIndexer +{ + private String username; + private Secret password; + private URI uri; + + @DataBoundConstructor + public ElasticSearch() + { + } + + public URI getUri() + { + return uri; + } + + + /* + * We use URL for the setter as stapler can autoconvert a string to a URL but not to a URI + */ + @DataBoundSetter + public void setUri(URL url) throws URISyntaxException + { + this.uri = url.toURI(); + } + + public void setUri(URI uri) throws URISyntaxException + { + this.uri = uri; + } + + public String getUsername() + { + return username; + } + + @DataBoundSetter + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return Secret.toString(password); + } + + @DataBoundSetter + public void setPassword(String password) + { + this.password = Secret.fromString(password); + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + return false; + if (this == obj) + return true; + if (getClass() != obj.getClass()) + return false; + ElasticSearch other = (ElasticSearch) obj; + if (!Secret.toString(password).equals(other.getPassword())) + { + return false; + } + if (uri == null) + { + if (other.uri != null) + return false; + } + else if (!uri.equals(other.uri)) + { + return false; + } + if (username == null) + { + if (other.username != null) + return false; + } + else if (!username.equals(other.username)) + { + return false; + } + return true; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((uri == null) ? 0 : uri.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + Secret.toString(password).hashCode(); + return result; + } + + @Override + public ElasticSearchDao createIndexerInstance() + { + return new ElasticSearchDao(getUri(), username, Secret.toString(password)); + } + + @Extension + public static class ElasticSearchDescriptor extends LogstashIndexerDescriptor + { + @Override + public String getDisplayName() + { + return "Elastic Search"; + } + + @Override + public int getDefaultPort() + { + return 0; + } + + public FormValidation doCheckUrl(@QueryParameter("value") String value) + { + if (StringUtils.isBlank(value)) + { + return FormValidation.warning(Messages.PleaseProvideHost()); + } + try + { + URL url = new URL(value); + + if (url.getUserInfo() != null) + { + return FormValidation.error("Please specify user and password not as part of the url."); + } + + if(StringUtils.isBlank(url.getPath()) || url.getPath().trim().matches("^\\/+$")) { + return FormValidation.warning("Elastic Search requires a key to be able to index the logs."); + } + + url.toURI(); + } + catch (MalformedURLException | URISyntaxException e) + { + return FormValidation.error(e.getMessage()); + } + return FormValidation.ok(); + } + } +} diff --git a/src/main/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer.java b/src/main/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer.java new file mode 100644 index 00000000..055551a6 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer.java @@ -0,0 +1,83 @@ +package jenkins.plugins.logstash.configuration; + +import org.kohsuke.stapler.DataBoundSetter; + +import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; + +public abstract class HostBasedLogstashIndexer extends LogstashIndexer +{ + private String host; + private int port; + + /** + * Returns the host for connecting to the indexer. + * + * @return Host of the indexer + */ + public String getHost() + { + return host; + } + + /** + * Sets the host for connecting to the indexer. + * + * @param host + * host to connect to. + */ + @DataBoundSetter + public void setHost(String host) + { + this.host = host; + } + + /** + * Returns the port for connecting to the indexer. + * + * @return Port of the indexer + */ + public int getPort() + { + return port; + } + + /** + * Sets the port used for connecting to the indexer + * + * @param port + * The port of the indexer + */ + @DataBoundSetter + public void setPort(int port) + { + this.port = port; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + port; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + HostBasedLogstashIndexer other = (HostBasedLogstashIndexer) obj; + if (host == null) { + if (other.host != null) + return false; + } else if (!host.equals(other.host)) + return false; + if (port != other.port) + return false; + return true; + } +} diff --git a/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java b/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java new file mode 100644 index 00000000..6f3955b5 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java @@ -0,0 +1,107 @@ +package jenkins.plugins.logstash.configuration; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; + +import hudson.DescriptorExtensionList; +import hudson.ExtensionPoint; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import hudson.model.ReconfigurableDescribable; +import hudson.model.Descriptor.FormException; +import hudson.util.FormValidation; +import jenkins.model.Jenkins; +import jenkins.plugins.logstash.Messages; +import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; +import net.sf.json.JSONObject; + +/** + * Extension point for logstash indexers. + * This extension point provides the configuration for the indexer. You also have to implement the actual + * indexer in a separate class extending {@link AbstractLogstashIndexerDao}. + * + * @param The class implementing the push to the indexer + */ +public abstract class LogstashIndexer + extends AbstractDescribableImpl> + implements ExtensionPoint, ReconfigurableDescribable> +{ + protected transient T instance; + + /** + * Gets the instance of the actual {@link AbstractLogstashIndexerDao} that is represented by this + * configuration. + * + * @return {@link AbstractLogstashIndexerDao} instance + */ + @Nonnull + public synchronized T getInstance() + { + if (instance == null) + { + instance = createIndexerInstance(); + } + return instance; + } + + + /** + * Creates a new {@link AbstractLogstashIndexerDao} instance corresponding to this configuration. + * + * @return {@link AbstractLogstashIndexerDao} instance + */ + protected abstract T createIndexerInstance(); + + + @SuppressWarnings("unchecked") + public static DescriptorExtensionList, Descriptor>> all() + { + return (DescriptorExtensionList) Jenkins.getInstance().getDescriptorList(LogstashIndexer.class); + } + + public static abstract class LogstashIndexerDescriptor extends Descriptor> + { + /* + * Form validation methods + */ + public FormValidation doCheckPort(@QueryParameter("value") String value) + { + try + { + Integer.parseInt(value); + } + catch (NumberFormatException e) + { + return FormValidation.error(Messages.ValueIsInt()); + } + + return FormValidation.ok(); + } + + public FormValidation doCheckHost(@QueryParameter("value") String value) + { + if (StringUtils.isBlank(value)) + { + return FormValidation.warning(Messages.PleaseProvideHost()); + } + + return FormValidation.ok(); + } + + public abstract int getDefaultPort(); + } + + /** + * {@inheritDoc} + */ + @Override + public LogstashIndexer reconfigure(StaplerRequest req, JSONObject form) throws FormException + { + req.bindJSON(this, form); + return this; + } +} diff --git a/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java b/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java new file mode 100644 index 00000000..b581ba84 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java @@ -0,0 +1,137 @@ +package jenkins.plugins.logstash.configuration; + +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; + +import hudson.Extension; +import hudson.util.FormValidation; +import hudson.util.Secret; +import jenkins.plugins.logstash.Messages; +import jenkins.plugins.logstash.persistence.RabbitMqDao; + +public class RabbitMq extends HostBasedLogstashIndexer +{ + + private String queue; + private String username; + private Secret password; + + @DataBoundConstructor + public RabbitMq() + { + } + + public String getQueue() + { + return queue; + } + + @DataBoundSetter + public void setQueue(String queue) + { + this.queue = queue; + } + + public String getUsername() + { + return username; + } + + @DataBoundSetter + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return Secret.toString(password); + } + + @DataBoundSetter + public void setPassword(String password) + { + this.password = Secret.fromString(password); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + RabbitMq other = (RabbitMq) obj; + if (!Secret.toString(password).equals(other.getPassword())) + { + return false; + } + if (queue == null) + { + if (other.queue != null) + return false; + } + else if (!queue.equals(other.queue)) + { + return false; + } + if (username == null) + { + if (other.username != null) + return false; + } + else if (!username.equals(other.username)) + { + return false; + } + return true; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((queue == null) ? 0 : queue.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + Secret.toString(password).hashCode(); + return result; + } + + @Override + public RabbitMqDao createIndexerInstance() + { + return new RabbitMqDao(getHost(), getPort(), queue, username, Secret.toString(password)); + } + + @Extension + public static class RabbitMqDescriptor extends LogstashIndexerDescriptor + { + @Override + public String getDisplayName() + { + return "RabbitMQ"; + } + + @Override + public int getDefaultPort() + { + return 5672; + } + + public FormValidation doCheckQueue(@QueryParameter("value") String value) + { + if (StringUtils.isBlank(value)) + { + return FormValidation.error(Messages.ValueIsRequired()); + } + + return FormValidation.ok(); + } + + } +} diff --git a/src/main/java/jenkins/plugins/logstash/configuration/Redis.java b/src/main/java/jenkins/plugins/logstash/configuration/Redis.java new file mode 100644 index 00000000..a1f22afd --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/Redis.java @@ -0,0 +1,117 @@ +package jenkins.plugins.logstash.configuration; + +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; + +import hudson.Extension; +import hudson.util.FormValidation; +import hudson.util.Secret; +import jenkins.plugins.logstash.Messages; +import jenkins.plugins.logstash.persistence.RedisDao; + +public class Redis extends HostBasedLogstashIndexer +{ + + protected String key; + protected Secret password; + + @DataBoundConstructor + public Redis() + { + } + + public String getKey() + { + return key; + } + + @DataBoundSetter + public void setKey(String key) + { + this.key = key; + } + + public String getPassword() + { + return Secret.toString(password); + } + + @DataBoundSetter + public void setPassword(String password) + { + this.password = Secret.fromString(password); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Redis other = (Redis) obj; + if (!Secret.toString(password).equals(other.getPassword())) + { + return false; + } + if (key == null) + { + if (other.key != null) + return false; + } + else if (!key.equals(other.key)) + { + return false; + } + return true; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + Secret.toString(password).hashCode(); + return result; + } + + + @Override + public RedisDao createIndexerInstance() + { + return new RedisDao(getHost(), getPort(), key, Secret.toString(password)); + } + + @Extension + public static class RedisDescriptor extends LogstashIndexerDescriptor + { + + @Override + public String getDisplayName() + { + return "Redis"; + } + + @Override + public int getDefaultPort() + { + return 6379; + } + + public FormValidation doCheckKey(@QueryParameter("value") String value) + { + if (StringUtils.isBlank(value)) + { + return FormValidation.error(Messages.ValueIsRequired()); + } + + return FormValidation.ok(); + } + + } +} diff --git a/src/main/java/jenkins/plugins/logstash/configuration/Syslog.java b/src/main/java/jenkins/plugins/logstash/configuration/Syslog.java new file mode 100644 index 00000000..748ea44f --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/Syslog.java @@ -0,0 +1,94 @@ +package jenkins.plugins.logstash.configuration; + +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import com.cloudbees.syslog.MessageFormat; + +import hudson.Extension; +import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogProtocol; +import jenkins.plugins.logstash.persistence.SyslogDao; + +public class Syslog extends HostBasedLogstashIndexer +{ + private MessageFormat messageFormat; + private SyslogProtocol syslogProtocol; + + @DataBoundConstructor + public Syslog() + {} + + public MessageFormat getMessageFormat() + { + return messageFormat; + } + + @DataBoundSetter + public void setMessageFormat(MessageFormat messageFormat) + { + this.messageFormat = messageFormat; + } + + public SyslogProtocol getSyslogProtocol() + { + return syslogProtocol; + } + + @DataBoundSetter() + public void setSyslogProtocol(SyslogProtocol syslogProtocol) + { + this.syslogProtocol = syslogProtocol; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((messageFormat == null) ? 0 : messageFormat.hashCode()); + result = prime * result + ((syslogProtocol == null) ? 0 : syslogProtocol.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Syslog other = (Syslog)obj; + if (messageFormat != other.messageFormat) + return false; + if (syslogProtocol != other.syslogProtocol) + return false; + return true; + } + + @Override + public SyslogDao createIndexerInstance() + { + SyslogDao syslogDao = new SyslogDao(getHost(), getPort()); + syslogDao.setMessageFormat(messageFormat); + return syslogDao; + } + + @Extension + public static class SyslogDescriptor extends LogstashIndexerDescriptor + { + + @Override + public String getDisplayName() + { + return "Syslog"; + } + + @Override + public int getDefaultPort() + { + return 519; + } + } +} diff --git a/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java b/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java index e3f408b5..f08d690b 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java @@ -35,29 +35,14 @@ /** * Abstract data access object for Logstash indexers. * + * TODO: a charset is only required for RabbitMq currently (ES as well but there it is currently configured via the ContentType), + * so better move this to the corresponding classes. * @author Rusty Gerard * @since 1.0.0 */ public abstract class AbstractLogstashIndexerDao implements LogstashIndexerDao { - protected final String host; - protected final int port; - protected final String key; - protected final String username; - protected final String password; private Charset charset; - public AbstractLogstashIndexerDao(String host, int port, String key, String username, String password) { - this.host = host; - this.port = port; - this.key = key; - this.username = username; - this.password = password; - - if (StringUtils.isBlank(host)) { - throw new IllegalArgumentException("host name is required"); - } - } - /** * Sets the charset used to push data to the indexer * @@ -92,9 +77,4 @@ public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List successCodes = closedOpen(200,300); + private String username; + private String password; + //primary constructor used by indexer factory - public ElasticSearchDao(String host, int port, String key, String username, String password) { - this(null, host, port, key, username, password); + public ElasticSearchDao(URI uri, String username, String password) { + this(null, uri, username, password); } // Factored for unit testing - ElasticSearchDao(HttpClientBuilder factory, String host, int port, String key, String username, String password) { - super(host, port, key, username, password); + ElasticSearchDao(HttpClientBuilder factory, URI uri, String username, String password) { - if (StringUtils.isBlank(key)) { - throw new IllegalArgumentException("elastic index name is required"); + if (uri == null) + { + throw new IllegalArgumentException("uri field must not be empty"); } - try { - uri = new URIBuilder(host) - .setPort(port) - // Normalizer will remove extra starting slashes, but missing slash will cause annoying failures - .setPath("/" + key) - .build(); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not create uri", e); - } + this.uri = uri; + this.username = username; + this.password = password; - if(StringUtils.isBlank(uri.getScheme())) { - throw new IllegalArgumentException("host field must specify scheme, such as 'http://'"); + + try + { + uri.toURL(); + } + catch (MalformedURLException e) + { + throw new IllegalArgumentException(e); } if (StringUtils.isNotBlank(username)) { @@ -96,19 +102,47 @@ public ElasticSearchDao(String host, int port, String key, String username, Stri clientBuilder = factory == null ? HttpClientBuilder.create() : factory; } - // for testing only - String getAuth() + public URI getUri() { - return auth; + return uri; } - //for testing only - URI getUri() + public String getHost() { - return uri; + return uri.getHost(); } - HttpPost getHttpPost(String data) { + public String getScheme() + { + return uri.getScheme(); + } + + public int getPort() + { + return uri.getPort(); + } + + public String getUsername() + { + return username; + } + + public String getPassword() + { + return password; + } + + public String getKey() + { + return uri.getPath(); + } + + String getAuth() + { + return auth; + } + + protected HttpPost getHttpPost(String data) { HttpPost postRequest; postRequest = new HttpPost(uri); StringEntity input = new StringEntity(data, ContentType.APPLICATION_JSON); @@ -173,5 +207,8 @@ private String getErrorMessage(CloseableHttpResponse response) { } @Override - public IndexerType getIndexerType() { return IndexerType.ELASTICSEARCH; } + public String getDescription() + { + return uri.toString(); + } } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java b/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java new file mode 100644 index 00000000..92feb616 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.plugins.logstash.persistence; + +import java.nio.charset.Charset; +import java.util.Calendar; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import net.sf.json.JSONObject; + +/** + * Abstract data access object for Logstash indexers. + * + * @since 2.0.0 + */ +public abstract class HostBasedLogstashIndexerDao extends AbstractLogstashIndexerDao { + private final String host; + private final int port; + private Charset charset; + + public HostBasedLogstashIndexerDao(String host, int port) { + this.host = host; + this.port = port; + if (StringUtils.isBlank(host)) { + throw new IllegalArgumentException("host name is required"); + } + } + + /** + * Sets the charset used to push data to the indexer + * + *@param charset The charset to push data + */ + @Override + public void setCharset(Charset charset) + { + this.charset = charset; + } + + /** + * Gets the configured charset used to push data to the indexer + * + * @return charste to push data + */ + @Override + public Charset getCharset() + { + return charset; + } + + @Override + public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines) { + JSONObject payload = new JSONObject(); + payload.put("data", buildData.toJson()); + payload.put("message", logLines); + payload.put("source", "jenkins"); + payload.put("source_host", jenkinsUrl); + payload.put("@buildTimestamp", buildData.getTimestamp()); + payload.put("@timestamp", BuildData.getDateFormatter().format(Calendar.getInstance().getTime())); + payload.put("@version", 1); + + return payload; + } + + public String getHost() + { + return host; + } + + public int getPort() + { + return port; + } + + @Override + public String getDescription() { + return this.host + ":" + this.port; + } +} diff --git a/src/main/java/jenkins/plugins/logstash/persistence/IndexerDaoFactory.java b/src/main/java/jenkins/plugins/logstash/persistence/IndexerDaoFactory.java deleted file mode 100644 index fdc326a1..00000000 --- a/src/main/java/jenkins/plugins/logstash/persistence/IndexerDaoFactory.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * The MIT License - * - * Copyright 2014 Rusty Gerard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package jenkins.plugins.logstash.persistence; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.exception.ExceptionUtils; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -/** - * Factory for AbstractLogstashIndexerDao objects. - * - * @author Rusty Gerard - * @since 1.0.0 - */ -public final class IndexerDaoFactory { - private static AbstractLogstashIndexerDao instance = null; - - private static final Map> INDEXER_MAP; - static { - Map> indexerMap = new HashMap>(); - - indexerMap.put(IndexerType.REDIS, RedisDao.class); - indexerMap.put(IndexerType.RABBIT_MQ, RabbitMqDao.class); - indexerMap.put(IndexerType.ELASTICSEARCH, ElasticSearchDao.class); - indexerMap.put(IndexerType.SYSLOG, SyslogDao.class); - - INDEXER_MAP = Collections.unmodifiableMap(indexerMap); - } - - /** - * Singleton instance accessor. - * - * @param type - * The type of indexer, not null - * @param host - * The host name or IP address of the indexer, not null - * @param port - * The port the indexer listens on - * @param key - * The subcollection to write to in the indexer, not null - * @param username - * The user name to authenticate with the indexer, nullable - * @param password - * The password to authenticate with the indexer, nullable - * @return The instance of the appropriate indexer DAO, never null - * @throws InstantiationException - */ - @SuppressFBWarnings(value="BX_UNBOXING_IMMEDIATELY_REBOXED", justification="Code becomes obsolete when changing to GlobalConfiguration") - public static synchronized LogstashIndexerDao getInstance(IndexerType type, String host, Integer port, String key, String username, String password) throws InstantiationException { - if (!INDEXER_MAP.containsKey(type)) { - throw new InstantiationException("[logstash-plugin]: Unknown IndexerType '" + type + "'. Did you forget to configure the plugin?"); - } - - // Prevent NPE - port = (port == null ? -1 : port.intValue()); - - if (shouldRefreshInstance(type, host, port, key, username, password)) { - try { - Class indexerClass = INDEXER_MAP.get(type); - Constructor constructor = indexerClass.getConstructor(String.class, int.class, String.class, String.class, String.class); - instance = (AbstractLogstashIndexerDao) constructor.newInstance(host, port, key, username, password); - } catch (NoSuchMethodException e) { - throw new InstantiationException(ExceptionUtils.getRootCauseMessage(e)); - } catch (InvocationTargetException e) { - throw new InstantiationException(ExceptionUtils.getRootCauseMessage(e)); - } catch (IllegalAccessException e) { - throw new InstantiationException(ExceptionUtils.getRootCauseMessage(e)); - } - } - - return instance; - } - - private static boolean shouldRefreshInstance(IndexerType type, String host, int port, String key, String username, String password) { - if (instance == null) { - return true; - } - - boolean matches = (instance.getIndexerType() == type) && - StringUtils.equals(instance.host, host) && - (instance.port == port) && - StringUtils.equals(instance.key, key) && - StringUtils.equals(instance.username, username) && - StringUtils.equals(instance.password, password); - return !matches; - } -} diff --git a/src/main/java/jenkins/plugins/logstash/persistence/LogstashIndexerDao.java b/src/main/java/jenkins/plugins/logstash/persistence/LogstashIndexerDao.java index d81140fb..0229ad88 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/LogstashIndexerDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/LogstashIndexerDao.java @@ -37,6 +37,7 @@ * @since 1.0.0 */ public interface LogstashIndexerDao { + @Deprecated static enum IndexerType { REDIS, RABBIT_MQ, @@ -44,6 +45,7 @@ static enum IndexerType { SYSLOG } + @Deprecated static enum SyslogFormat { RFC5424, RFC3164 @@ -55,9 +57,7 @@ static enum SyslogProtocol { public void setCharset(Charset charset); - String getDescription(); - - IndexerType getIndexerType(); + public String getDescription(); /** * Sends the log data to the Logstash indexer. @@ -67,7 +67,7 @@ static enum SyslogProtocol { * @throws java.io.IOException * The data is not written to the server */ - void push(String data) throws IOException; + public void push(String data) throws IOException; /** * Builds a JSON payload compatible with the Logstash schema. @@ -80,5 +80,5 @@ static enum SyslogProtocol { * The log data to transmit, not null * @return The formatted JSON object, never null */ - JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines); + public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines); } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java b/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java index d269b3e6..62f8ec86 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java @@ -35,22 +35,38 @@ /** * RabbitMQ Data Access Object. * + * TODO: make the charset configurable via the UI with UTF-8 being the default + * TODO: support TLS + * TODO: support vhost + * * @author Rusty Gerard * @since 1.0.0 */ -public class RabbitMqDao extends AbstractLogstashIndexerDao { +public class RabbitMqDao extends HostBasedLogstashIndexerDao { private final ConnectionFactory pool; + private String queue; + private String username; + private String password; + //primary constructor used by indexer factory public RabbitMqDao(String host, int port, String key, String username, String password) { this(null, host, port, key, username, password); } - // Factored for unit testing - RabbitMqDao(ConnectionFactory factory, String host, int port, String key, String username, String password) { - super(host, port, key, username, password); + /* + * TODO: this constructor is only for testing so one can inject a mocked ConnectionFactory. + * With Powermock we can intercept the creation of the ConnectionFactory and replace with a mock + * making this constructor obsolete + */ + RabbitMqDao(ConnectionFactory factory, String host, int port, String queue, String username, String password) { + super(host, port); - if (StringUtils.isBlank(key)) { + this.queue = queue; + this.username = username; + this.password = password; + + if (StringUtils.isBlank(queue)) { throw new IllegalArgumentException("rabbit queue name is required"); } @@ -67,6 +83,31 @@ public RabbitMqDao(String host, int port, String key, String username, String pa } } + public String getQueue() + { + return queue; + } + + public String getUsername() + { + return username; + } + + public String getPassword() + { + return password; + } + + + /* + * TODO: do we really need to open a connection each time? + * channels are not thread-safe so we need a new channel each time but the connection + * could be shared. Another idea would be to use one dao per build, reuse the channel and + * synchronize the push on the channel (for pipeline builds where we can have multiple + * threads writing, are freestyle projects guaranteed to be single threaded?). + * (non-Javadoc) + * @see jenkins.plugins.logstash.persistence.LogstashIndexerDao#push(java.lang.String) + */ @Override public void push(String data) throws IOException { Connection connection = null; @@ -74,31 +115,27 @@ public void push(String data) throws IOException { try { connection = pool.newConnection(); channel = connection.createChannel(); - // Ensure the queue exists + try { - channel.queueDeclarePassive(key); + channel.queueDeclarePassive(queue); } catch (IOException e) { // The queue does not exist and the channel has been closed finalizeChannel(channel); // Create the queue channel = connection.createChannel(); - channel.queueDeclare(key, true, false, false, null); + channel.queueDeclare(queue, true, false, false, null); } - channel.basicPublish("", key, null, data.getBytes(getCharset())); + channel.basicPublish("", queue, null, data.getBytes(getCharset())); } finally { finalizeChannel(channel); finalizeConnection(connection); } } - @Override - public IndexerType getIndexerType() { - return IndexerType.RABBIT_MQ; - } - + // TODO: connection.isOpen() should be avoided (see the rabbitmq doc) private void finalizeConnection(Connection connection) { if (connection != null && connection.isOpen()) { try { @@ -110,6 +147,7 @@ private void finalizeConnection(Connection connection) { } } + // TODO: connection.isOpen() should be avoided (see the rabbitmq doc) private void finalizeChannel(Channel channel) { if (channel != null && channel.isOpen()) { try { diff --git a/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java b/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java index 7b337e8f..b36a73a0 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java @@ -40,17 +40,27 @@ * @author Rusty Gerard * @since 1.0.0 */ -public class RedisDao extends AbstractLogstashIndexerDao { +public class RedisDao extends HostBasedLogstashIndexerDao { private final JedisPool pool; + private String password; + private String key; + //primary constructor used by indexer factory - public RedisDao(String host, int port, String key, String username, String password) { - this(null, host, port, key, username, password); + public RedisDao(String host, int port, String key, String password) { + this(null, host, port, key, password); } - // Factored for unit testing - RedisDao(JedisPool factory, String host, int port, String key, String username, String password) { - super(host, port, key, username, password); + /* + * TODO: this constructor is only for testing so one can inject a mocked JedisPool. + * With Powermock we can intercept the creation of the JedisPool and replace with a mock + * making this constructor obsolete + */ + RedisDao(JedisPool factory, String host, int port, String key, String password) { + super(host, port); + + this.key = key; + this.password = password; if (StringUtils.isBlank(key)) { throw new IllegalArgumentException("redis key is required"); @@ -61,6 +71,16 @@ public RedisDao(String host, int port, String key, String username, String passw pool = factory == null ? new JedisPool(new JedisPoolConfig(), host, port) : factory; } + public String getPassword() + { + return password; + } + + public String getKey() + { + return key; + } + @Override public void push(String data) throws IOException { Jedis jedis = null; @@ -90,9 +110,4 @@ public void push(String data) throws IOException { } } } - - @Override - public IndexerType getIndexerType() { - return IndexerType.REDIS; - } } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java b/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java index d71c880d..86f96d41 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java @@ -1,69 +1,59 @@ package jenkins.plugins.logstash.persistence; -import com.cloudbees.syslog.sender.UdpSyslogMessageSender; +import java.io.IOException; + import com.cloudbees.syslog.Facility; import com.cloudbees.syslog.MessageFormat; import com.cloudbees.syslog.Severity; +import com.cloudbees.syslog.sender.UdpSyslogMessageSender; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jenkins.model.Jenkins; -import jenkins.plugins.logstash.LogstashInstallation; -import jenkins.plugins.logstash.LogstashInstallation.Descriptor; - -public class SyslogDao extends AbstractLogstashIndexerDao { +/* + * TODO: add support for TcpSyslogMessageSender + */ +public class SyslogDao extends HostBasedLogstashIndexerDao { - private String syslogFormat = null; - private final static Logger LOG = Logger.getLogger(SyslogDao.class.getName()); + private MessageFormat messageFormat = MessageFormat.RFC_3164; private final UdpSyslogMessageSender messageSender; - public SyslogDao(String host, int port, String key, String username, String password) { - this(null, host, port, key, username, password); + public SyslogDao(String host, int port) { + this(null, host, port); } - public SyslogDao(UdpSyslogMessageSender udpSyslogMessageSender, String host, int port, String key, String username, String password) { - super(host, port, key, username, password); + /* + * TODO: this constructor is only for testing so one can inject a mocked UdpSyslogMessageSender. + * With Powermock we can intercept the creation of the UdpSyslogMessageSender and replace with a mock + * making this constructor obsolete + */ + + public SyslogDao(UdpSyslogMessageSender udpSyslogMessageSender, String host, int port) { + super(host, port); messageSender = udpSyslogMessageSender == null ? new UdpSyslogMessageSender() : udpSyslogMessageSender; } - public void setSyslogFormat(String format) { - syslogFormat = format; + public void setMessageFormat(MessageFormat format) { + messageFormat = format; + } + + public MessageFormat getMessageFormat() { + return messageFormat; } @Override public void push(String data) throws IOException { - - try { - Descriptor logstashPluginConfig = (Descriptor) Jenkins.getInstance().getDescriptor(LogstashInstallation.class); - syslogFormat = logstashPluginConfig.getSyslogFormat().toString(); - } catch (NullPointerException e){ - LOG.log(Level.WARNING, "Unable to read syslogFormat in the jenkins logstash plugin configuration"); - } - // Making the JSON document compliant to Common Event Expression (CEE) // Ref: http://www.rsyslog.com/json-elasticsearch/ data = " @cee: " + data; // SYSLOG Configuration - messageSender.setDefaultMessageHostname(host); + messageSender.setDefaultMessageHostname(getHost()); messageSender.setDefaultAppName("jenkins:"); messageSender.setDefaultFacility(Facility.USER); messageSender.setDefaultSeverity(Severity.INFORMATIONAL); - messageSender.setSyslogServerHostname(host); - messageSender.setSyslogServerPort(port); + messageSender.setSyslogServerHostname(getHost()); + messageSender.setSyslogServerPort(getPort()); // The Logstash syslog input module support only the RFC_3164 format // Ref: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-syslog.html - if (syslogFormat == "RFC3164" ) { - messageSender.setMessageFormat(MessageFormat.RFC_3164); - } - else { - messageSender.setMessageFormat(MessageFormat.RFC_5424); - } + messageSender.setMessageFormat(messageFormat); // Sending the message messageSender.sendMessage(data); } - - @Override - public IndexerType getIndexerType() { return IndexerType.SYSLOG; } } diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly new file mode 100644 index 00000000..565ea906 --- /dev/null +++ b/src/main/resources/index.jelly @@ -0,0 +1,3 @@ +
+ Adds the possibility to push builds logs and build data to a Logstash indexer such as Redis, RabbitMQ, Elastic Search or to Syslog. +
diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/config.jelly b/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/config.jelly new file mode 100644 index 00000000..168397ef --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/config.jelly @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-logstashIndexer.html b/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-logstashIndexer.html new file mode 100644 index 00000000..86e1fde5 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-logstashIndexer.html @@ -0,0 +1 @@ +The type of Logstash indexer to use. \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/global.jelly b/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/global.jelly index e58ee543..f0055c8a 100644 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/global.jelly +++ b/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/global.jelly @@ -1,33 +1,8 @@ - - - ${it.name()} - - - - - - - - - - - - - - - - - - - ${it.name()} - - - ${it.name()} - - - + + + + Logstash configuration has moved to the Global Configuration + + diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-key.html b/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-key.html deleted file mode 100644 index 3a83bfb0..00000000 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-key.html +++ /dev/null @@ -1,5 +0,0 @@ -
-

REDIS: The name of a Redis list or channel.
- RABBIT_MQ: The name of a RabbitMq queue.
- ELASTICSEARCH: The name and type path. Example: "/indexName/type"

-
diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogProtocol.html b/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogProtocol.html deleted file mode 100644 index d9f60024..00000000 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogProtocol.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

The Syslog protocol used to send the messages (only effective for SYSLOG Indexer type).

-
diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-type.html b/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-type.html deleted file mode 100644 index a137c868..00000000 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-type.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

The type of Logstash indexer to use.

-
diff --git a/src/main/resources/jenkins/plugins/logstash/Messages.properties b/src/main/resources/jenkins/plugins/logstash/Messages.properties index f2659941..b2844c22 100644 --- a/src/main/resources/jenkins/plugins/logstash/Messages.properties +++ b/src/main/resources/jenkins/plugins/logstash/Messages.properties @@ -23,4 +23,4 @@ DisplayName = Send console log to Logstash ValueIsInt = Value must be an integer ValueIsRequired = Value is required -PleaseProvideHost = Please set a valid host name +PleaseProvideHost = Please set a valid host name \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly new file mode 100644 index 00000000..a01fab63 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-password.html b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-password.html similarity index 100% rename from src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-password.html rename to src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-password.html diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-uri.html b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-uri.html new file mode 100644 index 00000000..9c7fab04 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-uri.html @@ -0,0 +1,3 @@ +
+ The full url to the elastic search index including scheme, port and key (<scheme>://<host>:<port>/<key>) +
diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-username.html b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-username.html similarity index 100% rename from src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-username.html rename to src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-username.html diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/config.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/config.jelly new file mode 100644 index 00000000..d4292310 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/config.jelly @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-host.html b/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-host.html similarity index 54% rename from src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-host.html rename to src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-host.html index 4e0b7ed4..4c978cc6 100644 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-host.html +++ b/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-host.html @@ -1,4 +1,3 @@

The host name or IP address of the indexer to send log data to.
- ELASTICSEARCH: Also specify scheme. Example: "https://myserver".

diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-port.html b/src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-port.html similarity index 100% rename from src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-port.html rename to src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-port.html diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/configure-advanced.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/configure-advanced.jelly new file mode 100644 index 00000000..eb714659 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/configure-advanced.jelly @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-password.html b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-password.html new file mode 100644 index 00000000..758cb9c5 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-password.html @@ -0,0 +1,4 @@ +
+

The password to use when connecting to the remote indexing server.
+ Leave this field blank if authentication is not enabled.

+
diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-queue.html b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-queue.html new file mode 100644 index 00000000..e903cd03 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-queue.html @@ -0,0 +1,3 @@ +
+ The name of a RabbitMq queue. +
diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-username.html b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-username.html new file mode 100644 index 00000000..886e0bfa --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-username.html @@ -0,0 +1,4 @@ +
+

The username to use when connecting to the remote indexing server.
+ Leave this field blank if authentication is not enabled or a username is not required.

+
diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/Redis/configure-advanced.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/configure-advanced.jelly new file mode 100644 index 00000000..9280df52 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/configure-advanced.jelly @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-key.html b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-key.html new file mode 100644 index 00000000..54217c1d --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-key.html @@ -0,0 +1,3 @@ +
+ The name of a Redis list or channel. +
diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-password.html b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-password.html new file mode 100644 index 00000000..758cb9c5 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-password.html @@ -0,0 +1,4 @@ +
+

The password to use when connecting to the remote indexing server.
+ Leave this field blank if authentication is not enabled.

+
diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/configure-advanced.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/configure-advanced.jelly new file mode 100644 index 00000000..57a244a7 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/configure-advanced.jelly @@ -0,0 +1,9 @@ + + + + ${it.name()} + + + ${it.name()} + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogFormat.html b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-messageFormat.html similarity index 68% rename from src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogFormat.html rename to src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-messageFormat.html index 0febfa89..c3c8f6f8 100644 --- a/src/main/resources/jenkins/plugins/logstash/LogstashInstallation/help-syslogFormat.html +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-messageFormat.html @@ -1,5 +1,5 @@
-

The Syslog format used to send the messages (only effective for SYSLOG Indexer type).
+

The Syslog format used to send the messages.
RFC5424: The default format.
RFC3164: The format supported by the Logstash Syslog input plugin.

diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-syslogProtocol.html b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-syslogProtocol.html new file mode 100644 index 00000000..8a22e75d --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-syslogProtocol.html @@ -0,0 +1,3 @@ +
+

The Syslog protocol used to send the messages.

+
diff --git a/src/test/java/jenkins/plugins/logstash/LogstashBuildWrapperTest.java b/src/test/java/jenkins/plugins/logstash/LogstashBuildWrapperTest.java index d31c6582..762450fe 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashBuildWrapperTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashBuildWrapperTest.java @@ -18,7 +18,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class LogstashBuildWrapperTest { diff --git a/src/test/java/jenkins/plugins/logstash/LogstashConfigurationMigrationTest.java b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationMigrationTest.java new file mode 100644 index 00000000..a89ce386 --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationMigrationTest.java @@ -0,0 +1,137 @@ +package jenkins.plugins.logstash; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.hamcrest.core.IsInstanceOf; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.jvnet.hudson.test.JenkinsRule; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.cloudbees.syslog.MessageFormat; + +import jenkins.plugins.logstash.LogstashInstallation.Descriptor; +import jenkins.plugins.logstash.configuration.ElasticSearch; +import jenkins.plugins.logstash.configuration.LogstashIndexer; +import jenkins.plugins.logstash.configuration.RabbitMq; +import jenkins.plugins.logstash.configuration.Redis; +import jenkins.plugins.logstash.configuration.Syslog; +import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; +import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogFormat; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ LogstashInstallation.class, Descriptor.class }) +@PowerMockIgnore({"javax.crypto.*"}) +public class LogstashConfigurationMigrationTest extends LogstashConfigurationTestBase +{ + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Mock + Descriptor descriptor; + + LogstashConfiguration configuration; + + @Before + public void setup() + { + mockStatic(LogstashInstallation.class); + configFile = new File("notExisting.xml"); + when(LogstashInstallation.getLogstashDescriptor()).thenReturn(descriptor); + when(descriptor.getHost()).thenReturn("localhost"); + when(descriptor.getPort()).thenReturn(4567); + when(descriptor.getKey()).thenReturn("logstash"); + when(descriptor.getUsername()).thenReturn("user"); + when(descriptor.getPassword()).thenReturn("pwd"); + configuration = new LogstashConfigurationForTest(); + } + + @Test + public void redisMigration() + { + when(descriptor.getType()).thenReturn(IndexerType.REDIS); + configuration.migrateData(); + LogstashIndexer indexer = configuration.getLogstashIndexer(); + assertThat(indexer, IsInstanceOf.instanceOf(Redis.class)); + Redis redis = (Redis) indexer; + assertThat(redis.getHost(),equalTo("localhost")); + assertThat(redis.getPort(),is(4567)); + assertThat(redis.getKey(), equalTo("logstash")); + assertThat(redis.getPassword(), equalTo("pwd")); + } + + @Test + public void syslogMigrationRFC3164() + { + when(descriptor.getType()).thenReturn(IndexerType.SYSLOG); + when(descriptor.getSyslogFormat()).thenReturn(SyslogFormat.RFC3164); + configuration.migrateData(); + LogstashIndexer indexer = configuration.getLogstashIndexer(); + assertThat(indexer, IsInstanceOf.instanceOf(Syslog.class)); + Syslog syslog = (Syslog) indexer; + assertThat(syslog.getHost(),equalTo("localhost")); + assertThat(syslog.getPort(),is(4567)); + assertThat(syslog.getMessageFormat(), equalTo(MessageFormat.RFC_3164)); + } + + @Test + public void syslogMigrationRFC5424() + { + when(descriptor.getType()).thenReturn(IndexerType.SYSLOG); + when(descriptor.getSyslogFormat()).thenReturn(SyslogFormat.RFC5424); + configuration.migrateData(); + LogstashIndexer indexer = configuration.getLogstashIndexer(); + assertThat(indexer, IsInstanceOf.instanceOf(Syslog.class)); + Syslog syslog = (Syslog) indexer; + assertThat(syslog.getHost(),equalTo("localhost")); + assertThat(syslog.getPort(),is(4567)); + assertThat(syslog.getMessageFormat(), equalTo(MessageFormat.RFC_5424)); + } + + @Test + public void elasticSearchMigration() throws URISyntaxException, MalformedURLException + { + when(descriptor.getType()).thenReturn(IndexerType.ELASTICSEARCH); + when(descriptor.getHost()).thenReturn("http://localhost"); + configuration.migrateData(); + LogstashIndexer indexer = configuration.getLogstashIndexer(); + assertThat(indexer, IsInstanceOf.instanceOf(ElasticSearch.class)); + ElasticSearch es = (ElasticSearch) indexer; + URI uri = new URI("http://localhost:4567/logstash"); + assertThat(es.getUri(),equalTo(uri)); + assertThat(es.getPassword(), equalTo("pwd")); + assertThat(es.getUsername(), equalTo("user")); + } + + @Test + public void rabbitMqMigration() + { + when(descriptor.getType()).thenReturn(IndexerType.RABBIT_MQ); + configuration.migrateData(); + LogstashIndexer indexer = configuration.getLogstashIndexer(); + assertThat(indexer, IsInstanceOf.instanceOf(RabbitMq.class)); + RabbitMq es = (RabbitMq) indexer; + assertThat(es.getHost(),equalTo("localhost")); + assertThat(es.getPort(),is(4567)); + assertThat(es.getQueue(), equalTo("logstash")); + assertThat(es.getPassword(), equalTo("pwd")); + assertThat(es.getUsername(), equalTo("user")); + } + +} diff --git a/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTest.java b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTest.java new file mode 100644 index 00000000..a992d388 --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTest.java @@ -0,0 +1,58 @@ +package jenkins.plugins.logstash; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.io.File; + +import org.hamcrest.core.IsInstanceOf; +import org.junit.Test; + +import jenkins.plugins.logstash.persistence.ElasticSearchDao; +import jenkins.plugins.logstash.persistence.RabbitMqDao; +import jenkins.plugins.logstash.persistence.RedisDao; +import jenkins.plugins.logstash.persistence.SyslogDao; + +public class LogstashConfigurationTest extends LogstashConfigurationTestBase +{ + + @Test + public void unconfiguredWillReturnNull() + { + LogstashConfigurationTestBase.configFile = new File("src/test/resources/notExisting.xml"); + LogstashConfiguration configuration = new LogstashConfigurationForTest(); + assertThat(configuration.getIndexerInstance(), equalTo(null)); + } + + @Test + public void elasticSearchIsProperlyConfigured() + { + LogstashConfigurationTestBase.configFile = new File("src/test/resources/elasticSearch.xml"); + LogstashConfiguration configuration = new LogstashConfigurationForTest(); + assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(ElasticSearchDao.class)); + } + + @Test + public void rabbitMqIsProperlyConfigured() + { + LogstashConfigurationTestBase.configFile = new File("src/test/resources/rabbitmq.xml"); + LogstashConfiguration configuration = new LogstashConfigurationForTest(); + assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(RabbitMqDao.class)); + } + + @Test + public void redisIsProperlyConfigured() + { + LogstashConfigurationTestBase.configFile = new File("src/test/resources/redis.xml"); + LogstashConfiguration configuration = new LogstashConfigurationForTest(); + assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(RedisDao.class)); + } + + @Test + public void syslogIsProperlyConfigured() + { + LogstashConfigurationTestBase.configFile = new File("src/test/resources/syslog.xml"); + LogstashConfiguration configuration = new LogstashConfigurationForTest(); + assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(SyslogDao.class)); + } +} diff --git a/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTestBase.java b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTestBase.java new file mode 100644 index 00000000..19ffa704 --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/LogstashConfigurationTestBase.java @@ -0,0 +1,26 @@ +package jenkins.plugins.logstash; + +import java.io.File; + +import hudson.XmlFile; + +public class LogstashConfigurationTestBase +{ + protected static File configFile; + + public static class LogstashConfigurationForTest extends LogstashConfiguration + { + + @Override + public synchronized void save() + { + } + + @Override + protected XmlFile getConfigFile() + { + return new XmlFile(configFile); + } + } + +} diff --git a/src/test/java/jenkins/plugins/logstash/LogstashIntegrationTest.java b/src/test/java/jenkins/plugins/logstash/LogstashIntegrationTest.java index 34070ed2..12bd6886 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashIntegrationTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashIntegrationTest.java @@ -5,8 +5,6 @@ import static org.junit.Assert.assertThat; import static org.powermock.api.mockito.PowerMockito.when; -import java.io.IOException; -import java.util.ArrayList; import java.util.List; import org.junit.Before; @@ -25,52 +23,41 @@ import hudson.model.Result; import hudson.model.Slave; import hudson.model.queue.QueueTaskFuture; -import jenkins.plugins.logstash.LogstashInstallation.Descriptor; -import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; -import jenkins.plugins.logstash.persistence.IndexerDaoFactory; -import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; +import jenkins.plugins.logstash.persistence.MemoryDao; import net.sf.json.JSONObject; @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.crypto.*"}) -@PrepareForTest({ IndexerDaoFactory.class, LogstashInstallation.class }) +@PrepareForTest(LogstashConfiguration.class) public class LogstashIntegrationTest { @Rule public JenkinsRule jenkins = new JenkinsRule(); + @Mock + private LogstashConfiguration logstashConfiguration; + private Slave slave; private FreeStyleProject project; private MemoryDao memoryDao; - @Mock - Descriptor descriptor; - @Before public void setup() throws Exception { - PowerMockito.mockStatic(IndexerDaoFactory.class); - PowerMockito.mockStatic(LogstashInstallation.class); - when(LogstashInstallation.getLogstashDescriptor()).thenReturn(descriptor); - when(descriptor.getType()).thenReturn(IndexerType.SYSLOG); - when(descriptor.getHost()).thenReturn("localhost"); - when(descriptor.getPort()).thenReturn(1); - when(descriptor.getUsername()).thenReturn("username"); - when(descriptor.getKey()).thenReturn("password"); - when(descriptor.getKey()).thenReturn("key"); - memoryDao = new MemoryDao(); - when(IndexerDaoFactory.getInstance(IndexerType.SYSLOG, descriptor.getHost(), descriptor.getPort(), - descriptor.getKey(),descriptor.getUsername(), descriptor.getPassword())).thenReturn(memoryDao); + PowerMockito.mockStatic(LogstashConfiguration.class); + when(LogstashConfiguration.getInstance()).thenReturn(logstashConfiguration); + when(logstashConfiguration.getIndexerInstance()).thenReturn(memoryDao); + slave = jenkins.createSlave(); slave.setLabelString("myLabel"); project = jenkins.createFreeStyleProject(); } @Test - public void test_buildWrapperOnMaster() throws Exception + public void buildWrapperOnMaster() throws Exception { project.getBuildWrappersList().add(new LogstashBuildWrapper()); QueueTaskFuture f = project.scheduleBuild2(0); @@ -87,7 +74,7 @@ public void test_buildWrapperOnMaster() throws Exception } @Test - public void test_buildWrapperOnSlave() throws Exception + public void buildWrapperOnSlave() throws Exception { project.getBuildWrappersList().add(new LogstashBuildWrapper()); project.setAssignedNode(slave); @@ -106,7 +93,7 @@ public void test_buildWrapperOnSlave() throws Exception } @Test - public void test_buildNotifierOnMaster() throws Exception + public void buildNotifierOnMaster() throws Exception { project.getPublishersList().add(new LogstashNotifier(10, false)); QueueTaskFuture f = project.scheduleBuild2(0); @@ -121,7 +108,7 @@ public void test_buildNotifierOnMaster() throws Exception } @Test - public void test_buildNotifierOnSlave() throws Exception + public void buildNotifierOnSlave() throws Exception { project.getPublishersList().add(new LogstashNotifier(10, false)); project.setAssignedNode(slave); @@ -135,33 +122,4 @@ public void test_buildNotifierOnSlave() throws Exception assertThat(data.getString("buildHost"),equalTo(slave.getDisplayName())); assertThat(data.getString("buildLabel"),equalTo(slave.getLabelString())); } - - private static class MemoryDao extends AbstractLogstashIndexerDao - { - List output = new ArrayList<>(); - - public MemoryDao() - { - super("localhost", 1, "key", "username", "password"); - } - - @Override - public IndexerType getIndexerType() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public void push(String data) throws IOException - { - JSONObject json = JSONObject.fromObject(data); - output.add(json); - } - - public List getOutput() - { - return output; - } - } } diff --git a/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java b/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java index 74f4cf76..13c4774f 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java @@ -15,7 +15,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @SuppressWarnings("resource") @RunWith(MockitoJUnitRunner.class) diff --git a/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java b/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java index 8dda3caa..31cfbc9c 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java @@ -1,5 +1,37 @@ package jenkins.plugins.logstash; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + import hudson.EnvVars; import hudson.model.AbstractBuild; import hudson.model.Computer; @@ -10,27 +42,7 @@ import hudson.tasks.test.AbstractTestResultAction; import jenkins.plugins.logstash.persistence.BuildData; import jenkins.plugins.logstash.persistence.LogstashIndexerDao; -import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; import net.sf.json.JSONObject; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.*; -import org.mockito.junit.MockitoJUnitRunner; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.List; - -import static org.hamcrest.core.StringContains.containsString; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class LogstashWriterTest { @@ -42,11 +54,7 @@ static LogstashWriter createLogstashWriter(final AbstractBuild testBuild, final BuildData data) { return new LogstashWriter(testBuild, error, null, testBuild.getCharset()) { @Override - protected LogstashIndexerDao createDao() throws InstantiationException { - if (indexer == null) { - throw new InstantiationException("DoaTestInstantiationException"); - } - + LogstashIndexerDao getIndexerDao() { return indexer; } @@ -118,7 +126,6 @@ public void before() throws Exception { .thenReturn(JSONObject.fromObject("{\"data\":{},\"message\":[\"test\"],\"source\":\"jenkins\",\"source_host\":\"http://my-jenkins-url\",\"@version\":1}")); Mockito.doNothing().when(mockDao).push(Matchers.anyString()); - when(mockDao.getIndexerType()).thenReturn(IndexerType.REDIS); when(mockDao.getDescription()).thenReturn("localhost:8080"); errorBuffer = new ByteArrayOutputStream(); @@ -175,8 +182,7 @@ public void constructorSuccess() throws Exception { @Test public void constructorSuccessNoDao() throws Exception { - String exMessage = "InstantiationException: DoaTestInstantiationException\n" + - "[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"; + String exMessage = "[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"; // Unit under test LogstashWriter writer = createLogstashWriter(mockBuild, errorBuffer, "http://my-jenkins-url", null, null); @@ -237,7 +243,6 @@ public void writeSuccess() throws Exception { verify(mockDao).push("{\"data\":{},\"message\":[\"test\"],\"source\":\"jenkins\",\"source_host\":\"http://my-jenkins-url\",\"@version\":1}"); verify(mockDao).setCharset(Charset.defaultCharset()); verify(mockBuild).getCharset(); - } @Test @@ -266,7 +271,7 @@ public void writeSuccessConnectionBroken() throws Exception { String msg = "test"; - String exMessage = "[logstash-plugin]: Failed to send log data to REDIS:localhost:8080.\n" + + String exMessage = "[logstash-plugin]: Failed to send log data: localhost:8080.\n" + "[logstash-plugin]: No Further logs will be sent to localhost:8080.\n" + "java.io.IOException: BOOM!"; @@ -299,11 +304,9 @@ public void writeSuccessConnectionBroken() throws Exception { //Verify calls were made to the dao logging twice, not three times. verify(mockDao, times(2)).buildPayload(Matchers.eq(mockBuildData), Matchers.eq("http://my-jenkins-url"), Matchers.anyListOf(String.class)); verify(mockDao, times(2)).push("{\"data\":{},\"message\":[\"test\"],\"source\":\"jenkins\",\"source_host\":\"http://my-jenkins-url\",\"@version\":1}"); - verify(mockDao).getIndexerType(); verify(mockDao, times(2)).getDescription(); verify(mockDao).setCharset(Charset.defaultCharset()); verify(mockBuild).getCharset(); - } @Test diff --git a/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java b/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java new file mode 100644 index 00000000..171ed06e --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java @@ -0,0 +1,69 @@ +package jenkins.plugins.logstash.configuration; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class ElasticSearchTest +{ + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private ElasticSearch indexer; + private ElasticSearch indexer2; + + @Before + public void setup() throws MalformedURLException, URISyntaxException + { + URL url = new URL("http://localhost:4567/key"); + indexer = new ElasticSearch(); + indexer.setUri(url); + indexer.setPassword("password"); + indexer.setUsername("user"); + + indexer2 = new ElasticSearch(); + indexer2.setUri(url); + indexer2.setPassword("password"); + indexer2.setUsername("user"); +} + + @Test + public void sameSettingsAreEqual() + { + assertThat(indexer.equals(indexer2), is(true)); + } + + @Test + public void passwordChangeIsNotEqual() + { + indexer.setPassword("newPassword"); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void urlChangeIsNotEqual() throws MalformedURLException, URISyntaxException + { + indexer.setUri(new URL("https://localhost:4567/key")); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void usernameChangeIsNotEqual() + { + indexer.setUsername("newUser"); + assertThat(indexer.equals(indexer2), is(false)); + } + +} diff --git a/src/test/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexerTest.java b/src/test/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexerTest.java new file mode 100644 index 00000000..7d203add --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexerTest.java @@ -0,0 +1,59 @@ +package jenkins.plugins.logstash.configuration; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Test; + +import jenkins.plugins.logstash.persistence.MemoryDao; + +public class HostBasedLogstashIndexerTest +{ + + private LogstashIndexerForTest indexer; + private LogstashIndexerForTest indexer2; + + @Before + public void setup() + { + indexer = new LogstashIndexerForTest("localhost", 4567); + indexer2 = new LogstashIndexerForTest("localhost", 4567); + } + + @Test + public void sameSettingsAreEqual() + { + assertThat(indexer.equals(indexer2), is(true)); + } + + @Test + public void hostChangeIsNotEqual() + { + indexer.setHost("remoteHost"); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void portChangeIsNotEqual() + { + indexer.setPort(7654); + assertThat(indexer.equals(indexer2), is(false)); + } + + public static class LogstashIndexerForTest extends HostBasedLogstashIndexer + { + + public LogstashIndexerForTest(String host, int port) + { + setHost(host); + setPort(port); + } + + @Override + public MemoryDao createIndexerInstance() + { + return new MemoryDao(); + } + } +} diff --git a/src/test/java/jenkins/plugins/logstash/configuration/RabbitMqTest.java b/src/test/java/jenkins/plugins/logstash/configuration/RabbitMqTest.java new file mode 100644 index 00000000..bf405d6a --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/configuration/RabbitMqTest.java @@ -0,0 +1,65 @@ +package jenkins.plugins.logstash.configuration; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class RabbitMqTest +{ + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private RabbitMq indexer; + private RabbitMq indexer2; + + @Before + public void setup() + { + indexer = new RabbitMq(); + indexer.setHost("localhost"); + indexer.setPort(4567); + indexer.setPassword("password"); + indexer.setUsername("user"); + indexer.setQueue("queue"); + + indexer2 = new RabbitMq(); + indexer2.setHost("localhost"); + indexer2.setPort(4567); + indexer2.setPassword("password"); + indexer2.setUsername("user"); + indexer2.setQueue("queue"); + } + + @Test + public void sameSettingsAreEqual() + { + assertThat(indexer.equals(indexer2), is(true)); + } + + @Test + public void passwordChangeIsNotEqual() + { + indexer.setPassword("newPassword"); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void usernameChangeIsNotEqual() + { + indexer.setUsername("newUser"); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void queueChangeIsNotEqual() + { + indexer.setQueue("newQueue"); + assertThat(indexer.equals(indexer2), is(false)); + } + +} diff --git a/src/test/java/jenkins/plugins/logstash/configuration/RedisTest.java b/src/test/java/jenkins/plugins/logstash/configuration/RedisTest.java new file mode 100644 index 00000000..14d2f92a --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/configuration/RedisTest.java @@ -0,0 +1,55 @@ +package jenkins.plugins.logstash.configuration; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class RedisTest +{ + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private Redis indexer; + private Redis indexer2; + + @Before + public void setup() + { + indexer = new Redis(); + indexer.setHost("localhost"); + indexer.setPort(4567); + indexer.setKey("key"); + indexer.setPassword("password"); + + indexer2 = new Redis(); + indexer2.setHost("localhost"); + indexer2.setPort(4567); + indexer2.setKey("key"); + indexer2.setPassword("password"); +} + + @Test + public void sameSettingsAreEqual() + { + assertThat(indexer.equals(indexer2), is(true)); + } + + @Test + public void passwordChangeIsNotEqual() + { + indexer.setPassword("newPassword"); + assertThat(indexer.equals(indexer2), is(false)); + } + + @Test + public void keyChangeIsNotEqual() + { + indexer.setKey("newKey"); + assertThat(indexer.equals(indexer2), is(false)); + } +} diff --git a/src/test/java/jenkins/plugins/logstash/configuration/SyslogTest.java b/src/test/java/jenkins/plugins/logstash/configuration/SyslogTest.java new file mode 100644 index 00000000..5052e248 --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/configuration/SyslogTest.java @@ -0,0 +1,44 @@ +package jenkins.plugins.logstash.configuration; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Test; + +import com.cloudbees.syslog.MessageFormat; + +public class SyslogTest +{ + + private Syslog indexer; + private Syslog indexer2; + + @Before + public void setup() + { + indexer = new Syslog(); + indexer.setHost("localhost"); + indexer.setPort(4567); + indexer.setMessageFormat(MessageFormat.RFC_3164); + + indexer2 = new Syslog(); + indexer2.setHost("localhost"); + indexer2.setPort(4567); + indexer2.setMessageFormat(MessageFormat.RFC_3164); +} + + @Test + public void sameSettingsAreEqual() + { + assertThat(indexer.equals(indexer2), is(true)); + } + + @Test + public void messageFormatChangeIsNotEqual() + { + indexer.setMessageFormat(MessageFormat.RFC_5424); + assertThat(indexer.equals(indexer2), is(false)); + } + +} diff --git a/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java index eb893e02..11de969e 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java @@ -13,7 +13,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class AbstractLogstashIndexerDaoTest { @@ -66,13 +66,15 @@ public void buildPayloadSuccessTwoLines() throws Exception { } private AbstractLogstashIndexerDao getInstance() { - return new AbstractLogstashIndexerDao("localhost", -1, "", "", "") { - - public IndexerType getIndexerType() { - return IndexerType.REDIS; + return new AbstractLogstashIndexerDao() { + @Override + public void push(String data) throws IOException {} + + @Override + public String getDescription() + { + return "test"; } - - public void push(String data) throws IOException {} }; } } diff --git a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java index b3296d45..e18e1e88 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java @@ -11,17 +11,21 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.*; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) @@ -33,14 +37,18 @@ public class ElasticSearchDaoTest { @Mock CloseableHttpResponse mockResponse; @Mock HttpEntity mockEntity; - ElasticSearchDao createDao(String host, int port, String key, String username, String password) { - return new ElasticSearchDao(mockClientBuilder, host, port, key, username, password); + @Rule + public ExpectedException thrown = ExpectedException.none(); + + ElasticSearchDao createDao(String url, String username, String password) throws URISyntaxException { + URI uri = new URI(url); + return new ElasticSearchDao(mockClientBuilder, uri, username, password); } @Before public void before() throws Exception { int port = (int) (Math.random() * 1000); - dao = createDao("http://localhost", port, "/jenkins/logstash", "username", "password"); + dao = createDao("http://localhost:8200/logstash", "username", "password"); when(mockClientBuilder.build()).thenReturn(mockHttpClient); when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse); @@ -53,67 +61,25 @@ public void after() throws Exception { verifyNoMoreInteractions(mockHttpClient); } - @Test(expected = IllegalArgumentException.class) - public void constructorFailNullHost() throws Exception { - try { - createDao(null, 8200, "logstash", "username", "password"); - } catch (IllegalArgumentException e) { - assertEquals("Wrong error message was thrown", "host name is required", e.getMessage()); - throw e; - } - } - - @Test(expected = IllegalArgumentException.class) - public void constructorFailEmptyHost() throws Exception { - try { - createDao(" ", 8200, "logstash", "username", "password"); - } catch (IllegalArgumentException e) { - assertEquals("Wrong error message was thrown", "host name is required", e.getMessage()); - throw e; - } - } - - @Test(expected = IllegalArgumentException.class) - public void constructorFailMissingScheme() throws Exception { - try { - createDao("localhost", 8200, "logstash", "username", "password"); - } catch (IllegalArgumentException e) { - assertEquals("Wrong error message was thrown", "host field must specify scheme, such as 'http://'", e.getMessage()); - throw e; - } - } - - @Test(expected = IllegalArgumentException.class) - public void constructorFailNullKey() throws Exception { - try { - createDao("http://localhost", 8200, null, "username", "password"); - } catch (IllegalArgumentException e) { - assertEquals("Wrong error message was thrown", "elastic index name is required", e.getMessage()); - throw e; - } - } - - @Test(expected = IllegalArgumentException.class) - public void constructorFailEmptyKey() throws Exception { - try { - createDao("http://localhost", 8200, " ", "username", "password"); - } catch (IllegalArgumentException e) { - assertEquals("Wrong error message was thrown", "elastic index name is required", e.getMessage()); - throw e; - } + @Test + public void constructorFailInvalidUrl() throws Exception { + URI uri = new URI("localhost:8000/logstash"); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("java.net.MalformedURLException: unknown protocol: localhost"); + new ElasticSearchDao(mockClientBuilder, uri, "username", "password"); } @Test public void constructorSuccess1() throws Exception { // Unit under test - dao = createDao("https://localhost", 8200, "logstash", "username", "password"); + dao = createDao("https://localhost:8200/logstash", "username", "password"); // Verify results - assertEquals("Wrong host name", "https://localhost", dao.host); - assertEquals("Wrong port", 8200, dao.port); - assertEquals("Wrong key", "logstash", dao.key); - assertEquals("Wrong name", "username", dao.username); - assertEquals("Wrong password", "password", dao.password); + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 8200, dao.getPort()); + assertEquals("Wrong key", "/logstash", dao.getKey()); + assertEquals("Wrong name", "username", dao.getUsername()); + assertEquals("Wrong password", "password", dao.getPassword()); assertEquals("Wrong auth", "dXNlcm5hbWU6cGFzc3dvcmQ=", dao.getAuth()); assertEquals("Wrong uri", new URI("https://localhost:8200/logstash"), dao.getUri()); } @@ -121,14 +87,15 @@ public void constructorSuccess1() throws Exception { @Test public void constructorSuccess2() throws Exception { // Unit under test - dao = createDao("http://localhost", 8200, "jenkins/logstash", "", "password"); + dao = createDao("http://localhost:8200/jenkins/logstash", "", "password"); // Verify results - assertEquals("Wrong host name", "http://localhost", dao.host); - assertEquals("Wrong port", 8200, dao.port); - assertEquals("Wrong key", "jenkins/logstash", dao.key); - assertEquals("Wrong name", "", dao.username); - assertEquals("Wrong password", "password", dao.password); + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 8200, dao.getPort()); + assertEquals("Wrong scheme", "http", dao.getScheme()); + assertEquals("Wrong key", "/jenkins/logstash", dao.getKey()); + assertEquals("Wrong name", "", dao.getUsername()); + assertEquals("Wrong password", "password", dao.getPassword()); assertEquals("Wrong auth", null, dao.getAuth()); assertEquals("Wrong uri", new URI("http://localhost:8200/jenkins/logstash"), dao.getUri()); } @@ -136,14 +103,15 @@ public void constructorSuccess2() throws Exception { @Test public void constructorSuccess3() throws Exception { // Unit under test - dao = createDao("http://localhost", 8200, "/jenkins//logstash/", "userlongername", null); + dao = createDao("http://localhost:8200/jenkins//logstash/", "userlongername", null); // Verify results - assertEquals("Wrong host name", "http://localhost", dao.host); - assertEquals("Wrong port", 8200, dao.port); - assertEquals("Wrong key", "/jenkins//logstash/", dao.key); - assertEquals("Wrong name", "userlongername", dao.username); - assertEquals("Wrong password", null, dao.password); + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 8200, dao.getPort()); + assertEquals("Wrong scheme", "http", dao.getScheme()); + assertEquals("Wrong key", "/jenkins//logstash/", dao.getKey()); + assertEquals("Wrong name", "userlongername", dao.getUsername()); + assertEquals("Wrong password", null, dao.getPassword()); assertEquals("Wrong auth", "dXNlcmxvbmdlcm5hbWU6", dao.getAuth()); assertEquals("Wrong uri", new URI("http://localhost:8200/jenkins//logstash/"), dao.getUri()); } @@ -151,7 +119,7 @@ public void constructorSuccess3() throws Exception { @Test public void getPostSuccessNoAuth() throws Exception { String json = "{ 'foo': 'bar' }"; - dao = createDao("http://localhost", 8200, "/jenkins/logstash", "", ""); + dao = createDao("http://localhost:8200/jenkins/logstash", "", ""); // Unit under test HttpPost post = dao.getHttpPost(json); @@ -170,7 +138,7 @@ public void getPostSuccessNoAuth() throws Exception { @Test public void getPostSuccessAuth() throws Exception { String json = "{ 'foo': 'bar' }"; - dao = createDao("https://localhost", 8200, "/jenkins/logstash", "username", "password"); + dao = createDao("https://localhost:8200/jenkins/logstash", "username", "password"); // Unit under test HttpPost post = dao.getHttpPost(json); @@ -192,7 +160,7 @@ public void getPostSuccessAuth() throws Exception { @Test public void pushSuccess() throws Exception { String json = "{ 'foo': 'bar' }"; - dao = createDao("http://localhost", 8200, "/jenkins/logstash", "", ""); + dao = createDao("http://localhost:8200/jenkins/logstash", "", ""); when(mockStatusLine.getStatusCode()).thenReturn(201); @@ -209,7 +177,7 @@ public void pushSuccess() throws Exception { @Test(expected = IOException.class) public void pushFailStatusCode() throws Exception { String json = "{ 'foo': 'bar' }"; - dao = createDao("http://localhost", 8200, "/jenkins/logstash", "username", "password"); + dao = createDao("http://localhost:8200/jenkins/logstash", "username", "password"); when(mockStatusLine.getStatusCode()).thenReturn(500); when(mockResponse.getEntity()).thenReturn(new StringEntity("Something bad happened.", ContentType.TEXT_PLAIN)); diff --git a/src/test/java/jenkins/plugins/logstash/persistence/IndexerDaoFactoryTest.java b/src/test/java/jenkins/plugins/logstash/persistence/IndexerDaoFactoryTest.java deleted file mode 100644 index 408c02d3..00000000 --- a/src/test/java/jenkins/plugins/logstash/persistence/IndexerDaoFactoryTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package jenkins.plugins.logstash.persistence; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; - -import org.junit.Test; - -public class IndexerDaoFactoryTest { - - @Test - public void getAllInstances() throws Exception { - for (IndexerType type : IndexerType.values()) { - String host = type == IndexerType.ELASTICSEARCH ? "http://localhost" : "localhost"; - LogstashIndexerDao dao = IndexerDaoFactory.getInstance(type, host, 1234, "key", "username", "password"); - - assertNotNull("Result was null", dao); - assertEquals("Result implements wrong IndexerType", type, dao.getIndexerType()); - } - } - - @Test - public void successNulls() throws Exception { - for (IndexerType type : IndexerType.values()) { - String host = type == IndexerType.ELASTICSEARCH ? "http://localhost" : "localhost"; - LogstashIndexerDao dao = IndexerDaoFactory.getInstance(type, host, null, "key", null, null); - - assertNotNull("Result was null", dao); - assertEquals("Result implements wrong IndexerType", type, dao.getIndexerType()); - } - } - - @Test(expected = InstantiationException.class) - public void failureNullType() throws Exception { - try { - IndexerDaoFactory.getInstance(null, "localhost", 1234, "key", "username", "password"); - } catch (InstantiationException e) { - String msg = "[logstash-plugin]: Unknown IndexerType 'null'. Did you forget to configure the plugin?"; - assertEquals("Wrong message", msg, e.getMessage()); - throw e; - } - } -} diff --git a/src/test/java/jenkins/plugins/logstash/persistence/MemoryDao.java b/src/test/java/jenkins/plugins/logstash/persistence/MemoryDao.java new file mode 100644 index 00000000..0738eb7d --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/persistence/MemoryDao.java @@ -0,0 +1,36 @@ +package jenkins.plugins.logstash.persistence; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; +import net.sf.json.JSONObject; + +public class MemoryDao extends AbstractLogstashIndexerDao +{ + List output = new ArrayList<>(); + + public MemoryDao() + { + super(); + } + + @Override + public void push(String data) throws IOException + { + JSONObject json = JSONObject.fromObject(data); + output.add(json); + } + + public List getOutput() + { + return output; + } + + @Override + public String getDescription() + { + return "test"; + } +} \ No newline at end of file diff --git a/src/test/java/jenkins/plugins/logstash/persistence/RabbitMqDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/RabbitMqDaoTest.java index 91599868..727652c3 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/RabbitMqDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/RabbitMqDaoTest.java @@ -14,7 +14,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import com.rabbitmq.client.AuthenticationFailureException; import com.rabbitmq.client.Channel; @@ -48,6 +48,7 @@ public void before() throws Exception { int port = (int) (Math.random() * 1000); // Note that we can't run these tests in parallel dao = createDao("localhost", port, "logstash", "username", "password"); + dao.setCharset(Charset.defaultCharset()); when(mockPool.newConnection()).thenReturn(mockConnection); @@ -110,11 +111,11 @@ public void constructorSuccess() throws Exception { dao = createDao("localhost", 5672, "logstash", "username", "password"); // Verify results - assertEquals("Wrong host name", "localhost", dao.host); - assertEquals("Wrong port", 5672, dao.port); - assertEquals("Wrong key", "logstash", dao.key); - assertEquals("Wrong name", "username", dao.username); - assertEquals("Wrong password", "password", dao.password); + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 5672, dao.getPort()); + assertEquals("Wrong key", "logstash", dao.getQueue()); + assertEquals("Wrong name", "username", dao.getUsername()); + assertEquals("Wrong password", "password", dao.getPassword()); } @Test(expected = IOException.class) @@ -198,6 +199,7 @@ public void pushSuccess() throws Exception { public void pushSuccessNoAuth() throws Exception { String json = "{ 'foo': 'bar' }"; dao = createDao("localhost", 5672, "logstash", null, null); + dao.setCharset(Charset.defaultCharset()); // Unit under test dao.push(json); diff --git a/src/test/java/jenkins/plugins/logstash/persistence/RedisDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/RedisDaoTest.java index 6a1fedbc..a666a508 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/RedisDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/RedisDaoTest.java @@ -12,7 +12,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -27,7 +27,7 @@ public class RedisDaoTest { @Mock Jedis mockJedis; RedisDao createDao(String host, int port, String key, String username, String password) { - return new RedisDao(mockPool, host, port, key, username, password); + return new RedisDao(mockPool, host, port, key, password); } @Before @@ -90,11 +90,10 @@ public void constructorSuccess() throws Exception { dao = createDao("localhost", 6379, "logstash", "username", "password"); // Verify results - assertEquals("Wrong host name", "localhost", dao.host); - assertEquals("Wrong port", 6379, dao.port); - assertEquals("Wrong key", "logstash", dao.key); - assertEquals("Wrong name", "username", dao.username); - assertEquals("Wrong password", "password", dao.password); + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 6379, dao.getPort()); + assertEquals("Wrong key", "logstash", dao.getKey()); + assertEquals("Wrong password", "password", dao.getPassword()); } @Test(expected = IOException.class) diff --git a/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTest.java index 24bf22b3..a23a0890 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTest.java @@ -7,7 +7,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import com.cloudbees.syslog.Facility; import com.cloudbees.syslog.MessageFormat; @@ -21,11 +21,10 @@ public class SyslogDaoTest { String host = "localhost"; String appname = "jenkins:"; int port = 514; - UdpSyslogMessageSender udpSyslogMessageSender = new UdpSyslogMessageSender(); @Mock UdpSyslogMessageSender mockUdpSyslogMessageSender; - + @Before - public void before() throws Exception { + public void before() throws Exception { dao = createDao(host, port, null, null, null); dao.push(data); } @@ -36,7 +35,7 @@ public void ceeMessageFormat() throws Exception { verify(mockUdpSyslogMessageSender, times(1)).sendMessage(" @cee: " + data); } - // Test the MessageSender configuration. + // Test the MessageSender configuration. @Test public void syslogConfig() throws Exception { verify(mockUdpSyslogMessageSender, times(1)).setDefaultMessageHostname(host); @@ -45,11 +44,11 @@ public void syslogConfig() throws Exception { verify(mockUdpSyslogMessageSender, times(1)).setSyslogServerPort(port); verify(mockUdpSyslogMessageSender, times(1)).setDefaultFacility(Facility.USER); verify(mockUdpSyslogMessageSender, times(1)).setDefaultSeverity(Severity.INFORMATIONAL); - verify(mockUdpSyslogMessageSender, times(1)).setMessageFormat(MessageFormat.RFC_5424); + verify(mockUdpSyslogMessageSender, times(1)).setMessageFormat(MessageFormat.RFC_3164); } - + SyslogDao createDao(String host, int port, String key, String username, String password) { - return new SyslogDao(mockUdpSyslogMessageSender, host, port, key, username, password); + return new SyslogDao(mockUdpSyslogMessageSender, host, port); } - + } diff --git a/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTestIT.java b/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTestIT.java index 1181d8f3..37d1684f 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTestIT.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTestIT.java @@ -1,6 +1,9 @@ package jenkins.plugins.logstash.persistence; +import com.cloudbees.syslog.MessageFormat; import com.cloudbees.syslog.sender.UdpSyslogMessageSender; + +import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.nio.file.Files; @@ -26,9 +29,9 @@ public class SyslogDaoTestIT{ int port = 514; int logstashTimeout = 15; UdpSyslogMessageSender udpSyslogMessageSender = new UdpSyslogMessageSender(); - + @Before - public void before() throws Exception { + public void before() throws Exception { dao = createDao(host, port, null, null, null); } @@ -37,7 +40,7 @@ public void before() throws Exception { public void syslogSendRFC3164UDP() throws Exception { PrintWriter writer = null; - + // Clean up the the logstash log file try { writer = new PrintWriter(logfile); @@ -46,23 +49,23 @@ public void syslogSendRFC3164UDP() throws Exception { fail("Unable to clean up the logstash log file: " + e.getMessage()); } finally { - if (writer != null) { - writer.close(); + if (writer != null) { + writer.close(); } } - - // Send the syslog message - dao.setSyslogFormat("RFC3164"); + + // Send the syslog message + dao.setMessageFormat(MessageFormat.RFC_3164); dao.push(data); - - // Await for logstash to process the message + + // Await for logstash to process the message try { await().atMost(logstashTimeout, TimeUnit.SECONDS).until(logfileIsNotEmpty()); } catch (Exception e) { fail("Unable to find any logstash generated data within the logfile " + logfile + ": " + e.getMessage()); } - + // Parse the logstash generated logfile try { JSONObject logjson = JSONObject.fromObject(logcontent); @@ -71,14 +74,15 @@ public void syslogSendRFC3164UDP() throws Exception { catch (Exception e) { fail("Unable to parse the logstash generated logfile content: " + e.getMessage()); } - - assertEquals(" @cee: " + data, logmessage); - + + assertEquals(" @cee: " + data, logmessage); + } - + private Callable logfileIsNotEmpty() { return new Callable() { - public Boolean call() throws Exception { + @Override + public Boolean call() throws Exception { // Check the content of logfile generated by logstash logcontent = new String(Files.readAllBytes(Paths.get(logfile))); // The condition that must be fulfilled @@ -86,9 +90,9 @@ public Boolean call() throws Exception { } }; } - + SyslogDao createDao(String host, int port, String key, String username, String password) { - return new SyslogDao(udpSyslogMessageSender, host, port, key, username, password); + return new SyslogDao(udpSyslogMessageSender, host, port); } - + } diff --git a/src/test/resources/elasticSearch.xml b/src/test/resources/elasticSearch.xml new file mode 100644 index 00000000..494e0bf0 --- /dev/null +++ b/src/test/resources/elasticSearch.xml @@ -0,0 +1,9 @@ + + + + http://localhost:9300/logstash + + {AQAAABAAAAAQdB13xsx5UlCScGiBcVUOL0GqWYwAU5syhW9iBb6tG+4=} + + true + \ No newline at end of file diff --git a/src/test/resources/rabbitmq.xml b/src/test/resources/rabbitmq.xml new file mode 100644 index 00000000..62497b34 --- /dev/null +++ b/src/test/resources/rabbitmq.xml @@ -0,0 +1,10 @@ + + + localhost + 5672 + logstash + + {AQAAABAAAAAQ5wlElDfWGri9VuaMh0MCBZWs1fjL31zvnxrkszfW5pA=} + + true + \ No newline at end of file diff --git a/src/test/resources/redis.xml b/src/test/resources/redis.xml new file mode 100644 index 00000000..a4cff3a5 --- /dev/null +++ b/src/test/resources/redis.xml @@ -0,0 +1,10 @@ + + + + localhost + 6379 + logstash + {AQAAABAAAAAQAwaAxyveddM0PF+kR0dYFAymdth9PpitQnvJW0SR6JU=} + + true + \ No newline at end of file diff --git a/src/test/resources/syslog.xml b/src/test/resources/syslog.xml new file mode 100644 index 00000000..2c6c0dc4 --- /dev/null +++ b/src/test/resources/syslog.xml @@ -0,0 +1,10 @@ + + + + localhost + 519 + RFC_3164 + UDP + + true + \ No newline at end of file