voonhous commented on code in PR #13147:
URL: https://github.com/apache/hudi/pull/13147#discussion_r3965336983
##########
hudi-timeline-service/src/main/java/org/apache/hudi/timeline/service/handlers/TimelineHandler.java:
##########
@@ -46,4 +135,185 @@ public List<InstantDTO> getLastInstant(String basePath) {
public TimelineDTO getTimeline(String basePath) {
return
TimelineDTO.fromTimeline(viewManager.getFileSystemView(basePath).getTimeline());
}
+
+ public UiTimelineDTO getUiTimeline(String basePath) {
+ // The active timeline is used, not the file-system-view write timeline:
the latter drops
+ // clean/rollback/savepoint/restore/indexing actions and all
requested/inflight states.
+ return
UiTimelineDTO.fromTimeline(createMetaClient(basePath).getActiveTimeline());
+ }
+
+ public Object getInstantDetails(String basePath, String requestedTime,
String action, String state) {
+ HoodieInstant.State parsedState;
+ try {
+ parsedState = HoodieInstant.State.valueOf(state);
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestResponse("Invalid instant state: " + state);
+ }
+
+ if
(!Arrays.asList(HoodieTimeline.VALID_ACTIONS_IN_TIMELINE).contains(action)) {
+ throw new BadRequestResponse("Invalid instant action: " + action);
+ }
+
+ HoodieTableMetaClient metaClient = createMetaClient(basePath);
+ HoodieTimeline activeTimeline = metaClient.getActiveTimeline();
+ CommitMetadataSerDe serde = metaClient.getCommitMetadataSerDe();
+
+ // Resolve the instant against the timeline rather than constructing it
from request params:
+ // an attacker-controlled instant would otherwise flow into a StoragePath
whose URI.normalize
+ // collapses ".." segments, enabling path traversal.
+ HoodieInstant instant = activeTimeline.getInstantsAsStream()
+ .filter(i -> i.requestedTime().equals(requestedTime)
+ && i.getAction().equals(action)
+ && i.getState() == parsedState)
+ .findFirst()
+ .orElseThrow(() -> new NotFoundResponse(
+ "Instant not found in active timeline: " + requestedTime + " " +
action + " " + parsedState));
+
+ try {
+ Object result;
+ switch (instant.getAction()) {
+ case HoodieTimeline.COMMIT_ACTION:
+ case HoodieTimeline.DELTA_COMMIT_ACTION:
+ result = readCommitMetadata(serde, activeTimeline, instant);
+ break;
+ case HoodieTimeline.CLEAN_ACTION:
+ result = instant.isCompleted()
+ ? readAs(serde, activeTimeline, instant,
HoodieCleanMetadata.class)
+ : readAs(serde, activeTimeline, requestedTwin(metaClient,
instant), HoodieCleanerPlan.class);
+ break;
+ case HoodieTimeline.ROLLBACK_ACTION:
+ result = instant.isCompleted()
+ ? readAs(serde, activeTimeline, instant,
HoodieRollbackMetadata.class)
+ : readAs(serde, activeTimeline, requestedTwin(metaClient,
instant), HoodieRollbackPlan.class);
+ break;
+ case HoodieTimeline.RESTORE_ACTION:
+ result = instant.isCompleted()
+ ? readAs(serde, activeTimeline, instant,
HoodieRestoreMetadata.class)
+ : readAs(serde, activeTimeline, requestedTwin(metaClient,
instant), HoodieRestorePlan.class);
+ break;
+ case HoodieTimeline.SAVEPOINT_ACTION:
+ // Savepoint has no requested state (inflight then completed); its
inflight file is empty and
+ // now deserializes to an empty instance, so always read the instant
itself.
+ result = readAs(serde, activeTimeline, instant,
HoodieSavepointMetadata.class);
+ break;
+ case HoodieTimeline.COMPACTION_ACTION:
+ case HoodieTimeline.LOG_COMPACTION_ACTION:
+ result = instant.isCompleted()
+ ? readCommitMetadata(serde, activeTimeline, instant)
+ : readAs(serde, activeTimeline, requestedTwin(metaClient,
instant), HoodieCompactionPlan.class);
+ break;
+ case HoodieTimeline.REPLACE_COMMIT_ACTION:
+ case HoodieTimeline.CLUSTERING_ACTION:
+ // A completed replacecommit/clustering file is avro
HoodieReplaceCommitMetadata on disk;
+ // reading it as avro HoodieCommitMetadata fails avro record-name
resolution. Read the POJO
+ // HoodieReplaceCommitMetadata: the serde deserializes the avro
record and converts it to POJO.
+ result = instant.isCompleted()
+ ? readAs(serde, activeTimeline, instant,
HoodieReplaceCommitMetadata.class)
+ : readAs(serde, activeTimeline, requestedTwin(metaClient,
instant), HoodieRequestedReplaceMetadata.class);
Review Comment:
Fixed: the arm now splits by state like `MetadataConversionUtils`. A
non-empty inflight file is read as `HoodieCommitMetadata`; requested instants
keep the twin. An empty inflight file (clustering) also falls back to the
requested plan, otherwise an inflight clustering would render as an empty
`HoodieCommitMetadata`. Both pending shapes are covered in `TestUiApi`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]