This is an automated email from the ASF dual-hosted git repository.
turcsanyi pushed a commit to branch support/nifi-1.x
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/support/nifi-1.x by this push:
new ce56117e43 NIFI-12441 Added No Tracking Strategy to ListS3
ce56117e43 is described below
commit ce56117e43c7cdce37ca76d54550a2b45acb508a
Author: Juldrixx <[email protected]>
AuthorDate: Sat Dec 23 04:38:03 2023 -0600
NIFI-12441 Added No Tracking Strategy to ListS3
This closes #8088
Signed-off-by: David Handermann <[email protected]>
(cherry picked from commit 9a919339090b26b0b39321758b8f8074597320ca)
---
.../org/apache/nifi/processors/aws/s3/ListS3.java | 110 ++++++++++++++++++---
.../apache/nifi/processors/aws/s3/TestListS3.java | 59 +++++++++++
2 files changed, 157 insertions(+), 12 deletions(-)
diff --git
a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
index 4be64b0953..431778f1ef 100644
---
a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
+++
b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
@@ -144,12 +144,17 @@ public class ListS3 extends AbstractS3Processor
implements VerifiableProcessor {
" However an additional DistributedMapCache controller service is
required and more JVM heap memory is used." +
" For more information on how the 'Entity Tracking Time Window'
property works, see the description.");
+ public static final AllowableValue NO_TRACKING = new
AllowableValue("none", "No Tracking",
+ "This strategy lists all entities without any tracking. The same
entities will be listed each time" +
+ " this processor is scheduled. It is recommended to change
the default run schedule value." +
+ " Any property that relates to the persisting state will
be ignored.");
+
public static final PropertyDescriptor LISTING_STRATEGY = new Builder()
.name("listing-strategy")
.displayName("Listing Strategy")
.description("Specify how to determine new/updated entities. See each
strategy descriptions for detail.")
.required(true)
- .allowableValues(BY_TIMESTAMPS, BY_ENTITIES)
+ .allowableValues(BY_TIMESTAMPS, BY_ENTITIES, NO_TRACKING)
.defaultValue(BY_TIMESTAMPS.getValue())
.build();
@@ -345,7 +350,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
try {
listedEntityTracker.clearListedEntities();
} catch (IOException e) {
- throw new RuntimeException("Failed to reset previously listed
entities due to " + e, e);
+ throw new RuntimeException("Failed to reset previously listed
entities", e);
}
}
resetEntityTrackingState = false;
@@ -465,11 +470,92 @@ public class ListS3 extends AbstractS3Processor
implements VerifiableProcessor {
listByTrackingTimestamps(context, session);
} else if (BY_ENTITIES.equals(listingStrategy)) {
listByTrackingEntities(context, session);
+ } else if (NO_TRACKING.equals(listingStrategy)) {
+ listNoTracking(context, session);
} else {
throw new ProcessException("Unknown listing strategy: " +
listingStrategy);
}
}
+ private void listNoTracking(ProcessContext context, ProcessSession
session) {
+ final AmazonS3 client = getClient(context);
+
+ S3BucketLister bucketLister = getS3BucketLister(context, client);
+
+ final long startNanos = System.nanoTime();
+ final long minAgeMilliseconds =
context.getProperty(MIN_AGE).asTimePeriod(TimeUnit.MILLISECONDS);
+ final Long maxAgeMilliseconds = context.getProperty(MAX_AGE) != null ?
context.getProperty(MAX_AGE).asTimePeriod(TimeUnit.MILLISECONDS) : null;
+ final long listingTimestamp = System.currentTimeMillis();
+
+ final String bucket =
context.getProperty(BUCKET).evaluateAttributeExpressions().getValue();
+ final int batchSize = context.getProperty(BATCH_SIZE).asInteger();
+
+ int listCount = 0;
+ int totalListCount = 0;
+
+ getLogger().trace("Start listing, listingTimestamp={}",
listingTimestamp);
+
+ final S3ObjectWriter writer;
+ final RecordSetWriterFactory writerFactory =
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
+ if (writerFactory == null) {
+ writer = new AttributeObjectWriter(session);
+ } else {
+ writer = new RecordObjectWriter(session, writerFactory,
getLogger(), context.getProperty(S3_REGION).getValue());
+ }
+
+ try {
+ writer.beginListing();
+
+ do {
+ VersionListing versionListing = bucketLister.listVersions();
+ for (S3VersionSummary versionSummary :
versionListing.getVersionSummaries()) {
+ long lastModified =
versionSummary.getLastModified().getTime();
+ if ((maxAgeMilliseconds != null && (lastModified <
(listingTimestamp - maxAgeMilliseconds)))
+ || lastModified > (listingTimestamp -
minAgeMilliseconds)) {
+ continue;
+ }
+
+ getLogger().trace("Listed key={}, lastModified={}",
versionSummary.getKey(), lastModified);
+
+ GetObjectTaggingResult taggingResult =
getTaggingResult(context, client, versionSummary);
+
+ ObjectMetadata objectMetadata = getObjectMetadata(context,
client, versionSummary);
+
+ // Write the entity to the listing
+ writer.addToListing(versionSummary, taggingResult,
objectMetadata, context.getProperty(S3_REGION).getValue());
+
+ listCount++;
+ }
+ bucketLister.setNextMarker();
+
+ totalListCount += listCount;
+
+ if (listCount >= batchSize && writer.isCheckpoint()) {
+ getLogger().info("Successfully listed {} new files from
S3; routing to success", listCount);
+ session.commitAsync();
+ }
+
+ listCount = 0;
+ } while (bucketLister.isTruncated());
+
+ writer.finishListing();
+ } catch (final Exception e) {
+ getLogger().error("Failed to list contents of bucket", e);
+ writer.finishListingExceptionally(e);
+ session.rollback();
+ context.yield();
+ return;
+ }
+
+ final long listMillis =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+ getLogger().info("Successfully listed S3 bucket {} in {} millis",
bucket, listMillis);
+
+ if (totalListCount == 0) {
+ getLogger().debug("No new objects in S3 bucket {} to list.
Yielding.", bucket);
+ context.yield();
+ }
+ }
+
private void listByTrackingTimestamps(ProcessContext context,
ProcessSession session) {
try {
restoreState(session);
@@ -494,7 +580,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
long latestListedTimestampInThisCycle = listingTimestamp;
final Set<String> listedKeys = new HashSet<>();
- getLogger().trace("Start listing, listingTimestamp={},
currentTimestamp={}, currentKeys={}", new Object[]{currentTimestamp,
listingTimestamp, currentKeys});
+ getLogger().trace("Start listing, listingTimestamp={},
currentTimestamp={}, currentKeys={}", currentTimestamp, listingTimestamp,
currentKeys);
final S3ObjectWriter writer;
final RecordSetWriterFactory writerFactory =
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
@@ -517,7 +603,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
continue;
}
- getLogger().trace("Listed key={}, lastModified={},
currentKeys={}", new Object[]{versionSummary.getKey(), lastModified,
currentKeys});
+ getLogger().trace("Listed key={}, lastModified={},
currentKeys={}", versionSummary.getKey(), lastModified, currentKeys);
// Write the entity to the listing
final GetObjectTaggingResult taggingResult =
getTaggingResult(context, client, versionSummary);
@@ -551,7 +637,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
writer.finishListing();
} catch (final Exception e) {
- getLogger().error("Failed to list contents of bucket due to {}",
e, e);
+ getLogger().error("Failed to list contents of bucket", e);
writer.finishListingExceptionally(e);
session.rollback();
context.yield();
@@ -572,10 +658,10 @@ public class ListS3 extends AbstractS3Processor
implements VerifiableProcessor {
});
final long listMillis =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
- getLogger().info("Successfully listed S3 bucket {} in {} millis", new
Object[]{bucket, listMillis});
+ getLogger().info("Successfully listed S3 bucket {} in {} millis",
bucket, listMillis);
if (totalListCount == 0) {
- getLogger().debug("No new objects in S3 bucket {} to list.
Yielding.", new Object[]{bucket});
+ getLogger().debug("No new objects in S3 bucket {} to list.
Yielding.", bucket);
context.yield();
}
}
@@ -612,7 +698,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
ProcessContext context,
ProcessSession session,
List<ListableEntityWrapper<S3VersionSummary>> updatedEntities
- ) throws IOException, SchemaNotFoundException {
+ ) {
publishListing(context, session, updatedEntities);
}
@@ -651,7 +737,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
listCount++;
if (listCount >= batchSize && writer.isCheckpoint()) {
- getLogger().info("Successfully listed {} new files
from S3; routing to success", new Object[]{listCount});
+ getLogger().info("Successfully listed {} new files
from S3; routing to success", listCount);
session.commitAsync();
}
@@ -661,7 +747,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
writer.finishListing();
} catch (final Exception e) {
- getLogger().error("Failed to list contents of bucket due to
{}", e, e);
+ getLogger().error("Failed to list contents of bucket", e);
writer.finishListingExceptionally(e);
session.rollback();
context.yield();
@@ -976,7 +1062,7 @@ public class ListS3 extends AbstractS3Processor implements
VerifiableProcessor {
try {
recordWriter.close();
} catch (IOException e) {
- logger.error("Failed to write listing as Records due to {}",
e, e);
+ logger.error("Failed to write listing as Records", e);
}
session.remove(flowFile);
@@ -1076,7 +1162,7 @@ public class ListS3 extends AbstractS3Processor
implements VerifiableProcessor {
}
@Override
- public void finishListing() throws IOException {
+ public void finishListing() {
}
@Override
diff --git
a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestListS3.java
b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestListS3.java
index 2deee332aa..aede9b2a55 100644
---
a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestListS3.java
+++
b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestListS3.java
@@ -563,4 +563,63 @@ public class TestListS3 {
Mockito.verify(mockS3Client,
Mockito.never()).listVersions(Mockito.any());
}
+
+ @Test
+ public void testNoTrackingList() {
+ runner.setProperty(ListS3.REGION, "eu-west-1");
+ runner.setProperty(ListS3.BUCKET, "test-bucket");
+ runner.setProperty(ListS3.LISTING_STRATEGY, ListS3.NO_TRACKING);
+
+ Date lastModified = new Date();
+ ObjectListing objectListing = new ObjectListing();
+ S3ObjectSummary objectSummary1 = new S3ObjectSummary();
+ objectSummary1.setBucketName("test-bucket");
+ objectSummary1.setKey("a");
+ objectSummary1.setLastModified(lastModified);
+ objectListing.getObjectSummaries().add(objectSummary1);
+ S3ObjectSummary objectSummary2 = new S3ObjectSummary();
+ objectSummary2.setBucketName("test-bucket");
+ objectSummary2.setKey("b/c");
+ objectSummary2.setLastModified(lastModified);
+ objectListing.getObjectSummaries().add(objectSummary2);
+ S3ObjectSummary objectSummary3 = new S3ObjectSummary();
+ objectSummary3.setBucketName("test-bucket");
+ objectSummary3.setKey("d/e");
+ objectSummary3.setLastModified(lastModified);
+ objectListing.getObjectSummaries().add(objectSummary3);
+
Mockito.when(mockS3Client.listObjects(Mockito.any(ListObjectsRequest.class))).thenReturn(objectListing);
+
+ runner.run();
+
+ ArgumentCaptor<ListObjectsRequest> captureRequest =
ArgumentCaptor.forClass(ListObjectsRequest.class);
+ Mockito.verify(mockS3Client,
Mockito.times(1)).listObjects(captureRequest.capture());
+ ListObjectsRequest request = captureRequest.getValue();
+ assertEquals("test-bucket", request.getBucketName());
+ assertFalse(request.isRequesterPays());
+ Mockito.verify(mockS3Client,
Mockito.never()).listVersions(Mockito.any());
+
+ runner.assertAllFlowFilesTransferred(ListS3.REL_SUCCESS, 3);
+ List<MockFlowFile> flowFiles =
runner.getFlowFilesForRelationship(ListS3.REL_SUCCESS);
+ MockFlowFile ff0 = flowFiles.get(0);
+ ff0.assertAttributeEquals("filename", "a");
+ ff0.assertAttributeEquals("s3.bucket", "test-bucket");
+ ff0.assertAttributeEquals("s3.region", "eu-west-1");
+ String lastModifiedTimestamp = String.valueOf(lastModified.getTime());
+ ff0.assertAttributeEquals("s3.lastModified", lastModifiedTimestamp);
+ flowFiles.get(1).assertAttributeEquals("filename", "b/c");
+ flowFiles.get(2).assertAttributeEquals("filename", "d/e");
+
+ final List<ConfigVerificationResult> results = ((VerifiableProcessor)
runner.getProcessor())
+ .verify(runner.getProcessContext(), runner.getLogger(),
Collections.emptyMap());
+ assertEquals(ConfigVerificationResult.Outcome.SUCCESSFUL,
results.get(0).getOutcome());
+ assertEquals(ConfigVerificationResult.Outcome.SUCCESSFUL,
results.get(1).getOutcome());
+ assertTrue(results.get(1).getExplanation().contains("finding 3"));
+
+ runner.clearTransferState();
+
+ runner.run();
+
+ runner.assertAllFlowFilesTransferred(ListS3.REL_SUCCESS, 3);
+ runner.getStateManager().assertStateEquals(ListS3.CURRENT_TIMESTAMP,
null, Scope.CLUSTER);
+ }
}