Skip to content

Commit 6cdec1a

Browse files
committed
refactor!: replace OL config map with host:port fields
1 parent 454e8cf commit 6cdec1a

8 files changed

Lines changed: 98 additions & 193 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file.
88

99
- BREAKING: Add required CLI argument and env var to set the image repository used to construct final product image names: `IMAGE_REPOSITORY` (`--image-repository`), eg. `oci.example.org/my/namespace` ([#684]).
1010
- Support for Spark `4.1.2` ([#708])
11-
- Add `spec.openLineage` to enable OpenLineage lineage emission: injects the OpenLineage Spark listener, HTTP transport (endpoint resolved from a discovery ConfigMap), a stable job name, and the required `--add-opens` JVM flag ([#XXXX]).
11+
- Add `spec.openLineage` to enable OpenLineage lineage emission: injects the OpenLineage Spark listener, HTTP transport (endpoint built from the `host` and `port` fields), a stable job name, and the required `--add-opens` JVM flag ([#XXXX]).
1212

1313
### Fixed
1414

docs/modules/spark-k8s/pages/usage-guide/openlineage.adoc

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ When configured, the operator injects everything required to make the https://op
1313
1414
== Enabling OpenLineage
1515

16-
The backend endpoint is not configured on the `SparkApplication` directly.
17-
Instead, mirroring the xref:usage-guide/logging.adoc[vector aggregator discovery], the operator reads it from a discovery ConfigMap referenced by `configMapName`:
16+
The backend endpoint is configured directly on the `SparkApplication` through the `host` and `port` fields.
17+
The operator points the HTTP transport at `http://<host>:<port>`:
1818

1919
[source,yaml]
2020
----
@@ -24,34 +24,22 @@ metadata:
2424
name: orders-by-nation
2525
spec:
2626
openLineage: # <1>
27-
configMapName: marquez-discovery # <2>
28-
# namespace: orders-lineage # <3>
29-
# appName: orders-by-nation # <4>
27+
host: marquez # <2>
28+
port: 5000 # <3>
29+
# namespace: orders-lineage # <4>
30+
# appName: orders-by-nation # <5>
3031
...
3132
----
3233
<1> Adding the `openLineage` block enables OpenLineage event emission. Omit it entirely to leave OpenLineage off (the default), in which case the operator emits nothing OpenLineage-related and behaviour is unchanged.
33-
<2> Name of a discovery ConfigMap holding the backend endpoint (see below).
34-
Must be in the same namespace as the `SparkApplication`.
35-
<3> Optional. The OpenLineage namespace events are reported under.
34+
<2> Host of the OpenLineage backend, for example the Marquez API service. Required.
35+
<3> Port of the OpenLineage backend. Required.
36+
<4> Optional. The OpenLineage namespace events are reported under.
3637
Defaults to the application's Kubernetes namespace.
37-
<4> Optional. The stable OpenLineage job name (see <<job-name>>).
38+
<5> Optional. The stable OpenLineage job name (see <<job-name>>).
3839

39-
The discovery ConfigMap holds the backend base URL under the `ADDRESS` key:
40+
The transport protocol is always `http`; explicit `https` support will be added later.
4041

41-
.Example discovery ConfigMap
42-
[source,yaml]
43-
----
44-
apiVersion: v1
45-
kind: ConfigMap
46-
metadata:
47-
name: marquez-discovery
48-
data:
49-
ADDRESS: http://marquez:5000 # <1>
50-
----
51-
<1> Base URL of the OpenLineage backend, for example the Marquez API service.
52-
53-
Alternatively, you can set the endpoint directly through `sparkConf` (`spark.openlineage.transport.url`).
54-
If the `openLineage` block is present and no endpoint can be resolved from either the discovery ConfigMap or `sparkConf`, reconciliation fails with a clear error rather than emitting a config that silently drops events.
42+
You can still override any of the injected values — including the full `spark.openlineage.transport.url` — through `sparkConf`.
5543

5644
[#job-name]
5745
== Job name

extra/crds.yaml

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,22 +1972,28 @@ spec:
19721972
`spark.app.name`, falling back to `metadata.name` (with a warning event).
19731973
nullable: true
19741974
type: string
1975-
configMapName:
1975+
host:
19761976
description: |-
1977-
Name of the OpenLineage backend [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery).
1978-
It must contain the key `ADDRESS` with the base URL of the OpenLineage backend
1979-
(e.g. `http://marquez:5000`). Mirrors the `vectorAggregatorConfigMapName` field on this CRD.
1980-
maxLength: 253
1981-
minLength: 1
1982-
nullable: true
1983-
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
1977+
Host of the OpenLineage backend the HTTP transport points at (e.g. `marquez`).
1978+
Combined with `port` into the transport URL `http://<host>:<port>`.
19841979
type: string
19851980
namespace:
19861981
description: |-
19871982
The OpenLineage namespace lineage is reported under.
19881983
Defaults to the application's Kubernetes namespace (`metadata.namespace`).
19891984
nullable: true
19901985
type: string
1986+
port:
1987+
description: |-
1988+
Port of the OpenLineage backend (e.g. `5000`).
1989+
Combined with `host` into the transport URL `http://<host>:<port>`.
1990+
format: uint16
1991+
maximum: 65535.0
1992+
minimum: 0.0
1993+
type: integer
1994+
required:
1995+
- host
1996+
- port
19911997
type: object
19921998
s3connection:
19931999
description: |-
@@ -6610,22 +6616,28 @@ spec:
66106616
`spark.app.name`, falling back to `metadata.name` (with a warning event).
66116617
nullable: true
66126618
type: string
6613-
configMapName:
6619+
host:
66146620
description: |-
6615-
Name of the OpenLineage backend [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery).
6616-
It must contain the key `ADDRESS` with the base URL of the OpenLineage backend
6617-
(e.g. `http://marquez:5000`). Mirrors the `vectorAggregatorConfigMapName` field on this CRD.
6618-
maxLength: 253
6619-
minLength: 1
6620-
nullable: true
6621-
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
6621+
Host of the OpenLineage backend the HTTP transport points at (e.g. `marquez`).
6622+
Combined with `port` into the transport URL `http://<host>:<port>`.
66226623
type: string
66236624
namespace:
66246625
description: |-
66256626
The OpenLineage namespace lineage is reported under.
66266627
Defaults to the application's Kubernetes namespace (`metadata.namespace`).
66276628
nullable: true
66286629
type: string
6630+
port:
6631+
description: |-
6632+
Port of the OpenLineage backend (e.g. `5000`).
6633+
Combined with `host` into the transport URL `http://<host>:<port>`.
6634+
format: uint16
6635+
maximum: 65535.0
6636+
minimum: 0.0
6637+
type: integer
6638+
required:
6639+
- host
6640+
- port
66296641
type: object
66306642
s3connection:
66316643
description: |-

rust/operator-binary/src/config/jvm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ mod tests {
152152
mainApplicationFile: test.py
153153
sparkImage:
154154
productVersion: 1.2.3
155-
openLineage: {}
155+
openLineage:
156+
host: marquez
157+
port: 5000
156158
"#;
157159

158160
let deserializer = serde_yaml::Deserializer::from_str(input);

rust/operator-binary/src/crd/constants.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,6 @@ pub const OPENLINEAGE_LISTENER_CLASS: &str = "io.openlineage.spark.agent.OpenLin
118118
pub const OPENLINEAGE_JAR_LOCAL_URI: &str =
119119
"local:///stackable/spark/openlineage/openlineage-spark_2.13-1.51.0.jar";
120120

121-
/// The key the OpenLineage discovery ConfigMap must hold, carrying the backend base URL. Mirrors the
122-
/// `ADDRESS` contract of the existing `vectorAggregatorConfigMapName` discovery ConfigMap.
123-
pub const OPENLINEAGE_CONFIG_MAP_ADDRESS_KEY: &str = "ADDRESS";
124-
125121
/// Java module-system flag OpenLineage requires on Spark 4.x: without it the driver throws a
126122
/// non-fatal `InaccessibleObjectException` and silently degrades extension-interface lineage.
127123
/// Appended to both driver and executor `extraJavaOptions`.

rust/operator-binary/src/crd/mod.rs

Lines changed: 29 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use constants::*;
99
use history::LogFileDirectorySpec;
1010
use logdir::ResolvedLogDir;
1111
use serde::{Deserialize, Serialize};
12-
use snafu::{OptionExt, ResultExt, Snafu, ensure};
12+
use snafu::{OptionExt, ResultExt, Snafu};
1313
use stackable_operator::{
1414
builder::pod::volume::{
1515
SecretFormat, SecretOperatorVolumeSourceBuilder, SecretOperatorVolumeSourceBuilderError,
@@ -115,13 +115,6 @@ pub enum Error {
115115

116116
#[snafu(display("failed to configure log directory"))]
117117
ConfigureLogDir { source: logdir::Error },
118-
119-
#[snafu(display(
120-
"OpenLineage is enabled but no backend endpoint could be resolved: set \
121-
`spec.openLineage.configMapName` (a discovery ConfigMap holding the `ADDRESS` key) or \
122-
`spark.openlineage.transport.url` in `sparkConf`"
123-
))]
124-
MissingOpenLineageEndpoint,
125118
}
126119

127120
pub type SparkApplicationJobRoleType =
@@ -576,7 +569,6 @@ impl v1alpha1::SparkApplication {
576569
s3conn: &Option<s3::v1alpha1::ConnectionSpec>,
577570
log_dir: &Option<ResolvedLogDir>,
578571
spark_image: &str,
579-
openlineage_endpoint: Option<&str>,
580572
) -> Result<Vec<String>, Error> {
581573
// mandatory properties
582574
let mode = &self.spec.mode;
@@ -754,26 +746,14 @@ impl v1alpha1::SparkApplication {
754746
// (`spark.jars`, `spark.extraListeners`) are handled AFTER the merge so a user value is
755747
// combined with — not clobbered by — ours. See the OpenLineage usage guide.
756748
if let Some(open_lineage) = &self.spec.open_lineage {
757-
// Fail fast rather than emit a transport that silently drops every event.
758-
let has_url_override = self
759-
.spec
760-
.spark_conf
761-
.contains_key("spark.openlineage.transport.url");
762-
ensure!(
763-
openlineage_endpoint.is_some() || has_url_override,
764-
MissingOpenLineageEndpointSnafu
765-
);
766-
767749
submit_conf.insert(
768750
"spark.openlineage.transport.type".to_string(),
769751
"http".to_string(),
770752
);
771-
if let Some(endpoint) = openlineage_endpoint {
772-
submit_conf.insert(
773-
"spark.openlineage.transport.url".to_string(),
774-
endpoint.to_string(),
775-
);
776-
}
753+
submit_conf.insert(
754+
"spark.openlineage.transport.url".to_string(),
755+
open_lineage.transport_url(),
756+
);
777757

778758
let namespace = match &open_lineage.namespace {
779759
Some(namespace) => namespace.clone(),
@@ -1640,13 +1620,11 @@ spec:
16401620
assert_eq!(got, expected);
16411621
}
16421622

1643-
/// Builds a `SparkApplication` from YAML and returns the assembled spark-submit command string,
1644-
/// with `openlineage_endpoint` standing in for the value the controller resolves from the
1645-
/// discovery ConfigMap.
1646-
fn build_command_with_openlineage(yaml: &str, openlineage_endpoint: Option<&str>) -> String {
1623+
/// Builds a `SparkApplication` from YAML and returns the assembled spark-submit command string.
1624+
fn build_command_with_openlineage(yaml: &str) -> String {
16471625
let spark_application = serde_yaml::from_str::<v1alpha1::SparkApplication>(yaml).unwrap();
16481626
spark_application
1649-
.build_command(&None, &None, "test-image", openlineage_endpoint)
1627+
.build_command(&None, &None, "test-image")
16501628
.unwrap()
16511629
.join(" ")
16521630
}
@@ -1663,13 +1641,14 @@ spec:
16631641
mainApplicationFile: test.py
16641642
sparkImage:
16651643
productVersion: 1.2.3
1666-
openLineage: {}
1644+
openLineage:
1645+
host: marquez
1646+
port: 5000
16671647
"#};
16681648

16691649
#[test]
16701650
fn test_openlineage_injects_conf_when_enabled() {
1671-
let command =
1672-
build_command_with_openlineage(OPENLINEAGE_ENABLED_APP, Some("http://marquez:5000"));
1651+
let command = build_command_with_openlineage(OPENLINEAGE_ENABLED_APP);
16731652

16741653
assert!(command.contains(r#"--conf "spark.openlineage.transport.type=http""#));
16751654
assert!(
@@ -1709,8 +1688,7 @@ spec:
17091688
sparkImage:
17101689
productVersion: 1.2.3
17111690
"#};
1712-
// Endpoint is supplied to prove it is ignored, not that it is simply missing.
1713-
let command = build_command_with_openlineage(yaml, Some("http://marquez:5000"));
1691+
let command = build_command_with_openlineage(yaml);
17141692

17151693
assert!(!command.contains("openlineage"));
17161694
assert!(!command.contains("OpenLineageSparkListener"));
@@ -1732,11 +1710,13 @@ spec:
17321710
mainApplicationFile: test.py
17331711
sparkImage:
17341712
productVersion: 1.2.3
1735-
openLineage: {}
1713+
openLineage:
1714+
host: marquez
1715+
port: 5000
17361716
sparkConf:
17371717
spark.extraListeners: com.example.CustomListener
17381718
"#};
1739-
let command = build_command_with_openlineage(yaml, Some("http://marquez:5000"));
1719+
let command = build_command_with_openlineage(yaml);
17401720

17411721
assert!(command.contains(&format!(
17421722
r#"--conf "spark.extraListeners=com.example.CustomListener,{OPENLINEAGE_LISTENER_CLASS}""#
@@ -1757,52 +1737,20 @@ spec:
17571737
mainApplicationFile: test.py
17581738
sparkImage:
17591739
productVersion: 1.2.3
1760-
openLineage: {}
1740+
openLineage:
1741+
host: marquez
1742+
port: 5000
17611743
sparkConf:
17621744
spark.openlineage.transport.url: http://custom:1234
17631745
spark.openlineage.namespace: custom-ns
17641746
"#};
1765-
let command = build_command_with_openlineage(yaml, Some("http://marquez:5000"));
1747+
let command = build_command_with_openlineage(yaml);
17661748

17671749
assert!(command.contains(r#"--conf "spark.openlineage.transport.url=http://custom:1234""#));
17681750
assert!(!command.contains("http://marquez:5000"));
17691751
assert!(command.contains(r#"--conf "spark.openlineage.namespace=custom-ns""#));
17701752
}
17711753

1772-
#[test]
1773-
fn test_openlineage_fails_fast_without_endpoint() {
1774-
// Enabled, but neither a resolved endpoint nor a sparkConf transport.url override.
1775-
let spark_application =
1776-
serde_yaml::from_str::<v1alpha1::SparkApplication>(OPENLINEAGE_ENABLED_APP).unwrap();
1777-
let result = spark_application.build_command(&None, &None, "test-image", None);
1778-
1779-
assert!(matches!(result, Err(Error::MissingOpenLineageEndpoint)));
1780-
}
1781-
1782-
#[test]
1783-
fn test_openlineage_endpoint_from_spark_conf_satisfies_fail_fast() {
1784-
// No resolved endpoint, but a sparkConf override → must NOT fail.
1785-
let yaml = indoc! {r#"
1786-
---
1787-
apiVersion: spark.stackable.tech/v1alpha1
1788-
kind: SparkApplication
1789-
metadata:
1790-
name: spark-examples
1791-
namespace: default
1792-
spec:
1793-
mode: cluster
1794-
mainApplicationFile: test.py
1795-
sparkImage:
1796-
productVersion: 1.2.3
1797-
openLineage: {}
1798-
sparkConf:
1799-
spark.openlineage.transport.url: http://custom:1234
1800-
"#};
1801-
let command = build_command_with_openlineage(yaml, None);
1802-
1803-
assert!(command.contains(r#"--conf "spark.openlineage.transport.url=http://custom:1234""#));
1804-
}
1805-
18061754
#[rstest]
18071755
#[case::explicit_app_name(
18081756
indoc! {r#"
@@ -1818,6 +1766,8 @@ spec:
18181766
sparkImage:
18191767
productVersion: 1.2.3
18201768
openLineage:
1769+
host: marquez
1770+
port: 5000
18211771
appName: explicit-name
18221772
sparkConf:
18231773
spark.app.name: spark-conf-name
@@ -1838,7 +1788,9 @@ spec:
18381788
mainApplicationFile: test.py
18391789
sparkImage:
18401790
productVersion: 1.2.3
1841-
openLineage: {}
1791+
openLineage:
1792+
host: marquez
1793+
port: 5000
18421794
sparkConf:
18431795
spark.app.name: spark-conf-name
18441796
"#},
@@ -1858,7 +1810,9 @@ spec:
18581810
mainApplicationFile: test.py
18591811
sparkImage:
18601812
productVersion: 1.2.3
1861-
openLineage: {}
1813+
openLineage:
1814+
host: marquez
1815+
port: 5000
18621816
"#},
18631817
"metadata-name",
18641818
true

0 commit comments

Comments
 (0)