shashank created CAMEL-25760:
--------------------------------

             Summary: camel-alibaba-oss, camel-huaweicloud-obs - one listed 
object that cannot be got (access denied, archived) fails every poll, so the 
consumer stops consuming the bucket (OSS: never again; OBS: the objects listed 
with it)
                 Key: CAMEL-25760
                 URL: https://issues.apache.org/jira/browse/CAMEL-25760
             Project: Camel
          Issue Type: Bug
            Reporter: shashank
            Assignee: shashank


The same defect in two object-store consumers (as CAMEL-25551 covered several 
modules in one issue); one fix per module, two pull requests.

h3. camel-alibaba-oss

{{OSSConsumer.poll}} (main e13303ef1ef7, lines 72-107) lists one page of the 
bucket ({{maxKeys}} = {{maxMessagesPerPoll}}, 10 by default) and gets every 
listed object in {{createExchanges}} / {{addExchange}} (lines 152-203). An 
object that no longer exists (404) is skipped since CAMEL-25551; any other 
failure of the get is rethrown, the exchanges already created are released, and 
the poll fails. The continuation token is only moved on after the batch was 
processed (lines 101-105), so the next poll lists the same page again:

{code:java}
Queue<Exchange> exchanges = createExchanges(bucketName, listing.contents());
int processed = processBatch(CastUtils.cast(exchanges));
if (processed >= 0) {
    continuationToken = nextToken;
}
{code}

So one object whose get fails every time (access denied for that object, an 
object of the Archive or Cold Archive storage class that is not restored, a KMS 
key that was disabled) fails every poll: none of the other objects of its page 
is consumed, and the consumer never reaches the following pages. If the bucket 
fits in one page, the consumer consumes nothing at all. Nothing but a WARN per 
poll ("Failed polling endpoint") says why, and it lasts until someone removes 
or restores the object.

camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed 
object whose get fails is skipped for that poll and reported to the exception 
handler with its key, the other listed objects are consumed, and the poll fails 
only when none of the listed objects could be got (expired credentials, no 
permission to get objects); the marker of the listing is moved on before the 
objects are got ({{AWS2S3Consumer}} lines 173-201, 340-356). 
camel-huaweicloud-obs moves its marker on before the gets, but also fails the 
poll for one object.

