Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ All notable changes to this project will be documented in this file.

- Don't default roleGroup replicas to zero when not specified ([#402]).
- [BREAKING] Removed field `autoFormatFs`, which was never read ([#422]).
- Include hdfs principals `dfs.journalnode.kerberos.principal`, `dfs.namenode.kerberos.principal`
and `dfs.datanode.kerberos.principal` in the discovery ConfigMap in case Kerberos is enabled ([#424]).

### Removed

Expand All @@ -38,6 +40,7 @@ All notable changes to this project will be documented in this file.
[#407]: https://github.com/stackabletech/hdfs-operator/pull/407
[#409]: https://github.com/stackabletech/hdfs-operator/pull/409
[#422]: https://github.com/stackabletech/hdfs-operator/pull/422
[#424]: https://github.com/stackabletech/hdfs-operator/pull/424

## [23.7.0] - 2023-07-14

Expand Down
11 changes: 10 additions & 1 deletion docs/modules/hdfs/pages/discovery.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,13 @@ The `ConfigMap` data values are formatted as Hadoop XML files which allows simpl
Contains the `fs.DefaultFS` which defaults to `hdfs://{clusterName}/`.

`hdfs-site.xml`::
Contains the `dfs.namenode.*` properties for `rpc` and `http` addresses for the `namenodes` as well as the `dfs.nameservices` property which defaults to `hdfs://{clusterName}/`.
Contains the `dfs.namenode.*` properties for `rpc` and `http` addresses for the `namenodes` as well as the `dfs.nameservices` property which defaults to `hdfs://{clusterName}/`.

=== Kerberos
In case Kerberos is enabled according the xref:usage-guide/security.adoc[security documentation] the discovery will also include the information that clients must authenticate themselves using Kerberos.

Some Kerberos-related configs require the environmental variable `KERBEROS_REALM` to be set (e.g. using `export KERBEROS_REALM=$(grep -oP 'default_realm = \K.*' /stackable/kerberos/krb5.conf)`).
When you want to use the discovery configmap outside of Stackable services, you need to provide this environment variable!
As an alternative you can text-replace `${env.KERBEROS_REALM}` with your actual realm (e.g. by using `sed -i -e 's/${{env.KERBEROS_REALM}}/'"$KERBEROS_REALM/g" hbase-site.xml`).

One example would be the property `dfs.namenode.kerberos.principal` being set to `nn/hdfs.kuttl-test-daring-mammal.svc.cluster.local@${env.KERBEROS_REALM}`.
6 changes: 4 additions & 2 deletions rust/crd/src/affinity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ spec:
replicas: 1
"#;
let hdfs: HdfsCluster = serde_yaml::from_str(input).unwrap();
let merged_config = role.merged_config(&hdfs, "default").unwrap();
let merged_config = role.merged_config(&hdfs, "simple-hdfs", "default").unwrap();

assert_eq!(
merged_config.affinity(),
Expand Down Expand Up @@ -168,7 +168,9 @@ spec:
- antarctica-west1
"#;
let hdfs: HdfsCluster = serde_yaml::from_str(input).unwrap();
let merged_config = HdfsRole::DataNode.merged_config(&hdfs, "default").unwrap();
let merged_config = HdfsRole::DataNode
.merged_config(&hdfs, "simple-hdfs", "default")
.unwrap();

assert_eq!(
merged_config.affinity(),
Expand Down
22 changes: 11 additions & 11 deletions rust/crd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use stackable_operator::{
api::core::v1::PodTemplateSpec,
apimachinery::pkg::{api::resource::Quantity, apis::meta::v1::LabelSelector},
},
kube::{runtime::reflector::ObjectRef, CustomResource, ResourceExt},
kube::{runtime::reflector::ObjectRef, CustomResource},
labels::role_group_selector_labels,
product_config_utils::{ConfigError, Configuration},
product_logging,
Expand Down Expand Up @@ -239,11 +239,12 @@ impl HdfsRole {
pub fn merged_config(
&self,
hdfs: &HdfsCluster,
hdfs_name: &str,
role_group: &str,
) -> Result<Box<dyn MergedConfig + Send + 'static>, Error> {
match self {
HdfsRole::NameNode => {
let default_config = NameNodeConfigFragment::default_config(&hdfs.name_any(), self);
let default_config = NameNodeConfigFragment::default_config(hdfs_name, self);
let role = hdfs
.spec
.name_nodes
Expand Down Expand Up @@ -282,7 +283,7 @@ impl HdfsRole {
))
}
HdfsRole::DataNode => {
let default_config = DataNodeConfigFragment::default_config(&hdfs.name_any(), self);
let default_config = DataNodeConfigFragment::default_config(hdfs_name, self);
let role = hdfs
.spec
.data_nodes
Expand Down Expand Up @@ -321,8 +322,7 @@ impl HdfsRole {
))
}
HdfsRole::JournalNode => {
let default_config =
JournalNodeConfigFragment::default_config(&hdfs.name_any(), self);
let default_config = JournalNodeConfigFragment::default_config(hdfs_name, self);
let role = hdfs
.spec
.journal_nodes
Expand Down Expand Up @@ -1319,7 +1319,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let resources = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap();
Expand Down Expand Up @@ -1357,7 +1357,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let resources = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap();
Expand Down Expand Up @@ -1390,7 +1390,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let resources = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap();
Expand Down Expand Up @@ -1444,7 +1444,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let resources = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap();
Expand Down Expand Up @@ -1494,7 +1494,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let rr: ResourceRequirements = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap()
Expand Down Expand Up @@ -1547,7 +1547,7 @@ spec:
let hdfs: HdfsCluster = serde_yaml::from_str(cr).unwrap();
let role = HdfsRole::DataNode;
let rr: ResourceRequirements = role
.merged_config(&hdfs, "default")
.merged_config(&hdfs, "hdfs", "default")
.unwrap()
.data_node_resources()
.unwrap()
Expand Down
55 changes: 41 additions & 14 deletions rust/operator/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::product_logging::{
};

use indoc::formatdoc;
use snafu::{OptionExt, ResultExt, Snafu};
use snafu::{ResultExt, Snafu};
use stackable_hdfs_crd::{
constants::{
DATANODE_ROOT_DATA_DIR_PREFIX, DEFAULT_DATA_NODE_METRICS_PORT,
Expand Down Expand Up @@ -151,6 +151,8 @@ impl ContainerConfig {
pub fn add_containers_and_volumes(
pb: &mut PodBuilder,
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
role: &HdfsRole,
resolved_product_image: &ResolvedProductImage,
merged_config: &(dyn MergedConfig + Send + 'static),
Expand All @@ -164,6 +166,8 @@ impl ContainerConfig {
pb.add_volumes(main_container_config.volumes(merged_config, object_name));
pb.add_container(main_container_config.main_container(
hdfs,
hdfs_name,
hdfs_namespace,
role,
resolved_product_image,
zk_config_map_name,
Expand Down Expand Up @@ -209,7 +213,7 @@ impl ContainerConfig {
SecretOperatorVolumeSourceBuilder::new(
&authentication_config.kerberos.secret_class,
)
.with_service_scope(hdfs.name_any())
.with_service_scope(hdfs_name)
.with_kerberos_service_name(role.kerberos_service_name())
.with_kerberos_service_name("HTTP")
.build(),
Expand All @@ -226,6 +230,8 @@ impl ContainerConfig {
pb.add_volumes(zkfc_container_config.volumes(merged_config, object_name));
pb.add_container(zkfc_container_config.main_container(
hdfs,
hdfs_name,
hdfs_namespace,
role,
resolved_product_image,
zk_config_map_name,
Expand All @@ -241,6 +247,8 @@ impl ContainerConfig {
);
pb.add_init_container(format_namenodes_container_config.init_container(
hdfs,
hdfs_name,
hdfs_namespace,
role,
resolved_product_image,
zk_config_map_name,
Expand All @@ -257,6 +265,8 @@ impl ContainerConfig {
);
pb.add_init_container(format_zookeeper_container_config.init_container(
hdfs,
hdfs_name,
hdfs_namespace,
role,
resolved_product_image,
zk_config_map_name,
Expand All @@ -274,6 +284,8 @@ impl ContainerConfig {
);
pb.add_init_container(wait_for_namenodes_container_config.init_container(
hdfs,
hdfs_name,
hdfs_namespace,
role,
resolved_product_image,
zk_config_map_name,
Expand Down Expand Up @@ -317,9 +329,13 @@ impl ContainerConfig {
/// - Namenode ZooKeeper fail over controller (ZKFC)
/// - Datanode main process
/// - Journalnode main process

#[allow(clippy::too_many_arguments)]
fn main_container(
&self,
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to add extra parameters for something i can get from the HdfsCluster?
Same for other methods.
Id prefer to just impl this on the cluster object and call it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #451 based on this review comment, please have a look there

role: &HdfsRole,
resolved_product_image: &ResolvedProductImage,
zookeeper_config_map_name: &str,
Expand All @@ -335,7 +351,7 @@ impl ContainerConfig {

cb.image_from_product_image(resolved_product_image)
.command(Self::command())
.args(self.args(hdfs, role, merged_config, &[])?)
.args(self.args(hdfs, hdfs_name, hdfs_namespace, role, merged_config, &[]))
.add_env_vars(self.env(
hdfs,
zookeeper_config_map_name,
Expand Down Expand Up @@ -364,6 +380,8 @@ impl ContainerConfig {
fn init_container(
&self,
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
role: &HdfsRole,
resolved_product_image: &ResolvedProductImage,
zookeeper_config_map_name: &str,
Expand All @@ -376,7 +394,14 @@ impl ContainerConfig {

cb.image_from_product_image(resolved_product_image)
.command(Self::command())
.args(self.args(hdfs, role, merged_config, namenode_podrefs)?)
.args(self.args(
hdfs,
hdfs_name,
hdfs_namespace,
role,
merged_config,
namenode_podrefs,
))
.add_env_vars(self.env(hdfs, zookeeper_config_map_name, env_overrides, None))
.add_volume_mounts(self.volume_mounts(hdfs, merged_config));

Expand Down Expand Up @@ -427,10 +452,12 @@ impl ContainerConfig {
fn args(
&self,
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
role: &HdfsRole,
merged_config: &(dyn MergedConfig + Send + 'static),
namenode_podrefs: &[HdfsPodRef],
) -> Result<Vec<String>, Error> {
) -> Vec<String> {
let mut args = String::new();
args.push_str(&self.create_config_directory_cmd());
args.push_str(&self.copy_config_xml_cmd());
Expand Down Expand Up @@ -489,7 +516,7 @@ wait_for_termination $!
// If there is no active namenode, the current pod is not formatted we format as
// active namenode. Otherwise as standby node.
if hdfs.has_kerberos_enabled() {
args.push_str(&Self::get_kerberos_ticket(hdfs, role)?);
args.push_str(&Self::get_kerberos_ticket(hdfs_name, hdfs_namespace, role));
}
args.push_str(&formatdoc!(
r###"
Expand Down Expand Up @@ -570,7 +597,7 @@ wait_for_termination $!
));
}
if hdfs.has_kerberos_enabled() {
args.push_str(&Self::get_kerberos_ticket(hdfs, role)?);
args.push_str(&Self::get_kerberos_ticket(hdfs_name, hdfs_namespace, role));
}
args.push_str(&formatdoc!(
r###"
Expand Down Expand Up @@ -610,7 +637,7 @@ wait_for_termination $!
));
}
}
Ok(vec![args])
vec![args]
}

// Command to export `KERBEROS_REALM` env var to default real from krb5.conf, e.g. `CLUSTER.LOCAL`
Expand All @@ -622,19 +649,19 @@ wait_for_termination $!
/// Command to `kinit` a ticket using the principal created for the specified hdfs role
/// Needs the KERBEROS_REALM env var, which will be written with `export_kerberos_real_env_var_command`
/// Needs the POD_NAME env var to be present, which will be provided by the PodSpec
fn get_kerberos_ticket(hdfs: &HdfsCluster, role: &HdfsRole) -> Result<String, Error> {
fn get_kerberos_ticket(hdfs_name: &str, hdfs_namespace: &str, role: &HdfsRole) -> String {
// Watch out, this is a bash substitution from a bash env var,
// not the substitution hdfs is doing.
let principal = format!(
"{service_name}/{hdfs_name}.{namespace}.svc.cluster.local@${{KERBEROS_REALM}}",
"{service_name}/{hdfs_name}.{hdfs_namespace}.svc.cluster.local@${{KERBEROS_REALM}}",
service_name = role.kerberos_service_name(),
hdfs_name = hdfs.name_any(),
namespace = hdfs.namespace().context(ObjectHasNoNamespaceSnafu)?,
);
Ok(formatdoc!(
formatdoc!(
r###"
echo "Getting ticket for {principal}" from /stackable/kerberos/keytab
kinit "{principal}" -kt /stackable/kerberos/keytab
"###,
))
)
}

fn get_namenode_service_state_command() -> String {
Expand Down
21 changes: 13 additions & 8 deletions rust/operator/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use stackable_operator::{
commons::product_image_selection::ResolvedProductImage,
error::OperatorResult,
k8s_openapi::api::core::v1::ConfigMap,
kube::ResourceExt,
};

/// Creates a discovery config map containing the `hdfs-site.xml` and `core-site.xml`
/// for clients.
pub fn build_discovery_configmap(
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
controller: &str,
namenode_podrefs: &[HdfsPodRef],
resolved_product_image: &ResolvedProductImage,
Expand All @@ -38,21 +39,21 @@ pub fn build_discovery_configmap(
)
.add_data(
HDFS_SITE_XML,
build_discovery_hdfs_site_xml(hdfs, hdfs.name_any(), namenode_podrefs),
build_discovery_hdfs_site_xml(hdfs, hdfs_name, namenode_podrefs),
)
.add_data(
CORE_SITE_XML,
build_discovery_core_site_xml(hdfs, hdfs.name_any()),
build_discovery_core_site_xml(hdfs, hdfs_name, hdfs_namespace),
)
.build()
}

fn build_discovery_hdfs_site_xml(
hdfs: &HdfsCluster,
logical_name: String,
hdfs_name: &str,
namenode_podrefs: &[HdfsPodRef],
) -> String {
HdfsSiteConfigBuilder::new(logical_name)
HdfsSiteConfigBuilder::new(hdfs_name.to_string())
.dfs_name_services()
.dfs_ha_namenodes(namenode_podrefs)
.dfs_namenode_rpc_address_ha(namenode_podrefs)
Expand All @@ -62,9 +63,13 @@ fn build_discovery_hdfs_site_xml(
.build_as_xml()
}

fn build_discovery_core_site_xml(hdfs: &HdfsCluster, logical_name: String) -> String {
CoreSiteConfigBuilder::new(logical_name)
fn build_discovery_core_site_xml(
hdfs: &HdfsCluster,
hdfs_name: &str,
hdfs_namespace: &str,
) -> String {
CoreSiteConfigBuilder::new(hdfs_name.to_string())
.fs_default_fs()
.security_discovery_config(hdfs)
.security_discovery_config(hdfs, hdfs_name, hdfs_namespace)
.build_as_xml()
}
Loading