-
Notifications
You must be signed in to change notification settings - Fork 96
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
Ability to add auth headers to Druid requests. #62
Conversation
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.
Just a few small things. Overall, looks pretty good
@@ -100,6 +100,7 @@ | |||
import com.codahale.metrics.health.HealthCheckRegistry; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
|
|||
import org.apache.commons.collections.map.HashedMap; |
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 use a built-in (ie. Java Collections) map instead of apache commons?
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper) { | ||
this(serviceConfig, initializeWebClient(serviceConfig.getTimeout()), mapper); | ||
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper, | ||
Supplier<Map<String, String>> authHeaders |
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.
Wrapping param lists should "chop" the list, rather than just wrap it. eg.:
public AsyncDruidWebServiceImpl(
DruidServiceConfig serviceConfig,
ObjectMapper mapper,
Supplier<Map<String, String>> authHeaders
) {
@@ -70,8 +72,10 @@ | |||
* @param serviceConfig Configuration for the Druid Service | |||
* @param mapper A shared jackson object mapper resource |
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.
Javadoc needed for the new parameter
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.
Addressed this in the constructor below.
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper) { | ||
this(serviceConfig, initializeWebClient(serviceConfig.getTimeout()), mapper); | ||
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper, | ||
Supplier<Map<String, String>> authHeaders |
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 re-name this? Rather than auth-specific, can this be named so that it can be any headers?
@@ -102,7 +106,9 @@ private static AsyncHttpClient initializeWebClient(int requestTimeout) { | |||
* @param asyncHttpClient the HTTP client | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig config, AsyncHttpClient asyncHttpClient, ObjectMapper mapper) { | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig config, AsyncHttpClient asyncHttpClient, ObjectMapper mapper, | |||
Supplier<Map<String, String>> authHeaders |
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.
Same comments re: wrapping and name apply here as well
@@ -61,7 +63,7 @@ | |||
public static final String DRUID_WEIGHTED_QUERY_TIMER = DRUID_TIMER + "_W_"; | |||
public static final String DRUID_SEGMENT_METADATA_TIMER = DRUID_TIMER + "_S_0"; | |||
|
|||
|
|||
private final Supplier<Map<String, String>> authHeaders; |
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 rename this to indicate any headers?
@@ -102,7 +106,9 @@ private static AsyncHttpClient initializeWebClient(int requestTimeout) { | |||
* @param asyncHttpClient the HTTP client | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig config, AsyncHttpClient asyncHttpClient, ObjectMapper mapper) { | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig config, AsyncHttpClient asyncHttpClient, ObjectMapper mapper, | |||
Supplier<Map<String, String>> authHeaders |
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 should also have constructors that default the header supplier for backwards compatibility. We can deprecate them so that people are required to supply a supplier eventually, but we should try to maintain backwards compatibility if we can.
The supplier can just return an empty map in the defaulting case.
@cdeszaq Addressed all the comments. |
@@ -72,10 +73,27 @@ | |||
* @param serviceConfig Configuration for the Druid Service | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper, | |||
Supplier<Map<String, String>> authHeaders | |||
@Deprecated |
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 you could add the @deprecated
annotation to the JavaDoc as well, with a brief explanation of why this constructor is being deprecated, and which constructor should be used instead, I would be grateful.
@@ -70,8 +73,27 @@ | |||
* @param serviceConfig Configuration for the Druid Service | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper) { | |||
this(serviceConfig, initializeWebClient(serviceConfig.getTimeout()), mapper); | |||
@Deprecated |
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 @deprecated
annotation should also be added to the JavaDoc, with a brief explanation of why this is being deprecated, and a pointer to which constructor should be used instead.
@@ -102,7 +124,29 @@ private static AsyncHttpClient initializeWebClient(int requestTimeout) { | |||
* @param asyncHttpClient the HTTP client | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig config, AsyncHttpClient asyncHttpClient, ObjectMapper mapper) { | |||
@Deprecated |
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 also needs a CHANGELOG entry. |
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.
Other than the minor JavaDoc update on the 2 deprecated constructors, and adding entries to the ChangeLog file (a bit in the ### Deprecated
section and a bit in the ### Changed
section), this PR looks like it's very close!
@@ -70,8 +73,27 @@ | |||
* @param serviceConfig Configuration for the Druid Service | |||
* @param mapper A shared jackson object mapper resource | |||
*/ | |||
public AsyncDruidWebServiceImpl(DruidServiceConfig serviceConfig, ObjectMapper mapper) { | |||
this(serviceConfig, initializeWebClient(serviceConfig.getTimeout()), mapper); | |||
@Deprecated |
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.
For these deprecations, we should add an @deprecated
tag to the javadoc indicating why we're deprecating them (because you should specify your header supplier), and what you should use instead (the matching constructor with that new parameter)
Applies to the other @Deprecated
instance in this PR as well
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.
Just a few thoughts around the extension hook for the Supplier
, but otherwise that extension looks good.
PR still needs CHANGELOG entries and updates the the JavaDoc for those @Deprecated
methods documenting the deprecation
@@ -919,7 +924,24 @@ protected JobPayloadBuilder buildJobPayloadBuilder() { | |||
* @return A DruidWebService | |||
*/ | |||
protected DruidWebService buildDruidWebService(DruidServiceConfig druidServiceConfig, ObjectMapper mapper) { | |||
return new AsyncDruidWebServiceImpl(druidServiceConfig, mapper, HashMap::new); | |||
Supplier<Map<String, String>> supplier = HashMap::new; | |||
String customSupplierClassString = SYSTEM_CONFIG.getStringProperty(DRUID_HEADER_SUPPLIER_CLASS, 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.
@michael-mclawhorn does this usage fit with how we usually use systemConfig?
Constructor<?> constructor = c.getConstructor(); | ||
supplier = (Supplier<Map<String, String>>) constructor.newInstance(); | ||
} catch (ClassNotFoundException | NoSuchMethodException | | ||
IllegalAccessException | InstantiationException | InvocationTargetException e) { |
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.
Rather than enumerate all of these, should we just catch Exception
? If anything goes wrong loading this, we should just fail anyway, no matter what the exception is, right?
); | ||
throw new IllegalStateException(e); | ||
} | ||
} |
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 we should pull the buildDruidWebServiceHeaderSupplier
code into it's own method. That will give another (fairly easy) override-based config mechanism if someone's Supplier
needs more than a no-param constructor.
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 assumed we could just pull the other params from system_config in the supplier. I guess there could be more complicated use cases?
d2f5179
to
fcdad74
Compare
@archolewa @cdeszaq Addressed all review comments. |
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 |
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 the Supplier<Map<S,S>> type should be seriously reconsidered.
A better name for the additional headers and documentation of the semantics (adding vs replacing) on the request would be a good idea.
Also, tests.
At minimum, postDruidQuery should be tested to verify that the added headers get added.
Abstract binder factory being tested with actually building a header source from a config parameter and an empty one from null would be really good.
Alternatives to Supplier would be an Iterable or an rx Observable.
@@ -61,17 +64,38 @@ | |||
public static final String DRUID_WEIGHTED_QUERY_TIMER = DRUID_TIMER + "_W_"; | |||
public static final String DRUID_SEGMENT_METADATA_TIMER = DRUID_TIMER + "_S_0"; | |||
|
|||
|
|||
private final Supplier<Map<String, String>> anyHeaders; | |||
private final DruidServiceConfig serviceConfig; |
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 dislike the name of the variable. If I understand the scope of this, these are mandatory headers appended (or possibly prepended, it looks like the latter based on digging into the netty code) to the client request. So possible 'requestAppendHeaders' or 'clientAppendHeaders'.
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're adding the headers (and it looks like added headers strictly add, don't replace), I think a name like headersToAdd
or headersToAppend
helps clarify this.
.addHeader("Content-Type", "application/json"); | ||
|
||
anyHeaders.get().forEach((k, v) -> requestBuilder.addHeader(k, v)); | ||
|
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 does not appear that headers are maplike in that there can't be more than one of a given key value. Given this, you might be better served with an iterable 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.
While the AsyncHttpClient
allows for multi-value headers, I think it's OK for us to restrict it down to just being able to add single-valued headers. We can expand that later if we need to.
Constructor<?> constructor = c.getConstructor(); | ||
supplier = (Supplier<Map<String, String>>) constructor.newInstance(); | ||
} catch (Exception e) { | ||
LOG.error( |
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 catch something more specific than 'Exception' 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.
We were matching something more specific in an earlier version of this PR and I suggested replacing that list of roughly 5 exceptions with just Exception
. Since we're re-throwing the exception anyways, catching everything so that we can log it (since it'll be fatal anyways) is (I think) fine, and lets the code be smaller / cleaner.
.setBody(entityBody) | ||
.addHeader("Content-Type", "application/json"); | ||
|
||
anyHeaders.get().forEach((k, v) -> requestBuilder.addHeader(k, v)); |
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 be replaced with method reference (requestBuilder::addHeader
)
To summarize my responses, since I distributed them across Rick's responses to my comments: |
fcdad74
to
0b6c2c4
Compare
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.
Definitely need to test that we're adding any supplied headers on the Druid requests.
I don't know if we have a hook for that in our testing framework yet, but I don't know if there's a need for such a hook in the functional tests (it would be OK to have one). If we don't want to add one, I think we should be able to test the Async client in isolation.
To do so, we may need to either extend an existing or add a new TestServlet
(which are the pieces we use to capture requests to "druid")
@cdeszaq Addressed remaining comments and added a unit test. |
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.
Very close, just a few minor things!
@@ -0,0 +1,53 @@ | |||
package com.yahoo.bard.webservice.druid.client.impl |
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.
Copyright needed
DruidClientConfigHelper.getNonUiServiceConfig(), | ||
new ObjectMapper(), | ||
supplier | ||
) |
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.
All of this stuff from here up that's in the when
block should go up in the setup
block, since it's still setting up the scenario.
If needed, you can use and:
blocks (with an optional comment: and: "a set of expected headers to add"
) to break up any block.
then: | ||
HttpHeaders actualHeaders = webServiceImplWrapper.getHeaders() | ||
for (Map.Entry<String, String> header : expectedHeaders) { | ||
actualHeaders.get(header.getKey()) == header.getValue() |
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.
Only top-level expressions in a then
block are treated as assertions. To treat non-top-level expressions as assertions, you need to explicitly add the assert
keyword: assert actualHeaders.get(header.getKey()) == header.getValue()
protected Supplier<Map<String, String>> buildDruidWebServiceHeaderSupplier() { | ||
Supplier<Map<String, String>> supplier = HashMap::new; | ||
String customSupplierClassString = SYSTEM_CONFIG.getStringProperty(DRUID_HEADER_SUPPLIER_CLASS, null); | ||
if (customSupplierClassString != null && customSupplierClassString != "") { |
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 should fix this too.
@cdeszaq Addressed the latest comments. |
👍 just needs to be squashed and rebased onto master and I think this is good to merge. |
4caa0eb
to
6edbda2
Compare
We are adding security to our Druid cluster and need Fili to be able to send a token in a header for authenticating our Druid requests.