Skip to content
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

Merged
merged 3 commits into from
May 16, 2017
Merged

Conversation

QubitPi
Copy link
Contributor

@QubitPi QubitPi commented May 4, 2017

2nd PR implementing #215

private final Meter httpErrorMeter;
private final Meter exceptionMeter;
protected final Meter httpErrorMeter;
protected final Meter exceptionMeter;
Copy link
Collaborator

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

Copy link
Collaborator

@garyluoex garyluoex left a 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
Copy link
Collaborator

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() {
Copy link
Collaborator

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() {
Copy link
Collaborator

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;
Copy link
Collaborator

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?

Copy link
Contributor Author

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;
Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed back to private

Copy link
Collaborator

@garyluoex garyluoex left a 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;
Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Contributor Author

@QubitPi QubitPi May 9, 2017

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

@QubitPi QubitPi force-pushed the Merge-Header-Info-into-JsonNode branch from 8df824f to 57678c3 Compare May 9, 2017 22:25
"following intervals %s where druid does not have data"
),

TOO_MUCH_INTERVAL_MISSING(
Copy link
Collaborator

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?

Copy link
Contributor Author

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(
Copy link
Collaborator

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());
Copy link
Collaborator

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();

@QubitPi QubitPi force-pushed the Merge-Header-Info-into-JsonNode branch from 57678c3 to e53307d Compare May 10, 2017 21:20
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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these going?

Copy link
Contributor Author

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"}
"""
Copy link
Collaborator

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?

Copy link
Collaborator

@garyluoex garyluoex May 10, 2017

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.

Copy link
Contributor Author

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?

Copy link
Collaborator

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here?

Copy link
Contributor Author

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"() {
Copy link
Collaborator

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();
Copy link
Collaborator

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.

Copy link
Collaborator

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();
Copy link
Collaborator

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();
Copy link
Collaborator

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.
Copy link
Collaborator

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?

Copy link
Collaborator

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(),
Copy link
Collaborator

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);
Copy link
Collaborator

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.
Copy link
Collaborator

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
Copy link
Collaborator

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);
Copy link
Collaborator

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))
Copy link
Collaborator

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.

Copy link
Contributor Author

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))
Copy link
Collaborator

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.

Copy link
Contributor Author

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> {
Copy link
Collaborator

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.

Copy link
Contributor Author

@QubitPi QubitPi May 16, 2017

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?

Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Contributor Author

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))
Copy link
Collaborator

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"},
Copy link
Collaborator

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
Copy link
Collaborator

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`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to update.

Copy link
Collaborator

@cdeszaq cdeszaq left a 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)
Copy link
Collaborator

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)

Copy link
Collaborator

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 =
Copy link
Collaborator

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final not needed

Copy link
Contributor Author

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
Copy link
Collaborator

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.

}

Copy link
Collaborator

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.
Copy link
Collaborator

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 {
Copy link
Collaborator

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final not needed

Copy link
Collaborator

@garyluoex garyluoex left a 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"));
Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modify test accordingly also.

@QubitPi QubitPi force-pushed the Merge-Header-Info-into-JsonNode branch from 037a96a to f6125d8 Compare May 16, 2017 20:07
@QubitPi QubitPi force-pushed the Merge-Header-Info-into-JsonNode branch from f6125d8 to 2f87c3d Compare May 16, 2017 20:07
@QubitPi QubitPi merged commit c9a7df2 into master May 16, 2017
@QubitPi QubitPi deleted the Merge-Header-Info-into-JsonNode branch May 16, 2017 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants