Skip to content

Commit 86ced24

Browse files
committed
[TEST][VL] Forward Hadoop filesystem configuration per runtime
Collect user-provided filesystem settings once per Spark session and carry them through whole-stage RDDs and native writer paths without adding credentials to the Substrait plan. Create per-runtime Velox connector configuration, use credential-safe runtime identity, and redact sensitive filesystem and UGI settings. Preserve legacy overloads and constructors for backend compatibility.
1 parent 8aeff65 commit 86ced24

38 files changed

Lines changed: 1960 additions & 81 deletions

File tree

backends-velox/src-delta33/main/scala/org/apache/spark/sql/delta/GlutenParquetFileFormat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class GlutenParquetFileFormat
6262
conf.set(ParquetOutputFormat.COMPRESSION, parquetOptions.compressionCodecClassName)
6363
val nativeConf =
6464
GlutenFormatFactory("parquet")
65-
.nativeConf(options, parquetOptions.compressionCodecClassName)
65+
.nativeConf(sparkSession, options, parquetOptions.compressionCodecClassName)
6666

6767
new OutputWriterFactory {
6868
override def getFileExtension(context: TaskAttemptContext): String = {

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package org.apache.gluten.backendsapi.velox
1818

1919
import org.apache.gluten.GlutenBuildInfo._
2020
import org.apache.gluten.backendsapi._
21-
import org.apache.gluten.config.{GlutenConfig, VeloxConfig}
21+
import org.apache.gluten.config.{GlutenConfig, HadoopConfContributor, VeloxConfig}
2222
import org.apache.gluten.exception.GlutenNotSupportException
2323
import org.apache.gluten.execution.ValidationResult
2424
import org.apache.gluten.execution.WriteFilesExecTransformer
@@ -52,7 +52,7 @@ import org.apache.hadoop.fs.Path
5252

5353
import scala.util.control.Breaks.breakable
5454

55-
class VeloxBackend extends SubstraitBackend {
55+
class VeloxBackend extends SubstraitBackend with HadoopConfContributor {
5656
import VeloxBackend._
5757

5858
override def name(): String = VeloxBackend.BACKEND_NAME
@@ -72,6 +72,7 @@ class VeloxBackend extends SubstraitBackend {
7272
override def settings(): BackendSettingsApi = VeloxBackendSettings
7373
override def convFuncOverride(): ConventionFunc.Override = new ConvFunc()
7474
override def costers(): Seq[LongCoster] = Seq(LegacyCoster, RoughCoster)
75+
override def interestedPrefixes(): Set[String] = Set("fs.")
7576
}
7677

7778
object VeloxBackend {

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,30 @@ class VeloxIteratorApi extends IteratorApi with Logging {
202202
inputIterators: Seq[Iterator[ColumnarBatch]] = Seq(),
203203
enableCudf: Boolean = false,
204204
wsContext: WholeStageTransformContext = null): Iterator[ColumnarBatch] = {
205+
genFirstStageIterator(
206+
inputPartition,
207+
context,
208+
pipelineTime,
209+
updateInputMetrics,
210+
updateNativeMetrics,
211+
partitionIndex,
212+
inputIterators,
213+
enableCudf,
214+
wsContext,
215+
Map.empty)
216+
}
217+
218+
override def genFirstStageIterator(
219+
inputPartition: BaseGlutenPartition,
220+
context: TaskContext,
221+
pipelineTime: SQLMetric,
222+
updateInputMetrics: InputMetricsWrapper => Unit,
223+
updateNativeMetrics: IMetrics => Unit,
224+
partitionIndex: Int,
225+
inputIterators: Seq[Iterator[ColumnarBatch]],
226+
enableCudf: Boolean,
227+
wsContext: WholeStageTransformContext,
228+
fsConf: Map[String, String]): Iterator[ColumnarBatch] = {
205229
assert(
206230
inputPartition.isInstanceOf[GlutenPartition],
207231
"Velox backend only accept GlutenPartition.")
@@ -210,7 +234,9 @@ class VeloxIteratorApi extends IteratorApi with Logging {
210234
iter => new ColumnarBatchInIterator(BackendsApiManager.getBackendName, iter.asJava)
211235
}
212236

213-
val extraConf = Map(GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> enableCudf.toString).asJava
237+
val extraConf = VeloxIteratorApi
238+
.buildExtraConf(fsConf, enableCudf, supportsValueStreamDynamicFilter = true)
239+
.asJava
214240
val transKernel = NativePlanEvaluator.create(BackendsApiManager.getBackendName, extraConf)
215241

216242
val splitInfoByteArray = inputPartition
@@ -262,11 +288,36 @@ class VeloxIteratorApi extends IteratorApi with Logging {
262288
materializeInput: Boolean,
263289
enableCudf: Boolean = false,
264290
supportsValueStreamDynamicFilter: Boolean = true): Iterator[ColumnarBatch] = {
265-
val extraConfMap = mutable.Map(GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> enableCudf.toString)
266-
if (!supportsValueStreamDynamicFilter) {
267-
extraConfMap(VeloxConfig.VALUE_STREAM_DYNAMIC_FILTER_ENABLED.key) = "false"
268-
}
269-
val extraConf = extraConfMap.asJava
291+
genFinalStageIterator(
292+
context,
293+
inputIterators,
294+
sparkConf,
295+
rootNode,
296+
pipelineTime,
297+
updateNativeMetrics,
298+
partitionIndex,
299+
materializeInput,
300+
enableCudf,
301+
supportsValueStreamDynamicFilter,
302+
Map.empty
303+
)
304+
}
305+
306+
override def genFinalStageIterator(
307+
context: TaskContext,
308+
inputIterators: Seq[Iterator[ColumnarBatch]],
309+
sparkConf: SparkConf,
310+
rootNode: PlanNode,
311+
pipelineTime: SQLMetric,
312+
updateNativeMetrics: IMetrics => Unit,
313+
partitionIndex: Int,
314+
materializeInput: Boolean,
315+
enableCudf: Boolean,
316+
supportsValueStreamDynamicFilter: Boolean,
317+
fsConf: Map[String, String]): Iterator[ColumnarBatch] = {
318+
val extraConf = VeloxIteratorApi
319+
.buildExtraConf(fsConf, enableCudf, supportsValueStreamDynamicFilter)
320+
.asJava
270321
val transKernel = NativePlanEvaluator.create(BackendsApiManager.getBackendName, extraConf)
271322
val columnarNativeIterator =
272323
inputIterators.map {
@@ -303,6 +354,21 @@ class VeloxIteratorApi extends IteratorApi with Logging {
303354
}
304355

305356
object VeloxIteratorApi {
357+
private[gluten] def buildExtraConf(
358+
fsConf: Map[String, String],
359+
enableCudf: Boolean,
360+
supportsValueStreamDynamicFilter: Boolean): Map[String, String] = {
361+
val dynamicFilterConf =
362+
if (supportsValueStreamDynamicFilter) {
363+
Map.empty[String, String]
364+
} else {
365+
Map(VeloxConfig.VALUE_STREAM_DYNAMIC_FILTER_ENABLED.key -> "false")
366+
}
367+
val controlConf =
368+
Map(GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> enableCudf.toString) ++ dynamicFilterConf
369+
fsConf.filter { case (key, _) => key.startsWith("spark.hadoop.fs.") } ++ controlConf
370+
}
371+
306372
// lookup table to translate '0' -> 0 ... 'F'/'f' -> 15
307373
private val unhexDigits = {
308374
val array = Array.fill[Byte](128)(-1)

backends-velox/src/main/scala/org/apache/gluten/datasource/VeloxDataSourceUtil.scala

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,60 @@ import java.util
3131

3232
object VeloxDataSourceUtil {
3333
def readSchema(files: Seq[FileStatus]): Option[StructType] = {
34+
readSchema(files, new util.HashMap[String, String]())
35+
}
36+
37+
def readSchema(
38+
files: Seq[FileStatus],
39+
fsConf: util.Map[String, String]): Option[StructType] = {
3440
if (files.isEmpty) {
3541
throw new IllegalArgumentException("No input file specified")
3642
}
37-
readSchema(files.toList.head)
43+
readSchema(files.toList.head, fsConf)
3844
}
3945

4046
def readSchema(file: FileStatus): Option[StructType] = {
47+
readSchema(file, new util.HashMap[String, String]())
48+
}
49+
50+
def readSchema(
51+
file: FileStatus,
52+
fsConf: util.Map[String, String]): Option[StructType] = {
4153
val allocator = ArrowBufferAllocators.contextInstance()
42-
val runtime = Runtimes.contextInstance(BackendsApiManager.getBackendName, "VeloxWriter")
54+
val runtime =
55+
Runtimes.contextInstance(BackendsApiManager.getBackendName, "VeloxWriter", fsConf)
4356
val datasourceJniWrapper = VeloxDataSourceJniWrapper.create(runtime)
44-
val dsHandle =
45-
datasourceJniWrapper.init(file.getPath.toString, -1, new util.HashMap[String, String]())
46-
val cSchema = ArrowSchema.allocateNew(allocator)
47-
datasourceJniWrapper.inspectSchema(dsHandle, cSchema.memoryAddress())
57+
withDataSourceResource[ArrowSchema, Option[StructType]](
58+
() => datasourceJniWrapper.init(file.getPath.toString, -1, fsConf),
59+
() => ArrowSchema.allocateNew(allocator),
60+
(dsHandle, cSchema) => {
61+
datasourceJniWrapper.inspectSchema(dsHandle, cSchema.memoryAddress())
62+
Option(SparkSchemaUtil.fromArrowSchema(ArrowAbiUtil.importToSchema(allocator, cSchema)))
63+
},
64+
datasourceJniWrapper.close
65+
)
66+
}
67+
68+
private[datasource] def withDataSourceResource[R <: AutoCloseable, T](
69+
initialize: () => Long,
70+
allocate: () => R,
71+
use: (Long, R) => T,
72+
closeHandle: Long => Unit): T = {
73+
var dsHandle: Option[Long] = None
74+
var resource: R = null.asInstanceOf[R]
4875
try {
49-
Option(SparkSchemaUtil.fromArrowSchema(ArrowAbiUtil.importToSchema(allocator, cSchema)))
76+
val handle = initialize()
77+
dsHandle = Some(handle)
78+
resource = allocate()
79+
use(handle, resource)
5080
} finally {
51-
cSchema.close()
52-
datasourceJniWrapper.close(dsHandle)
81+
try {
82+
if (resource != null) {
83+
resource.close()
84+
}
85+
} finally {
86+
dsHandle.foreach(closeHandle)
87+
}
5388
}
5489
}
5590
}

backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxFormatWriterInjects.scala

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import org.apache.gluten.backendsapi.BackendsApiManager
2020
import org.apache.gluten.columnarbatch.ColumnarBatches
2121
import org.apache.gluten.datasource.{VeloxDataSourceJniWrapper, VeloxDataSourceUtil}
2222
import org.apache.gluten.exception.GlutenException
23-
import org.apache.gluten.execution.BatchCarrierRow
23+
import org.apache.gluten.execution.{BatchCarrierRow, HadoopConfCollector}
2424
import org.apache.gluten.execution.datasource.GlutenRowSplitter
2525
import org.apache.gluten.memory.arrow.alloc.ArrowBufferAllocators
2626
import org.apache.gluten.runtime.Runtimes
@@ -33,12 +33,33 @@ import org.apache.spark.sql.internal.SQLConf
3333
import org.apache.spark.sql.types.StructType
3434
import org.apache.spark.sql.utils.SparkArrowUtil
3535
import org.apache.spark.sql.vectorized.ColumnarBatch
36+
import org.apache.spark.task.TaskResources
3637

3738
import org.apache.arrow.c.ArrowSchema
3839
import org.apache.hadoop.fs.{FileStatus, Path}
3940
import org.apache.hadoop.mapreduce.TaskAttemptContext
4041

4142
import java.io.IOException
43+
import java.util
44+
45+
import scala.collection.JavaConverters._
46+
47+
object VeloxFormatWriterInjects {
48+
private val SparkHadoopFsPrefix = "spark.hadoop.fs."
49+
50+
private[velox] def runtimeFsConf(
51+
nativeConf: util.Map[String, String]): util.Map[String, String] = {
52+
nativeConf.asScala.filter {
53+
case (key, _) => key.startsWith(SparkHadoopFsPrefix)
54+
}.asJava
55+
}
56+
57+
private[velox] def runWithTaskResources[T](sparkSession: SparkSession)(body: => T): T = {
58+
sparkSession.withActive {
59+
TaskResources.runUnsafe(body)
60+
}
61+
}
62+
}
4263

4364
trait VeloxFormatWriterInjects extends GlutenFormatWriterInjectsBase {
4465
def createOutputWriter(
@@ -60,7 +81,10 @@ trait VeloxFormatWriterInjects extends GlutenFormatWriterInjectsBase {
6081
SparkArrowUtil.toArrowSchema(dataSchema, SQLConf.get.sessionLocalTimeZone)
6182
val cSchema = ArrowSchema.allocateNew(ArrowBufferAllocators.contextInstance())
6283
var dsHandle = -1L
63-
val runtime = Runtimes.contextInstance(BackendsApiManager.getBackendName, "VeloxWriter")
84+
val runtime = Runtimes.contextInstance(
85+
BackendsApiManager.getBackendName,
86+
"VeloxWriter",
87+
VeloxFormatWriterInjects.runtimeFsConf(nativeConf))
6488
val datasourceJniWrapper = VeloxDataSourceJniWrapper.create(runtime)
6589
val allocator = ArrowBufferAllocators.contextInstance()
6690
try {
@@ -103,7 +127,9 @@ trait VeloxFormatWriterInjects extends GlutenFormatWriterInjectsBase {
103127
sparkSession: SparkSession,
104128
options: Map[String, String],
105129
files: Seq[FileStatus]): Option[StructType] = {
106-
VeloxDataSourceUtil.readSchema(files)
130+
VeloxFormatWriterInjects.runWithTaskResources(sparkSession) {
131+
VeloxDataSourceUtil.readSchema(files, HadoopConfCollector.collect(sparkSession).asJava)
132+
}
107133
}
108134
}
109135

backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxParquetWriterInjects.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
package org.apache.spark.sql.execution.datasources.velox
1818

1919
import org.apache.gluten.config.GlutenConfig
20+
import org.apache.gluten.execution.HadoopConfCollector
2021

22+
import org.apache.spark.sql.SparkSession
2123
import org.apache.spark.sql.internal.SQLConf
2224

25+
import java.util
26+
2327
import scala.collection.JavaConverters.mapAsJavaMapConverter
2428
import scala.collection.mutable
2529

@@ -60,5 +64,17 @@ class VeloxParquetWriterInjects extends VeloxFormatWriterInjects {
6064
sparkOptions.asJava
6165
}
6266

67+
override def nativeConf(
68+
sparkSession: SparkSession,
69+
options: Map[String, String],
70+
compressionCodec: String): java.util.Map[String, String] = {
71+
val sparkOptions = new util.HashMap[String, String](
72+
sparkSession.withActive {
73+
nativeConf(options, compressionCodec)
74+
})
75+
sparkOptions.putAll(HadoopConfCollector.collect(sparkSession).asJava)
76+
sparkOptions
77+
}
78+
6379
override val formatName: String = "parquet"
6480
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.gluten.backendsapi.velox
18+
19+
import org.apache.gluten.config.{GlutenConfig, VeloxConfig}
20+
21+
import org.scalatest.funsuite.AnyFunSuite
22+
23+
class VeloxHadoopConfTransportSuite extends AnyFunSuite {
24+
25+
test("extra conf preserves filesystem credentials and overrides control keys") {
26+
val cudfKey = GlutenConfig.COLUMNAR_CUDF_ENABLED.key
27+
val dynamicFilterKey = VeloxConfig.VALUE_STREAM_DYNAMIC_FILTER_ENABLED.key
28+
val fsConf = Map(
29+
"spark.hadoop.fs.s3a.access.key" -> "s3-access-key",
30+
"spark.hadoop.fs.azure.account.key.container.dfs.core.windows.net" -> "azure-key",
31+
"spark.hadoop.fs.gs.auth.service.account.private.key" -> "gcs-key",
32+
"spark.hadoop.fs.oss.endpoint" -> "unknown-scheme-endpoint",
33+
cudfKey -> "true",
34+
dynamicFilterKey -> "true"
35+
)
36+
val original = fsConf
37+
38+
val merged = VeloxIteratorApi.buildExtraConf(
39+
fsConf,
40+
enableCudf = false,
41+
supportsValueStreamDynamicFilter = false)
42+
43+
assert(merged("spark.hadoop.fs.s3a.access.key") == "s3-access-key")
44+
assert(
45+
merged("spark.hadoop.fs.azure.account.key.container.dfs.core.windows.net") == "azure-key")
46+
assert(merged("spark.hadoop.fs.gs.auth.service.account.private.key") == "gcs-key")
47+
assert(merged("spark.hadoop.fs.oss.endpoint") == "unknown-scheme-endpoint")
48+
assert(merged(cudfKey) == "false")
49+
assert(merged(dynamicFilterKey) == "false")
50+
assert(fsConf == original)
51+
assert(fsConf(cudfKey) == "true")
52+
assert(fsConf(dynamicFilterKey) == "true")
53+
}
54+
55+
test("extra conf enables CUDF without injecting an enabled dynamic filter") {
56+
val merged = VeloxIteratorApi.buildExtraConf(
57+
Map.empty,
58+
enableCudf = true,
59+
supportsValueStreamDynamicFilter = true)
60+
61+
assert(merged(GlutenConfig.COLUMNAR_CUDF_ENABLED.key) == "true")
62+
assert(!merged.contains(VeloxConfig.VALUE_STREAM_DYNAMIC_FILTER_ENABLED.key))
63+
}
64+
65+
test("empty fsConf does not add filesystem keys to extra conf") {
66+
val merged = VeloxIteratorApi.buildExtraConf(
67+
Map.empty,
68+
enableCudf = false,
69+
supportsValueStreamDynamicFilter = false)
70+
71+
assert(!merged.keys.exists(_.startsWith("spark.hadoop.fs.")))
72+
}
73+
74+
test("extra conf drops non-filesystem settings at the native boundary") {
75+
val merged = VeloxIteratorApi.buildExtraConf(
76+
Map(
77+
"spark.hadoop.fs.s3a.endpoint" -> "s3-endpoint",
78+
"spark.sql.session.timeZone" -> "UTC",
79+
"spark.gluten.ugi.tokens" -> "delegation-token"),
80+
enableCudf = false,
81+
supportsValueStreamDynamicFilter = true
82+
)
83+
84+
assert(merged("spark.hadoop.fs.s3a.endpoint") == "s3-endpoint")
85+
assert(!merged.contains("spark.sql.session.timeZone"))
86+
assert(!merged.contains("spark.gluten.ugi.tokens"))
87+
}
88+
}

0 commit comments

Comments
 (0)