-
Notifications
You must be signed in to change notification settings - Fork 281
AWS CloudWatch Event Sink Implementation #1965
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
base: main
Are you sure you want to change the base?
AWS CloudWatch Event Sink Implementation #1965
Conversation
Didn't look into the whole PR, but two things I noticed:
|
Hi @snazy,
Yes, I'm assuming you're referring to the creation of one new event (
I didn't want to sink (no pun intended) more time trying to setup LocalStack for this repository if this approach was wildly off-the-mark. If you think the general approach is reasonable and the main things that will need change are implementation details, I will be glad to add that in the next revision. IIRC, this is also something that we only get with AWS - both Azure and GCP equivalents of CloudWatch do not have equivalent testing solutions (this could be outdated information but I can't find anything in a quick Google search). If having parity across CSPs on testing is important, maybe we settle for unit tests instead? Let me know your thoughts. |
I've added a test using LocalStack, which should help emulate CloudWatch. This PR is ready for a full review. |
service/common/src/main/java/org/apache/polaris/service/events/EventListenerConfiguration.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/PolarisEventListener.java
Outdated
Show resolved
Hide resolved
...e/common/src/test/java/org/apache/polaris/service/events/AwsCloudWatchEventListenerTest.java
Outdated
Show resolved
Hide resolved
...java/org/apache/polaris/service/quarkus/events/QuarkusPolarisEventListenerConfiguration.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
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 are some thread-safety/concurrency issues and memory leaks in this change, which have to be addressed.
The implementation can also be simplified.
I'd also recommend to move this to its own module to streamline CI and testing in general.
private static final String DEFAULT_LOG_STREAM_NAME = "polaris-cloudwatch-default-stream"; | ||
private static final String DEFAULT_LOG_GROUP_NAME = "polaris-cloudwatch-default-group"; | ||
private static final String DEFAULT_REGION = "us-east-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.
Those hard coded defaults shouldn't be there. Defaulting to an AWS region is not great and will cause user confusion. I do not mind having defaults of the log stream/group name in the Quarkus configuration, but region is awkward IMHO.
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 had added the defaults based on your previous advice that things should work "out of the box". Without a default region, this listener will fail for users.
Shifted the defaults to QuarkusPolarisEventListenerConfiguration
.
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
service/common/src/main/java/org/apache/polaris/service/events/AwsCloudWatchEventListener.java
Outdated
Show resolved
Hide resolved
LoggerFactory.getLogger(AwsCloudWatchEventListenerTest.class); | ||
|
||
private static final DockerImageName LOCALSTACK_IMAGE = | ||
DockerImageName.parse("localstack/localstack:3.4"); |
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.
Hard coding dependency versions in source isn't good practice. This should be replaced with the container-spec-helper.
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. I'm not sure if my changes are what you meant, but please let me know if not.
|
||
// Event overrides below | ||
@Override | ||
public void onAfterTableRefreshed(AfterTableRefreshedEvent event) { |
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 still think we need to refactor these out so that the listener itself with all its Future-wrangling logic does not become exceedingly long
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've done this on the new revision!
return; | ||
} | ||
InputLogEvent inputLogEvent = createLogEvent(eventAsJson, getCurrentTimestamp(callContext)); | ||
Future<?> future = executorService.submit(() -> sendAndHandleCloudWatchCall(inputLogEvent)); |
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.
Two thoughts:
- For this listener, I think we should offer a simple synchronous submission mode
- As we add more listeners, seems like some of this asynchronous handling logic will be duplicated so it might be worth refactoring it out. This can wait until we add more listeners though.
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 this listener, I think we should offer a simple synchronous submission mode
I still disagree with this, to be honest. It will likely cause a sizable slowdown to fast operations. But I can add a mode that does this in-line instead. Adding to the next revision.
- As we add more listeners, seems like some of this asynchronous handling logic will be duplicated so it might be worth refactoring it out. This can wait until we add more listeners though.
Sounds good, will do on the next 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.
I think this is getting really close. Left a couple of comments on the listener structure.
|
||
import java.util.HashMap; | ||
|
||
public abstract class JsonEventListener extends PolarisEventListener { |
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.
nit: it's more like a MapBasedEventListener
right? There's no JSON 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.
The map will transform into a JSON string when it sinks, which is why I made it this name. But open to change if you think MapBasedEventListener
is better?
properties.put("table_identifier", event.tableIdentifier().toString()); | ||
transformAndSendEvent(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.
super nit: extra whitespace
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.
LGTM, thanks!
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.
Thank you @adnanhemani for the change, it mostly LGTM !
@StaticInitSafe | ||
@ConfigMapping(prefix = "polaris.event-listener.aws-cloudwatch") | ||
@ApplicationScoped | ||
public interface QuarkusAwsCloudWatchConfiguration extends AwsCloudWatchConfiguration { |
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 its a public interface would be nice to have a doc
try { | ||
client.createLogGroup(CreateLogGroupRequest.builder().logGroupName(logGroup).build()); | ||
} catch (ResourceAlreadyExistsException ignored) { | ||
LOGGER.debug("Log group {} already exists", logGroup); | ||
} | ||
|
||
try { | ||
client.createLogStream( | ||
CreateLogStreamRequest.builder().logGroupName(logGroup).logStreamName(logStream).build()); | ||
} catch (ResourceAlreadyExistsException ignored) { | ||
LOGGER.debug("Log stream {} already exists", logStream); | ||
} |
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 only on sdk retries for this creation ?
what if we create a log group but creating log stream fails, are we ok overall ?
# Dockerfile to provide the image name and tag to a test. | ||
# Version is managed by Renovate - do not edit. | ||
FROM localstack/localstack:3.4 | ||
|
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.
nit: extra line ?
@Override | ||
protected void transformAndSendEvent(HashMap<String, Object> properties) { | ||
properties.put("realm", callContext.getRealmContext().getRealmIdentifier()); | ||
properties.put("principal", securityContext.getUserPrincipal().getName()); |
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 also adding the activated prinicpal roles too ?
EventWithRetry currValue = futures.remove(future); | ||
if (currFutureState.equals(Future.State.FAILED) | ||
|| currFutureState.equals(Future.State.CANCELLED)) { | ||
if (currValue.retryCount >= 3) { |
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 to have] is it possible to make this retry count configurable ?
As per discussion at this morning's Polaris Community Sync, I am introducing this event listener that will allow users to sink events to AWS CloudWatch Logs - where they can search through events for auditing purposes, if they wish. I do intend to introduce similar changes for Azure and GCP once this is merged so that we have parity across all CSPs - but this will help nail down the exact pattern to make this work.
Below is a screenshot of a sample event sent to CloudWatch: