Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Pdb struct and PdbBuilder #653

Merged
merged 25 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub mod configmap;
pub mod event;
pub mod meta;
pub mod pdb;
pub mod pod;

#[deprecated(since = "0.15.0", note = "Please use `builder::configmap::*` instead.")]
Expand Down
267 changes: 267 additions & 0 deletions src/builder/pdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
use crate::{
builder::ObjectMetaBuilder,
error::OperatorResult,
labels::{role_selector_labels, APP_MANAGED_BY_LABEL},
utils::format_full_controller_name,
};
use k8s_openapi::{
api::policy::v1::{PodDisruptionBudget, PodDisruptionBudgetSpec},
apimachinery::pkg::{
apis::meta::v1::{LabelSelector, ObjectMeta},
util::intstr::IntOrString,
},
};
use kube::{Resource, ResourceExt};

#[derive(Debug, Default)]
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
pub struct PdbBuilder<ObjectMeta, LabelSelector, Constraints> {
metadata: ObjectMeta,
selector: LabelSelector,
/// We intentionally only support fixed numbers, so percentage, see ADR on Pod disruptions for details.
/// We use u16, as [`IntOrString`] takes an i32 and we don't want to allow negative numbers. u16 will always fit in i32.
max_unavailable: Option<u16>,
/// We intentionally only support fixed numbers, so percentage, see ADR on Pod disruptions for details.
/// We use u16, as [`IntOrString`] takes an i32 and we don't want to allow negative numbers. u16 will always fit in i32.
min_available: Option<u16>,
/// Tracks wether either `max_unavailable` or `min_available` are set
_constraints: Constraints,
}

impl PdbBuilder<(), (), ()> {
pub fn new() -> Self {
PdbBuilder::default()
}

pub fn new_for_role<T: Resource<DynamicType = ()>>(
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
owner: &T,
app_name: &str,
role: &str,
operator_name: &str,
controller_name: &str,
) -> OperatorResult<PdbBuilder<ObjectMeta, LabelSelector, ()>> {
let role_selector_labels = role_selector_labels(owner, app_name, role);
let metadata = ObjectMetaBuilder::new()
.namespace_opt(owner.namespace())
.name(format!("{}-{}", owner.name_any(), role))
.ownerreference_from_resource(owner, None, Some(true))?
.with_labels(role_selector_labels.clone())
.with_label(
APP_MANAGED_BY_LABEL.to_string(),
format_full_controller_name(operator_name, controller_name),
)
.build();
Ok(PdbBuilder {
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
metadata,
selector: LabelSelector {
match_expressions: None,
match_labels: Some(role_selector_labels),
},
..PdbBuilder::default()
})
}

pub fn metadata(self, metadata: impl Into<ObjectMeta>) -> PdbBuilder<ObjectMeta, (), ()> {
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
PdbBuilder {
metadata: metadata.into(),
selector: self.selector,
max_unavailable: self.max_unavailable,
min_available: self.min_available,
_constraints: self._constraints,
}
}
}

impl PdbBuilder<ObjectMeta, (), ()> {
pub fn selector(self, selector: LabelSelector) -> PdbBuilder<ObjectMeta, LabelSelector, ()> {
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
PdbBuilder {
metadata: self.metadata,
selector,
max_unavailable: self.max_unavailable,
min_available: self.min_available,
_constraints: self._constraints,
}
}
}

impl PdbBuilder<ObjectMeta, LabelSelector, ()> {
pub fn max_unavailable(
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
self,
max_unavailable: u16,
) -> PdbBuilder<ObjectMeta, LabelSelector, bool> {
PdbBuilder {
metadata: self.metadata,
selector: self.selector,
max_unavailable: Some(max_unavailable),
min_available: self.min_available,
_constraints: true, // Some dummy value to set Constraints to something other than ()
}
}

#[deprecated(
since = "0.51.0",
note = "It is strongly recommended to use [`max_unavailable`]. Please read the ADR on Pod disruptions before using this function."
)]
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
pub fn min_available(self, min_available: u16) -> PdbBuilder<ObjectMeta, LabelSelector, bool> {
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
PdbBuilder {
metadata: self.metadata,
selector: self.selector,
max_unavailable: self.max_unavailable,
min_available: Some(min_available),
_constraints: true, // Some dummy value to set Constraints to something other than ()
}
}
}

impl PdbBuilder<ObjectMeta, LabelSelector, bool> {
pub fn build(self) -> PodDisruptionBudget {
PodDisruptionBudget {
metadata: self.metadata,
spec: Some(PodDisruptionBudgetSpec {
max_unavailable: self.max_unavailable.map(i32::from).map(IntOrString::Int),
min_available: self.min_available.map(i32::from).map(IntOrString::Int),
selector: Some(self.selector),
/// As this is beta as of 1.27 we can not use it yet,
/// so this builder does not offer this attribute.
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
unhealthy_pod_eviction_policy: Default::default(),
}),
..Default::default()
}
}
}

#[cfg(test)]
mod test {
use std::collections::BTreeMap;

use k8s_openapi::{
api::policy::v1::{PodDisruptionBudget, PodDisruptionBudgetSpec},
apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString},
};
use kube::{core::ObjectMeta, CustomResource};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::builder::{ObjectMetaBuilder, OwnerReferenceBuilder};

use super::PdbBuilder;

