Siyao Meng created HDDS-17197:
---------------------------------

             Summary: Tenant assign and create add table cache entries before a 
check that can fail, so a rejected assign can leave a dangling accessId
                 Key: HDDS-17197
                 URL: https://issues.apache.org/jira/browse/HDDS-17197
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-7-check-before-tenant-cache-entries.patch

h3. Mechanism
{{OMTenantAssignUserAccessIdRequest.validateAndUpdateCache}} adds the new 
accessId to the {{tenantAccessIdTable}} cache and the updated user row to the 
{{principalToAccessIdsTable}} cache, and only then checks that the accessId is 
absent from S3SecretTable. When that check fails with 
{{TENANT_USER_ACCESS_ID_ALREADY_EXISTS}}, the response writes nothing to the 
DB, but the two cache entries stay. They are visible to every later request on 
that OM until the double buffer flushes the failed transaction and the cache 
drops entries of that epoch.

{{OMTenantCreateRequest.validateAndUpdateCache}} has the same order: it adds 
the volume and the owner's volume list to the volume and user table caches, and 
only then checks {{tenantStateTable}} for the tenant. A 
{{TENANT_ALREADY_EXISTS}} at that point leaves both cache entries until the 
flush.

h3. Impact
* While the failed assign's entries are visible, the next write of the same 
{{principalToAccessIdsTable}} row starts from the cached row, which already 
lists the failed accessId, and persists it. The DB then has a principal row 
that lists an accessId with no {{tenantAccessIdTable}} row.
* No request removes that dangling reference: no tenant request drops an 
accessId without a {{tenantAccessIdTable}} row from a principal row. It stays 
across restart, and there is no repair command for the tenant tables.
* Every later assign of the same user to any tenant reads that row and fails 
with {{INVALID_ACCESS_ID}} ("accessIdInfo is null").
* Whether the failed entries are still visible depends on when each OM flushes, 
which is local to each OM. OMs that apply the same log can therefore persist 
different {{principalToAccessIdsTable}} entries for that user. Shown with two 
metadata managers applying the same transactions in one JVM, not on a Ratis HA 
cluster.
* {{OMMultiTenantManagerImpl.isTenantAdmin}} returns {{false}} at the dangling 
accessId, so a tenant admin check of that user can fail permanently. That is 
HDDS-17196; with its patch this effect goes away.
* For create, a volume and an owner volume list entry that the DB never gets 
are visible until the flush, so a request that reads them in between can 
persist state against a volume that does not exist. Argued from the code, not 
run.

h3. Patch
[^CR-7-check-before-tenant-cache-entries.patch], against 
a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at 
1753288f333.

The patch only moves existing checks. In {{OMTenantAssignUserAccessIdRequest}} 
the S3SecretTable check, together with its {{updateCache}} of the new secret, 
moves ahead of the two tenant table cache adds, which is the order the class 
Javadoc already describes. In {{OMTenantCreateRequest}} the 
{{tenantStateTable}} check moves to right after the volume existence check, 
before the user lock and the volume creation, so {{VOLUME_ALREADY_EXISTS}} 
still takes precedence. A failed assign or create therefore adds no table cache 
entries, and no later request can read and persist state that the failed one 
never wrote. Successful requests write the same cache entries and DB batch as 
before, and failed ones return the same result codes; the audit entry of a 
{{TENANT_ALREADY_EXISTS}} from apply no longer carries the volume fields. 
Dangling references that are already in the DB are not cleaned up.

The issue has no standalone reproduction. The two new tests fail without the 
change and pass with it:
* 
{{TestOMTenantAssignUserAccessIdRequest.testFailedAssignLeavesNoTableCacheEntries}}
 (new class): an assign that fails the S3SecretTable check must leave no 
{{tenantAccessIdTable}} or {{principalToAccessIdsTable}} entry, and the next 
assign of the same user must store only its own accessId. Without the change it 
fails with {{expected: null but was: OmDBAccessIdInfo}}.
* {{TestOMTenantCreateRequest.testValidateAndUpdateCacheWhenTenantExists}}: a 
create that fails the tenant check in apply must leave no volume or user table 
entry. Without the change it fails with {{expected: null but was: 
OmVolumeArgs}}.

With the patch the tenant request and tenant manager unit suites pass, 
{{TestMultiTenantVolume}} and {{TestOzoneTenantShell}} pass, and checkstyle on 
{{hadoop-ozone/ozone-manager}} is clean.

Found by code review of the OM multi-tenancy and Ranger sync paths, as part of 
the TLA+ verification effort under HDDS-15926, on commit 
a6b7bdb937109ba2893688b41a89470bc95c88cf. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. Related but not 
duplicates: HDDS-7001 (lock release in the same create method), HDDS-7368 
(added the volume existence check to create preExecute). The attached patch is 
a proposal for review. Generated with Specula (Claude Opus 5.5).



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

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to