Skip to content

Commit fa148a6

Browse files
committed
feat: use static vector.toml, add test
1 parent a41fc34 commit fa148a6

4 files changed

Lines changed: 408 additions & 46 deletions

File tree

rust/operator-binary/src/controller/build/properties/logging.rs renamed to rust/operator-binary/src/controller/build/properties/product_logging/mod.rs

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
//! Builds the log4j and Vector logging configuration for the rolegroup `ConfigMap`.
1+
//! Builders for the logging-related files in the rolegroup `ConfigMap`: the per-container
2+
//! `*.log4j.properties` configs and the (static) Vector agent config (`vector.yaml`).
23
34
use std::{borrow::Cow, fmt::Display};
45

56
use stackable_operator::{
6-
kube::runtime::reflector::ObjectRef,
77
memory::{BinaryMultiple, MemoryQuantity},
88
product_logging::{
99
self,
1010
spec::{ContainerLogConfig, ContainerLogConfigChoice},
1111
},
12-
role_utils::RoleGroupRef,
13-
v2::types::operator::RoleGroupName,
12+
v2::product_logging::framework::STACKABLE_LOG_DIR,
1413
};
1514

16-
use crate::{
17-
controller::ValidatedCluster,
18-
crd::{AnyNodeConfig, DataNodeContainer, HdfsNodeRole, NameNodeContainer},
19-
};
15+
use crate::crd::{AnyNodeConfig, DataNodeContainer, NameNodeContainer};
2016

21-
pub const STACKABLE_LOG_DIR: &str = "/stackable/log";
2217
// We have a maximum of 4 continuous logging files for Namenodes. Datanodes and Journalnodes
2318
// require less.
2419
// - name node main container
@@ -60,6 +55,19 @@ const FORMAT_NAMENODES_LOG_FILE: &str = "format-namenodes.log4j.xml";
6055
const FORMAT_ZOOKEEPER_LOG_FILE: &str = "format-zookeeper.log4j.xml";
6156
const WAIT_FOR_NAMENODES_LOG_FILE: &str = "wait-for-namenodes.log4j.xml";
6257

58+
/// The vendored Vector agent configuration (`vector.yaml`).
59+
///
60+
/// It is static: per-rolegroup values (namespace, cluster, role, role group, log/data dirs and the
61+
/// aggregator address) are interpolated at runtime by Vector from injected environment variables.
62+
/// The accompanying `vector-test.yaml` exercises the VRL in this file; run it with
63+
/// `./test-vector.sh` (requires the `vector` binary).
64+
const VECTOR_CONFIG: &str = include_str!("vector.yaml");
65+
66+
/// Returns the content of the static Vector agent config (`vector.yaml`).
67+
pub fn vector_config_file_content() -> String {
68+
VECTOR_CONFIG.to_owned()
69+
}
70+
6371
/// Renders the `*.log4j.properties` files for every container of this role group that uses the
6472
/// operator's automatic logging configuration.
6573
///
@@ -151,42 +159,15 @@ fn add_log4j_config_if_automatic(
151159
}
152160
}
153161

