Skip to content

Commit e1ebe06

Browse files
committed
refactor: switch to EnvVarSet, introduce role_utils
1 parent 8f92ec9 commit e1ebe06

11 files changed

Lines changed: 549 additions & 601 deletions

File tree

rust/operator-binary/src/controller.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ fn build_rolegroup_statefulset(
504504
druid_auth_config: &Option<DruidAuthenticationConfig>,
505505
service_account: &ServiceAccount,
506506
) -> Result<StatefulSet> {
507-
let merged_rolegroup_config = &rg.merged_config;
507+
let merged_rolegroup_config = &rg.config;
508508
// prepare container builder
509509
let prepare_container_name = Container::Prepare.to_string();
510510
let mut cb_prepare = ContainerBuilder::new(&prepare_container_name).context(
@@ -625,7 +625,7 @@ fn build_rolegroup_statefulset(
625625
metadata_database_connection_details.add_to_container(&mut cb_druid);
626626

627627
// rest of env: the validated env overrides, rendered in sorted-by-name order.
628-
let mut rest_env: Vec<EnvVar> = rg.env.clone().into();
628+
let mut rest_env: Vec<EnvVar> = rg.env_overrides.clone().into();
629629

630630
if let Some(auth_config) = druid_auth_config {
631631
rest_env.extend(auth_config.get_env_var_mounts(druid, role))
@@ -757,11 +757,8 @@ fn build_rolegroup_statefulset(
757757
}
758758

759759
let mut pod_template = pb.build_template();
760-
pod_template.merge_from(druid.pod_overrides_for_role(role).clone());
761-
if let Some(pod_overrides) = druid.pod_overrides_for_role_group(role, &rolegroup_ref.role_group)
762-
{
763-
pod_template.merge_from(pod_overrides.clone());
764-
}
760+
// The role and rolegroup pod overrides were already merged (rolegroup wins) during validation.
761+
pod_template.merge_from(rg.pod_overrides.clone());
765762

766763
Ok(StatefulSet {
767764
metadata: ObjectMetaBuilder::new()
@@ -931,23 +928,17 @@ mod test {
931928
use rstest::*;
932929
use stackable_operator::{
933930
database_connections::drivers::jdbc::JdbcDatabaseConnection,
934-
v2::{
935-
builder::pod::container::EnvVarSet,
936-
types::{
937-
kubernetes::{NamespaceName, Uid},
938-
operator::ClusterName,
939-
},
931+
v2::types::{
932+
kubernetes::{NamespaceName, Uid},
933+
operator::ClusterName,
940934
},
941935
};
942936

943937
use super::*;
944938
use crate::{
945939
controller::{
946-
build::{
947-
config_map::build_rolegroup_config_map,
948-
properties::{ConfigFileName, runtime_properties},
949-
},
950-
validate::{DruidRoleGroupConfig, ValidatedCluster, ValidatedClusterConfig},
940+
build::{config_map::build_rolegroup_config_map, properties::ConfigFileName},
941+
validate::{ValidatedCluster, ValidatedClusterConfig},
951942
},
952943
crd::{PROP_SEGMENT_CACHE_LOCATIONS, authentication::AuthenticationClassesResolved},
953944
extensions::get_extension_list,
@@ -993,22 +984,22 @@ mod test {
993984
Some("tls".to_string()),
994985
);
995986

996-
let merged = druid.merged_config().expect("merged config");
997-
let merged_config = merged
998-
.common_config(&DruidRole::Historical, tested_rolegroup_name)
999-
.expect("common config for tested rolegroup");
1000-
1001987
// The segment cache property is injected dynamically by the config_map builder from the
1002-
// merged resources, independent of the precomputed runtime_config. We still populate the
1003-
// runtime_config with the static role defaults to mirror the production path.
1004-
let rg = DruidRoleGroupConfig {
1005-
merged_config,
1006-
runtime_config: runtime_properties::defaults(&DruidRole::Historical),
1007-
security_config: BTreeMap::new(),
1008-
env: EnvVarSet::new(),
1009-
// The test only asserts on runtime.properties, so the rendered jvm.config is irrelevant.
1010-
jvm_config: String::new(),
1011-
};
988+
// merged resources of the validated role group config.
989+
let rg = druid
990+
.merged_role(&DruidRole::Historical)
991+
.expect("merged historical role")
992+
.get(tested_rolegroup_name)
993+
.expect("tested rolegroup")
994+
.clone();
995+
996+
// The build step renders jvm.config from the erased role; populate the one role the test
997+
// exercises.
998+
let mut roles = BTreeMap::new();
999+
roles.insert(
1000+
DruidRole::Historical,
1001+
druid.get_role(&DruidRole::Historical),
1002+
);
10121003

10131004
let extensions = get_extension_list(&druid, &druid_tls_security, &None);
10141005
let metadata_storage_type = druid
@@ -1039,8 +1030,10 @@ mod test {
10391030
extensions,
10401031
metadata_storage_type,
10411032
metadata_db_connection,
1033+
deep_storage: druid.spec.cluster_config.deep_storage.clone(),
10421034
},
10431035
BTreeMap::new(),
1036+
roles,
10441037
);
10451038

10461039
let rolegroup_ref = RoleGroupRef {

rust/operator-binary/src/controller/build/config_map.rs

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//! Builds the rolegroup [`ConfigMap`] from a [`ValidatedCluster`].
22
//!
3-
//! The per-file "validated config" maps (runtime.properties / security.properties) are taken
4-
//! from the [`DruidRoleGroupConfig`] precomputed in the validate step; product-config is no
5-
//! longer involved.
3+
//! The per-file configs (runtime.properties / security.properties / jvm.config) are rendered here
4+
//! from the merged [`DruidRoleGroupConfig`] (config plus the merged config overrides); the
5+
//! recommended cluster-level runtime properties and the erased roles needed for `jvm.config` are
6+
//! carried on `ValidatedCluster`. Product-config is no longer involved.
67
//!
78
//! Metadata, owner reference and recommended labels are derived entirely from `ValidatedCluster`
89
//! (which carries the validated name/namespace/uid and implements `Resource`).
910
//!
10-
//! The builder no longer reads the raw [`v1alpha1::DruidCluster`] at all: the extensions load
11-
//! list, the metadata-database connection / storage type and the rendered `jvm.config` are all
12-
//! precomputed on `ValidatedCluster` during the validate step.
11+
//! The builder does not read the raw [`v1alpha1::DruidCluster`] at all: everything it needs is
12+
//! carried on `ValidatedCluster` (resolved during the validate step).
1313
1414
use std::collections::BTreeMap;
1515

@@ -28,15 +28,19 @@ use stackable_operator::{
2828
use crate::{
2929
controller::{
3030
DRUID_CONTROLLER_NAME,
31-
build::properties::{
32-
ConfigFileName,
33-
logging::{build_log4j2_config, build_vector_config},
31+
build::{
32+
jvm::construct_jvm_args,
33+
properties::{
34+
ConfigFileName,
35+
logging::{build_log4j2_config, build_vector_config},
36+
runtime_properties, security_properties,
37+
},
3438
},
3539
validate::{DruidRoleGroupConfig, ValidatedCluster},
3640
},
3741
crd::{
38-
DruidRole, build_recommended_labels, build_string_list, env_var_reference, file_reference,
39-
v1alpha1,
42+
DruidConfigOverrides, DruidRole, STACKABLE_TRUST_STORE, STACKABLE_TRUST_STORE_PASSWORD,
43+
build_recommended_labels, build_string_list, env_var_reference, file_reference, v1alpha1,
4044
},
4145
};
4246

@@ -87,10 +91,56 @@ pub enum Error {
8791
GenerateAuthenticationRuntimeSettings {
8892
source: crate::authentication::Error,
8993
},
94+
95+
#[snafu(display("failed to derive Druid memory settings from resources"))]
96+
DeriveMemorySettings { source: crate::crd::resource::Error },
97+
98+
#[snafu(display("failed to construct the jvm.config"))]
99+
GetJvmConfig {
100+
source: crate::controller::build::jvm::Error,
101+
},
90102
}
91103

92104
type Result<T, E = Error> = std::result::Result<T, E>;
93105

106+
const INDEXER_JAVA_OPTS: &str = "druid.indexer.runner.javaOptsArray";
107+
108+
/// The `druid.indexer.runner.javaOptsArray` entry that must be present in *every* rendered file
109+
/// (runtime.properties and security.properties) for MiddleManagers.
110+
fn middlemanager_indexer_java_opts() -> (String, String) {
111+
(
112+
INDEXER_JAVA_OPTS.to_string(),
113+
build_string_list(&[
114+
format!("-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}"),
115+
format!("-Djavax.net.ssl.trustStorePassword={STACKABLE_TRUST_STORE_PASSWORD}"),
116+
"-Djavax.net.ssl.trustStoreType=pkcs12".to_owned(),
117+
]),
118+
)
119+
}
120+
121+
/// Returns the user-supplied key/value overrides for the given config file from a
122+
/// [`DruidConfigOverrides`].
123+
///
124+
/// The CRD override map allows a value-less key (`someKey:` / null in YAML), modelled as
125+
/// `Option<String>`. We flatten `None` to an empty string here, matching how the Java properties
126+
/// writer rendered a missing value (`key=`), so the rest of the pipeline can work with plain
127+
/// `String` values.
128+
fn key_value_overrides(
129+
overrides: &DruidConfigOverrides,
130+
file: ConfigFileName,
131+
) -> BTreeMap<String, String> {
132+
let raw = match file {
133+
ConfigFileName::RuntimeProperties => overrides.runtime_properties.overrides.clone(),
134+
ConfigFileName::SecurityProperties => overrides.security_properties.overrides.clone(),
135+
// log4j2.properties is rendered by the logging framework, and jvm.config is rendered from
136+
// JVM argument overrides; neither is assembled from key/value overrides here.
137+
ConfigFileName::Log4j2Properties | ConfigFileName::JvmConfig => BTreeMap::new(),
138+
};
139+
raw.into_iter()
140+
.map(|(k, v)| (k, v.unwrap_or_default()))
141+
.collect()
142+
}
143+
94144
/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator
95145
pub fn build_rolegroup_config_map(
96146
cluster: &ValidatedCluster,
@@ -117,7 +167,7 @@ pub fn build_rolegroup_config_map(
117167
// This has to be done here since there is no other suitable place for it.
118168
// Previously such properties were added in the compute_files() function,
119169
// but that code path is now incompatible with the design of fragment merging.
120-
rg.merged_config
170+
rg.config
121171
.resources
122172
.update_druid_config_file(&mut conf)
123173
.context(UpdateDruidConfigFromResourcesSnafu)?;
@@ -217,8 +267,22 @@ pub fn build_rolegroup_config_map(
217267
);
218268
};
219269

220-
// extend the config to respect the precomputed defaults and overrides
221-
conf.extend(rg.runtime_config.clone());
270+
// Role/rolegroup runtime.properties: the recommended cluster-config-derived properties,
271+
// the MiddleManager indexer opts, the per-role defaults and finally the user overrides
272+
// (each layer wins over the previous, and over the cluster-level properties above).
273+
conf.extend(runtime_properties::cluster_runtime_properties(
274+
&cluster_config.deep_storage,
275+
cluster_config.opa_connection_string.is_some(),
276+
));
277+
if *role == DruidRole::MiddleManager {
278+
let (k, v) = middlemanager_indexer_java_opts();
279+
conf.insert(k, v);
280+
}
281+
conf.extend(runtime_properties::defaults(role));
282+
conf.extend(key_value_overrides(
283+
&rg.config_overrides,
284+
ConfigFileName::RuntimeProperties,
285+
));
222286

223287
let runtime_properties =
224288
to_java_properties_string(conf.iter()).context(SerializeRuntimePropertiesSnafu)?;
@@ -229,14 +293,37 @@ pub fn build_rolegroup_config_map(
229293
}
230294

231295
// ----- jvm.config -----
232-
// Precomputed during validation; see `DruidRoleGroupConfig::jvm_config`.
233-
cm_conf_data.insert(ConfigFileName::JvmConfig.to_string(), rg.jvm_config.clone());
296+
{
297+
let (heap, direct) = rg
298+
.config
299+
.resources
300+
.get_memory_sizes(role)
301+
.context(DeriveMemorySettingsSnafu)?;
302+
let jvm_config = construct_jvm_args(
303+
role,
304+
cluster.get_role(role),
305+
&rolegroup.role_group,
306+
heap,
307+
direct,
308+
)
309+
.context(GetJvmConfigSnafu)?;
310+
cm_conf_data.insert(ConfigFileName::JvmConfig.to_string(), jvm_config);
311+
}
234312

235313
// ----- security.properties -----
236314
{
315+
let mut security_config: BTreeMap<String, String> = BTreeMap::new();
316+
if *role == DruidRole::MiddleManager {
317+
let (k, v) = middlemanager_indexer_java_opts();
318+
security_config.insert(k, v);
319+
}
320+
let security_overrides =
321+
key_value_overrides(&rg.config_overrides, ConfigFileName::SecurityProperties);
322+
security_config.extend(security_properties::build(&security_overrides));
323+
237324
cm_conf_data.insert(
238325
ConfigFileName::SecurityProperties.to_string(),
239-
to_java_properties_string(rg.security_config.iter()).with_context(|_| {
326+
to_java_properties_string(security_config.iter()).with_context(|_| {
240327
JvmSecurityPropertiesSnafu {
241328
rolegroup: rolegroup.role_group.clone(),
242329
}
@@ -265,11 +352,11 @@ pub fn build_rolegroup_config_map(
265352
config_map_builder.add_data(filename, file_content);
266353
}
267354

268-
if let Some(log4j2_config) = build_log4j2_config(&rg.merged_config.logging) {
355+
if let Some(log4j2_config) = build_log4j2_config(&rg.config.logging) {
269356
config_map_builder.add_data(ConfigFileName::Log4j2Properties.to_string(), log4j2_config);
270357
}
271358

272-
if let Some(vector_config) = build_vector_config(rolegroup, &rg.merged_config.logging) {
359+
if let Some(vector_config) = build_vector_config(rolegroup, &rg.config.logging) {
273360
config_map_builder.add_data(VECTOR_CONFIG_FILE, vector_config);
274361
}
275362

rust/operator-binary/src/controller/build/jvm.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,11 @@ mod tests {
294294
serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap();
295295

296296
let role = druid.get_role(druid_role);
297-
let merged_config = druid.merged_config().unwrap();
298-
let (heap, direct) = merged_config
299-
.common_config(druid_role, "default")
297+
let merged_role = druid.merged_role(druid_role).unwrap();
298+
let (heap, direct) = merged_role
299+
.get("default")
300300
.unwrap()
301+
.config
301302
.resources
302303
.get_memory_sizes(druid_role)
303304
.unwrap();

rust/operator-binary/src/controller/build/properties/runtime_properties.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,63 @@
99
1010
use std::collections::BTreeMap;
1111

12-
use crate::crd::DruidRole;
12+
use crate::crd::{DeepStorageSpec, DruidRole, METRICS_PORT};
13+
14+
// deep storage
15+
const DS_TYPE: &str = "druid.storage.type";
16+
const DS_DIRECTORY: &str = "druid.storage.storageDirectory";
17+
const DS_BASE_KEY: &str = "druid.storage.baseKey";
18+
// OPA
19+
const AUTH_AUTHORIZERS: &str = "druid.auth.authorizers";
20+
const AUTH_AUTHORIZERS_VALUE: &str = "[\"OpaAuthorizer\"]";
21+
const AUTH_AUTHORIZER_OPA_TYPE: &str = "druid.auth.authorizer.OpaAuthorizer.type";
22+
const AUTH_AUTHORIZER_OPA_TYPE_VALUE: &str = "opa";
23+
// metrics
24+
const PROMETHEUS_PORT: &str = "druid.emitter.prometheus.port";
25+
26+
/// The recommended cluster-level `runtime.properties` derived from the cluster config (deep
27+
/// storage, OPA authorization and metrics). These are independent of role and role group.
28+
///
29+
/// `opa_authorization_enabled` mirrors `authorization.opa` being configured (equivalently, the
30+
/// OPA connection string having been resolved during dereferencing).
31+
pub fn cluster_runtime_properties(
32+
deep_storage: &DeepStorageSpec,
33+
opa_authorization_enabled: bool,
34+
) -> BTreeMap<String, String> {
35+
let mut result = BTreeMap::new();
36+
37+
// OPA
38+
if opa_authorization_enabled {
39+
result.insert(
40+
AUTH_AUTHORIZERS.to_string(),
41+
AUTH_AUTHORIZERS_VALUE.to_string(),
42+
);
43+
result.insert(
44+
AUTH_AUTHORIZER_OPA_TYPE.to_string(),
45+
AUTH_AUTHORIZER_OPA_TYPE_VALUE.to_string(),
46+
);
47+
// The opaUri still needs to be set, but that requires a discovery config map and is
48+
// handled in the controller.
49+
}
50+
51+
// deep storage
52+
result.insert(DS_TYPE.to_string(), deep_storage.to_string());
53+
match deep_storage {
54+
DeepStorageSpec::Hdfs(hdfs) => {
55+
result.insert(DS_DIRECTORY.to_string(), hdfs.directory.clone());
56+
}
57+
DeepStorageSpec::S3(s3_spec) => {
58+
if let Some(key) = &s3_spec.base_key {
59+
result.insert(DS_BASE_KEY.to_string(), key.to_string());
60+
}
61+
}
62+
}
63+
64+
// metrics
65+
result.insert(PROMETHEUS_PORT.to_string(), METRICS_PORT.to_string());
66+
67+
result
68+
}
1369

1470
/// Defaults rendered for every role.
1571
const ALL_ROLES: &[(&str, &str)] = &[

0 commit comments

Comments
 (0)