*Fix*: the same in {{OSSConsumer}}: a failed get of one listed object is 
collected, the object is skipped and reported to the consumer's exception 
handler ("Error getting object <key> from bucket <bucket>. The object is 
skipped for this poll."), and the other objects are consumed; when none of the 
listed objects could be got, the poll fails with the first failure as before. 
The continuation token is set from the listing before the objects are got, so a 
page whose objects cannot be got does not hold the consumer back (its objects 
are listed again when the listing starts over). An object deleted after the 
listing is still skipped silently. Upgrade note in the 4.24 guide.

*Tests*: new {{OSSConsumerObjectThatCannotBeGotTest}} (3 tests; a fake bucket 
that lists keys in order, {{maxKeys}} at a time with a continuation token, and 
answers the get of a forbidden object with 403). 
{{objectThatCannotBeGotDoesNotBlockTheOtherObjects}} (a.txt, b.txt forbidden, 
c.txt) and {{pageOfObjectsThatCannotBeGotDoesNotBlockTheNextPage}} 
({{maxMessagesPerPoll=1}}, a.txt forbidden) fail on main: no object is received 
({{mock://result Received message count. Expected: <2> but was: <0>}}). 
{{pollFailsWhenNoListedObjectCanBeGot}} passes on main and with the fix; it 
guards the case the fix must not swallow (no object can be got: the poll 
fails). camel-alibaba-oss unit tests: 22 run, 0 failures.

*How it was found*: differential reading of sibling consumers of object stores 
against the same listing / get / commit model: camel-aws2-s3 (after 
CAMEL-25558), camel-huaweicloud-obs and camel-alibaba-oss. camel-aws2-s3 skips 
the object; camel-huaweicloud-obs fails the poll but moves its marker on, so it 
stops for good only when the bucket fits in one page (see below); 
camel-alibaba-oss fails the poll and keeps its listing position, so it stops 
for good whatever the size of the bucket. The JUnit tests above are the 
evidence; the OSS status codes for archived and forbidden objects are from the 
OSS documentation and were not run against OSS.

Not changed here: camel-minio (a failed get in {{MinioConsumer.processBatch}}, 
main lines 319-331, stops the rest of the batch and fails the poll) and 
camel-google-storage ({{GoogleCloudStorageConsumer.createExchanges}} / 
{{addExchange}}, lines 166-208, fail the poll for one object) have the same 
kind of defect; they are left as follow-ups.

*Affected*: main, camel-4.23.x (the same lines; read, not run). The component 
is new in 4.23.

h3. camel-huaweicloud-obs

{{OBSConsumer.poll}} (main e13303ef1ef7, lines 139-166) lists the bucket 
({{maxKeys}} = {{maxMessagesPerPoll}}, 10 by default) and gets every listed 
object in {{createExchanges}} (lines 218-245). An object that no longer exists 
(404) is skipped since CAMEL-25551; any other failure of the get is rethrown, 
the exchanges already created are released, and the poll fails:

{code:java}
} catch (ObsException e) {
    if (e.getResponseCode() == 404) { ... continue; }
    throw e;
}
...
} catch (RuntimeException e) {
    // none of the objects of this poll is processed
    releaseNotProcessed(answer);
    throw e;
}
{code}

So one object whose get fails every time (access denied for that object, an 
object of the Archive or Deep Archive storage class that is not restored, an 
SSE-KMS key that was disabled) fails every poll that lists it, and the other 
objects of that listing are not consumed either. The marker of a truncated 
listing is moved on before the gets, but a listing that is not truncated sets 
the marker back to {{null}}, so:

* a bucket (or prefix) whose objects fit in one listing (10 objects with the 
default {{maxMessagesPerPoll}}) is never consumed again: every poll lists the 
same objects and fails on the same one;
* in a larger bucket the objects that are listed together with it are skipped 
at every pass over the bucket.

Nothing but a WARN per poll ("Failed polling endpoint") says why, and it lasts 
until someone removes or restores the object.

camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed 
object whose get fails is skipped for that poll and reported to the exception 
handler with its key, the other listed objects are consumed, and the poll fails 
only when none of the listed objects could be got ({{AWS2S3Consumer}} lines 
340-356).

*Fix*: the same in {{OBSConsumer.createExchanges}}: a failed get of one listed 
object is collected, the object is skipped and reported to the consumer's 
exception handler ("Error getting object <key> from bucket <bucket>. The object 
is skipped for this poll."), and the other objects are consumed; when none of 
the listed objects could be got, the poll fails with the first failure as 
before. An object deleted after the listing is still skipped silently; with the 
{{fileName}} option a failed get still fails the poll. Upgrade note in the 4.24 
guide.

*Tests*: new {{OBSConsumerObjectThatCannotBeGotTest}} (3 tests; a fake bucket 
that lists keys in order, {{maxKeys}} at a time with the last key as next 
marker, and answers the get of a forbidden object with 403). 
{{objectThatCannotBeGotDoesNotBlockTheOtherObjects}} (a.txt, b.txt forbidden, 
c.txt, one listing) and 
{{objectThatCannotBeGotDoesNotBlockTheObjectsOfItsPage}} 
({{maxMessagesPerPoll=2}}, a.txt forbidden, listed with b.txt) fail on main. 
{{pollFailsWhenNoListedObjectCanBeGot}} passes on main and with the fix; it 
guards the case the fix must not swallow (no object can be got: the poll 
fails). camel-huaweicloud-obs unit tests: 47 run, 0 failures, 18 skipped 
(functional tests that need OBS credentials).

*How it was found*: differential reading of sibling consumers of object stores 
against the same listing / get / commit model: camel-aws2-s3 (after 
CAMEL-25558), camel-huaweicloud-obs and camel-alibaba-oss. camel-aws2-s3 skips 
the object; camel-huaweicloud-obs and camel-alibaba-oss fail the poll. The 
JUnit tests above are the evidence; the OBS status codes for archived and 
forbidden objects are from the OBS documentation and were not run against OBS.

Not changed here: camel-minio (a failed get in {{MinioConsumer.processBatch}}, 
main lines 319-331, stops the rest of the batch and fails the poll) and 
camel-google-storage ({{GoogleCloudStorageConsumer.createExchanges}} / 
{{addExchange}}, lines 166-208, fail the poll for one object) have the same 
kind of defect; they are left as follow-ups.

*Affected*: main and camel-4.23.x (the same lines); camel-4.22.x, camel-4.18.x 
and camel-4.14.x do not catch the get at all, so there every failed get, also 
of an object deleted after the listing, fails the poll (read, not run).

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to