154-
/// Renders the Vector agent config (`vector.yaml`).
155-
///
156-
/// Returns `None` when the Vector agent is disabled for this role group.
157-
pub fn build_vector_config(
158-
cluster: &ValidatedCluster,
159-
role: &HdfsNodeRole,
160-
role_group_name: &RoleGroupName,
161-
merged_config: &AnyNodeConfig,
162-
) -> Option<String> {
163-
if !merged_config.vector_logging_enabled() {
164-
return None;
165-
}
166-
167-
let vector_log_config = merged_config.vector_logging();
168-
let vector_log_config = if let ContainerLogConfig {
169-
choice: Some(ContainerLogConfigChoice::Automatic(log_config)),
170-
} = &*vector_log_config
171-
{
172-
Some(log_config)
173-
} else {
174-
None
175-
};
176-
177-
// TODO: The framework's `create_vector_config` still requires a `RoleGroupRef`. We build one
178-
// over the `ValidatedCluster` (not the raw cluster) purely to satisfy this API; it only reads
179-
// the cluster name/namespace and the role/role-group strings, so the output is unchanged.
180-
// Hive ships a static `vector.yaml` instead and avoids `RoleGroupRef` entirely - we should
181-
// follow once a static config is available for HDFS, which would drop this last usage.
182-
let rolegroup = RoleGroupRef {
183-
cluster: ObjectRef::<ValidatedCluster>::from_obj(cluster),
184-
role: role.to_string(),
185-
role_group: role_group_name.to_string(),
186-
};
162+
#[cfg(test)]
163+
mod tests {
164+
use super::*;
187165

188-
Some(product_logging::framework::create_vector_config(
189-
&rolegroup,
190-
vector_log_config,
191-
))
166+
#[test]
167+
fn test_vector_config_file_content() {
168+
let content = vector_config_file_content();
169+
assert!(!content.is_empty());
170+
// HDFS containers log via log4j to `*.log4j.xml`, so the `files_log4j` source matches them.
171+
assert!(content.contains("files_log4j"));
172+
}
192173
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env sh
2+
3+
DATA_DIR=/stackable/log/_vector-state \
4+
LOG_DIR=/stackable/log \
5+
NAMESPACE=default \
6+
CLUSTER_NAME=test-hdfs \
7+
ROLE_NAME=namenode \
8+
ROLE_GROUP_NAME=default \
9+
VECTOR_AGGREGATOR_ADDRESS=vector-aggregator \
10+
VECTOR_FILE_LOG_LEVEL=info \
11+
vector test vector.yaml vector-test.yaml
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Run tests with `./test-vector.sh`
2+
#
3+
# A downside of these test cases is that they compare the whole event and that the message can
4+
# contain source code positions in vector.yaml, e.g. "function call error for \"parse_xml\" at
5+
# (584:643)". Please adapt the tests if you change VRL code in vector.yaml.
6+
---
7+
tests:
8+
- name: Test stdout log entry
9+
inputs:
10+
- type: log
11+
insert_at: processed_files_stdout
12+
log_fields:
13+
file: /stackable/log/namenode/namenode.stdout.log
14+
message: Starting NameNode
15+
pod: test-hdfs-namenode-default-0
16+
source_type: file
17+
timestamp: 2025-10-02T09:27:28.582Z
18+
outputs:
19+
- extract_from: extended_logs
20+
conditions:
21+
- type: vrl
22+
source: |
23+
expected_log_event = {
24+
"cluster": "test-hdfs",
25+
"container": "namenode",
26+
"file": "namenode.stdout.log",
27+
"level": "INFO",
28+
"logger": "ROOT",
29+
"message": "Starting NameNode",
30+
"namespace": "default",
31+
"pod": "test-hdfs-namenode-default-0",
32+
"role": "namenode",
33+
"roleGroup": "default",
34+
"timestamp": "2025-10-02T09:27:28.582Z"
35+
}
36+
37+
assert_eq!(expected_log_event, .)
38+
- name: Test stderr log entry
39+
inputs:
40+
- type: log
41+
insert_at: processed_files_stderr
42+
log_fields:
43+
file: /stackable/log/namenode/namenode.stderr.log
44+
message: "Exception in thread \"main\""
45+
pod: test-hdfs-namenode-default-0
46+
source_type: file
47+
timestamp: 2025-10-02T09:27:28.582Z
48+
outputs:
49+
- extract_from: extended_logs
50+
conditions:
51+
- type: vrl
52+
source: |
53+
expected_log_event = {
54+
"cluster": "test-hdfs",
55+
"container": "namenode",
56+
"file": "namenode.stderr.log",
57+
"level": "ERROR",
58+
"logger": "ROOT",
59+
"message": "Exception in thread \"main\"",
60+
"namespace": "default",
61+
"pod": "test-hdfs-namenode-default-0",
62+
"role": "namenode",
63+
"roleGroup": "default",
64+
"timestamp": "2025-10-02T09:27:28.582Z"
65+
}
66+
67+
assert_eq!(expected_log_event, .)
68+
- name: Test log4j XML log entry without stacktrace
69+
inputs:
70+
- type: log
71+
insert_at: processed_files_log4j
72+
log_fields:
73+
file: /stackable/log/namenode/namenode.log4j.xml
74+
message: >
75+
<log4j:event logger="org.apache.hadoop.hdfs.server.namenode.NameNode"
76+
timestamp="1759397248582" level="INFO"
77+
thread="main"><log4j:message>STARTUP_MSG: Starting
78+
NameNode</log4j:message></log4j:event>
79+
pod: test-hdfs-namenode-default-0
80+
source_type: file
81+
timestamp: 2025-10-02T09:27:29.473487331Z
82+
outputs:
83+
- extract_from: extended_logs
84+
conditions:
85+
- type: vrl
86+
source: |
87+
expected_log_event = {
88+
"cluster": "test-hdfs",
89+
"container": "namenode",
90+
"file": "namenode.log4j.xml",
91+
"level": "INFO",
92+
"logger": "org.apache.hadoop.hdfs.server.namenode.NameNode",
93+
"message": "STARTUP_MSG: Starting NameNode",
94+
"namespace": "default",
95+
"pod": "test-hdfs-namenode-default-0",
96+
"role": "namenode",
97+
"roleGroup": "default",
98+
"timestamp": t'2025-10-02T09:27:28.582Z'
99+
}
100+
101+
assert_eq!(expected_log_event, .)
102+
- name: Test log4j XML log entry with stacktrace
103+
inputs:
104+
- type: log
105+
insert_at: processed_files_log4j
106+
log_fields:
107+
file: /stackable/log/namenode/namenode.log4j.xml
108+
message: >
109+
<log4j:event logger="org.apache.hadoop.hdfs.server.datanode.DataNode"
110+
timestamp="1759397248582" level="WARN"
111+
thread="main"><log4j:message>IOException in
112+
BlockReceiver</log4j:message><log4j:throwable>java.io.IOException: Connection
113+
reset by peer</log4j:throwable></log4j:event>
114+
pod: test-hdfs-namenode-default-0
115+
source_type: file
116+
timestamp: 2025-10-02T09:27:29.473487331Z
117+
outputs:
118+
- extract_from: extended_logs
119+
conditions:
120+
- type: vrl
121+
source: |
122+
expected_log_event = {
123+
"cluster": "test-hdfs",
124+
"container": "namenode",
125+
"file": "namenode.log4j.xml",
126+
"level": "WARN",
127+
"logger": "org.apache.hadoop.hdfs.server.datanode.DataNode",
128+
"message": "IOException in BlockReceiver\njava.io.IOException: Connection reset by peer",
129+
"namespace": "default",
130+
"pod": "test-hdfs-namenode-default-0",
131+
"role": "namenode",
132+
"roleGroup": "default",
133+
"timestamp": t'2025-10-02T09:27:28.582Z'
134+
}
135+
136+
assert_eq!(expected_log_event, .)
137+
- name: Test Vector internal logs
138+
inputs:
139+
- type: log
140+
insert_at: filtered_logs_vector
141+
log_fields:
142+
arch: x86_64
143+
message: Vector has started.
144+
metadata:
145+
kind: event
146+
level: INFO
147+
module_path: vector::internal_events::process
148+
target: vector
149+
pid: 14
150+
pod: test-hdfs-namenode-default-0
151+
source_type: internal_logs
152+
timestamp: 2025-10-02T09:46:14.479381097Z
153+
version: 0.49.0
154+
outputs:
155+
- extract_from: extended_logs
156+
conditions:
157+
- type: vrl
158+
source: |
159+
expected_log_event = {
160+
"arch": "x86_64",
161+
"cluster": "test-hdfs",
162+
"container": "vector",
163+
"level": "INFO",
164+
"logger": "vector::internal_events::process",
165+
"message": "Vector has started.",
166+
"namespace": "default",
167+
"pod": "test-hdfs-namenode-default-0",
168+
"role": "namenode",
169+
"roleGroup": "default",
170+
"timestamp": "2025-10-02T09:46:14.479381097Z",
171+
"version": "0.49.0"
172+
}
173+
174+
assert_eq!(expected_log_event, .)
175+
- name: Test Vector internal log level filtering - INFO passes
176+
inputs:
177+
- type: log
178+
insert_at: filtered_logs_vector
179+
log_fields:
180+
metadata:
181+
level: INFO
182+
outputs:
183+
- extract_from: filtered_logs_vector
184+
conditions:
185+
- type: vrl
186+
source: |
187+
assert_eq!("INFO", .metadata.level)
188+
- name: Test Vector internal log level filtering - DEBUG dropped
189+
inputs:
190+
- type: log
191+
insert_at: filtered_logs_vector
192+
log_fields:
193+
metadata:
194+
level: DEBUG
195+
no_outputs_from:
196+
- filtered_logs_vector

0 commit comments

Comments
 (0)