Skip to content

Commit 43235f5

Browse files
committed
tests: better testing of the hbase connectors (#717)
* tests: better testing of the hbase connectors * bump hbase * update supported versions * update fixture
1 parent 739b49a commit 43235f5

7 files changed

Lines changed: 61 additions & 6 deletions

File tree

docs/modules/spark-k8s/partials/supported-versions.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
- 4.1.2 (Hadoop 3.4.3, Scala 2.13, Python 3.12, Java 21) (LTS)
77
- 4.1.1 (Hadoop 3.4.3, Scala 2.13, Python 3.12, Java 21) (Deprecated)
8-
- 3.5.8 (Hadoop 3.4.2, Scala 2.12, Python 3.11, Java 17) (Deprecated)
8+
- 3.5.8 (HBase 2.6.6, Hadoop 3.4.3, Scala 2.12, Python 3.11, Java 17) (Deprecated)
99
1010
Apache Spark 4.1.1 & 4.1.2 have the following known issues (as of July 2026):
1111

tests/templates/kuttl/hbase-connector/10-deploy-spark-app.yaml.j2

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ data:
7777
spark = SparkSession.builder.appName("test-hbase").getOrCreate()
7878

7979
df = spark.createDataFrame(
80-
[("row1", "Hello, Stackable!")],
80+
[
81+
("row1", "Hello, Stackable!"),
82+
("row2", "Hello, HBase!"),
83+
("row3", "Hello, Spark!"),
84+
],
8185
"key: string, value: string"
8286
)
8387

@@ -101,3 +105,53 @@ data:
101105
.option('catalog', catalog)\
102106
.option('newtable', '5')\
103107
.save()
108+
109+
# Read the whole table back and register it as a temporary view so we can
110+
# run SQL queries against it.
111+
#
112+
# hbase.spark.pushdown.columnfilter=false disables server-side predicate
113+
# pushdown: the connector would otherwise ship a SparkSQLPushDownFilter to
114+
# the RegionServers, which requires the hbase-spark connector classes on
115+
# the RegionServer classpath (not present in the Stackable HBase image).
116+
# With pushdown off, WHERE clauses are evaluated Spark-side over a full
117+
# scan, which is all this test needs.
118+
read_df = spark\
119+
.read\
120+
.format("org.apache.hadoop.hbase.spark")\
121+
.option('catalog', catalog)\
122+
.option('hbase.spark.pushdown.columnfilter', 'false')\
123+
.load()
124+
read_df.createOrReplaceTempView("test_hbase")
125+
126+
# Full scan: all rows that were written must be readable again.
127+
all_rows = spark.sql("SELECT key, value FROM test_hbase").collect()
128+
actual = {row["key"]: row["value"] for row in all_rows}
129+
expected = {
130+
"row1": "Hello, Stackable!",
131+
"row2": "Hello, HBase!",
132+
"row3": "Hello, Spark!",
133+
}
134+
assert actual == expected, f"full scan mismatch: {actual} != {expected}"
135+
136+
# Row-key point lookup.
137+
point = spark.sql("SELECT value FROM test_hbase WHERE key = 'row2'").collect()
138+
assert len(point) == 1, f"point lookup returned {len(point)} rows, expected 1"
139+
assert point[0]["value"] == "Hello, HBase!", f"unexpected value: {point[0]['value']}"
140+
141+
# Range scan on the row key.
142+
range_rows = spark.sql(
143+
"SELECT key FROM test_hbase WHERE key >= 'row2' ORDER BY key"
144+
).collect()
145+
assert [r["key"] for r in range_rows] == ["row2", "row3"], \
146+
f"unexpected range scan result: {[r['key'] for r in range_rows]}"
147+
148+
# Filter on a non-rowkey column value.
149+
like_rows = spark.sql(
150+
"SELECT key FROM test_hbase WHERE value LIKE 'Hello, S%' ORDER BY key"
151+
).collect()
152+
assert [r["key"] for r in like_rows] == ["row1", "row3"], \
153+
f"unexpected value filter result: {[r['key'] for r in like_rows]}"
154+
155+
# Aggregation over the scanned rows.
156+
count = spark.sql("SELECT COUNT(*) AS c FROM test_hbase").collect()[0]["c"]
157+
assert count == 3, f"expected 3 rows, got {count}"

tests/templates/kuttl/product-config-compat/10-deploy-spark-app.yaml.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ metadata:
55
name: pyspark-pi
66
spec:
77
sparkImage:
8+
pullPolicy: IfNotPresent
89
{% if test_scenario['values']['spark-regression'].find(",") > 0 %}
910
custom: "{{ test_scenario['values']['spark-regression'].split(',')[1] }}"
1011
productVersion: "{{ test_scenario['values']['spark-regression'].split(',')[0] }}"

tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"log4j2.properties": "appenders = FILE, CONSOLE\n\nappender.CONSOLE.type = Console\nappender.CONSOLE.name = CONSOLE\nappender.CONSOLE.target = SYSTEM_ERR\nappender.CONSOLE.layout.type = PatternLayout\nappender.CONSOLE.layout.pattern = %d{ISO8601} %p [%t] %c - %m%n\nappender.CONSOLE.filter.threshold.type = ThresholdFilter\nappender.CONSOLE.filter.threshold.level = INFO\n\nappender.FILE.type = RollingFile\nappender.FILE.name = FILE\nappender.FILE.fileName = /stackable/log/spark/spark.log4j2.xml\nappender.FILE.filePattern = /stackable/log/spark/spark.log4j2.xml.%i\nappender.FILE.layout.type = XMLLayout\nappender.FILE.policies.type = Policies\nappender.FILE.policies.size.type = SizeBasedTriggeringPolicy\nappender.FILE.policies.size.size = 5MB\nappender.FILE.strategy.type = DefaultRolloverStrategy\nappender.FILE.strategy.max = 1\nappender.FILE.filter.threshold.type = ThresholdFilter\nappender.FILE.filter.threshold.level = INFO\n\n\nrootLogger.level=INFO\nrootLogger.appenderRefs = CONSOLE, FILE\nrootLogger.appenderRef.CONSOLE.ref = CONSOLE\nrootLogger.appenderRef.FILE.ref = FILE",
33
"security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n",
44
"spark-env.sh": "",
5-
"template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n prometheus.io/scrape: 'true'\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: Always\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: config\n"
5+
"template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n prometheus.io/scrape: 'true'\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: IfNotPresent\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-driver-pod-template\n name: config\n"
66
}

tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"log4j2.properties": "appenders = FILE, CONSOLE\n\nappender.CONSOLE.type = Console\nappender.CONSOLE.name = CONSOLE\nappender.CONSOLE.target = SYSTEM_ERR\nappender.CONSOLE.layout.type = PatternLayout\nappender.CONSOLE.layout.pattern = %d{ISO8601} %p [%t] %c - %m%n\nappender.CONSOLE.filter.threshold.type = ThresholdFilter\nappender.CONSOLE.filter.threshold.level = INFO\n\nappender.FILE.type = RollingFile\nappender.FILE.name = FILE\nappender.FILE.fileName = /stackable/log/spark/spark.log4j2.xml\nappender.FILE.filePattern = /stackable/log/spark/spark.log4j2.xml.%i\nappender.FILE.layout.type = XMLLayout\nappender.FILE.policies.type = Policies\nappender.FILE.policies.size.type = SizeBasedTriggeringPolicy\nappender.FILE.policies.size.size = 5MB\nappender.FILE.strategy.type = DefaultRolloverStrategy\nappender.FILE.strategy.max = 1\nappender.FILE.filter.threshold.type = ThresholdFilter\nappender.FILE.filter.threshold.level = INFO\n\n\nrootLogger.level=INFO\nrootLogger.appenderRefs = CONSOLE, FILE\nrootLogger.appenderRef.CONSOLE.ref = CONSOLE\nrootLogger.appenderRef.FILE.ref = FILE",
33
"security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n",
44
"spark-env.sh": "",
5-
"template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: Always\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: config\n"
5+
"template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: spark\n app.kubernetes.io/instance: pyspark-pi\n app.kubernetes.io/managed-by: spark.stackable.tech_sparkapplication\n app.kubernetes.io/name: spark-k8s\n app.kubernetes.io/role-group: sparkapplication\n app.kubernetes.io/version: 3.5.8-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: _STACKABLE_PRE_HOOK\n value: containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n image: oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev\n imagePullPolicy: IfNotPresent\n name: spark\n resources:\n limits:\n cpu: '2'\n memory: 1Gi\n requests:\n cpu: '1'\n memory: 1Gi\n volumeMounts:\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/log\n name: log\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n serviceAccountName: pyspark-pi\n volumes:\n - emptyDir:\n sizeLimit: 39Mi\n name: log\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: log-config\n - configMap:\n name: pyspark-pi-executor-pod-template\n name: config\n"
66
}

tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
}
3232
],
3333
"image": "oci.stackable.tech/sdp/spark-k8s:3.5.8-stackable0.0.0-dev",
34-
"imagePullPolicy": "Always",
34+
"imagePullPolicy": "IfNotPresent",
3535
"name": "spark-submit",
3636
"resources": {
3737
"limits": {

tests/test-definition.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ dimensions:
5656
- "CLUSTER.LOCAL"
5757
- name: hbase
5858
values:
59-
- 2.6.4
59+
- 2.6.6
6060
- name: hdfs-latest
6161
values:
6262
- 3.5.0

0 commit comments

Comments
 (0)