Andrey Yarovoy created HDDS-16950:
-------------------------------------

             Summary: Remove the per-call InfoBucket from rooted-ofs mkdirs
                 Key: HDDS-16950
                 URL: https://issues.apache.org/jira/browse/HDDS-16950
             Project: Apache Ozone
          Issue Type: Improvement
          Components: Ozone Client
            Reporter: Andrey Yarovoy


`BasicRootedOzoneClientAdapterImpl.createDirectory` (behind 
`FileSystem.mkdirs`) calls
`getBucket(ofsPath, createIfNotExist=true)` and then 
`OzoneBucket.createDirectory`. So every `mkdirs` below bucket level sends at 
least 2 OM requests: `InfoBucket` + `CreateDirectory`, plus `InfoVolume` + 
`InfoBucket` per link hop.

The fetch does two jobs:

1. **Layout check.** `OzoneFSUtils.validateBucketLayout` rejects OBJECT_STORE.
2. **Auto-create.** When the volume or bucket is missing, `getBucket` creates 
it (`InfoVolume` /
`CreateVolume` / `CreateBucket`), so `mkdirs /vol/bucket/dir` works on an empty 
namespace. When the path
is only `/vol/bucket`, this is the whole operation.

Both must be handled before the fetch can be dropped, the same way HDDS-16760 
handled listStatus's two
jobs.

### Why the rejection is opt-in

`OMDirectoryCreateRequest` is registered for `BucketLayout.OBJECT_STORE` (that 
registration also serves
LEGACY), so `CreateDirectory` succeeds on an OBJECT_STORE bucket today. Its 
callers:
* The S3 Gateway's `ObjectEndpoint` creates directories for a zero-byte key 
ending in `/`, but only when
`bucket.getBucketLayout() == FILE_SYSTEM_OPTIMIZED`.
* `KeyLifecycleService`, on non-OBJECT_STORE buckets.
* `cli-shell`'s `DeleteKeyHandler`, on FSO buckets.
* Direct Java API callers of `OzoneBucket.createDirectory`, which may target 
OBJECT_STORE.

the client asks for the check:

{code}
message CreateDirectoryRequest {
required KeyArgs keyArgs = 1;
// Set by file system clients: OM rejects the request with
// NOT_SUPPORTED_OPERATION if the resolved bucket layout is OBJECT_STORE.
optional bool requireFileSystemLayout = 2;
}
{code}

### Proposed change

*OM.* Check on the leader before Ratis, in 
`OMDirectoryCreateRequest.preExecute` / 
`OMDirectoryCreateRequestWithFSO.preExecute`
or right after `createClientRequest` in 
`OMExecutionFlow.submitExecutionToRatis`. Use the link-resolved layout that
`BucketLayoutAwareOMKeyRequestFactory.createRequest` already read, so there is 
no new lookup. Use the
`validateBucketLayout` message, wrapped in 
`OMException(NOT_SUPPORTED_OPERATION)`. Do not put the check
in `createRequest` itself, because that also runs at apply time on every OM. 
See the setTimes Jira for
the reasoning.

*Client.*
* For a path below bucket level, when `proxy.getOmVersion() >=` a new 
`OzoneManagerVersion` constant
(placed after `LIST_STATUS_REJECTS_OBS(101)`, never below `ZDU(100)`), the 
adapter sends `CreateDirectory`
with the field set, using a locally built handle or a `ClientProtocol` call. No 
`getBucket()` first.
* **Auto-create on the error path:** on `VOLUME_NOT_FOUND` / 
`BUCKET_NOT_FOUND`, call today's
`getBucket(ofsPath, true)`. It creates what is missing and applies the 
client-side layout check. Then
retry `CreateDirectory` once. Only an `mkdirs` that creates a bucket pays the 
extra round trip, and it
already pays several.
* A link whose source is missing must keep its current behaviour. `getBucket(…, 
true)` on an orphan link
must not create a bucket under the link's name. The error path calls 
`getBucket()` as a whole, so it
behaves exactly as today. The HDDS-16760 orphan-link handling has the same 
property.
* `NOT_SUPPORTED_OPERATION` maps to `IllegalArgumentException`. 
`FILE_ALREADY_EXISTS` keeps mapping to
`FileAlreadyExistsException`.
* `mkdirs /vol/bucket` (no key) keeps calling `getBucket(…, true)`, because 
that call is the operation.
* Against any older OM, including one that reports ZDU, keep the current path.

### Compatibility

| | Behavior |
|---|---|
| New client, new OM, bucket exists | 1 RPC; OM rejects OBJECT_STORE |
| New client, new OM, bucket missing | failed `CreateDirectory` → `getBucket(…, 
true)` (creates) → `CreateDirectory` |
| New client, older OM (incl. ZDU) | as today |
| Old client / S3 Gateway / lifecycle / direct `createDirectory`, new OM | 
field absent ⇒ unchanged |

There is one new optional proto field, no new result code and no new 
configuration.

Points for review:
* The BUCKET/READ ACL check from `getBucketDetails` is dropped. 
`CreateDirectory` keeps its own ACL
checks (bucket WRITE / key CREATE).
* The audit log shows a failed `CREATE_DIRECTORY` before the `CREATE_BUCKET` in 
the auto-create case.

## Testing

* Adapter (Mockito):
* Existing bucket: exactly one `CreateDirectory`, carrying the field, and no 
`getBucket()`.
* Missing bucket: `CreateDirectory` fails, then `getBucket(…, true)`, then one 
retry.
* Orphan link: today's result.
*`NOT_SUPPORTED_OPERATION` maps to `IllegalArgumentException`, and 
`FILE_ALREADY_EXISTS` to
`FileAlreadyExistsException`.
* An older OM, including one reporting `ZDU`, falls back.
* OM request tests (both the FSO and non-FSO classes):
* With the field set, OBJECT_STORE is rejected before Ratis, and FSO and 
**LEGACY** succeed.
* Without the field, OBJECT_STORE succeeds.
* A link to an OBJECT_STORE bucket is rejected.
* `AbstractRootedOzoneFileSystemTest`: `mkdirs` into a missing volume/bucket 
still auto-creates both, and
`mkdirs` on an OBJECT_STORE bucket still fails with `IllegalArgumentException`. 
Count RPCs in the
adapter test, not with OMMetrics deltas (HDDS-16513).
* Benchmark `TestOfsMkdirsRpcBenchmark`, same shape as 
`TestOfsListStatusRpcBenchmark`:
* 200 FSO buckets, unique directory names, single-threaded and 10 threads.
* n=3 per arm, built and run strictly one after the other.
* Invariant: exactly 1 OM RPC per `mkdirs` into an existing bucket, instead of 
2.



--
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