github-actions[bot] commented on code in PR #66004:
URL: https://github.com/apache/doris/pull/66004#discussion_r3649321836
##########
fe/fe-filesystem/fe-filesystem-jfs/src/main/java/org/apache/doris/filesystem/jfs/JfsFileSystemProvider.java:
##########
@@ -54,10 +53,43 @@ public boolean supports(Map<String, String> properties) {
}
@Override
- public FileSystem create(Map<String, String> properties) throws
IOException {
+ public JfsProperties bind(Map<String, String> properties) {
JfsProperties jfsProperties = new JfsProperties(properties);
jfsProperties.initNormalizeAndCheckProps();
- return new DFSFileSystem(jfsProperties.getBackendConfigProperties());
+ return jfsProperties;
+ }
+
+ @Override
+ public FileSystem create(JfsProperties properties) throws IOException {
+ return new DFSFileSystem(properties.getBackendConfigProperties());
Review Comment:
[P1] Preserve Kerberos semantics in the JFS typed binding
`jfs://` used to bind through `HdfsProperties`, which validates both
supported Kerberos alias families, translates them to Hadoop keys, and creates
a Kerberos execution authenticator. The new `JfsProperties` has none of those
auth fields, inherits `isKerberos() == false` and a simple authenticator, and
this direct create receives its unaligned backend map.
`StorageAdapter.alignJfsBackendMap` only repairs facade-derived maps; it cannot
fix this direct path or the Iceberg/Paimon authenticator delegated from the SPI
object. Consequently a JFS catalog using
`hdfs.authentication.kerberos.{principal,keytab}` can initialize without the
legacy validation and then run outside the requested Kerberos identity. Please
reuse the HDFS auth binding/validation/authenticator behavior here and cover
both alias families, missing credentials, direct creation, and metastore
authenticator selection.
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageUriUtils.java:
##########
@@ -0,0 +1,370 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.storage;
+
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.S3URI;
+import org.apache.doris.foundation.property.StoragePropertiesException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+/**
+ * fe-core-only URI validation/normalization glue for {@link StorageAdapter}.
+ *
+ * <p>Ports the URI subset of the legacy {@code S3PropertyUtils} and {@code
AzurePropertyUtils}
+ * pure functions so the facade does not depend on the to-be-deleted
+ * {@code datasource.property.storage} package. Behavior is kept identical,
with one deliberate
+ * exception: the legacy full-parse path could throw the checked {@code
UserException} (from
+ * {@link S3URI#create}); the facade surface is unchecked, so that exception
is rethrown as
+ * {@link StoragePropertiesException} with the same message.</p>
+ */
+public final class StorageUriUtils {
+
+ private static final String URI_KEY = "uri";
+ private static final String SCHEME_DELIM = "://";
+ private static final String S3_SCHEME_PREFIX = "s3://";
+
+ // S3-compatible schemes that can be converted to s3:// with simple string
replacement
+ // Format: scheme://bucket/key -> s3://bucket/key
+ private static final String[] SIMPLE_S3_COMPATIBLE_SCHEMES = {
+ "s3a", "s3n", "oss", "cos", "cosn", "obs", "bos", "gs"
+ };
+
+ private static final Pattern ONELAKE_PATTERN = Pattern.compile(
+
"abfs[s]?://([^@]+)@([^/]+)\\.dfs\\.fabric\\.microsoft\\.com(/.*)?",
Pattern.CASE_INSENSITIVE);
+
+ private StorageUriUtils() {
+ }
+
+ /**
+ * Port of legacy {@code S3PropertyUtils.validateAndNormalizeUri}:
validates and normalizes
+ * the given path into a standard {@code s3://bucket/key} URI.
+ *
+ * <p>Also carries the legacy {@code
OSSProperties.validateAndNormalizeUri} override: OSS
+ * bindings first rewrote a virtual-hosted {@code
scheme://bucket.endpoint/key} authority to
+ * {@code scheme://bucket/key} ({@code rewriteOssBucketIfNecessary})
before the base
+ * normalization, so an OSS bucket-domain URI under a non-http scheme
never reached BE or the
+ * filesystem with the whole host as the bucket. The facade has no
provider key at this call
+ * site (StorageAdapter routes every S3-compatible binding here), so the
rewrite is gated on
+ * the URI shape instead — see {@link
#rewriteOssBucketIfNecessary(String)}.</p>
+ */
+ public static String validateAndNormalizeS3Uri(String path,
+ String stringUsePathStyle,
+ String stringForceParsingByStandardUri) {
+ if (StringUtils.isBlank(path)) {
+ throw new StoragePropertiesException("path is null");
+ }
+
+ // Legacy OSSProperties applied the bucket-domain rewrite before the
base normalization.
+ path = rewriteOssBucketIfNecessary(path);
Review Comment:
[P1] Apply the OSS bucket-domain rewrite only to OSS bindings
This helper is called for every S3-compatible adapter, so the OSS-specific
rewrite changes non-OSS behavior. With an explicit generic S3 binding
(`fs.s3.support=true`) and `oss://logs.prod/archive/a.parquet`, legacy
`S3Properties` returned `s3://logs.prod/archive/a.parquet`; this code first
truncates the authority and returns `s3://logs/archive/a.parquet`, silently
targeting a different bucket. Please carry provider identity into normalization
(or apply this rewrite only in the OSS adapter) and add parity cases for dotted
`oss://` and Aliyun-looking `s3://` authorities under explicit non-OSS bindings.
##########
fe/fe-filesystem/fe-filesystem-minio/src/main/java/org/apache/doris/filesystem/minio/MinioFileSystemProvider.java:
##########
@@ -65,6 +65,83 @@ public FileSystem create(MinioFileSystemProperties
properties) throws IOExceptio
new S3ObjStorage(delegate, properties.getSupportedSchemes()));
}
+ private static final String[] MINIO_GUESS_IDENTIFIERS = {
+ "minio.access_key", "AWS_ACCESS_KEY", "ACCESS_KEY", "access_key",
"s3.access_key",
+ "minio.endpoint", "s3.endpoint", "AWS_ENDPOINT", "endpoint",
"ENDPOINT"};
+ private static final String[] MINIO_GUESS_ENDPOINT_NAMES = {
+ "minio.endpoint", "s3.endpoint", "AWS_ENDPOINT", "endpoint",
"ENDPOINT"};
+ // Endpoint markers of the clouds whose guessIsMe fe-core's
MinioProperties.guessIsMe
+ // consults for mutual exclusion (Azure/COS/OSS/S3, plus GCS which the
dialect split adds).
+ private static final String[] OTHER_CLOUD_ENDPOINT_MARKERS = {
+ "aliyuncs.com", "myqcloud.com", "amazonaws.com",
"storage.googleapis.com",
+ ".blob.core.windows.net", ".dfs.core.windows.net",
".blob.core.chinacloudapi.cn"};
+ private static final String[] S3_GUESS_REGION_NAMES = {
+ "s3.region", "glue.region", "aws.glue.region",
"iceberg.rest.signing-region",
+ "rest.signing-region", "client.region"};
+
+ @Override
+ public boolean supportsExplicit(Map<String, String> properties) {
+ return S3CompatSignals.isFsSupport(properties, FS_MINIO_SUPPORT);
+ }
+
+ @Override
+ public boolean supportsGuess(Map<String, String> properties) {
+ // Verbatim port of fe-core MinioProperties.guessIsMe: MinIO is the
"any other
+ // S3-compatible" fallback — it claims the map iff none of
Azure/COS/OSS/S3 would claim
+ // it AND at least one identifier key is present. The sibling guesses
are replicated
+ // here (plugins cannot call each other's classes), preserving legacy
control flow
+ // EXACTLY: legacy S3.guessIsMe SHORT-CIRCUITS on a present endpoint
alias (claiming
+ // only an amazonaws endpoint), and consults its uri/region fallbacks
ONLY when no
+ // endpoint alias exists. A present non-cloud endpoint (e.g. a private
MinIO address)
+ // therefore stays MinIO's even when an s3.region key is also set.
+ if
("azure".equalsIgnoreCase(properties.get(S3CompatSignals.PROVIDER_KEY))) {
+ return false;
+ }
+ String endpoint = null;
+ for (String name : MINIO_GUESS_ENDPOINT_NAMES) {
+ String value = properties.get(name);
+ if (value != null && !value.isBlank()) {
+ endpoint = value;
+ break;
+ }
+ }
+ if (endpoint != null) {
+ String lower = endpoint.toLowerCase(java.util.Locale.ROOT);
+ for (String marker : OTHER_CLOUD_ENDPOINT_MARKERS) {
Review Comment:
[P1] Reuse Azure's host-suffix predicate for the MinIO exclusion
This marker scan is not equivalent to the legacy `AzureProperties.guessIsMe`
call. For example, `https://acct.dfs.core.usgovcloudapi.net` is recognized by
the Azure provider from `Config.azure_blob_host_suffixes`, but that suffix is
absent from this marker list, so `bindAll` also creates a MINIO binding.
`CatalogProperty` keeps both, and S3-style location and connectivity fallbacks
prefer MINIO, routing the Azure catalog through the wrong provider. Custom
mutable suffixes have the same problem, while `contains` can also reject a
non-Azure host merely containing a public suffix. Please use the same host
extraction and injected live suffix list as the Azure provider and add full
`bindPrimary`/`bindAll` parity coverage.
##########
fe/fe-core/src/main/java/org/apache/doris/fs/FileSystemPluginManager.java:
##########
@@ -141,6 +158,162 @@ public void registerProvider(FileSystemProvider provider)
{
DatasourcePrintableMap.registerSensitiveKeys(provider.sensitivePropertyKeys());
}
+ /** Returns an unmodifiable view of the loaded providers, in registration
order. */
+ public List<FileSystemProvider> getProviders() {
+ return Collections.unmodifiableList(providers);
+ }
+
+ /**
+ * Selection priority for raw-user-props binding = {@code
StorageRegistry.Provider} declaration
+ * order (single source of truth; mirrors the legacy
StorageProperties.PROVIDERS registry
+ * order, with JFS hoisted ahead of HDFS so a jfs:// uri beats HDFS's
key-hint guess).
+ */
+
+ private static final String DEPRECATED_OSS_HDFS_SUPPORT =
"oss.hdfs.enabled";
+
+ /**
+ * Binds the first matching provider for raw user properties, mirroring
+ * {@code StorageProperties.createPrimary}: fixed priority, explicit
{@code fs.<x>.support}
+ * flags first-class, heuristic guesses globally disabled once any
explicit flag is present,
+ * and no default fallback — throws when nothing matches.
+ */
+ public FileSystemProperties bindPrimary(Map<String, String> properties) {
+ boolean useGuess = !hasAnyExplicitFsSupport(properties);
+ Map<String, String> probeView = withProbeContext(properties);
+ for (StorageRegistry.Provider meta :
StorageRegistry.Provider.values()) {
+ FileSystemProvider provider = providerByName(meta.name());
+ if (provider == null) {
+ continue;
+ }
+ if (provider.supportsExplicit(properties) || (useGuess &&
provider.supportsGuess(probeView))) {
+ return provider.bind(properties);
+ }
+ }
+ // Providers not in the StorageRegistry.Provider table (out-of-tree
plugins) are consulted
+ // after every known provider, in registration order — adding a plugin
needs no fe-core
+ // priority-list change and can never preempt the legacy fourteen.
+ for (FileSystemProvider provider : providers) {
+ if (StorageRegistry.Provider.byName(provider.name()).isPresent()) {
+ continue;
+ }
+ if (provider.supportsExplicit(properties) || (useGuess &&
provider.supportsGuess(probeView))) {
Review Comment:
[P1] Keep legacy out-of-tree providers selectable
A provider compiled against the documented SPI implements `supports(Map)`
and `create(Map)` only. It therefore inherits `supportsExplicit() == false`,
`supportsGuess() == false`, and the throwing default `bind()`, so this new loop
never even consults its `supports()` result. All migrated storage consumers
bind through `StorageAdapter` first, which makes such directory-loaded
providers invisible before the existing raw-map creation fallback can run. This
also contradicts `FileSystemProvider.bind`'s statement that legacy providers
may keep implementing `create(Map)` during migration; the extensibility test
covers only a newly typed provider. Please retain a raw-properties bridge for
legacy providers (including `supports()` selection and a bind representation),
or explicitly version/reject the old SPI instead of silently skipping it.
--
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]