-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
move configuration from ToolInstallation to GlobalConfiguration #43
Conversation
use the executor of the build to get the node. builtOn is set too late for us to get the node and we end up using master
properly get the node when BuildData is initilaized from a pipeline pump mockito to latest version add integration test, that use Jenkins test harness
the plugin used a ToolInstallation for configuration. But logstash is not a tool that can be installed. Instead this should be a GlobalConfiguration. The indexers have different configuration requirements. Host and port are common, but things like username are not required everywhere. Also the previous implementation was totally unflexible. In order to add a new indexer one has to modify the sources. By configuring the indexers via an extension point we make it easily possible to have individual configuration jelly for each type. Adding a new indexer is just adding new classes. So it can be even moved to a separate plugin. Many of the fields in the plugin were public or package private. They have been made private or protected with corresponding getters and setters and direct calls to the fields replaced. Get the charset of a run and use it whenever we need to convert the String to a byte array Use DateFormatter from apache common lang to avoid potential threading problems.
properly get displayname of node (jenkinsci#38)
use Jenkins 2.7.4 instead of 2.60.3 fix problem with cast that is requried with java 7
Thanks, that's great.
Can you check #41? We are adding a new global config field there, but hopefully this PR might supersede it? |
Would it be possible to send each of those as a separate PR? It will make it much easier to review and integrate (e.g. hopefully we can merge the purely refactoring changes immediatelly) |
Use FastDateFormat from apache which is thread safe Avoid possible NPE in Node detection Avoid possible NPE in result (previous logic already avoided it but findbugs still claimed it to be a problem) Node detection is same for pipeline and freestyle so move it to initData method
OK will split this in several PRs |
extends AbstractDescribableImpl<LogstashIndexer<?>> | ||
implements ExtensionPoint, ReconfigurableDescribable<LogstashIndexer<?>> | ||
{ | ||
protected String host; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but maybe it would be better to extract those fields into a separate data object?
It seems cleaner to push the check in shouldRefreshInstance()
into the equals()
method instead of overriding shouldRefreshInstance()
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we're not comparing the same objects in this method so the equals()
method is not the right place. Currently there is a separation between the configuration and the actual dao that does the push to the indexer. The configuration class must decide if it should create a new dao instance or return the existing one which is done in the shouldRefreshInstance().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I don't like here is that LogstashIndexer is an abstract factory for the dao while also being the model for the configuration view (jelly). My intuition suggest extracting the model into a separate entity.
Maybe let me send you a patch to illustrate (as it might be easier than explaining) and you can revert it if you don't like it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwinter69 can you check ae96695 ?
It's only a rough draft but I think it demonstrates the idea (e.g. I'd rather have the data object immutable than cloning it).
Please excuse me for the messed up formatting, I accidentally formatted the whole Syslog.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes the whole thing with stapler and configuration more complex. Not sure if we have to make the Data object then also a describable so that stapler can auto configure it when global configuration is saved.
We could also move the comparison to the GlobalConfiguration class.
A third option would be to make the daos directly the thing that is configured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With your explanation I think it's OK to leave the code as is. It's still better than previous one.
We could also move the comparison to the GlobalConfiguration class.
Can you explain what you mean by this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at https://github.com/mwinter69/logstash-plugin/tree/configuration2
One effect of stapler is that when you bind a request to the configuration you always get a new instance of the logstashindexer. Thats why I had this check and implemented the reconfigure.
In this branch I replaced this by comparing (with equals) the newly configured instance with the active instance. If they are different, the active is replaced with the new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like configuration2
more since we are comparing instances of LogstashIndexer
instead of comparing LogstashIndexer
and a LogstashDao
.
…into jenkinsci-master
reduce visibility of fields (jenkinsci#46)
update javadoc to get more information for developers that want to extend the plugin. Update the readme Use the not deprecated junit runner of mockito
* @return {@link AbstractLogstashIndexerDao} instance | ||
*/ | ||
@Nonnull | ||
public final T getInstance() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to synchronize it? I can't find anything via a quick google
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good question. Potentially after a reconfiguration or initially we might have 2 builds running into the if at the same time. Probably it will not hurt to synchronize. The worst that happens is that 2 builds have 2 different daos with the same configuration. Will this be a problem? We will already have different daos active when configuration is changed (the builds that were running when configuration changed and builds started after change).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at the moment, but I think it would be nice to enable a close()
method on the Daos later to let them use long living connections etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another problem is somebody calling setHost()
concurrently with getInstance()
-- those are public methods after all.
Maybe it's covered by creating a new instance
when you bind a request to the configuration you always get a new instance of the logstashindexer
but then I don't actually understand how can caching the Dao instance in the LogstashIndexer work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is cached because I use the reconfigure instead of the default behaviour of staper to create new instance. But this question is not relevant when we switch to the change in my configuration2 branch.
protected boolean shouldRefreshInstance() | ||
{ | ||
return super.shouldRefreshInstance() || | ||
!instance.getMessageFormat().equals(messageFormat); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are not checking the protocol -- is this intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol is currently not used in the dao. Maybe we can remove this completely. Or we also implement the tcp protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were attempts to implement TCP in the past: #26
I'd either remove the field or use it in comparison to avoid future confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will add it to comparison. We should also implement the tcp protocol I think, just for completeness
@@ -37,7 +37,7 @@ | |||
* @since 1.0.0 | |||
*/ | |||
public interface LogstashIndexerDao { | |||
static enum IndexerType { | |||
public static enum IndexerType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interface members are public by default, I'd prefer to not restate default modifiers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
</f:entry> | ||
</f:advanced> | ||
</f:section> | ||
<!-- We need to keep this file in order to prevent that in the UI we get the option to configure a Logstash Installation --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about leaving some explanatory text here, e.g. "Configuration moved to Global Configuration"? Bonus points for a link :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be enough to document this on the wiki page.
btw I suggest to upgrade the version to 2.0. And maybe we find a way to show that plugin changed in a way that when you go back to previous version you have to manually adjust configuration (this is possible somehow).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know they should check the wiki page, but this makes for less frustration.
Can we leave a short message here with a link to the wiki page then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will add a link to global configuration page
@@ -1,4 +1,4 @@ | |||
<div> | |||
<p>The host name or IP address of the indexer to send log data to.<br> | |||
ELASTICSEARCH: Also specify scheme. Example: "https://myserver". </p> | |||
Also specify scheme. Example: "https://myserver". </p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a separate scheme field instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually what would be even better is to have a single field accepting a full URL instead. Do you think it's possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we could also remove the key, as this is just added to the url
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
@Test | ||
public void test_redisMigration() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get rid of the test_
prefixes? It's not needed in JUnit4
implements ExtensionPoint, ReconfigurableDescribable<LogstashIndexer<?>> | ||
{ | ||
protected String host; | ||
protected int port; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a different note: maybe we should push the host and port up?
MemoryDao
is one obvious example that doesn't use those, but I think it might be cleaner code in general.
We could have another helper abstract class to avoid copying this for all the indexer types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we replace for elastic search the host and port with a url this might make sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
rename junit test methods synchronize getting the dao instance to avoid problems. deprecate enums
// when we bind the stapler request we get a new instance of logstashIndexer | ||
staplerRequest.bindJSON(this, json); | ||
|
||
if (logstashIndexer != null && !logstashIndexer.equals(activeIndexer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Objects.equals()
seems shorter
introduce an abstract host based indexer that serves as base for most indexers. Elastic search uses a uri approach where we need scheme, host, port and url path, so we allow to configure a uri instead.
{ | ||
uri = (new URIBuilder(descriptor.getHost())) | ||
.setPort(descriptor.getPort()) | ||
.setPath("/" + descriptor.getKey()).build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this will work properly for keys that already start with /
does this UriBuilder
collapse consecutive /
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exactly the same behaviour as it was before in the ElasticSearchDao
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK then
|
||
@DataBoundConstructor | ||
public ElasticSearch() | ||
public ElasticSearch(String uri) throws URISyntaxException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we changing the constructor instead of using a @DataBoundSetter
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the uri is not valid it will fail to save the configuration when it is checked in the constructor.
When using a DataBoundSetter, you can save the globalconfiguration with invalid uri
} | ||
|
||
return FormValidation.ok(); | ||
if(uri.getPort() == -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not let users rely on default port?
https://logstash.internal/index
looks better than https://logstash.internal:443/index
} | ||
|
||
if(StringUtils.isBlank(uri.getPath()) || uri.getPath().trim().matches("^\\/+$")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mixed feelings here.
It's obviously useful for 99% of usages, but probably somebody somewhere with some weird Apache/modrewrite in front will want to use an empty path.
Can we make this into a warning instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before it was also a must to have a non empty key
* @param uri | ||
* @throws IllegalArgumentException when one of scheme, port or path | ||
*/ | ||
public static void validateUri(URI uri) throws IllegalArgumentException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we ignore those I'd add a check enforcing empty username and password (in authority part)
} | ||
|
||
@DataBoundSetter | ||
public void setKey(String key) | ||
public void setUri(String value) throws URISyntaxException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor; for sake of consistency can we make setter accept URI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With URI stapler is not able to autoconvert the String to an URI. I will switch to URL as persistence here. Then I can also use a DataBoundSetter that accepts a URL
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
|
||
public class HostConfiguration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find any references to that class, am I missing something?
/** | ||
* Abstract data access object for Logstash indexers. | ||
* | ||
* @author Rusty Gerard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see some tasty copy-pasta here. Unless you are an alter ego of mr. Gerard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please just remove the @author
tags, I think they are useless in a world with git blame
{ | ||
URI uri = (new URIBuilder("localhost:8000")).setPath("/logstash").build(); | ||
|
||
//System.out.println(uri.toURL()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commented out code
actually it's weird FB isn't flagging this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot to delete this main class
stapler is not able to autconvert String to URI add more TODOs
// Clean up the the logstash log file | ||
try { | ||
File file = new File(logfile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: it's better to use http://junit.org/junit4/javadoc/4.12/org/junit/rules/TemporaryFolder.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a try to get this test running in Eclipse. It always fails in Eclipse for me, thought it was due to missing folders, but didn't help. I'll remove that again.
} catch (IllegalArgumentException e) { | ||
assertEquals("Wrong error message was thrown", "elastic index name is required", e.getMessage()); | ||
throw e; | ||
assertEquals("Wrong error message was thrown", "java.net.MalformedURLException: unknown protocol: localhost", e.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: http://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html looks like a better idea here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider using ExpectedException as a bad practice (see also https://artemzin.com/blog/test-expected-badpracticeexception/)
Why rethrow when we already catched it and checked the message is what we expect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I was talking about the ExpectedException rule
@Rule
public ExpectedException thrown= ExpectedException.none();
// ...
thrown.expect(NullPointerException.class);
thrown.expectMessage("happened");
throw new NullPointerException("What happened?");
public LogstashConfiguration() | ||
{ | ||
load(); | ||
if (logstashIndexer != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not always assign? I don't see how activeIndexer
could be non-null here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be null when you install the plugin. But the activeIndexer being null is not a problem
{ | ||
// when we bind the stapler request we get a new instance of logstashIndexer | ||
staplerRequest.bindJSON(this, json); | ||
if (!Objects.equals(logstashIndexer, activeIndexer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add further comment explaining that we check for equality because Indexer
instance is a holder for Dao
} | ||
// String comparison is not optimal but comparing the urls directly is | ||
// criticized by findbugs as being a blocking operation | ||
else if (!url.toString().equals(other.url.toString())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: I'd suggest to have an URI field instead and do an URL -> URI conversion in setter
} | ||
} | ||
|
||
public static void main(String[] args) throws MalformedURLException, URISyntaxException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover test method?
@Override | ||
public int getDefaultPort() | ||
{ | ||
return 9300; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
umm.. isn't ES default port 9200?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes but with URI, we don't need this at all.
LGTM except for the minor issues listed above. I think I should do another release, and then we can merge it and bump the major version. |
URI can be easier compared some minor changes in tests
The plugin used a ToolInstallation for configuration. But logstash is
not a tool that can be installed. Instead this should be a
GlobalConfiguration.
The indexers have different configuration requirements. Host and port
are common, but things like username are not required everywhere. Also
the previous implementation was totally unflexible. In order to add a
new indexer one has to modify the sources. By configuring the indexers
via an extension point we make it easily possible to have individual
configuration jelly for each type. Adding a new indexer is just adding
new classes. So it can be even moved to a separate plugin.