Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions deploy/helm/opensearch-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ spec:
spec:
description: A OpenSearch cluster stacklet. This resource is managed by the Stackable operator for OpenSearch. Find more information on how to use it and the resources that the operator generates in the [operator documentation](https://docs.stackable.tech/home/nightly/opensearch/).
properties:
clusterConfig:
properties:
tls:
properties:
secretClass:
default: tls
description: 'Only affects client connections. This setting controls: - If TLS encryption is used at all - Which cert the servers should use to authenticate themselves against the client'
type: string
type: object
required:
- tls
type: object
clusterOperation:
default:
reconciliationPaused: false
Expand Down Expand Up @@ -461,6 +473,7 @@ spec:
- roleGroups
type: object
required:
- clusterConfig
- image
- nodes
type: object
Expand Down
12 changes: 10 additions & 2 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ use build::build;
use snafu::{ResultExt, Snafu};
use stackable_operator::{
cluster_resources::ClusterResourceApplyStrategy,
commons::{affinity::StackableAffinity, product_image_selection::ProductImage},
commons::{
affinity::StackableAffinity, networking::DomainName, product_image_selection::ProductImage,
},
crd::listener::v1alpha1::Listener,
k8s_openapi::api::{
apps::v1::StatefulSet,
batch::v1::Job,
core::v1::{ConfigMap, Service, ServiceAccount},
policy::v1::PodDisruptionBudget,
rbac::v1::RoleBinding,
Expand All @@ -25,7 +28,7 @@ use validate::validate;
use crate::{
crd::{
NodeRoles,
v1alpha1::{self},
v1alpha1::{self, OpenSearchClusterConfig},
},
framework::{
ClusterName, ControllerName, HasNamespace, HasObjectName, HasUid, IsLabelValue,
Expand All @@ -43,6 +46,7 @@ pub struct ContextNames {
pub product_name: ProductName,
pub operator_name: OperatorName,
pub controller_name: ControllerName,
pub cluster_domain_name: DomainName,
}

pub struct Context {
Expand All @@ -52,6 +56,7 @@ pub struct Context {

impl Context {
pub fn new(client: stackable_operator::client::Client, operator_name: OperatorName) -> Self {
let cluster_domain_name = client.kubernetes_cluster_info.cluster_domain.clone();
Context {
client,
names: ContextNames {
Expand All @@ -60,6 +65,7 @@ impl Context {
operator_name,
controller_name: ControllerName::from_str("opensearchcluster")
.expect("should be a valid controller name"),
cluster_domain_name,
},
}
}
Expand Down Expand Up @@ -126,6 +132,7 @@ pub struct ValidatedCluster {
pub name: ClusterName,
pub namespace: String,
pub uid: String,
pub cluster_config: OpenSearchClusterConfig,
pub role_config: GenericRoleConfig,
// "validated" means that labels are valid and no ugly rolegroup name broke them
pub role_group_configs: BTreeMap<RoleGroupName, OpenSearchRoleGroupConfig>,
Expand Down Expand Up @@ -282,5 +289,6 @@ struct KubernetesResources<T> {
service_accounts: Vec<ServiceAccount>,
role_bindings: Vec<RoleBinding>,
pod_disruption_budgets: Vec<PodDisruptionBudget>,
jobs: Vec<Job>,
status: PhantomData<T>,
}
3 changes: 3 additions & 0 deletions rust/operator-binary/src/controller/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ impl<'a> Applier<'a> {

let pod_disruption_budgets = self.add_resources(resources.pod_disruption_budgets).await?;

let jobs = self.add_resources(resources.jobs).await?;

self.cluster_resources
.delete_orphaned_resources(self.client)
.await
Expand All @@ -85,6 +87,7 @@ impl<'a> Applier<'a> {
service_accounts,
role_bindings,
pod_disruption_budgets,
jobs,
status: PhantomData,
})
}
Expand Down
7 changes: 7 additions & 0 deletions rust/operator-binary/src/controller/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::marker::PhantomData;
use role_builder::RoleBuilder;

use super::{ContextNames, KubernetesResources, Prepared, ValidatedCluster};
use crate::controller::build::job_builder::JobBuilder;

pub mod job_builder;
pub mod node_config;
pub mod role_builder;
pub mod role_group_builder;
Expand All @@ -13,8 +15,10 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou
let mut stateful_sets = vec![];
let mut services = vec![];
let mut listeners = vec![];
let mut jobs = vec![];

let role_builder = RoleBuilder::new(cluster.clone(), names);
let job_builder = JobBuilder::new(cluster.clone(), names);

for role_group_builder in role_builder.role_group_builders() {
config_maps.push(role_group_builder.build_config_map());
Expand All @@ -32,6 +36,8 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou

let pod_disruption_budgets = role_builder.build_pdb().into_iter().collect();

jobs.push(job_builder.build_run_securityadmin_job());

KubernetesResources {
stateful_sets,
services,
Expand All @@ -40,6 +46,7 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou
service_accounts,
role_bindings,
pod_disruption_budgets,
jobs,
status: PhantomData,
}
}
201 changes: 201 additions & 0 deletions rust/operator-binary/src/controller/build/job_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use stackable_operator::{
builder::{
meta::ObjectMetaBuilder,
pod::{
container::ContainerBuilder, resources::ResourceRequirementsBuilder,
volume::SecretFormat,
},
},
k8s_openapi::api::{
batch::v1::{Job, JobSpec},
core::v1::{
PodSecurityContext, PodSpec, PodTemplateSpec, SecretVolumeSource, Volume, VolumeMount,
},
},
kube::api::ObjectMeta,
kvp::{
Label, Labels,
consts::{STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE},
},
time::Duration,
};

use crate::{
controller::{ContextNames, ValidatedCluster},
framework::{
IsLabelValue,
builder::{meta::ownerreference_from_resource, volume::build_tls_volume},
role_utils::ResourceNames,
},
};

const RUN_SECURITYADMIN_CERT_VOLUME_NAME: &str = "tls";
const RUN_SECURITYADMIN_CERT_VOLUME_MOUNT: &str = "/stackable/tls-client";
const SECURITY_CONFIG_VOLUME_NAME: &str = "security-config";
const SECURITY_CONFIG_VOLUME_MOUNT: &str = "/stackable/opensearch/config/opensearch-security";
const RUN_SECURITYADMIN_CONTAINER_NAME: &str = "run-securityadmin";

pub struct JobBuilder<'a> {
cluster: ValidatedCluster,
context_names: &'a ContextNames,
resource_names: ResourceNames,
}

impl<'a> JobBuilder<'a> {
pub fn new(cluster: ValidatedCluster, context_names: &'a ContextNames) -> JobBuilder<'a> {
JobBuilder {
cluster: cluster.clone(),
context_names,
resource_names: ResourceNames {
cluster_name: cluster.name.clone(),
product_name: context_names.product_name.clone(),
},
}
}

pub fn build_run_securityadmin_job(&self) -> Job {
let product_image = self
.cluster
.image
.resolve("opensearch", crate::built_info::PKG_VERSION);
// Maybe add a suffix for consecutive
let metadata = self.common_metadata(format!(
"{}-run-securityadmin",
self.resource_names.cluster_name,
));

let args = [
"plugins/opensearch-security/tools/securityadmin.sh".to_string(),
"--hostname".to_string(),
self.opensearch_master_fqdn(),
"--configdir".to_string(),
"config/opensearch-security/".to_string(),
"-cacert".to_string(),
"/stackable/tls-client/ca.crt".to_string(),
"-cert".to_string(),
"/stackable/tls-client/tls.crt".to_string(),
"-key".to_string(),
"/stackable/tls-client/tls.key".to_string(),
];

let mut cb = ContainerBuilder::new(RUN_SECURITYADMIN_CONTAINER_NAME)
.expect("should be a valid container name");
let container = cb
.image_from_product_image(&product_image)
.command(vec!["sh".to_string(), "-c".to_string()])
.args(vec![args.join(" ")])
// The VolumeMount for the secret operator key store certificates
.add_volume_mounts(vec![
VolumeMount {
mount_path: SECURITY_CONFIG_VOLUME_MOUNT.to_owned(),
name: SECURITY_CONFIG_VOLUME_NAME.to_owned(),
..VolumeMount::default()
},
VolumeMount {
mount_path: RUN_SECURITYADMIN_CERT_VOLUME_MOUNT.to_owned(),
name: RUN_SECURITYADMIN_CERT_VOLUME_NAME.to_owned(),
..VolumeMount::default()
},
])
.expect("the mount paths are statically defined and there should be no duplicates")
.resources(
ResourceRequirementsBuilder::new()
.with_cpu_request("100m")
.with_cpu_limit("400m")
.with_memory_request("128Mi")
.with_memory_limit("512Mi")
.build(),
)
.build();
let pod_template = PodTemplateSpec {
metadata: Some(metadata.clone()),
spec: Some(PodSpec {
containers: vec![container],
security_context: Some(PodSecurityContext {
fs_group: Some(1000),
..PodSecurityContext::default()
}),
restart_policy: Some("OnFailure".to_string()),
service_account_name: Some(self.resource_names.service_account_name()),
volumes: Some(vec![
Volume {
name: SECURITY_CONFIG_VOLUME_NAME.to_owned(),
secret: Some(SecretVolumeSource {
secret_name: Some("opensearch-security-config".to_string()),
..Default::default()
}),
..Volume::default()
},
build_tls_volume(
RUN_SECURITYADMIN_CERT_VOLUME_NAME,
&self.cluster.cluster_config.tls.secret_class,
Vec::<String>::new(),
SecretFormat::TlsPem,
&Duration::from_days_unchecked(15),
None,
),
]),
..PodSpec::default()
}),
};

Job {
metadata,
spec: Some(JobSpec {
backoff_limit: Some(100),
ttl_seconds_after_finished: Some(120),
template: pod_template,
..JobSpec::default()
}),
..Job::default()
}
}

fn opensearch_master_fqdn(&self) -> String {
let cluster_manager_service_name = self.resource_names.discovery_service_name();
let namespace = &self.cluster.namespace;
let cluster_domain = &self.context_names.cluster_domain_name;
format!("{cluster_manager_service_name}.{namespace}.svc.{cluster_domain}")
}

fn common_metadata(&self, resource_name: impl Into<String>) -> ObjectMeta {
ObjectMetaBuilder::new()
.name(resource_name)
.namespace(&self.cluster.namespace)
.ownerreference(ownerreference_from_resource(
&self.cluster,
None,
Some(true),
))
.with_labels(self.labels())
.build()
}

/// Labels on role resources
fn labels(&self) -> Labels {
// Well-known Kubernetes labels
let mut labels = Labels::role_selector(
&self.cluster,
&self.context_names.product_name.to_label_value(),
&ValidatedCluster::role_name().to_label_value(),
)
.unwrap();

let managed_by = Label::managed_by(
&self.context_names.operator_name.to_string(),
&self.context_names.controller_name.to_string(),
)
.unwrap();
let version = Label::version(&self.cluster.product_version.to_string()).unwrap();

labels.insert(managed_by);
labels.insert(version);

// Stackable-specific labels
labels
.parse_insert((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE))
.unwrap();

labels
}
}
Loading