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
NIFI-4932: Enable S2S work behind a Reverse Proxy #2510
Conversation
Dear reviewers, I've tested this improvement with Nginx and AWS ALB (Application Load Balancer). I used docker to run different Nginx configurations to test, and those docker environments are available here in my github project, which may be useful for reviewing, too. https://github.com/ijokarumawak/nifi-reverseproxy Please build this PR and see updated administration-guide.html for details. Thank you! |
Adding S2S endpoint Reverse Proxy mapping capability.
Thanks Koji, this is really important work. I can't review immediately but when I get a couple of my tickets knocked out, I'll take a look. Hopefully we get multiple sets of eyes on this one. |
@alopresto Thank you! The RAT check failed with SVG files for docs. I didn't run contrib check after I added docs, my bad. I'll update it. |
Will review... |
map.put(format("%s.secure", prefix), String.valueOf(peer.isSecure())); | ||
} | ||
|
||
public boolean isModificationNeeded(final SiteToSiteTransportProtocol 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.
Does it make sense to make this method private and invoke it from within the modify method? It seems like the general usage is to check if modification is needed and then invoke modify. Alternatively, we could invoke modify unconditionally and check if modification is needed within. If modification is not needed, then target could just be returned.
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.
@mcgilman The reason of this method being separated is to avoid unnecessary object creations at the caller. I imagine most of deployments will not use this configuration, hence if caller side (e.g. SiteToSiteResource) always create objects to call modify unconditionally, but doing nothing actually because not having modification definitions, that would be waste or resources.
|
||
private static final String PROPERTY_PREFIX = "nifi.remote.route."; | ||
|
||
public PeerDescriptionModifier(final NiFiProperties properties) { |
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.
A couple comments on this method...
- Can we make this code a little more readable by not using single letter variable names? It made it difficult to understand what was happening here.
- Can we add better error handling throughout this method? For instance, if the properties are misconfigured it's likely this could fail exceptionally with IndexOutOfBounds or EL Parsing Exception. Can we handle those specifics and log the underlying issue and then continue to fail. We should be able to identify when the routes are misconfigured and help drive the user to the exact issue.
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.
@mcgilman Agreed. I've changed variable names for better readability. Also changed validation logic to throw Exceptions with detailed error message.
For EL parsing, I tried several invalid ELs, but couldn't get EL parsing exception when it's compiled. Instead it errors out when it is evaluated.
More unit tests are added as well for invalid configuration scenarios.
I confirmed NiFi does not start with invalid configs.
} | ||
}); | ||
return route; | ||
}).filter(Route::isValid).collect(Collectors.groupingBy(r -> r.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.
If a route is invalid, would it be better to fail startup or ignore the route? I have some concern that a user wouldn't notice the WARN message in the log when filtering an invalid route.
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 changed it to throw Exceptions if invalid.
|
||
String targetIsSecure = secure == null ? null : secure.evaluateExpressions(variables, null); | ||
if (isBlank(targetIsSecure)) { | ||
targetIsSecure = "false"; |
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.
Is there a reason we are defaulting this to false here? If so, is this something that should be called out in the documentation?
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.
@mcgilman The blank check is not needed since Boolean.valueOf returns false
for null/empty string. I will remove these lines and also callout that default is 'false' in the docs.
image:s2s-rproxy-servername.svg["Server name to Node mapping"] | ||
|
||
1. Client1 initiates Site-to-Site protocol, the request is routed to one of upstream NiFi nodes. The NiFi node computes Site-to-Site port for RAW. By routing 'example1', port 10443 is returned. | ||
2. Client1 asks peers to 'nifi.example.com:10443', the request is routed to 'nifi0:8081'. The NiFi node computes available peers, by 'example1' routing rule, 'nifi0:8081' is converted to 'nifi0.example.com:10443', so are nifi1 and nifi2. As a result, 'nifi0.eample.com:10443', 'nifi1.example.com:10443' and 'nifi2.example.com:10443' are returned. |
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.
Type: 'eample'
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.
Thanks, fixed.
5. Client2 asks peers from 'nifi1:8081'. The 'example1' does not match, so the original 'nifi0:8081', 'nifi1:8081' and 'nifi2:8081' are returned as they are. | ||
6. Client2 decides to use 'nifi2:8081' for further communication. | ||
|
||
nifi.properties (all node has the same routing configuration) |
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.
Would it make sense to move this example configuration above these 1 - 6 steps? The steps reference 'example1' which was confusing until I realized that 'example1' was used as the route name in the configuration.
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 point. I added more wording to the listed texts so that readers can be guided to the nifi.properties example. I wanted to make the diagram and the steps closer together.
@mcgilman Thanks for reviewing. I've incorporated all feedback. Please take a look it again. Thanks! |
route.protocol = SiteToSiteTransportProtocol.valueOf(protocolAndRoutingName[0].toUpperCase()); | ||
route.name = protocolAndRoutingName[1]; | ||
routeDefinition.getValue().forEach(propertyKey -> { | ||
final String routingConfigName = propertyKey.substring(propertyKey.lastIndexOf('.') + 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.
I believe we should still better handle the property parse. I indirectly mentioned this in the last review with my comment to IndexOutOfBoundsException. Sorry if that wasn't clear previously. If a user misconfigures their properties the error isn't helpful:
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.apache.nifi.remote.PeerDescriptionModifier.lambda$new$4(PeerDescriptionModifier.java:93)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1683)
There is another substring
method above that is likely susceptible to the same issue.
final String routingConfigValue = properties.getProperty(propertyKey); | ||
switch (routingConfigName) { | ||
case "when": | ||
route.predicate = Query.prepare(routingConfigValue); |
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 also could not make prepare
throw but the JavaDocs state that it can. Could we add a try/catch around this switch to gracefully handle if it does throw?
@ijokarumawak Just wanted to add that I have verified this capability running standalone and clustered and everything seems to be working nicely. Just a couple more minor error handling cases. Thanks! |
- Use regex to check property key processing. - Catch AttributeExpressionLanguageParsingException.
@mcgilman Thanks for clarifying the String manipulation exception, I just didn't have enough imagination to come up with such invalid inputs. I switched to use regex to check and parse property keys. Now it should be more robust and provide more user friendly error messages. Also, added try/catch for EL parse failure. Thanks! |
Thanks @ijokarumawak! This has been merged to master. |
Adding S2S endpoint Reverse Proxy mapping capability.
Thank you for submitting a contribution to Apache NiFi.
In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:
For all changes:
Is there a JIRA ticket associated with this PR? Is it referenced
in the commit message?
Does your PR title start with NIFI-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
Has your PR been rebased against the latest commit within the target branch (typically master)?
Is your initial contribution a single, squashed commit?
For code changes:
For documentation related changes:
Note:
Please ensure that once the PR is submitted, you check travis-ci for build issues and submit an update to your PR as soon as possible.