From 8dd1a1feb7a7b8c731c2d6d0326e1d7a8889b72b Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 14:02:27 +0100 Subject: [PATCH 1/4] feat: Collect the containerdebug logs --- CHANGELOG.md | 7 + Cargo.nix | 7 +- .../spark-k8s/pages/usage-guide/logging.adoc | 8 + rust/operator-binary/Cargo.toml | 1 + .../src/connect/controller/build/executor.rs | 48 ++- .../src/connect/controller/build/server.rs | 24 +- rust/operator-binary/src/crd/constants.rs | 14 +- rust/operator-binary/src/crd/mod.rs | 29 +- .../controller/build/resource/statefulset.rs | 23 +- .../src/product_logging/vector-test.yaml | 279 ++++++++++++++++++ .../src/product_logging/vector.yaml | 87 ++++++ .../src/spark_k8s_controller/build/pod.rs | 4 +- .../spark-vector-aggregator-values.yaml.j2 | 190 ++++++++++-- .../pyspark-pi-driver-pod-template-data.json | 2 +- ...pyspark-pi-executor-pod-template-data.json | 2 +- .../pyspark-pi-job-template-spec.json | 14 +- .../fixtures/spark-connect-server-data.json | 2 +- 17 files changed, 645 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dceab257..4bde55f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ All notable changes to this project will be documented in this file. that their Pods are automatically rolled when a mounted ConfigMap or Secret changes ([#754]). - Add `/ready` endpoint to the operator Deployment, which reports the CRD installation status ([#779]). - Add support for Spark 4.x and HBase connectors ([#797]). +- The logs of the `containerdebug` process are now collected by the Vector agent and forwarded to + the Vector aggregator ([#798]). ### Changed @@ -88,6 +90,10 @@ All notable changes to this project will be documented in this file. executors. With the default `Always`, these containers pull on every start instead of using the node's image cache ([#764]). - The submit Job and the history server no longer print S3 credentials to the container log ([#796]). +- The `containerdebug` process writes its log file again. The operator set + `CONTAINERDEBUG_LOG_DIRECTORY`, which containerdebug stopped reading in its version 0.2.0 when it + moved to `stackable-telemetry`; the directory is now set via `FILE_LOG_DIRECTORY`. The file is + rotated hourly and at most two files are kept, because rotation is opt-in ([#798]). [#721]: https://github.com/stackabletech/spark-k8s-operator/pull/721 [#726]: https://github.com/stackabletech/spark-k8s-operator/pull/726 @@ -116,6 +122,7 @@ All notable changes to this project will be documented in this file. [#795]: https://github.com/stackabletech/spark-k8s-operator/pull/795 [#796]: https://github.com/stackabletech/spark-k8s-operator/pull/796 [#797]: https://github.com/stackabletech/spark-k8s-operator/pull/797 +[#798]: https://github.com/stackabletech/spark-k8s-operator/pull/798 ## [26.7.0] - 2026-07-21 diff --git a/Cargo.nix b/Cargo.nix index e9bfd7c7..f533fa57 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -10931,7 +10931,7 @@ rec { "time" = [ "stackable-shared/time" ]; "webhook" = [ "dep:stackable-webhook" ]; }; - resolvedDefaultFeatures = [ "crds" "default" "webhook" ]; + resolvedDefaultFeatures = [ "crds" "default" "test-support" "webhook" ]; }; "stackable-operator-derive" = rec { crateName = "stackable-operator-derive"; @@ -11155,6 +11155,11 @@ rec { name = "rstest"; packageId = "rstest"; } + { + name = "stackable-operator"; + packageId = "stackable-operator"; + features = [ "webhook" "test-support" ]; + } ]; }; diff --git a/docs/modules/spark-k8s/pages/usage-guide/logging.adoc b/docs/modules/spark-k8s/pages/usage-guide/logging.adoc index 07edf9e5..42996c5a 100644 --- a/docs/modules/spark-k8s/pages/usage-guide/logging.adoc +++ b/docs/modules/spark-k8s/pages/usage-guide/logging.adoc @@ -8,6 +8,14 @@ It is the user's responsibility to install and configure the vector aggregator, NOTE: Only logs produced by the application's driver and executors are collected. Logs produced by `spark-submit` are discarded. +NOTE: The logs of the xref:concepts:observability/containerdebug.adoc[`containerdebug` process] are collected as well, from the driver and executor Pods and from the history server. +They are labelled `container: containerdebug`, although no such Kubernetes container exists. +The process runs in the background of the `spark` and `spark-history` containers. +It also runs in the `spark-submit` Pod, whose logs are discarded as described above. +The label comes from `/stackable/log/containerdebug`, the directory which the operator sets with the `FILE_LOG_DIRECTORY` environment variable and from which the Vector agent derives the container name. +Overriding `FILE_LOG_DIRECTORY` with `envOverrides` therefore changes how these logs are aggregated. +The agent only collects files one level below `/stackable/log`, so a sibling directory such as `/stackable/log/spark` relabels the events instead of dropping them, while any deeper or shallower path stops the aggregation. + == History server The following snippet shows how to configure log aggregation for the history server: diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 882a53e0..e8efd211 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -29,6 +29,7 @@ yaml_serde.workspace = true [dev-dependencies] indoc.workspace = true rstest.workspace = true +stackable-operator = { workspace = true, features = ["test-support"] } [build-dependencies] built.workspace = true diff --git a/rust/operator-binary/src/connect/controller/build/executor.rs b/rust/operator-binary/src/connect/controller/build/executor.rs index 7042ba5f..17312392 100644 --- a/rust/operator-binary/src/connect/controller/build/executor.rs +++ b/rust/operator-binary/src/connect/controller/build/executor.rs @@ -38,10 +38,10 @@ use crate::{ s3, }, crd::constants::{ - CONTAINERDEBUG_LOG_DIRECTORY, JVM_SECURITY_PROPERTIES_FILE, LOG4J2_CONFIG_FILE, - MAX_SPARK_LOG_FILES_SIZE, METRICS_PROPERTIES_FILE, POD_TEMPLATE_FILE, - VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, - VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, + JVM_SECURITY_PROPERTIES_FILE, LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, + METRICS_PROPERTIES_FILE, POD_TEMPLATE_FILE, VOLUME_MOUNT_NAME_CONFIG, + VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, + VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, }, product_logging, }; @@ -93,7 +93,10 @@ pub fn executor_pod_template( let config = &validated.executor_config; let resolved_product_image = &validated.resolved_product_image; let resolved_s3 = &validated.cluster_config.resolved_s3; - let container_env = executor_env(&validated.executor_overrides.env_overrides); + // The operator sets no environment variables on the executor container, so the user's + // `envOverrides` are all there is. `FILE_LOG_DIRECTORY` in particular would have no effect, + // because `containerdebug` is not started in the executor containers. + let container_env = validated.executor_overrides.env_overrides.clone(); let (s3_volumes, s3_volume_mounts) = resolved_s3 .volumes_and_mounts() @@ -101,12 +104,17 @@ pub fn executor_pod_template( let mut container = new_container_builder(SparkConnectContainer::Spark.name()); container - .add_env_vars(container_env) .add_volume_mount(VOLUME_MOUNT_NAME_CONFIG.as_ref(), VOLUME_MOUNT_PATH_CONFIG) .expect("The mount paths are statically defined and there should be no duplicates.") .add_volume_mount(VOLUME_MOUNT_NAME_LOG.as_ref(), VOLUME_MOUNT_PATH_LOG) .expect("The mount paths are statically defined and there should be no duplicates."); + // `add_env_vars` creates the `env` list even for an empty set, which would show up as + // `env: []` in the rendered pod template. + if container_env.iter().next().is_some() { + container.add_env_vars(container_env); + } + let metadata = ObjectMetaBuilder::new() .with_labels(recommended_labels_for_role_resources( validated, @@ -213,19 +221,6 @@ pub fn executor_pod_template( Ok(result) } -/// The environment variables of the executor container. -/// -/// The user's `envOverrides` are merged in last so that they override any operator-set -/// environment variable. -fn executor_env(env_overrides: &EnvVarSet) -> EnvVarSet { - EnvVarSet::new() - .with_value( - &CONTAINERDEBUG_LOG_DIRECTORY, - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), - ) - .merge(env_overrides.clone()) -} - pub(crate) fn executor_properties( validated: &ValidatedSparkConnectServer, ) -> Result>, Error> { @@ -384,6 +379,7 @@ mod tests { use stackable_operator::{ commons::product_image_selection::PullPolicy, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::apis::meta::v1::ObjectMeta}, + v2::builder::pod::container::EnvVarName, }; use super::*; @@ -430,15 +426,13 @@ mod tests { ); } - /// `envOverrides` must be applied after all operator-set environment variables, so a user - /// override replaces the operator-set value instead of duplicating it or being ignored. + /// The user's `envOverrides` must reach the executor container exactly once. The operator + /// sets no environment variables there, so there is no operator-set value to replace. #[test] - fn env_overrides_override_operator_set_env_vars() { + fn env_overrides_are_applied_to_the_executor_container() { let mut validated = minimal_validated_cluster(); validated.executor_overrides.env_overrides = EnvVarSet::new().with_value( - &"CONTAINERDEBUG_LOG_DIRECTORY" - .parse() - .expect("valid env var name"), + &EnvVarName::from_str_unsafe("FILE_LOG_DIRECTORY"), "/custom/log/dir", ); @@ -466,10 +460,10 @@ mod tests { let matching: Vec<&EnvVar> = env .iter() - .filter(|env_var| env_var.name == "CONTAINERDEBUG_LOG_DIRECTORY") + .filter(|env_var| env_var.name == "FILE_LOG_DIRECTORY") .collect(); - // The override must replace the operator-set value, not duplicate it. + // Exactly once, so a future operator-set value cannot end up duplicated next to it. assert_eq!(matching.len(), 1); assert_eq!(matching[0].value.as_deref(), Some("/custom/log/dir")); } diff --git a/rust/operator-binary/src/connect/controller/build/server.rs b/rust/operator-binary/src/connect/controller/build/server.rs index b2667899..8c21b97f 100644 --- a/rust/operator-binary/src/connect/controller/build/server.rs +++ b/rust/operator-binary/src/connect/controller/build/server.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, str::FromStr}; +use const_format::concatcp; use indoc::formatdoc; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -60,12 +61,12 @@ use crate::{ }, crd::{ constants::{ - CONTAINERDEBUG_LOG_DIRECTORY, JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, - LISTENER_VOLUME_NAME, LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, - METRICS_PROPERTIES_FILE, POD_TEMPLATE_FILE, SPARK_DEFAULTS_FILE_NAME, - SPARK_NO_DAEMONIZE, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, - VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, - VOLUME_MOUNT_PATH_LOG_CONFIG, + FILE_LOG_DIRECTORY, FILE_LOG_MAX_FILES, FILE_LOG_ROTATION_PERIOD, + JVM_SECURITY_PROPERTIES_FILE, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, + LOG4J2_CONFIG_FILE, MAX_SPARK_LOG_FILES_SIZE, METRICS_PROPERTIES_FILE, + POD_TEMPLATE_FILE, SPARK_DEFAULTS_FILE_NAME, SPARK_NO_DAEMONIZE, + VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, + VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, }, listener_ext, }, @@ -207,6 +208,9 @@ pub(crate) fn build_stateful_set( VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG.as_ref()) .with_empty_dir( None::, + // Only the Spark log files are budgeted. The `containerdebug` files in this + // volume are rotated by time, not by size, so they have no budget; raising + // `FILE_LOG_LEVEL` via `envOverrides` can therefore evict the Pod. Some(calculate_log_volume_size_limit(&[MAX_SPARK_LOG_FILES_SIZE])), ) .build(), @@ -363,6 +367,7 @@ pub(crate) fn build_stateful_set( #[allow(clippy::result_large_err)] pub(crate) fn command_args(user_args: &[String]) -> Vec { let mut command = formatdoc! { " + mkdir --parents \"$FILE_LOG_DIRECTORY\" containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop & cp {VOLUME_MOUNT_PATH_CONFIG}/{SPARK_DEFAULTS_FILE_NAME} /tmp/spark.properties @@ -387,9 +392,12 @@ pub(crate) fn command_args(user_args: &[String]) -> Vec { fn env(env_overrides: &EnvVarSet) -> EnvVarSet { EnvVarSet::new() .with_value( - &CONTAINERDEBUG_LOG_DIRECTORY, - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), + &FILE_LOG_DIRECTORY, + concatcp!(VOLUME_MOUNT_PATH_LOG, "/containerdebug"), ) + // Rotation is opt-in, so without these the file would grow unbounded. + .with_value(&FILE_LOG_ROTATION_PERIOD, "hourly") + .with_value(&FILE_LOG_MAX_FILES, "2") .with_value(&SPARK_NO_DAEMONIZE, "true") .merge(env_overrides.clone()) } diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index f667d74b..ee99b5a4 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -39,9 +39,13 @@ pub const VOLUME_MOUNT_PATH_LOG_CONFIG: &str = "/stackable/log_config"; constant!(pub VOLUME_MOUNT_NAME_LOG: VolumeName = "log"); pub const VOLUME_MOUNT_PATH_LOG: &str = "/stackable/log"; -// Tells the `containerdebug` process running in the background of a product container where to -// write its tracing information. -constant!(pub CONTAINERDEBUG_LOG_DIRECTORY: EnvVarName = "CONTAINERDEBUG_LOG_DIRECTORY"); +// Let the `containerdebug` process write its tracing information to a file. These are the generic +// `stackable-telemetry` variables and apply to the whole product container, so any other +// telemetry-based process in it would log into the same directory and the Vector agent would label +// its events with `container: containerdebug`. +constant!(pub FILE_LOG_DIRECTORY: EnvVarName = "FILE_LOG_DIRECTORY"); +constant!(pub FILE_LOG_MAX_FILES: EnvVarName = "FILE_LOG_MAX_FILES"); +constant!(pub FILE_LOG_ROTATION_PERIOD: EnvVarName = "FILE_LOG_ROTATION_PERIOD"); // Prevents the history/connect server from detaching itself from the start script, which would // terminate the Pod immediately. constant!(pub SPARK_NO_DAEMONIZE: EnvVarName = "SPARK_NO_DAEMONIZE"); @@ -193,7 +197,9 @@ mod tests { #[test] fn test_constants() { // Test that dereferencing the constants does not panic. - let _ = *CONTAINERDEBUG_LOG_DIRECTORY; + let _ = *FILE_LOG_DIRECTORY; + let _ = *FILE_LOG_MAX_FILES; + let _ = *FILE_LOG_ROTATION_PERIOD; let _ = *LISTENER_VOLUME_NAME; let _ = *SPARK_NO_DAEMONIZE; let _ = *VOLUME_MOUNT_NAME_CONFIG; diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 15f33085..41196d68 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -6,6 +6,7 @@ use std::{ str::FromStr, }; +use const_format::concatcp; use constants::*; use history::LogFileDirectorySpec; use logdir::ResolvedLogDir; @@ -669,11 +670,11 @@ impl v1alpha1::SparkApplication { format!( "--conf spark.driver.defaultJavaOptions=-Dlog4j.configurationFile={VOLUME_MOUNT_PATH_LOG_CONFIG}/{LOG4J2_CONFIG_FILE}" ), - format!("--conf spark.driver.extraClassPath=/stackable/spark/extra-jars/*"), + "--conf spark.driver.extraClassPath=/stackable/spark/extra-jars/*".to_owned(), format!( "--conf spark.executor.defaultJavaOptions=-Dlog4j.configurationFile={VOLUME_MOUNT_PATH_LOG_CONFIG}/{LOG4J2_CONFIG_FILE}" ), - format!("--conf spark.executor.extraClassPath=/stackable/spark/extra-jars/*"), + "--conf spark.executor.extraClassPath=/stackable/spark/extra-jars/*".to_owned(), ]; // See https://spark.apache.org/docs/latest/running-on-kubernetes.html#dependency-management @@ -859,7 +860,7 @@ impl v1alpha1::SparkApplication { self.spec .main_class .clone() - .map(|mc| format! {"--class {mc}"}), + .map(|mc| format!("--class {mc}")), ); let artifact = self.application_artifact(); @@ -868,6 +869,10 @@ impl v1alpha1::SparkApplication { submit_cmd.extend(self.spec.args.clone()); Ok(vec![ + // The log directory is created here, because `FILE_LOG_MAX_FILES` makes the telemetry + // library prune old log files before it creates the first one, which complains about + // the directory not existing yet. + "mkdir --parents \"$FILE_LOG_DIRECTORY\"".to_owned(), format!( "containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &" ), @@ -904,20 +909,26 @@ impl v1alpha1::SparkApplication { // These env variables enable the `containerdebug` process in driver and executor pods. // More precisely, this process runs in the background of every `spark` container. - // - `CONTAINERDEBUG_LOG_DIRECTORY` - is the location where tracing information from the process - // is written. This directory is created by the process itself. + // - `FILE_LOG_DIRECTORY` - is the location where tracing information from the process is + // written. It must be exactly one level below the log directory, because the Vector agent + // derives the container name from that path segment. The rotation is opt-in, so without + // the other two variables the file would grow unbounded. // - `_STACKABLE_PRE_HOOK` - is evaluated by the entrypoint script (run-spark.sh) in the Spark images // before the actual JVM process is started. The result of this evaluation is that the - // `containerdebug` process is executed in the background. + // `containerdebug` process is executed in the background. The log directory is created + // there, because `FILE_LOG_MAX_FILES` makes the telemetry library prune old log files + // before it creates the first one, which complains about the directory not existing yet. env = env .with_value( - &CONTAINERDEBUG_LOG_DIRECTORY, - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), + &FILE_LOG_DIRECTORY, + concatcp!(VOLUME_MOUNT_PATH_LOG, "/containerdebug"), ) + .with_value(&FILE_LOG_ROTATION_PERIOD, "hourly") + .with_value(&FILE_LOG_MAX_FILES, "2") .with_value( &STACKABLE_PRE_HOOK, format!( - "containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &" + "mkdir --parents \"$FILE_LOG_DIRECTORY\"; containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &" ), ); diff --git a/rust/operator-binary/src/history/controller/build/resource/statefulset.rs b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs index 57285dcf..a6b22ab8 100644 --- a/rust/operator-binary/src/history/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use const_format::concatcp; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ @@ -43,11 +44,12 @@ constant!(SPARK_HISTORY_OPTS: EnvVarName = "SPARK_HISTORY_OPTS"); use crate::{ crd::{ constants::{ - ACCESS_KEY_ID, CONTAINERDEBUG_LOG_DIRECTORY, HISTORY_UI_PORT, LISTENER_VOLUME_DIR, - LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, METRICS_PORT, SECRET_ACCESS_KEY, - SPARK_DEFAULTS_FILE_NAME, SPARK_NO_DAEMONIZE, STACKABLE_TRUST_STORE, - VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, VOLUME_MOUNT_NAME_LOG_CONFIG, - VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, VOLUME_MOUNT_PATH_LOG_CONFIG, + ACCESS_KEY_ID, FILE_LOG_DIRECTORY, FILE_LOG_MAX_FILES, FILE_LOG_ROTATION_PERIOD, + HISTORY_UI_PORT, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, MAX_SPARK_LOG_FILES_SIZE, + METRICS_PORT, SECRET_ACCESS_KEY, SPARK_DEFAULTS_FILE_NAME, SPARK_NO_DAEMONIZE, + STACKABLE_TRUST_STORE, VOLUME_MOUNT_NAME_CONFIG, VOLUME_MOUNT_NAME_LOG, + VOLUME_MOUNT_NAME_LOG_CONFIG, VOLUME_MOUNT_PATH_CONFIG, VOLUME_MOUNT_PATH_LOG, + VOLUME_MOUNT_PATH_LOG_CONFIG, }, history::SparkHistoryServerContainer, logdir::ResolvedLogDir, @@ -151,6 +153,9 @@ pub(crate) fn build_stateful_set( VolumeBuilder::new(VOLUME_MOUNT_NAME_LOG.as_ref()) .with_empty_dir( None::, + // Only the Spark log files are budgeted. The `containerdebug` files in this + // volume are rotated by time, not by size, so they have no budget; raising + // `FILE_LOG_LEVEL` via `envOverrides` can therefore evict the Pod. Some(calculate_log_volume_size_limit(&[MAX_SPARK_LOG_FILES_SIZE])), ) .build(), @@ -172,9 +177,12 @@ pub(crate) fn build_stateful_set( // overrides are merged in last so that they override any operator-set environment variable. let merged_env = EnvVarSet::new() .with_value( - &CONTAINERDEBUG_LOG_DIRECTORY, - format!("{VOLUME_MOUNT_PATH_LOG}/containerdebug"), + &FILE_LOG_DIRECTORY, + concatcp!(VOLUME_MOUNT_PATH_LOG, "/containerdebug"), ) + // Rotation is opt-in, so without these the file would grow unbounded. + .with_value(&FILE_LOG_ROTATION_PERIOD, "hourly") + .with_value(&FILE_LOG_MAX_FILES, "2") .with_value(&SPARK_NO_DAEMONIZE, "true") .with_value(&SPARK_DAEMON_CLASSPATH, "/stackable/spark/extra-jars/*") .with_value( @@ -292,6 +300,7 @@ fn command_args(logdir: &ResolvedLogDir) -> Vec { } command.extend(vec![ + "mkdir --parents \"$FILE_LOG_DIRECTORY\"".to_owned(), format!("containerdebug --output={VOLUME_MOUNT_PATH_LOG}/containerdebug-state.json --loop &"), format!("/stackable/spark/sbin/start-history-server.sh --properties-file {VOLUME_MOUNT_PATH_CONFIG}/{SPARK_DEFAULTS_FILE_NAME}"), ]); diff --git a/rust/operator-binary/src/product_logging/vector-test.yaml b/rust/operator-binary/src/product_logging/vector-test.yaml index e0df68e8..8b0e1888 100644 --- a/rust/operator-binary/src/product_logging/vector-test.yaml +++ b/rust/operator-binary/src/product_logging/vector-test.yaml @@ -102,6 +102,285 @@ tests: "timestamp": t'2025-10-02T09:27:28.582Z' } + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"message":"found disk","disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "found disk\n\ndisk.name=overlay", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test unparsable tracing-rs log + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{ unparsable' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "errors": [ + .errors[0] + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "{ unparsable", + "timestamp": .timestamp, + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The remainder of the message contains character offsets into + # the VRL source, so only the stable prefix is asserted. + assert!(starts_with!(.errors[0], "JSON not parsable: ")) + - name: Test tracing-rs log which is not a JSON object + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: 'false' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "errors": [ + "Parsed event is not a JSON object." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "false", + "timestamp": .timestamp, + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log with invalid fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"unparsable","level":"INVALID","fields":"none","target":null}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "errors": [ + .errors[0], + "Logger/target not found.", + "Level \"INVALID\" unknown, using \"INFO\" instead.", + "Fields are not an object." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "{\"timestamp\":\"unparsable\",\"level\":\"INVALID\",\"fields\":\"none\",\"target\":null}", + "timestamp": .timestamp, + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The remainder of the message contains character offsets into + # the VRL source, so only the stable prefix is asserted. + assert!(starts_with!(.errors[0], "Timestamp not parsable, using current time instead: ")) + - name: Test tracing-rs log without a timestamp + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"level":"INFO","fields":{"message":"found disk"},"target":"containerdebug::system_information::disk"}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "errors": [ + "Timestamp not found, using current time instead." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "found disk", + "timestamp": .timestamp, + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The current time is used and not the timestamp which the file + # source attached to the event. The latter reaches the transform + # as a string, whereas now() yields a timestamp. + assert!(is_timestamp(.timestamp)) + - name: Test tracing-rs log without a message but with other fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "disk.name=overlay", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log with a message which is not a string + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"message":42,"disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "disk.name=overlay\nmessage=42", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log without any fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{},"target":"containerdebug::system_information::disk"}' + pod: spark-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "spark", + "container": "containerdebug", + "errors": [ + "Message not found." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "spark-node-default-0", + "role": "node", + "roleGroup": "default" + } + assert_eq!(expected_log_event, .) - name: Test Vector internal logs inputs: diff --git a/rust/operator-binary/src/product_logging/vector.yaml b/rust/operator-binary/src/product_logging/vector.yaml index 2fd3d923..f94d3915 100644 --- a/rust/operator-binary/src/product_logging/vector.yaml +++ b/rust/operator-binary/src/product_logging/vector.yaml @@ -25,6 +25,12 @@ sources: - ${LOG_DIR}/*/*.log4j2.xml line_delimiter: "\r\n" + # Reads the logs of the containerdebug process + files_tracing_rs: + type: file + include: + - ${LOG_DIR}/*/*.tracing-rs.json + transforms: processed_files_stdout: inputs: @@ -173,12 +179,93 @@ transforms: } } + # Transforms the tracing-rs JSON logs written by the containerdebug process + processed_files_tracing_rs: + inputs: + - files_tracing_rs + type: remap + source: | + raw_message = string!(.message) + + .timestamp = now() + .logger = "" + .level = "INFO" + .message = "" + .errors = [] + + parsed_event, err = parse_json(raw_message) + if err != null { + error = "JSON not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else if !is_object(parsed_event) { + error = "Parsed event is not a JSON object." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + event = object!(parsed_event) + + timestamp_string, err = string(event.timestamp) + if err == null { + parsed_timestamp, err = parse_timestamp(timestamp_string, "%+") + if err == null { + .timestamp = parsed_timestamp + } else { + .errors = push(.errors, "Timestamp not parsable, using current time instead: " + err) + } + } else { + .errors = push(.errors, "Timestamp not found, using current time instead.") + } + + .logger, err = string(event.target) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger/target not found.") + } + + level, err = string(event.level) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], upcase(level)) { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } else { + .level = upcase(level) + } + + fields, err = object(event.fields) + if err == null { + message, err = string(fields.message) + if err == null { + # The message is prepended to the other fields, so remove it from + # them. + del(fields.message) + } else { + # Keep an unusable message among the other fields instead of + # discarding its content. + message = "" + } + + other_fields = encode_key_value(fields, field_delimiter: "\n") + .message = join!(compact([message, other_fields]), "\n\n") + } else { + .errors = push(.errors, "Fields are not an object.") + # Fall back to the raw line, so that its content is not lost. + .message = raw_message + } + + if is_empty(.message) { + .errors = push(.errors, "Message not found.") + } + } + # Extends the processed files with the fields "container" and "file" extended_logs_files: inputs: - processed_files_stdout - processed_files_stderr - processed_files_log4j2 + - processed_files_tracing_rs type: remap source: | del(.source_type) diff --git a/rust/operator-binary/src/spark_k8s_controller/build/pod.rs b/rust/operator-binary/src/spark_k8s_controller/build/pod.rs index 08c1c114..431b5141 100644 --- a/rust/operator-binary/src/spark_k8s_controller/build/pod.rs +++ b/rust/operator-binary/src/spark_k8s_controller/build/pod.rs @@ -541,7 +541,7 @@ mod tests { productVersion: 1.2.3 driver: envOverrides: - CONTAINERDEBUG_LOG_DIRECTORY: /custom/log/dir + FILE_LOG_DIRECTORY: /custom/log/dir "#}; let spark_application: v1alpha1::SparkApplication = stackable_operator::utils::yaml_from_str_singleton_map(yaml) @@ -602,7 +602,7 @@ mod tests { let matching: Vec<&EnvVar> = env .iter() - .filter(|env_var| env_var.name == "CONTAINERDEBUG_LOG_DIRECTORY") + .filter(|env_var| env_var.name == "FILE_LOG_DIRECTORY") .collect(); // The override must replace the operator-set value, not duplicate it. diff --git a/tests/templates/kuttl/logging/spark-vector-aggregator-values.yaml.j2 b/tests/templates/kuttl/logging/spark-vector-aggregator-values.yaml.j2 index f18a3b5a..96773346 100644 --- a/tests/templates/kuttl/logging/spark-vector-aggregator-values.yaml.j2 +++ b/tests/templates/kuttl/logging/spark-vector-aggregator-values.yaml.j2 @@ -22,76 +22,117 @@ customConfig: transforms: validEvents: type: filter - inputs: [vector] + inputs: + - vector condition: is_null(.errors) # SparkHistoryServer spark-history + filteredSparkHistoryAutomaticLogConfigContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .pod == "spark-history-node-automatic-log-config-0" && + .container == "containerdebug" filteredSparkHistoryAutomaticLogConfigSparkHistory: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "spark-history-node-automatic-log-config-0" && .container == "spark-history" filteredSparkHistoryAutomaticLogConfigVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "spark-history-node-automatic-log-config-0" && .container == "vector" + filteredSparkHistoryCustomLogConfigContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .pod == "spark-history-node-custom-log-config-0" && + .container == "containerdebug" filteredSparkHistoryCustomLogConfigSparkHistory: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "spark-history-node-custom-log-config-0" && .container == "spark-history" filteredSparkHistoryCustomLogConfigVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "spark-history-node-custom-log-config-0" && .container == "vector" # SparkApplication spark-automatic-log-config + filteredSparkAutomaticLogConfigDriverContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "spark-automatic-log-config" && + ends_with(string!(.pod), "-driver") && + .container == "containerdebug" filteredSparkAutomaticLogConfigDriverSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "spark" filteredSparkAutomaticLogConfigDriverJob: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "job" filteredSparkAutomaticLogConfigDriverVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "vector" + filteredSparkAutomaticLogConfigExecutorContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "spark-automatic-log-config" && + ends_with(string!(.pod), "-exec-1") && + .container == "containerdebug" filteredSparkAutomaticLogConfigExecutorSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "spark" filteredSparkAutomaticLogConfigExecutorJob: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "job" filteredSparkAutomaticLogConfigExecutorVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && @@ -99,44 +140,66 @@ customConfig: # SparkApplication spark-custom-log-config + filteredSparkCustomLogConfigDriverContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "spark-custom-log-config" && + ends_with(string!(.pod), "-driver") && + .container == "containerdebug" filteredSparkCustomLogConfigDriverSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "spark" filteredSparkCustomLogConfigDriverJob: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "job" filteredSparkCustomLogConfigDriverVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "vector" + filteredSparkCustomLogConfigExecutorContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "spark-custom-log-config" && + ends_with(string!(.pod), "-exec-1") && + .container == "containerdebug" filteredSparkCustomLogConfigExecutorSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "spark" filteredSparkCustomLogConfigExecutorJob: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "job" filteredSparkCustomLogConfigExecutorVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "spark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && @@ -144,44 +207,66 @@ customConfig: # SparkApplication pyspark-automatic-log-config + filteredPysparkAutomaticLogConfigDriverContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "pyspark-automatic-log-config" && + ends_with(string!(.pod), "-driver") && + .container == "containerdebug" filteredPysparkAutomaticLogConfigDriverSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "spark" filteredPysparkAutomaticLogConfigDriverRequirements: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "requirements" filteredPysparkAutomaticLogConfigDriverVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-driver") && .container == "vector" + filteredPysparkAutomaticLogConfigExecutorContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "pyspark-automatic-log-config" && + ends_with(string!(.pod), "-exec-1") && + .container == "containerdebug" filteredPysparkAutomaticLogConfigExecutorSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "spark" filteredPysparkAutomaticLogConfigExecutorRequirements: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "requirements" filteredPysparkAutomaticLogConfigExecutorVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-automatic-log-config" && ends_with(string!(.pod), "-exec-1") && @@ -189,44 +274,66 @@ customConfig: # SparkApplication pyspark-custom-log-config + filteredPysparkCustomLogConfigDriverContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "pyspark-custom-log-config" && + ends_with(string!(.pod), "-driver") && + .container == "containerdebug" filteredPysparkCustomLogConfigDriverSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "spark" filteredPysparkCustomLogConfigDriverRequirements: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "requirements" filteredPysparkCustomLogConfigDriverVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-driver") && .container == "vector" + filteredPysparkCustomLogConfigExecutorContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .cluster == "pyspark-custom-log-config" && + ends_with(string!(.pod), "-exec-1") && + .container == "containerdebug" filteredPysparkCustomLogConfigExecutorSpark: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "spark" filteredPysparkCustomLogConfigExecutorRequirements: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && .container == "requirements" filteredPysparkCustomLogConfigExecutorVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .cluster == "pyspark-custom-log-config" && ends_with(string!(.pod), "-exec-1") && @@ -236,7 +343,8 @@ customConfig: filteredInvalidEvents: type: filter - inputs: [vector] + inputs: + - vector condition: |- .timestamp == from_unix_timestamp!(0) || is_null(.level) || @@ -244,11 +352,29 @@ customConfig: is_null(.message) sinks: test: - inputs: [filtered*] + inputs: + - filtered* type: blackhole + # Shows all events received from the agents. This sink is intentional and + # not a debugging leftover: if a "filtered*" transform gathers no event, + # then the assertion only reports a count, whereas this log shows which + # pod and container names actually arrived. A failure in CI cannot be + # reproduced by enabling this afterwards, so it is kept enabled at the + # price of the log volume. + stdout: + inputs: + - vector + type: console + encoding: + codec: json + buffer: + # Avoid back pressure from a stalling stdout. The test should not + # fail because of a sink which only aids debugging. + when_full: drop_newest {% if lookup('env', 'VECTOR_AGGREGATOR') %} aggregator: - inputs: [vector] + inputs: + - vector type: vector routing: endpoints: diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json index c8664e09..7b80a4f7 100644 --- a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-driver-pod-template-data.json @@ -2,5 +2,5 @@ "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", "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", "spark-env.sh": "", - "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/version: 4.2.0-stackable0.0.0-dev\n prometheus.io/scrape: 'true'\n stackable.tech/vendor: Stackable\n name: spark\n ownerReferences:\n - apiVersion: spark.stackable.tech/v1alpha1\n kind: SparkApplication\n name: pyspark-pi\n uid: __UID__\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 & . /stackable/spark/conf/spark-env.sh; export SPARK_ENV_LOADED=1\n image: oci.stackable.tech/sdp/spark-k8s:4.2.0-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/spark/conf\n name: config\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 runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\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" + "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/version: 4.2.0-stackable0.0.0-dev\n prometheus.io/scrape: 'true'\n stackable.tech/vendor: Stackable\n name: spark\n ownerReferences:\n - apiVersion: spark.stackable.tech/v1alpha1\n kind: SparkApplication\n name: pyspark-pi\n uid: __UID__\nspec:\n affinity: {}\n containers:\n - env:\n - name: FILE_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: FILE_LOG_MAX_FILES\n value: '2'\n - name: FILE_LOG_ROTATION_PERIOD\n value: hourly\n - name: _STACKABLE_PRE_HOOK\n value: mkdir --parents \"$FILE_LOG_DIRECTORY\"; containerdebug --output=/stackable/log/containerdebug-state.json --loop & . /stackable/spark/conf/spark-env.sh; export SPARK_ENV_LOADED=1\n image: oci.stackable.tech/sdp/spark-k8s:4.2.0-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/spark/conf\n name: config\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 runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\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" } diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json index 0a6347f5..d1579691 100644 --- a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-executor-pod-template-data.json @@ -2,5 +2,5 @@ "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", "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", "spark-env.sh": "", - "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/version: 4.2.0-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 & . /stackable/spark/conf/spark-env.sh; export SPARK_ENV_LOADED=1\n image: oci.stackable.tech/sdp/spark-k8s:4.2.0-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/spark/conf\n name: config\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 runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\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" + "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/version: 4.2.0-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\n name: spark\nspec:\n affinity: {}\n containers:\n - env:\n - name: FILE_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n - name: FILE_LOG_MAX_FILES\n value: '2'\n - name: FILE_LOG_ROTATION_PERIOD\n value: hourly\n - name: _STACKABLE_PRE_HOOK\n value: mkdir --parents \"$FILE_LOG_DIRECTORY\"; containerdebug --output=/stackable/log/containerdebug-state.json --loop & . /stackable/spark/conf/spark-env.sh; export SPARK_ENV_LOADED=1\n image: oci.stackable.tech/sdp/spark-k8s:4.2.0-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/spark/conf\n name: config\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 runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\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" } diff --git a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json index f0611097..d79ecc73 100644 --- a/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json +++ b/tests/templates/kuttl/product-config-compat/fixtures/pyspark-pi-job-template-spec.json @@ -3,7 +3,7 @@ "containers": [ { "args": [ - "containerdebug --output=/stackable/log/containerdebug-state.json --loop &\n\nset +x\necho 'Running \"spark-submit [...]\", which is not shown as it can contain credentials'\n/stackable/spark/bin/spark-submit --verbose --master k8s://https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS} --deploy-mode cluster --name pyspark-pi --conf spark.kubernetes.driver.podTemplateFile=/stackable/spark/driver-pod-templates/template.yaml --conf spark.kubernetes.executor.podTemplateFile=/stackable/spark/executor-pod-templates/template.yaml --conf spark.kubernetes.driver.podTemplateContainerName=spark --conf spark.kubernetes.executor.podTemplateContainerName=spark --conf spark.kubernetes.namespace=__NAMESPACE__ --conf spark.kubernetes.driver.container.image=oci.stackable.tech/sdp/spark-k8s:4.2.0-stackable0.0.0-dev --conf spark.kubernetes.executor.container.image=oci.stackable.tech/sdp/spark-k8s:4.2.0-stackable0.0.0-dev --conf spark.driver.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.driver.extraClassPath=/stackable/spark/extra-jars/* --conf spark.executor.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.executor.extraClassPath=/stackable/spark/extra-jars/* --conf spark.driver.extraJavaOptions=\"-Djava.security.properties=/stackable/spark/conf/security.properties\" --conf spark.executor.extraJavaOptions=\"-Djava.security.properties=/stackable/spark/conf/security.properties\" --conf spark.metrics.conf.\\*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet --conf spark.metrics.conf.\\*.sink.prometheusServlet.path=/metrics/prometheus --conf spark.ui.prometheus.enabled=true --conf spark.sql.streaming.metricsEnabled=true --conf \"spark.driver.cores=2\" --conf \"spark.driver.memory=640m\" --conf \"spark.executor.cores=2\" --conf \"spark.executor.instances=1\" --conf \"spark.executor.memory=640m\" --conf \"spark.kubernetes.container.image.pullPolicy=IfNotPresent\" --conf \"spark.kubernetes.driver.limit.cores=2\" --conf \"spark.kubernetes.driver.request.cores=1\" --conf \"spark.kubernetes.executor.limit.cores=2\" --conf \"spark.kubernetes.executor.request.cores=1\" --conf \"spark.kubernetes.memoryOverheadFactor=0.0\" local:///stackable/spark/examples/src/main/python/pi.py" + "mkdir --parents \"$FILE_LOG_DIRECTORY\"\ncontainerdebug --output=/stackable/log/containerdebug-state.json --loop &\n\nset +x\necho 'Running \"spark-submit [...]\", which is not shown as it can contain credentials'\n/stackable/spark/bin/spark-submit --verbose --master k8s://https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS} --deploy-mode cluster --name pyspark-pi --conf spark.kubernetes.driver.podTemplateFile=/stackable/spark/driver-pod-templates/template.yaml --conf spark.kubernetes.executor.podTemplateFile=/stackable/spark/executor-pod-templates/template.yaml --conf spark.kubernetes.driver.podTemplateContainerName=spark --conf spark.kubernetes.executor.podTemplateContainerName=spark --conf spark.kubernetes.namespace=__NAMESPACE__ --conf spark.kubernetes.driver.container.image=oci.stackable.tech/sdp/spark-k8s:4.2.0-stackable0.0.0-dev --conf spark.kubernetes.executor.container.image=oci.stackable.tech/sdp/spark-k8s:4.2.0-stackable0.0.0-dev --conf spark.driver.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.driver.extraClassPath=/stackable/spark/extra-jars/* --conf spark.executor.defaultJavaOptions=-Dlog4j.configurationFile=/stackable/log_config/log4j2.properties --conf spark.executor.extraClassPath=/stackable/spark/extra-jars/* --conf spark.driver.extraJavaOptions=\"-Djava.security.properties=/stackable/spark/conf/security.properties\" --conf spark.executor.extraJavaOptions=\"-Djava.security.properties=/stackable/spark/conf/security.properties\" --conf spark.metrics.conf.\\*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet --conf spark.metrics.conf.\\*.sink.prometheusServlet.path=/metrics/prometheus --conf spark.ui.prometheus.enabled=true --conf spark.sql.streaming.metricsEnabled=true --conf \"spark.driver.cores=2\" --conf \"spark.driver.memory=640m\" --conf \"spark.executor.cores=2\" --conf \"spark.executor.instances=1\" --conf \"spark.executor.memory=640m\" --conf \"spark.kubernetes.container.image.pullPolicy=IfNotPresent\" --conf \"spark.kubernetes.driver.limit.cores=2\" --conf \"spark.kubernetes.driver.request.cores=1\" --conf \"spark.kubernetes.executor.limit.cores=2\" --conf \"spark.kubernetes.executor.request.cores=1\" --conf \"spark.kubernetes.memoryOverheadFactor=0.0\" local:///stackable/spark/examples/src/main/python/pi.py" ], "command": [ "/bin/bash", @@ -14,9 +14,17 @@ ], "env": [ { - "name": "CONTAINERDEBUG_LOG_DIRECTORY", + "name": "FILE_LOG_DIRECTORY", "value": "/stackable/log/containerdebug" }, + { + "name": "FILE_LOG_MAX_FILES", + "value": "2" + }, + { + "name": "FILE_LOG_ROTATION_PERIOD", + "value": "hourly" + }, { "name": "SPARK_CONF_DIR", "value": "/stackable/spark/conf" @@ -27,7 +35,7 @@ }, { "name": "_STACKABLE_PRE_HOOK", - "value": "containerdebug --output=/stackable/log/containerdebug-state.json --loop &" + "value": "mkdir --parents \"$FILE_LOG_DIRECTORY\"; containerdebug --output=/stackable/log/containerdebug-state.json --loop &" } ], "image": "oci.stackable.tech/sdp/spark-k8s:4.2.0-stackable0.0.0-dev", diff --git a/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json index 21556c45..613f6609 100644 --- a/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json +++ b/tests/templates/kuttl/product-config-compat/fixtures/spark-connect-server-data.json @@ -2,5 +2,5 @@ "metrics.properties": "*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet\n*.sink.prometheusServlet.path=/metrics/prometheus\n", "security.properties": "networkaddress.cache.negative.ttl=0\nnetworkaddress.cache.ttl=30\n", "spark-defaults.conf": "spark.driver.cores=3\nspark.driver.defaultJavaOptions=-Djava.security.properties\\=/stackable/spark/conf/security.properties\\ -Dlog4j.configurationFile\\=/stackable/log_config/log4j2.properties\\ -Dmy.custom.jvm.arg\\=customValue\nspark.driver.extraClassPath=/stackable/spark/extra-jars/*\\:/stackable/spark/connect/spark-connect-4.2.0.jar\nspark.driver.host=spark-connect-server-headless\nspark.executor.defaultJavaOptions=-Djava.security.properties\\=/stackable/spark/conf/security.properties\\ -Dlog4j.configurationFile\\=/stackable/log_config/log4j2.properties\nspark.executor.extraClassPath=/stackable/spark/extra-jars/*\\:/stackable/spark/connect/spark-connect-4.2.0.jar\nspark.executor.instances=3\nspark.executor.memory=1024M\nspark.executor.memoryOverhead=1m\nspark.kubernetes.authenticate.driver.serviceAccountName=spark-connect-serviceaccount\nspark.kubernetes.container.image.pullPolicy=IfNotPresent\nspark.kubernetes.driver.container.image=oci.stackable.tech/sdp/spark-k8s\\:4.2.0-stackable0.0.0-dev\nspark.kubernetes.driver.pod.name=${env\\:HOSTNAME}\nspark.kubernetes.executor.container.image=oci.stackable.tech/sdp/spark-k8s\\:4.2.0-stackable0.0.0-dev\nspark.kubernetes.executor.limit.cores=1\nspark.kubernetes.executor.podTemplateContainerName=spark\nspark.kubernetes.executor.podTemplateFile=/stackable/spark/conf/template.yaml\nspark.kubernetes.executor.request.cores=1\nspark.kubernetes.namespace=__NAMESPACE__\nspark.metrics.conf=/stackable/spark/conf/metrics.properties\nspark.ui.prometheus.enabled=false\n", - "template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/managed-by: spark.stackable.tech_connect\n app.kubernetes.io/name: spark-connect\n app.kubernetes.io/version: 4.2.0-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\nspec:\n affinity:\n podAntiAffinity:\n preferredDuringSchedulingIgnoredDuringExecution:\n - podAffinityTerm:\n labelSelector:\n matchLabels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/name: spark-connect\n topologyKey: kubernetes.io/hostname\n weight: 70\n containers:\n - env:\n - name: CONTAINERDEBUG_LOG_DIRECTORY\n value: /stackable/log/containerdebug\n name: spark\n volumeMounts:\n - mountPath: /stackable/spark/conf\n name: config\n - mountPath: /stackable/log\n name: log\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/truststore\n name: stackable-truststore\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\n volumes:\n - emptyDir:\n sizeLimit: 30Mi\n name: log\n - configMap:\n name: spark-connect-executor\n name: config\n - configMap:\n name: spark-connect-log-config\n name: log-config\n - emptyDir: {}\n name: stackable-truststore\n" + "template.yaml": "metadata:\n labels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/managed-by: spark.stackable.tech_connect\n app.kubernetes.io/name: spark-connect\n app.kubernetes.io/version: 4.2.0-stackable0.0.0-dev\n stackable.tech/vendor: Stackable\nspec:\n affinity:\n podAntiAffinity:\n preferredDuringSchedulingIgnoredDuringExecution:\n - podAffinityTerm:\n labelSelector:\n matchLabels:\n app.kubernetes.io/component: executor\n app.kubernetes.io/instance: spark-connect\n app.kubernetes.io/name: spark-connect\n topologyKey: kubernetes.io/hostname\n weight: 70\n containers:\n - name: spark\n volumeMounts:\n - mountPath: /stackable/spark/conf\n name: config\n - mountPath: /stackable/log\n name: log\n - mountPath: /stackable/log_config\n name: log-config\n - mountPath: /stackable/truststore\n name: stackable-truststore\n enableServiceLinks: false\n securityContext:\n fsGroup: 1000\n runAsNonRoot: true\n seccompProfile:\n type: RuntimeDefault\n volumes:\n - emptyDir:\n sizeLimit: 30Mi\n name: log\n - configMap:\n name: spark-connect-executor\n name: config\n - configMap:\n name: spark-connect-log-config\n name: log-config\n - emptyDir: {}\n name: stackable-truststore\n" } From ca0042158f580c1a05b0eb288bfb59a6fa2883a3 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 14:04:24 +0100 Subject: [PATCH 2/4] chore: Update changelog --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bde55f0..f3bd7293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ All notable changes to this project will be documented in this file. - Add `/ready` endpoint to the operator Deployment, which reports the CRD installation status ([#779]). - Add support for Spark 4.x and HBase connectors ([#797]). - The logs of the `containerdebug` process are now collected by the Vector agent and forwarded to - the Vector aggregator ([#798]). + the Vector aggregator ([#802]). ### Changed @@ -93,7 +93,7 @@ All notable changes to this project will be documented in this file. - The `containerdebug` process writes its log file again. The operator set `CONTAINERDEBUG_LOG_DIRECTORY`, which containerdebug stopped reading in its version 0.2.0 when it moved to `stackable-telemetry`; the directory is now set via `FILE_LOG_DIRECTORY`. The file is - rotated hourly and at most two files are kept, because rotation is opt-in ([#798]). + rotated hourly and at most two files are kept, because rotation is opt-in ([#802]). [#721]: https://github.com/stackabletech/spark-k8s-operator/pull/721 [#726]: https://github.com/stackabletech/spark-k8s-operator/pull/726 @@ -122,7 +122,7 @@ All notable changes to this project will be documented in this file. [#795]: https://github.com/stackabletech/spark-k8s-operator/pull/795 [#796]: https://github.com/stackabletech/spark-k8s-operator/pull/796 [#797]: https://github.com/stackabletech/spark-k8s-operator/pull/797 -[#798]: https://github.com/stackabletech/spark-k8s-operator/pull/798 +[#802]: https://github.com/stackabletech/spark-k8s-operator/pull/802 ## [26.7.0] - 2026-07-21 From 833d88a4430d53ea0c38d319dbacab816dc3ba62 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 14:27:21 +0100 Subject: [PATCH 3/4] chore: Downgrade serde-saphyr --- Cargo.lock | 6 +++--- Cargo.nix | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10805151..796807f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,12 +2868,12 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "2.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4dc2714fc5632417a6e979602d7044bad8f2880c63fc7217087180acec31b6" +checksum = "a253edfdea34032da21af84e16fcc94f4c7a6ba237f9f3bc2dbf43cea8535c44" dependencies = [ "annotate-snippets", - "base64 0.23.1", + "base64 0.22.1", "encoding_rs_io", "granit-parser", "nohash-hasher", diff --git a/Cargo.nix b/Cargo.nix index f533fa57..79a7aaaf 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -9824,10 +9824,10 @@ rec { }; "serde-saphyr" = rec { crateName = "serde-saphyr"; - version = "2.1.0"; + version = "2.0.0"; edition = "2024"; crateBin = []; - sha256 = "1diixjn80w882xrgqqqci3rdifj4f0nn0yg9lqbj8qy59xqw4k9z"; + sha256 = "0i2waflcwhxz5nyg7y9pl9mplk2gr7y1ckpq3ai2s0rlxbyyslx2"; libName = "serde_saphyr"; license = "MIT OR Apache-2.0"; dependencies = [ @@ -9838,7 +9838,7 @@ rec { } { name = "base64"; - packageId = "base64 0.23.1"; + packageId = "base64 0.22.1"; optional = true; } { From 659fa0d02a307b18bd8f321e7b1dc88ede3e9d10 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 17:31:09 +0100 Subject: [PATCH 4/4] chore: Improve code style --- rust/operator-binary/src/connect/controller/build/server.rs | 3 ++- rust/operator-binary/src/crd/history.rs | 3 +-- .../src/history/controller/build/resource/statefulset.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/connect/controller/build/server.rs b/rust/operator-binary/src/connect/controller/build/server.rs index 8c21b97f..fbf5458a 100644 --- a/rust/operator-binary/src/connect/controller/build/server.rs +++ b/rust/operator-binary/src/connect/controller/build/server.rs @@ -553,6 +553,7 @@ mod tests { use stackable_operator::{ commons::product_image_selection::PullPolicy, k8s_openapi::{api::core::v1::EnvVar, apimachinery::pkg::apis::meta::v1::ObjectMeta}, + v2::builder::pod::container::EnvVarName, }; use super::*; @@ -608,7 +609,7 @@ mod tests { fn env_overrides_override_operator_set_env_vars() { let mut validated = minimal_validated_cluster(); validated.server_overrides.env_overrides = EnvVarSet::new().with_value( - &"SPARK_NO_DAEMONIZE".parse().expect("valid env var name"), + &EnvVarName::from_str_unsafe("SPARK_NO_DAEMONIZE"), "overridden", ); diff --git a/rust/operator-binary/src/crd/history.rs b/rust/operator-binary/src/crd/history.rs index b6d167b0..c1154d76 100644 --- a/rust/operator-binary/src/crd/history.rs +++ b/rust/operator-binary/src/crd/history.rs @@ -317,7 +317,6 @@ fn default_listener_class() -> ListenerClassName { #[cfg(test)] mod test { - use std::str::FromStr; use indoc::indoc; use stackable_operator::{ @@ -420,7 +419,7 @@ mod test { assert_eq!( env_overrides - .get(&EnvVarName::from_str("TEST_SPARK_HIST_VAR").expect("valid env var name")) + .get(&EnvVarName::from_str_unsafe("TEST_SPARK_HIST_VAR")) .and_then(|env_var| env_var.value.clone()), Some("ROLEGROUP".to_string()) ); diff --git a/rust/operator-binary/src/history/controller/build/resource/statefulset.rs b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs index a6b22ab8..2b1475bb 100644 --- a/rust/operator-binary/src/history/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/history/controller/build/resource/statefulset.rs @@ -342,7 +342,7 @@ mod tests { .expect("the default role group exists") .config .env_overrides = EnvVarSet::new().with_value( - &EnvVarName::from_str("SPARK_NO_DAEMONIZE").expect("valid env var name"), + &EnvVarName::from_str_unsafe("SPARK_NO_DAEMONIZE"), "overridden", );