-
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
Merge Header Info into JsonNode #267
Conversation
private final Meter httpErrorMeter; | ||
private final Meter exceptionMeter; | ||
protected final Meter httpErrorMeter; | ||
protected final Meter exceptionMeter; |
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 exposing fields directly, we should use methods instead. In this case, methods to count errors, etc. are probably the right call, and accessors will likely work for the other variables made protected in this pr
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.
Add change log
@@ -169,6 +170,11 @@ | |||
public static final String DEPRECATED_PERMISSIVE_AVAILABILITY_FLAG = SYSTEM_CONFIG.getPackageVariableName( | |||
"permissive_column_availability_enabled"); | |||
|
|||
public static final int DRUID_UNCOVERED_INTERVAL_LIMIT = SYSTEM_CONFIG.getIntProperty( | |||
SYSTEM_CONFIG.getPackageVariableName("druid_uncovered_interval_limit"), | |||
-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.
Default to 0
as discussed earlier.
@@ -356,4 +357,12 @@ public Integer getTimeout() { | |||
public DruidServiceConfig getServiceConfig() { | |||
return serviceConfig; | |||
} | |||
|
|||
public Meter getHttpErrorMeter() { |
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.
should be protected.
return httpErrorMeter; | ||
} | ||
|
||
public Meter getExceptionMeter() { |
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.
should be protected.
@@ -66,8 +67,8 @@ | |||
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>> headersToAppend; | |||
private final DruidServiceConfig serviceConfig; | |||
protected final Supplier<Map<String, String>> headersToAppend; |
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 this needs to be protected?
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.
changed back to private
private final Supplier<Map<String, String>> headersToAppend; | ||
private final DruidServiceConfig serviceConfig; | ||
protected final Supplier<Map<String, String>> headersToAppend; | ||
protected 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.
Why this needs to be protected?
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.
changed back to private
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 probably do a test to make sure that the jsonNode we built are as what we expect it to be.
) | ||
) { | ||
rootNode = jsonParser.readValueAsTree(); | ||
objectNode = (ObjectNode) rootNode; |
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.
Simply casting is not a good choice i think, we should create a new ObjectNode and put things into the ObjectNode.
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 would try out and consider moving the response parsing into jsonNode process into its own method in AsyncDruidWebserviceImpl.java and simply override it here instead of overriding the whole sendRequest method. If we do that, we also need to move the response status check on the top of this sendRequest method into its own method and override it here also. If you think this makes sense too, try 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.
Also fix the unused import things
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.
@garyluoex It's a long method. So, yeah, let's break it down
8df824f
to
57678c3
Compare
"following intervals %s where druid does not have data" | ||
), | ||
|
||
TOO_MUCH_INTERVAL_MISSING( |
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 did we remove these?
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.
They are back
@@ -215,17 +215,6 @@ | |||
|
|||
RESULT_MAPPING_FAILURE( | |||
"Error occurred while processing response data: %s" | |||
), | |||
|
|||
DATA_AVAILABILITY_MISMATCH( |
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 did we remove these?
|
||
@Override | ||
protected JsonNode constructJsonResponse(Response response) throws IOException { | ||
ObjectNode objectNode = (ObjectNode) OBJECT_MAPPER.readTree(response.getResponseBody()); |
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 reason I suggested to create a new ObjectNode is because I suspect that the JsonNode created by
(ObjectNode) OBJECT_MAPPER.readTree(response.getResponseBody());
is actually an ArrayNode
, force casting it to ObjectNode
will cause runtime error. Which I want the test to test, but it seems like the test assumes that the response.getResponseBody()
returns a object, but I think it is actually an array.
To create a new node,
ObjectNode node = JsonNodeFactory.instance.objectNode();
57678c3
to
e53307d
Compare
TOO_MUCH_INTERVAL_MISSING( | ||
"More than %s interval missing information received from druid, inspect if query " + | ||
"expects more than %s missing intervals or increase " + | ||
"uncoveredIntervalsLimit configuration value" |
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.
Where are these going?
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.
They are back
response.getResponseBody() >> | ||
""" | ||
{"k1":"v1"} | ||
""" |
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 is this using a multi-line string? Can it just be 1 line?
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.
Also the first level should be array instead of object.
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.
@garyluoex Could you give an example?
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, why should it be an array and not an object? Are you saying that Druid's response is a top-level array?
) | ||
|
||
expect: | ||
print asyncDruidWebServiceImplV2.constructJsonResponse(response) |
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 is this 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 forgot to delete it
import java.util.function.Supplier; | ||
|
||
class AsyncDruidWebServiceImplV2Spec extends Specification { | ||
def "Make sure X-Druid-Response-Context and status-code are mreged into existing JsonNode"() { |
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.
s/mreged/merged
*/ | ||
public class AsyncDruidWebServiceImplV2 extends AsyncDruidWebServiceImpl { | ||
|
||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
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 not make a new mapper. We should get the same mapper that the rest of the system is using.
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 also should not be using a static one.
* @throws IOException when there is a JSON parsing error | ||
*/ | ||
protected JsonNode constructJsonResponse(Response response) throws IOException { | ||
MappingJsonFactory jsonFactory = new MappingJsonFactory(); |
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 probably be inlined?
* @param error callback for handling http errors. | ||
*/ | ||
private void markError(Status status, Response response, String druidQueryId, HttpErrorCallback error) { | ||
httpErrorMeter.mark(); |
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.
Should use the getter, not the direct field access
} | ||
|
||
/** | ||
* Log request using RequestLog. |
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 doing much more than just logging the request, right? We should probably call out all of those different responsibilities of this method?
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's also worth noting that this isn't logging the request, but it's logging the completed response. If it was logging the request, it would not need to be triggering with pieces of the response. And if it was for any response, it should handle the failure cases as well.
|
||
LOG.debug( | ||
"druid {} response code: {} {} and druid query id: {}", | ||
serviceConfig.getNameAndUrl(), |
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.
Should use the getter, not direct field access
rootNode = jp.readValueAsTree(); | ||
} | ||
success.invoke(rootNode); | ||
JsonNode jsonNode = constructJsonResponse(response); |
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.
Inline
} | ||
|
||
/** | ||
* Logs request using RequestLog and logs Druid response. |
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 restores the serialized request log info back into the RequestLog object, stops / starts some timers, counts outstanding queries, etc. We should definitely call those things out, since those are very important side-effects of this method.
/** | ||
* Represents the druid web service endpoint for partial data V2. | ||
* <p> | ||
* When Druid returns data with missing intervals with a non-OK status code, instead of |
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 Druid have a non-OK response code in that case? (ie. where there are missing intervals) Or does it still return as a 200 OK?
|
||
JsonNode responseNode = new MappingJsonFactory().createParser(response.getResponseBodyAsStream()) | ||
.readValueAsTree(); | ||
objectNode.put("response", responseNode); |
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 version of put
is deprecated.
""" | ||
{"k1":"v1"} | ||
""" | ||
response.getResponseBodyAsStream() >> new ByteArrayInputStream("{\"k1\":\"v1\"}".getBytes(StandardCharsets.UTF_8)) |
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 escaping the quotes, we should use Strings (instead of GStrings) if we don't need the templating behavior.
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.
Beautiful!
def "Make sure X-Druid-Response-Context and status-code are merged into existing JsonNode"() { | ||
given: | ||
Response response = Mock(Response) | ||
response.getResponseBodyAsStream() >> new ByteArrayInputStream('{"k1":"v1"}'.getBytes(StandardCharsets.UTF_8)) |
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 body should be an array at the top level instead of object.
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.
Changed
/** | ||
* A strategy that takes a JSON Node built from a Response, nests it and makes it a peer with the Response headers. | ||
*/ | ||
public class HeaderNestingJsonBuilderStrategy implements Function<Response, JsonNode> { |
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 remove the word Strategy? It makes the name longer without adding much information 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.
@garyluoex No problem. @michael-mclawhorn Do I have the permission to remove 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.
It's naming the Function
that this extends from, indicating how the function is being used (ie. the Strategy pattern, to determine some behavior in a broader set of processing). This name is really 2 parts: HeaderNesting
and JsonBuilderStrategy
. The 1st part is describing this implementation, and the 2nd part is really what purpose this Function
is serving.
So, tl;dr: I'd keep it. But that's just my opinion.
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 critical, just leave it 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.
OK
def "Make sure X-Druid-Response-Context and status-code are merged into existing JsonNode"() { | ||
given: | ||
Response response = Mock(Response) | ||
response.getResponseBodyAsStream() >> new ByteArrayInputStream('[{"k1":"v1"}]'.getBytes(StandardCharsets.UTF_8)) | ||
response.getResponseBodyAsStream() >> new ByteArrayInputStream('{"k1":"v1"}'.getBytes(StandardCharsets.UTF_8)) |
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.
Rebase error, need to put back the list bracket.
''' | ||
{ | ||
"response": "[{"k1":"v1"}]", | ||
"response": {"k1":"v1"}, |
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.
Rebase error, need to put back the list bracket.
CHANGELOG.md
Outdated
@@ -14,6 +14,12 @@ Current | |||
* Add a configurable property named "druid_uncovered_interval_limit" | |||
* Add new response error messages as needed by Partial Data V2 | |||
|
|||
- [Merge Druid Response Header into Druid Response Body Json Node in AsyncDruidWebServiceImplV2](https://github.com/yahoo/fili/pull/267) | |||
* Added `AsyncDruidWebServiceImplV2` which overrides `sendRequest` method in `AsyncDruidWebServiceImpl` by adding |
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.
Need to update.
CHANGELOG.md
Outdated
- [Merge Druid Response Header into Druid Response Body Json Node in AsyncDruidWebServiceImplV2](https://github.com/yahoo/fili/pull/267) | ||
* Added `AsyncDruidWebServiceImplV2` which overrides `sendRequest` method in `AsyncDruidWebServiceImpl` by adding | ||
"X-Druid-Response-Context" to the response JSON | ||
* AbstractBinderFactory::buildDruidWebService returns `AsyncDruidWebServiceImplV2` or `AsyncDruidWebServiceImplV2` |
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.
Need to update.
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.
Nothing big. Just some minor formatting, javadoc, and changelog cleanup.
@@ -14,6 +14,10 @@ Current | |||
* Add a configurable property named "druid_uncovered_interval_limit" | |||
* Add new response error messages as needed by Partial Data V2 | |||
|
|||
- [Merge Druid Response Header into Druid Response Body Json Node in AsyncDruidWebServiceImplV2](https://github.com/yahoo/fili/pull/267) |
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 may be worth calling out here that this is not a change to the default behavior, but is instead opt-in via configuration (and including how to configure that opt-in)
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.
Should also call out the change to the AsyncDruidWebServiceClientImpl
, in that it now takes a strategy for building the JSON from the entire response. (since that's what the ability we're adding hinges on)
public static final String DRUID_TIMER = "DruidProcessing"; | ||
public static final String DRUID_QUERY_TIMER = DRUID_TIMER + "_Q_"; | ||
public static final String DRUID_QUERY_ALL_TIMER = DRUID_QUERY_TIMER + "All"; | ||
public static final String DRUID_QUERY_MAX_TIMER = DRUID_QUERY_TIMER + "Max"; | ||
public static final String DRUID_WEIGHTED_QUERY_TIMER = DRUID_TIMER + "_W_"; | ||
public static final String DRUID_SEGMENT_METADATA_TIMER = DRUID_TIMER + "_S_0"; | ||
|
||
public static final Function<Response, JsonNode> DEFAULT_JSON_NODE_BUILDER_STRATEGY = |
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.
Needs JavaDoc (since this essentially a class) explaining what it does. "Default" isn't super descriptive.
new Function<Response, JsonNode>() { | ||
|
||
@Override | ||
public JsonNode apply(final Response response) { |
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.
final
not needed
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.
@cdeszaq I removed final
, but why is it not needed? I'm just curious.
initializeWebClient(serviceConfig.getTimeout()), | ||
mapper, | ||
headersToAppend, | ||
DEFAULT_JSON_NODE_BUILDER_STRATEGY |
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 on this should call out that this constructor uses a particular strategy as the default.
} | ||
|
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.
Keep 1 blank line between methods
} | ||
|
||
/** | ||
* IOC 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.
JavaDoc should call out what strategy is used as the default, since this constructor doesn't take a strategy.
* @return the relevant information in a JSON node | ||
* @throws IOException when there is a JSON parsing error | ||
*/ | ||
protected JsonNode constructJsonResponse(Response response) throws IOException { |
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 method is never used, and seems to have been replaced by the strategy pattern.
} | ||
|
||
@Override | ||
public JsonNode apply(final Response response) { |
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.
final
not needed
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.
Last problem, otherwise good for now.
public JsonNode apply(final Response response) { | ||
ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); | ||
objectNode.set("response", baseStrategy.apply(response)); | ||
objectNode.put("X-Druid-Response-Context", response.getHeader("X-Druid-Response-Context")); |
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.
Should use set
instead of put
.
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.
put is deprecated, we should parse it as a jsonNode
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.
Modify test accordingly also.
037a96a
to
f6125d8
Compare
f6125d8
to
2f87c3d
Compare
2nd PR implementing #215