Skip to content

Commit 3209375

Browse files
Implement terminationGracePeriodSeconds
1 parent 4f30021 commit 3209375

10 files changed

Lines changed: 120 additions & 70 deletions

File tree

rust/operator-binary/src/controller.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ use update_status::update_status;
2222
use validate::validate;
2323

2424
use crate::{
25-
crd::v1alpha1::{self},
25+
crd::{
26+
NodeRoles,
27+
v1alpha1::{self},
28+
},
2629
framework::{
2730
ClusterName, ControllerName, HasNamespace, HasObjectName, HasUid, IsLabelValue,
2831
OperatorName, ProductName, ProductVersion, RoleGroupName,
@@ -96,7 +99,14 @@ impl ReconcilerError for Error {
9699
}
97100

98101
type OpenSearchRoleGroupConfig =
99-
RoleGroupConfig<GenericProductSpecificCommonConfig, v1alpha1::OpenSearchConfig>;
102+
RoleGroupConfig<GenericProductSpecificCommonConfig, ValidatedOpenSearchConfig>;
103+
104+
#[derive(Clone, Debug, PartialEq)]
105+
pub struct ValidatedOpenSearchConfig {
106+
pub node_roles: NodeRoles,
107+
pub resources: stackable_operator::commons::resources::Resources<v1alpha1::StorageConfig>,
108+
pub termination_grace_period_seconds: i64,
109+
}
100110

101111
// validated and converted to validated and safe types
102112
// no user errors
@@ -255,7 +265,7 @@ pub async fn reconcile(
255265
struct Prepared;
256266
struct Applied;
257267

258-
struct Resources<T> {
268+
struct KubernetesResources<T> {
259269
stateful_sets: Vec<StatefulSet>,
260270
services: Vec<Service>,
261271
config_maps: Vec<ConfigMap>,

rust/operator-binary/src/controller/apply.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use stackable_operator::{
77
};
88
use strum::{EnumDiscriminants, IntoStaticStr};
99

10-
use super::{Applied, ContextNames, Prepared, Resources};
10+
use super::{Applied, ContextNames, KubernetesResources, Prepared};
1111
use crate::framework::{
1212
HasNamespace, HasObjectName, HasUid, cluster_resources::cluster_resources_new,
1313
};
@@ -54,7 +54,10 @@ impl<'a> Applier<'a> {
5454
}
5555
}
5656

57-
pub async fn apply(mut self, resources: Resources<Prepared>) -> Result<Resources<Applied>> {
57+
pub async fn apply(
58+
mut self,
59+
resources: KubernetesResources<Prepared>,
60+
) -> Result<KubernetesResources<Applied>> {
5861
let stateful_sets = self.add_resources(resources.stateful_sets).await?;
5962

6063
let services = self.add_resources(resources.services).await?;
@@ -72,7 +75,7 @@ impl<'a> Applier<'a> {
7275
.await
7376
.context(DeleteOrphanedResourcesSnafu)?;
7477

75-
Ok(Resources {
78+
Ok(KubernetesResources {
7679
stateful_sets,
7780
services,
7881
config_maps,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ use std::{marker::PhantomData, str::FromStr};
22

33
use role_builder::RoleBuilder;
44

5-
use super::{ContextNames, Prepared, Resources, ValidatedCluster};
5+
use super::{ContextNames, KubernetesResources, Prepared, ValidatedCluster};
66
use crate::framework::RoleName;
77

88
pub mod node_config;
99
pub mod role_builder;
1010
pub mod role_group_builder;
1111

12-
pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> Resources<Prepared> {
12+
pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResources<Prepared> {
1313
let mut config_maps = vec![];
1414
let mut stateful_sets = vec![];
1515
let mut services = vec![];
@@ -33,7 +33,7 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> Resources<Prepa
3333

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

36-
Resources {
36+
KubernetesResources {
3737
stateful_sets,
3838
services,
3939
config_maps,

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

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
use stackable_operator::{
2-
builder::{
3-
meta::ObjectMetaBuilder,
4-
pod::{PodBuilder, container::ContainerBuilder},
5-
},
2+
builder::{meta::ObjectMetaBuilder, pod::container::ContainerBuilder},
63
k8s_openapi::{
74
DeepMerge,
85
api::{
96
apps::v1::{StatefulSet, StatefulSetSpec},
107
core::v1::{
118
ConfigMap, ConfigMapVolumeSource, Container, ContainerPort, PodSecurityContext,
12-
PodTemplateSpec, Probe, Service, ServicePort, ServiceSpec, TCPSocketAction, Volume,
13-
VolumeMount,
9+
PodSpec, PodTemplateSpec, Probe, Service, ServicePort, ServiceSpec,
10+
TCPSocketAction, Volume, VolumeMount,
1411
},
1512
},
1613
apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString},
@@ -136,8 +133,6 @@ impl<'a> RoleGroupBuilder<'a> {
136133
}
137134

138135
fn build_pod_template(&self) -> PodTemplateSpec {
139-
let mut builder = PodBuilder::new();
140-
141136
let mut node_role_labels = Labels::new();
142137
for node_role in self.role_group_config.config.node_roles.iter() {
143138
node_role_labels.insert(
@@ -153,24 +148,35 @@ impl<'a> RoleGroupBuilder<'a> {
153148

154149
let container = self.build_container(&self.role_group_config);
155150

156-
let mut pod_template = builder
157-
.metadata(metadata)
158-
.add_container(container)
159-
.add_volume(Volume {
160-
name: CONFIG_VOLUME_NAME.to_owned(),
161-
config_map: Some(ConfigMapVolumeSource {
162-
name: self.resource_names.role_group_config_map(),
163-
..Default::default()
151+
// The PodBuilder is not used because it re-validates the values which are already
152+
// validated. For instance, it would be necessary to convert the
153+
// termination_grace_period_seconds into a Duration, the PodBuilder parses the Duration,
154+
// converts it back into seconds and fails if this is not possible.
155+
let mut pod_template = PodTemplateSpec {
156+
metadata: Some(metadata),
157+
spec: Some(PodSpec {
158+
containers: vec![container],
159+
security_context: Some(PodSecurityContext {
160+
fs_group: Some(1000),
161+
..PodSecurityContext::default()
164162
}),
165-
..Default::default()
166-
})
167-
.expect("The volume names are statically defined and there should be no duplicates.")
168-
.security_context(PodSecurityContext {
169-
fs_group: Some(1000),
170-
..PodSecurityContext::default()
171-
})
172-
.service_account_name(&self.service_account_name)
173-
.build_template();
163+
service_account_name: Some(self.service_account_name.clone()),
164+
termination_grace_period_seconds: Some(
165+
self.role_group_config
166+
.config
167+
.termination_grace_period_seconds,
168+
),
169+
volumes: Some(vec![Volume {
170+
name: CONFIG_VOLUME_NAME.to_owned(),
171+
config_map: Some(ConfigMapVolumeSource {
172+
name: self.resource_names.role_group_config_map(),
173+
..Default::default()
174+
}),
175+
..Volume::default()
176+
}]),
177+
..PodSpec::default()
178+
}),
179+
};
174180

175181
pod_template.merge_from(self.role_group_config.pod_overrides.clone());
176182

rust/operator-binary/src/controller/update_status.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use stackable_operator::{
88
};
99
use strum::{EnumDiscriminants, IntoStaticStr};
1010

11-
use super::{Applied, ContextNames, Resources};
11+
use super::{Applied, ContextNames, KubernetesResources};
1212
use crate::crd::v1alpha1::{self, OpenSearchClusterStatus};
1313

1414
#[derive(Snafu, Debug, EnumDiscriminants)]
@@ -36,7 +36,7 @@ pub async fn update_status(
3636
client: &Client,
3737
names: &ContextNames,
3838
cluster: &v1alpha1::OpenSearchCluster,
39-
applied_resources: Resources<Applied>,
39+
applied_resources: KubernetesResources<Applied>,
4040
) -> Result<()> {
4141
let mut stateful_set_condition_builder = StatefulSetConditionBuilder::default();
4242
for stateful_set in applied_resources.stateful_sets {

rust/operator-binary/src/controller/validate.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
use std::{collections::BTreeMap, str::FromStr};
1+
use std::{collections::BTreeMap, num::TryFromIntError, str::FromStr};
22

33
use snafu::{OptionExt, ResultExt, Snafu};
4-
use stackable_operator::kube::{Resource, ResourceExt};
4+
use stackable_operator::{
5+
kube::{Resource, ResourceExt},
6+
role_utils::RoleGroup,
7+
time::Duration,
8+
};
59
use strum::{EnumDiscriminants, IntoStaticStr};
610

7-
use super::{ProductVersion, RoleGroupName, ValidatedCluster};
11+
use super::{ProductVersion, RoleGroupName, ValidatedCluster, ValidatedOpenSearchConfig};
812
use crate::{
9-
crd::v1alpha1,
13+
crd::v1alpha1::{self, OpenSearchConfig},
1014
framework::{
1115
ClusterName,
1216
role_utils::{RoleGroupConfig, with_validated_config},
@@ -38,10 +42,17 @@ pub enum Error {
3842
ValidateOpenSearchConfig {
3943
source: stackable_operator::config::fragment::ValidationError,
4044
},
45+
46+
#[snafu(display("termination grace period is too long (got {duration}, maximum allowed is {max})", max = Duration::from_secs(i64::MAX as u64)))]
47+
TerminationGracePeriodTooLong {
48+
source: TryFromIntError,
49+
duration: Duration,
50+
},
4151
}
4252

4353
type Result<T, E = Error> = std::result::Result<T, E>;
4454

55+
// TODO split
4556
// no client needed
4657
pub fn validate(cluster: &v1alpha1::OpenSearchCluster) -> Result<ValidatedCluster> {
4758
let raw_cluster_name = cluster.meta().name.clone().context(GetClusterNameSnafu)?;
@@ -59,13 +70,38 @@ pub fn validate(cluster: &v1alpha1::OpenSearchCluster) -> Result<ValidatedCluste
5970
let role_group_name =
6071
RoleGroupName::from_str(raw_role_group_name).context(ParseRoleGroupNameSnafu)?;
6172

62-
let validated_role_group = with_validated_config(
73+
let merged_role_group: RoleGroup<OpenSearchConfig, _> = with_validated_config(
6374
role_group_config,
6475
&cluster.spec.nodes,
6576
&v1alpha1::OpenSearchConfig::default_config(),
6677
)
6778
.context(ValidateOpenSearchConfigSnafu)?;
68-
let validated_role_group_config = RoleGroupConfig::from(validated_role_group);
79+
80+
let graceful_shutdown_timeout = merged_role_group.config.config.graceful_shutdown_timeout;
81+
82+
let termination_grace_period_seconds = graceful_shutdown_timeout
83+
.as_secs()
84+
.try_into()
85+
.context(TerminationGracePeriodTooLongSnafu {
86+
duration: graceful_shutdown_timeout,
87+
})?;
88+
89+
let validated_config = ValidatedOpenSearchConfig {
90+
node_roles: merged_role_group.config.config.node_roles,
91+
resources: merged_role_group.config.config.resources,
92+
termination_grace_period_seconds,
93+
};
94+
95+
let validated_role_group_config = RoleGroupConfig {
96+
// Kubernetes defaults to 1 if not set
97+
replicas: merged_role_group.replicas.unwrap_or(1),
98+
config: validated_config,
99+
config_overrides: merged_role_group.config.config_overrides,
100+
env_overrides: merged_role_group.config.env_overrides,
101+
cli_overrides: merged_role_group.config.cli_overrides,
102+
pod_overrides: merged_role_group.config.pod_overrides,
103+
product_specific_common_config: merged_role_group.config.product_specific_common_config,
104+
};
69105

70106
role_group_configs.insert(role_group_name, validated_role_group_config);
71107
}

rust/operator-binary/src/crd/mod.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::slice;
1+
use std::{slice, str::FromStr};
22

33
use serde::{Deserialize, Serialize};
44
use stackable_operator::{
@@ -19,6 +19,7 @@ use stackable_operator::{
1919
role_utils::{GenericRoleConfig, Role},
2020
schemars::{self, JsonSchema},
2121
status::condition::{ClusterCondition, HasStatusCondition},
22+
time::Duration,
2223
versioned::versioned,
2324
};
2425
use strum::Display;
@@ -115,6 +116,11 @@ pub mod versioned {
115116

116117
#[fragment_attrs(serde(default))]
117118
pub resources: Resources<StorageConfig>,
119+
120+
/// Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the
121+
/// operator documentation for details.
122+
#[fragment_attrs(serde(default))]
123+
pub graceful_shutdown_timeout: Duration,
118124
}
119125

120126
#[derive(Clone, Debug, Default, JsonSchema, PartialEq, Fragment)]
@@ -159,6 +165,14 @@ impl HasStatusCondition for v1alpha1::OpenSearchCluster {
159165
impl v1alpha1::OpenSearchConfig {
160166
pub fn default_config() -> v1alpha1::OpenSearchConfigFragment {
161167
v1alpha1::OpenSearchConfigFragment {
168+
// Defaults taken from the Helm chart, see
169+
// https://github.com/opensearch-project/helm-charts/blob/opensearch-3.0.0/charts/opensearch/values.yaml#L16-L20
170+
node_roles: Some(NodeRoles(vec![
171+
v1alpha1::NodeRole::ClusterManager,
172+
v1alpha1::NodeRole::Ingest,
173+
v1alpha1::NodeRole::Data,
174+
v1alpha1::NodeRole::RemoteClusterClient,
175+
])),
162176
resources: ResourcesFragment {
163177
memory: MemoryLimitsFragment {
164178
// An idle node already requires 2 Gi.
@@ -184,14 +198,11 @@ impl v1alpha1::OpenSearchConfig {
184198
},
185199
},
186200
},
187-
// Defaults taken from the Helm chart, see
188-
// https://github.com/opensearch-project/helm-charts/blob/opensearch-3.0.0/charts/opensearch/values.yaml#L16-L20
189-
node_roles: Some(NodeRoles(vec![
190-
v1alpha1::NodeRole::ClusterManager,
191-
v1alpha1::NodeRole::Ingest,
192-
v1alpha1::NodeRole::Data,
193-
v1alpha1::NodeRole::RemoteClusterClient,
194-
])),
201+
// Default taken from the Helm chart, see
202+
// https://github.com/opensearch-project/helm-charts/blob/opensearch-3.0.0/charts/opensearch/values.yaml#L364
203+
graceful_shutdown_timeout: Some(
204+
Duration::from_str("2m").expect("should be a valid duration"),
205+
),
195206
}
196207
}
197208
}

rust/operator-binary/src/framework/role_utils.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,6 @@ impl<ProductSpecificCommonConfig, T> RoleGroupConfig<ProductSpecificCommonConfig
4545
}
4646
}
4747

48-
impl<ProductSpecificCommonConfig, T> From<RoleGroup<T, ProductSpecificCommonConfig>>
49-
for RoleGroupConfig<ProductSpecificCommonConfig, T>
50-
{
51-
fn from(value: RoleGroup<T, ProductSpecificCommonConfig>) -> Self {
52-
RoleGroupConfig {
53-
// Kubernetes defaults to 1 if not set
54-
replicas: value.replicas.unwrap_or(1),
55-
config: value.config.config,
56-
config_overrides: value.config.config_overrides,
57-
env_overrides: value.config.env_overrides,
58-
cli_overrides: value.config.cli_overrides,
59-
pod_overrides: value.config.pod_overrides,
60-
product_specific_common_config: value.config.product_specific_common_config,
61-
}
62-
}
63-
}
64-
6548
// RoleGroup::validate_config with fixed types
6649
pub fn validate_config<C, ProductSpecificCommonConfig, T, U>(
6750
role_group: &RoleGroup<T, ProductSpecificCommonConfig>,

tests/templates/kuttl/smoke/10-assert.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ spec:
111111
fsGroup: 1000
112112
serviceAccount: opensearch-serviceaccount
113113
serviceAccountName: opensearch-serviceaccount
114-
terminationGracePeriodSeconds: 30
114+
terminationGracePeriodSeconds: 180
115115
volumes:
116116
- configMap:
117117
defaultMode: 420
@@ -261,7 +261,7 @@ spec:
261261
fsGroup: 1000
262262
serviceAccount: opensearch-serviceaccount
263263
serviceAccountName: opensearch-serviceaccount
264-
terminationGracePeriodSeconds: 30
264+
terminationGracePeriodSeconds: 120
265265
volumes:
266266
- configMap:
267267
defaultMode: 420

tests/templates/kuttl/smoke/10-install-opensearch.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ spec:
1717
storage:
1818
data:
1919
capacity: 100Mi
20+
gracefulShutdownTimeout: 3m
2021
replicas: 3
2122
podOverrides:
2223
spec:

0 commit comments

Comments
 (0)