diff --git a/charts/tembo-operator/Chart.lock b/charts/tembo-operator/Chart.lock index ba2804e61..b1387b355 100644 --- a/charts/tembo-operator/Chart.lock +++ b/charts/tembo-operator/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: cloudnative-pg repository: https://cloudnative-pg.github.io/charts - version: 0.21.6 -digest: sha256:3922d990e9dec07c6dda1f7b8799e9cfd2ef28450357f5a3f260a3d4773e5db2 -generated: "2024-09-04T09:47:10.610286988-05:00" + version: 0.22.1 +digest: sha256:3154534b59a8f8aa31308f2c3a13358241708a677a46f9012d19f69425ce882f +generated: "2024-12-19T14:42:05.362791735-06:00" diff --git a/charts/tembo-operator/Chart.yaml b/charts/tembo-operator/Chart.yaml index c635b1f73..13f22dd9f 100644 --- a/charts/tembo-operator/Chart.yaml +++ b/charts/tembo-operator/Chart.yaml @@ -17,6 +17,6 @@ maintainers: url: https://tembocommunity.slack.com dependencies: - name: cloudnative-pg - version: 0.21.6 + version: 0.22.1 repository: https://cloudnative-pg.github.io/charts condition: cloudnative-pg.enabled diff --git a/charts/tembo-operator/charts/cloudnative-pg-0.21.6.tgz b/charts/tembo-operator/charts/cloudnative-pg-0.21.6.tgz deleted file mode 100644 index 3028f0f66..000000000 Binary files a/charts/tembo-operator/charts/cloudnative-pg-0.21.6.tgz and /dev/null differ diff --git a/charts/tembo-operator/charts/cloudnative-pg-0.22.1.tgz b/charts/tembo-operator/charts/cloudnative-pg-0.22.1.tgz new file mode 100644 index 000000000..3d7d7e972 Binary files /dev/null and b/charts/tembo-operator/charts/cloudnative-pg-0.22.1.tgz differ diff --git a/tembo-operator/Cargo.lock b/tembo-operator/Cargo.lock index 70ec03a0d..24fa5d848 100644 --- a/tembo-operator/Cargo.lock +++ b/tembo-operator/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -503,7 +503,7 @@ dependencies = [ [[package]] name = "controller" -version = "0.52.1" +version = "0.53.0" dependencies = [ "actix-web", "anyhow", @@ -530,6 +530,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "serial_test", "strum", "thiserror", "tokio", @@ -2227,6 +2228,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scc" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b13f8ea6177672c49d12ed964cca44836f59621981b04a3e26b87e675181de" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.23" @@ -2267,6 +2277,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdd" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478f121bb72bbf63c52c93011ea1791dca40140dfe13f8336c4c5ac952c33aa9" + [[package]] name = "secrecy" version = "0.8.0" @@ -2385,6 +2401,31 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/tembo-operator/Cargo.toml b/tembo-operator/Cargo.toml index 9391865d4..1181b8845 100644 --- a/tembo-operator/Cargo.toml +++ b/tembo-operator/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "controller" description = "Tembo Operator for Postgres" -version = "0.52.1" +version = "0.53.0" edition = "2021" default-run = "controller" license = "Apache-2.0" @@ -68,6 +68,7 @@ rand = "0.8.5" tower-test = "0.4.0" futures-util = "0.3" regex = "1" +serial_test = "3" [dependencies.kube] features = ["runtime", "client", "derive", "ws"] diff --git a/tembo-operator/Dockerfile b/tembo-operator/Dockerfile index 068b92f09..2e1e51b88 100644 --- a/tembo-operator/Dockerfile +++ b/tembo-operator/Dockerfile @@ -1,6 +1,6 @@ # This does not build on ARM laptops, even with the --platform flag. -FROM --platform=linux/amd64 quay.io/tembo/muslrust:1.77.0-stable AS builder +FROM quay.io/tembo/muslrust:1.82.0-stable AS builder WORKDIR /build @@ -8,7 +8,7 @@ COPY . . RUN cargo build --release --target=x86_64-unknown-linux-musl -FROM --platform=linux/amd64 quay.io/tembo/alpine:3.18.2 +FROM quay.io/tembo/alpine:3.21.0 RUN adduser -D nonroot diff --git a/tembo-operator/justfile b/tembo-operator/justfile index 1f02b674c..4c5057090 100644 --- a/tembo-operator/justfile +++ b/tembo-operator/justfile @@ -3,7 +3,7 @@ ORG := "localhost:5001" VERSION := `git rev-parse HEAD` SEMVER_VERSION := `grep version Cargo.toml | awk -F"\"" '{print $2}' | head -n 1` NAMESPACE := "default" -KUBE_VERSION := env_var_or_default('KUBE_VERSION', '1.25.8') +KUBE_VERSION := env_var_or_default('KUBE_VERSION', '1.29.8') STORAGE_CLASS_NAME := "standard" default: diff --git a/tembo-operator/src/apis/coredb_types.rs b/tembo-operator/src/apis/coredb_types.rs index 12d87bda0..0ea2e7a30 100644 --- a/tembo-operator/src/apis/coredb_types.rs +++ b/tembo-operator/src/apis/coredb_types.rs @@ -526,7 +526,7 @@ impl DedicatedNetworking { /// Generate the Kubernetes wrapper struct `CoreDB` from our Spec and Status struct /// /// This provides a hook for generating the CRD yaml (in crdgen.rs) - +/// /// CoreDBSpec represents the specification for a CoreDB instance. It defines /// various configuration options for deploying and managing the database. /// with the tembo-controller diff --git a/tembo-operator/src/cloudnativepg/hibernate.rs b/tembo-operator/src/cloudnativepg/hibernate.rs index a1803821d..afadae973 100644 --- a/tembo-operator/src/cloudnativepg/hibernate.rs +++ b/tembo-operator/src/cloudnativepg/hibernate.rs @@ -4,10 +4,11 @@ use crate::cloudnativepg::cnpg::{get_cluster, get_pooler, get_scheduled_backups} use crate::cloudnativepg::poolers::Pooler; use crate::cloudnativepg::scheduledbackups::ScheduledBackup; use crate::ingress::{delete_ingress_route, delete_ingress_route_tcp}; +use crate::prometheus::podmonitor_crd as podmon; use crate::Error; use crate::{patch_cdb_status_merge, requeue_normal_with_jitter, Context}; -use kube::api::{Patch, PatchParams}; +use kube::api::{DeleteParams, Patch, PatchParams}; use kube::runtime::controller::Action; use kube::{Api, ResourceExt}; use serde_json::json; @@ -74,77 +75,9 @@ pub async fn reconcile_cluster_hibernation(cdb: &CoreDB, ctx: &Arc) -> let client = ctx.client.clone(); let coredbs: Api = Api::namespaced(client.clone(), &namespace); - let deployment_api: Api = Api::namespaced(client.clone(), &namespace); - let ps = PatchParams::apply("patch_merge").force(); - - // Along with the CNPG cluster itself, we also need to stop each of the - // associated app services. We can do this by retrieving a list of depolyments - // in the cluster and setting their replica count to 0 so they spin down. - // Conversely, setting it back to 1 if the cluster is started should reverse - // the process. - - let replicas = if cdb.spec.stop { 0 } else { 1 }; - let replica_patch = json!({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "spec": { - "replicas": replicas, - } - }); - let deployment_list = match get_appservice_deployment_objects(&client, &namespace, &name).await - { - Ok(deployments) => deployments, - Err(e) => { - warn!( - "Could not retrieve deployment list for cluster {}; retrying", - name - ); - debug!("Caught error {}", e); - return Err(requeue_normal_with_jitter()); - } - }; - - // We just need to patch any deployment that has a mismatched replica count. - // If we're stopped, the deployment should have 0 replicas, or 1 otherwise. - // We may need to rethink this logic in the future if we ever have deployments - // that require allow more than 1 active replica. - - for deployment in deployment_list { - let spec = match deployment.spec { - Some(spec) => spec, - None => continue, - }; - let dep_name = match deployment.metadata.name { - Some(dep_name) => dep_name, - None => continue, - }; - - if Some(replicas) == spec.replicas { - continue; - } - - match deployment_api - .patch(&dep_name, &ps, &Patch::Apply(&replica_patch)) - .await - .map_err(Error::KubeError) - { - Ok(_) => { - debug!( - "ns: {}, patched AppService Deployment: {}", - &namespace, dep_name - ); - } - Err(e) => { - warn!( - "Could not patch deployment {} for cluster {}; retrying", - dep_name, name - ); - debug!("Caught error {}", e); - return Err(requeue_normal_with_jitter()); - } - } - } + // Patch all AppService deployments to match the hibernation state + patch_appservice_deployments(ctx, &namespace, &name, cdb).await?; if cdb.spec.stop { // Remove IngressRoutes for stopped instances @@ -264,12 +197,18 @@ pub async fn reconcile_cluster_hibernation(cdb: &CoreDB, ctx: &Arc) -> let hibernation_value = if cdb.spec.stop { "on" } else { "off" }; // Build the hibernation patch we want to apply to disable the CNPG cluster. + // This will also disable the PodMonitor for the cluster. let patch_hibernation_annotation = json!({ "metadata": { "annotations": { "cnpg.io/hibernation": hibernation_value, "cnpg.io/reconciliationLoop": stop_cnpg_reconciliation_value, } + }, + "spec": { + "monitoring": { + "enablePodMonitor": !cdb.spec.stop, + } } }); // Update ScheduledBackup if it exists @@ -337,6 +276,132 @@ pub async fn reconcile_cluster_hibernation(cdb: &CoreDB, ctx: &Arc) -> Ok(()) } +/// Patches AppService deployments in a Kubernetes cluster by updating their replica count based on the CoreDB specification. +/// +/// This function performs the following operations: +/// * Updates the replica count to 0 if CoreDB is stopped, or 1 if it's running +/// * Deletes associated PodMonitor resources when scaling down to 0 replicas +/// * Applies patches to all relevant deployments in the specified namespace +/// +/// # Arguments +/// +/// * `ctx` - A shared reference to the Context containing the Kubernetes client +/// * `namespace` - The Kubernetes namespace where the deployments exist +/// * `name` - The name identifier for the cluster +/// * `cdb` - Reference to the CoreDB specification containing the stop status +/// +/// # Returns +/// +/// * `Ok(())` if all patches were successfully applied +/// * `Err(Action)` if any operation fails, triggering a requeue with jitter +/// +/// # Errors +/// +/// This function will return an error in the following situations: +/// * Failed to retrieve deployment list +/// * Failed to delete PodMonitor when scaling down +/// * Failed to apply deployment patches +async fn patch_appservice_deployments( + ctx: &Arc, + namespace: &str, + name: &str, + cdb: &CoreDB, +) -> Result<(), Action> { + let client = ctx.client.clone(); + let deployment_api: Api = Api::namespaced(client.clone(), namespace); + let podmonitor_api: Api = Api::namespaced(client.clone(), namespace); + let ps = PatchParams::apply("patch_merge").force(); + + let replicas = if cdb.spec.stop { 0 } else { 1 }; + let replica_patch = json!({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "spec": { + "replicas": replicas, + } + }); + + let deployment_list = match get_appservice_deployment_objects(&client, namespace, name).await { + Ok(deployments) => deployments, + Err(e) => { + warn!( + "Could not retrieve deployment list for cluster {}; retrying", + name + ); + debug!("Caught error {}", e); + return Err(requeue_normal_with_jitter()); + } + }; + + for deployment in deployment_list { + let Some(spec) = deployment.spec else { + continue; + }; + let Some(dep_name) = deployment.metadata.name else { + continue; + }; + + if Some(replicas) == spec.replicas { + continue; + } + + // Each appService deployment will have a corresponding PodMonitor + // We need to delete the PodMonitor when scaling down + if replicas == 0 { + // When scaling down, try to delete the PodMonitor if it exists + match podmonitor_api + .delete(&dep_name, &DeleteParams::default()) + .await + { + Ok(_) => { + debug!( + "ns: {}, deleted PodMonitor for Deployment: {}", + &namespace, dep_name + ); + } + Err(kube::Error::Api(api_err)) if api_err.code == 404 => { + // PodMonitor doesn't exist, that's fine + debug!( + "ns: {}, no PodMonitor found for Deployment: {}", + &namespace, dep_name + ); + } + Err(e) => { + warn!( + "Could not delete PodMonitor for deployment {} in cluster {}; retrying", + dep_name, name + ); + debug!("Caught error {}", e); + return Err(requeue_normal_with_jitter()); + } + } + } + + match deployment_api + .patch(&dep_name, &ps, &Patch::Apply(&replica_patch)) + .await + .map_err(Error::KubeError) + { + Ok(_) => { + debug!( + "ns: {}, patched AppService Deployment: {}", + &namespace, dep_name + ); + } + Err(e) => { + warn!( + "Could not patch deployment {} for cluster {}; retrying", + dep_name, name + ); + debug!("Caught error {}", e); + return Err(requeue_normal_with_jitter()); + } + } + } + + Ok(()) +} + async fn update_pooler_instances( pooler: &Option, cdb: &CoreDB, @@ -491,9 +556,9 @@ mod tests { #[test] fn test_is_cluster_hibernated() { // Not hibernated yet: still in progress - assert_eq!(is_cluster_hibernated(&hibernation_in_progress()), false); + assert!(!is_cluster_hibernated(&hibernation_in_progress())); // Not hibernated: unrelated condition - assert_eq!(is_cluster_hibernated(&backed_up_cluster()), false); + assert!(!is_cluster_hibernated(&backed_up_cluster())); // Hibernated: "type" is "cnpg.io/hibernation" and "status" is "True" assert!(is_cluster_hibernated(&hibernation_completed())); } diff --git a/tembo-operator/tests/integration_tests.rs b/tembo-operator/tests/integration_tests.rs index 2a632a9e6..a9d4659f8 100644 --- a/tembo-operator/tests/integration_tests.rs +++ b/tembo-operator/tests/integration_tests.rs @@ -55,6 +55,7 @@ mod test { time::Duration, }; + use serial_test::serial; use tokio::{io::AsyncReadExt, time::timeout}; const API_VERSION: &str = "coredb.io/v1alpha1"; @@ -714,7 +715,7 @@ mod test { let mut passed_retry = false; let mut resource_list: Vec = Vec::new(); for _ in 0..retry { - let resources = api.list(&list_params).await?; + let resources = api.list(list_params).await?; if resources.items.len() == num_expected { resource_list.extend(resources.items); passed_retry = true; @@ -955,6 +956,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_basic_cnpg() { let test_name = "test-basic-cnpg"; @@ -1036,6 +1038,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_basic_cnpg_assuming_latest_version() { let test_name = "test-basic-cnpg"; @@ -1115,6 +1118,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_basic_cnpg_pg16() { // Initialize the Kubernetes client @@ -1219,6 +1223,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_cnpg_metrics_create() { // Initialize the Kubernetes client @@ -1494,6 +1499,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_cnpg_pgparams() { // Initialize the Kubernetes client @@ -1717,6 +1723,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_skip_reconciliation() { // Initialize the Kubernetes client @@ -1798,6 +1805,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_delete_namespace() { // Initialize the Kubernetes client @@ -1902,6 +1910,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn test_networking() { // Initialize the Kubernetes client @@ -2333,6 +2342,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ha_basic_cnpg() { // Initialize the Kubernetes client @@ -2419,6 +2429,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ha_upgrade_cnpg() { // Initialize the Kubernetes client @@ -2587,6 +2598,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_shared_preload_libraries() { // Initialize the Kubernetes client @@ -2718,6 +2730,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ext_with_load() { // Initialize the Kubernetes client @@ -3079,6 +3092,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ha_two_replicas() { // Initialize the Kubernetes client @@ -3236,6 +3250,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ha_verify_extensions_ha_later() { // Initialize the Kubernetes client @@ -3461,6 +3476,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_ha_shared_preload_libraries() { // Initialize the Kubernetes client @@ -3750,6 +3766,7 @@ mod test { } #[tokio::test] + #[serial] #[ignore] async fn functional_test_app_service() { // Initialize the Kubernetes client @@ -4405,6 +4422,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn restarts_postgres_correctly() { async fn wait_til_status_is_filled(coredbs: &Api, name: &str) { @@ -4613,6 +4631,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_status_configs() { async fn runtime_cfg(coredbs: &Api, name: &str) -> Option> { @@ -4846,6 +4865,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_backup_and_restore() { // Initialize the Kubernetes client @@ -5266,6 +5286,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_pooler() { // Initialize the Kubernetes client @@ -5338,8 +5359,19 @@ CREATE EVENT TRIGGER pgrst_watch // Check for pooler service let pooler_services: Api = Api::namespaced(client.clone(), &namespace); - let _pooler_service = pooler_services.get(&pooler_name).await.unwrap(); - println!("Found pooler service: {}", pooler_name); + for _ in 0..12 { + match pooler_services.get(&pooler_name).await { + Ok(_) => { + println!("Found pooler service: {}", pooler_name); + break; + } + Err(_) => { + println!("Waiting for pooler service to be created: {}", pooler_name); + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + } + } + } // Check for pooler secret let pooler_secrets: Api = Api::namespaced(client.clone(), &namespace); @@ -5370,11 +5402,23 @@ CREATE EVENT TRIGGER pgrst_watch // Check for pooler IngressRouteTCP let pooler_ingressroutetcps: Api = Api::namespaced(client.clone(), &namespace); - let _pooler_ingressroutetcp = pooler_ingressroutetcps - .get(format!("{pooler_name}-0").as_str()) - .await - .unwrap(); - println!("Found pooler IngressRouteTCP: {pooler_name}-0"); + let ingressroute_name = format!("{pooler_name}-0"); + for _ in 0..12 { + match pooler_ingressroutetcps.get(&ingressroute_name).await { + Ok(_) => { + println!("Found pooler IngressRouteTCP: {}", ingressroute_name); + break; + } + Err(_) => { + println!( + "Waiting for pooler IngressRouteTCP to be created: {}", + ingressroute_name + ); + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + } + } + } // Query the database to make sure the pgbouncer role was created let _pgb_query = wait_until_psql_contains( @@ -5452,6 +5496,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_restart_and_update_replicas() { // Initialize the Kubernetes client @@ -5602,6 +5647,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_cnpg_update_image_and_params() { // Initialize the Kubernetes client @@ -5807,6 +5853,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] /// Test the hibernation system /// Ensure that the cluster performs the following: @@ -5921,6 +5968,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_hibernate_with_app_service() { let test_name = "test-hibernate-cnpg-with-app-service"; @@ -6060,7 +6108,28 @@ CREATE EVENT TRIGGER pgrst_watch // Assert there are 4 pods running: postgres, pooler, postgrest and ferretdb let pods: Api = Api::namespaced(test.client.clone(), &namespace); let pods_list = pods.list(&Default::default()).await.unwrap(); - assert_eq!(pods_list.items.len(), 4); + let required_pods = ["postgres", "pooler", "postgrest", "fdb-api"]; + + // Check each required pod exists + for required_pod in required_pods { + let pod_exists = pods_list.items.iter().any(|pod| { + pod.metadata + .name + .as_ref() + .map_or(false, |name| name.contains(required_pod)) + }); + + assert!( + pod_exists, + "Required pod '{}' was not found in namespace. Found pods: {:?}", + required_pod, + pods_list + .items + .iter() + .filter_map(|pod| pod.metadata.name.as_ref()) + .collect::>() + ); + } // Stop the cluster and check to make sure it's not running to ensure // hibernate is doing its job. @@ -6113,7 +6182,28 @@ CREATE EVENT TRIGGER pgrst_watch // Assert there are 4 pods running: postgres, pooler, postgrest and ferretdb let pods_list = pods.list(&Default::default()).await.unwrap(); - assert_eq!(pods_list.items.len(), 4); + let required_pods = ["postgres", "pooler", "postgrest", "fdb-api"]; + + // Check each required pod exists + for required_pod in required_pods { + let pod_exists = pods_list.items.iter().any(|pod| { + pod.metadata + .name + .as_ref() + .map_or(false, |name| name.contains(required_pod)) + }); + + assert!( + pod_exists, + "Required pod '{}' was not found in namespace. Found pods: {:?}", + required_pod, + pods_list + .items + .iter() + .filter_map(|pod| pod.metadata.name.as_ref()) + .collect::>() + ); + } // Assert there are 4 IngressRouteTCPs created after starting. One for postgres, pooler, // ferretdb and postgrest @@ -6144,6 +6234,7 @@ CREATE EVENT TRIGGER pgrst_watch } #[tokio::test] + #[serial] #[ignore] async fn functional_test_app_service_podmonitor() { // Validates PodMonitor resource created and destroyed properly @@ -6176,6 +6267,7 @@ CREATE EVENT TRIGGER pgrst_watch "name": cdb_name }, "spec": { + "stop": false, "appServices": [ { "name": "dummy-exporter", @@ -6208,6 +6300,51 @@ CREATE EVENT TRIGGER pgrst_watch let pmon_spec = podmon.spec.pod_metrics_endpoints.unwrap(); assert_eq!(pmon_spec.len(), 1); + // pause instance to make sure we remove the PodMonitor + let paused_app = serde_json::json!({ + "apiVersion": API_VERSION, + "kind": kind, + "metadata": { + "name": cdb_name + }, + "spec": { + "stop": true, + "appServices": [ + { + "name": "dummy-exporter", + "image": "prom/blackbox-exporter", + "routing": [ + { + "port": 9115, + "ingressPath": "/metrics", + "ingressType": "http" + } + ], + "metrics": { + "path": "/metrics", + "port": 9115 + } + }, + ] + } + }); + let params = PatchParams::apply("tembo-integration-test"); + let patch = Patch::Apply(&paused_app); + let _coredb_resource = coredbs.patch(cdb_name, ¶ms, &patch).await.unwrap(); + let podmon = + get_resource::(client.clone(), &namespace, &pmon_name, 50, false).await; + assert!(podmon.is_err()); + + // renable it, assert it exists again + let patch = Patch::Apply(&full_app); + let _coredb_resource = coredbs.patch(cdb_name, ¶ms, &patch).await.unwrap(); + let podmon = get_resource::(client.clone(), &namespace, &pmon_name, 50, true) + .await + .unwrap(); + + let pmon_spec = podmon.spec.pod_metrics_endpoints.unwrap(); + assert_eq!(pmon_spec.len(), 1); + // disable metrics let no_metrics_app = serde_json::json!({ "apiVersion": API_VERSION, @@ -6290,6 +6427,7 @@ CREATE EVENT TRIGGER pgrst_watch /// There used to be an issue figuring out the Trunk project version of one of the built-in Postgres language extensions (e.g. plpython, pltcl, plperl) /// given its extension version. This test should replicate that scenario. #[tokio::test] + #[serial] #[ignore] async fn functional_test_basic_cnpg_plpython() { let test_name = "test-basic-cnpg-plpython";