@@ -125,12 +125,41 @@ class VeloxIteratorApi extends IteratorApi with Logging {
125125 // Only serialize plan once, save lots time when plan is complex.
126126 val planByteArray = wsCtx.root.toProtobuf.toByteArray
127127
128+ // Capture fs.azure.* / fs.s3a.* / fs.gs.* keys from the driver-side
129+ // Hadoop configuration NOW, while we are still on the driver, and embed
130+ // them in every GlutenPartition. These keys are set by the user via
131+ // spark.conf.set("fs.azure.account.auth.type", ...) or
132+ // sparkContext.hadoopConfiguration.set(...)
133+ // Spark's withSQLConfPropagated only forwards keys starting with "spark"
134+ // as task-local-properties, so "fs.*" keys never reach the executor's
135+ // SQLConf. Serialising them inside the partition is the only safe way
136+ // to make them available to the native runtime on the executor.
137+ // Capture fs.azure.* / fs.s3a.* / fs.gs.* keys while on the driver.
138+ // SparkPlan.sqlContext is available on the driver -- using the first leaf
139+ // gives us access to sessionState.newHadoopConf() which includes all keys
140+ // set via spark.conf.set(), sparkContext.hadoopConfiguration, and
141+ // DataFrameReader.option(). These are NOT propagated to executors by
142+ // Spark's withSQLConfPropagated (it only forwards keys starting with
143+ // "spark"), so embedding them in the serialised GlutenPartition is the
144+ // only reliable transport mechanism.
145+ val fsPrefixes = Seq (" fs.azure." , " fs.s3a." , " fs.gs." )
146+ val hadoopConf = leaves.headOption
147+ .map(_ => org.apache.spark.sql.SparkSession .active.sessionState.newHadoopConf())
148+ .getOrElse(org.apache.spark.SparkContext .getOrCreate().hadoopConfiguration)
149+ val fsConf = {
150+ hadoopConf.iterator().asScala
151+ .filter(e => fsPrefixes.exists(e.getKey.startsWith))
152+ .map(e => e.getKey -> e.getValue)
153+ .toMap
154+ }
155+
128156 splitInfos.zipWithIndex.map {
129157 case (splitInfos, index) =>
130158 GlutenPartition (
131159 index,
132160 planByteArray,
133- splitInfos.toArray
161+ splitInfos.toArray,
162+ fsConf = fsConf
134163 )
135164 }
136165 }
@@ -199,7 +228,16 @@ class VeloxIteratorApi extends IteratorApi with Logging {
199228 iter => new ColumnarBatchInIterator (BackendsApiManager .getBackendName, iter.asJava)
200229 }
201230
202- val extraConf = Map (GlutenConfig .COLUMNAR_CUDF_ENABLED .key -> enableCudf.toString).asJava
231+ // Merge the fs.* keys captured on the driver (stored in GlutenPartition.fsConf)
232+ // into the extraConf passed to NativePlanEvaluator / VeloxRuntime.
233+ // Runtimes.contextInstance() will call GlutenConfig.getNativeSessionConf() which
234+ // merges extraConf on top of SQLConf.get.getAllConfs. Because the executor-side
235+ // SQLConf never receives "fs.*" keys (Spark only propagates "spark.*" keys via
236+ // task local properties), this is the only path these credentials can take to
237+ // reach the native session config and ultimately the Velox ABFS connector.
238+ val partitionFsConf = inputPartition.asInstanceOf [GlutenPartition ].fsConf
239+ val extraConf = (partitionFsConf +
240+ (GlutenConfig .COLUMNAR_CUDF_ENABLED .key -> enableCudf.toString)).asJava
203241 val transKernel = NativePlanEvaluator .create(BackendsApiManager .getBackendName, extraConf)
204242
205243 val splitInfoByteArray = inputPartition
0 commit comments