#[test]
pub fn test_normal_build() {
let pdb = PdbBuilder::new()
.metadata(
ObjectMetaBuilder::new()
.namespace("default")
.name("trino")
.build(),
)
.selector(LabelSelector {
match_expressions: None,
match_labels: Some(BTreeMap::from([("foo".to_string(), "bar".to_string())])),
})
.min_available(42)
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
.build();

assert_eq!(
pdb,
PodDisruptionBudget {
metadata: ObjectMeta {
name: Some("trino".to_string()),
namespace: Some("default".to_string()),
..Default::default()
},
spec: Some(PodDisruptionBudgetSpec {
min_available: Some(IntOrString::Int(42)),
selector: Some(LabelSelector {
match_expressions: None,
match_labels: Some(BTreeMap::from([(
"foo".to_string(),
"bar".to_string()
)])),
}),
..Default::default()
}),
..Default::default()
}
)
}

#[test]
pub fn test_build_from_role() {
#[derive(
Clone, CustomResource, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize,
)]
#[kube(group = "test", version = "v1", kind = "TrinoCluster", namespaced)]
pub struct TrinoClusterSpec {}
let trino: TrinoCluster = serde_yaml::from_str(
"
apiVersion: test/v1
kind: TrinoCluster
metadata:
name: simple-trino
namespace: default
uid: 123 # Needed for the ownerreference
spec: {}
",
)
.unwrap();
let app_name = "trino";
let role = "worker";
let operator_name = "trino.stackable.tech";
let controller_name = "trino-operator-trino-controller";
let pdb = PdbBuilder::new_for_role(&trino, app_name, role, operator_name, controller_name)
.unwrap()
.max_unavailable(2)
.build();

assert_eq!(
pdb,
PodDisruptionBudget {
metadata: ObjectMeta {
name: Some("simple-trino-worker".to_string()),
namespace: Some("default".to_string()),
labels: Some(BTreeMap::from([
("app.kubernetes.io/name".to_string(), "trino".to_string()),
(
"app.kubernetes.io/instance".to_string(),
"simple-trino".to_string()
),
(
"app.kubernetes.io/managed-by".to_string(),
"trino.stackable.tech_trino-operator-trino-controller".to_string()
),
(
"app.kubernetes.io/component".to_string(),
"worker".to_string()
)
])),
owner_references: Some(vec![OwnerReferenceBuilder::new()
.initialize_from_resource(&trino)
.block_owner_deletion_opt(None)
.controller_opt(Some(true))
.build()
.unwrap()]),
..Default::default()
},
spec: Some(PodDisruptionBudgetSpec {
max_unavailable: Some(IntOrString::Int(2)),
selector: Some(LabelSelector {
match_expressions: None,
match_labels: Some(BTreeMap::from([
("app.kubernetes.io/name".to_string(), "trino".to_string()),
(
"app.kubernetes.io/instance".to_string(),
"simple-trino".to_string()
),
(
"app.kubernetes.io/component".to_string(),
"worker".to_string()
)
])),
}),
..Default::default()
}),
..Default::default()
}
)
}
}
32 changes: 16 additions & 16 deletions src/cluster_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@ use crate::{
},
},
error::{Error, OperatorResult},
k8s_openapi::{
api::{
apps::v1::{DaemonSet, DaemonSetSpec, StatefulSet, StatefulSetSpec},
batch::v1::Job,
core::v1::{
ConfigMap, ObjectReference, PodSpec, PodTemplateSpec, Secret, Service,
ServiceAccount,
},
rbac::v1::RoleBinding,
},
apimachinery::pkg::apis::meta::v1::{LabelSelector, LabelSelectorRequirement},
NamespaceResourceScope,
},
kube::{Resource, ResourceExt},
labels::{APP_INSTANCE_LABEL, APP_MANAGED_BY_LABEL, APP_NAME_LABEL},
utils::format_full_controller_name,
};

use kube::core::ErrorResponse;
use k8s_openapi::{
api::{
apps::v1::{DaemonSet, DaemonSetSpec, StatefulSet, StatefulSetSpec},
batch::v1::Job,
core::v1::{
ConfigMap, ObjectReference, PodSpec, PodTemplateSpec, Secret, Service, ServiceAccount,
},
policy::v1::PodDisruptionBudget,
rbac::v1::RoleBinding,
},
apimachinery::pkg::apis::meta::v1::{LabelSelector, LabelSelectorRequirement},
NamespaceResourceScope,
};
use kube::{core::ErrorResponse, Resource, ResourceExt};
use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::{BTreeMap, HashSet},
Expand Down Expand Up @@ -161,10 +160,11 @@ impl ClusterResourceApplyStrategy {
}

impl ClusterResource for ConfigMap {}
impl ClusterResource for Secret {}
impl ClusterResource for Service {}
impl ClusterResource for ServiceAccount {}
impl ClusterResource for RoleBinding {}
impl ClusterResource for Secret {}
impl ClusterResource for PodDisruptionBudget {}

impl ClusterResource for Job {
fn pod_spec(&self) -> Option<&PodSpec> {
Expand Down
1 change: 1 addition & 0 deletions src/commons/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod authentication;
pub mod cluster_operation;
pub mod listener;
pub mod opa;
pub mod pdb;
pub mod product_image_selection;
pub mod rbac;
pub mod resources;
Expand Down
29 changes: 29 additions & 0 deletions src/commons/pdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Pdb {
/// Wether a PodDisruptionBudget should be written out for this role.
/// Disabling this enables you to specify you own - custom - one.
/// Defaults to true.
#[serde(default = "default_pdb_enabled")]
pub enabled: bool,
/// The number of Pods that are allowed to be down because of voluntary disruptions.
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
/// If you don't explicitly set this, the operator will use a sane default based
/// upon knowledge about the individual product.
pub max_unavailable: Option<u16>,
}

fn default_pdb_enabled() -> bool {
true
}
sbernauer marked this conversation as resolved.
Show resolved Hide resolved

impl Default for Pdb {
fn default() -> Self {
Self {
enabled: true,
max_unavailable: None,
}
}
}
Loading
Loading