diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8571479..101119a 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -24,14 +24,26 @@ jobs:
format-and-clippy:
uses: ./.github/workflows/format-workflow.yaml
secrets: inherit
+ tests:
+ uses: ./.github/workflows/tests-workflow.yaml
+ secrets: inherit
build:
runs-on: ubuntu-latest
- needs: [format-and-clippy]
+ needs: [format-and-clippy, tests]
+ strategy:
+ fail-fast: true
+ matrix:
+ include:
+ - name: "library"
+ path: "."
+ - name: "client"
+ path: "pvc-snapshotter-client"
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt, clippy
toolchain: ${{ env.RUST_VERSION }}
- - name: Build
+ - name: Cargo Build ${{ matrix.name }}
run: cargo build --verbose
+ working-directory: ${{ matrix.path }}
diff --git a/.github/workflows/tests-workflow.yaml b/.github/workflows/tests-workflow.yaml
new file mode 100644
index 0000000..82adc44
--- /dev/null
+++ b/.github/workflows/tests-workflow.yaml
@@ -0,0 +1,24 @@
+name: Tests Pipeline
+
+on:
+ workflow_call:
+
+env:
+ RUST_VERSION: 1.81.0
+
+jobs:
+ tests:
+ name: Run Tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ components: rustfmt, clippy
+ toolchain: ${{ env.RUST_VERSION }}
+ - name: Install cargo-nextest
+ uses: baptiste0928/cargo-install@v3
+ with:
+ crate: cargo-nextest
+ - name: Run tests
+ run: cargo nextest run --all
diff --git a/Cargo.lock b/Cargo.lock
index 8f3101e..adebcbb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -769,6 +769,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "downcast"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
+
[[package]]
name = "dyn-clone"
version = "1.0.17"
@@ -838,6 +844,12 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fragile"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
+
[[package]]
name = "futures"
version = "0.3.30"
@@ -1517,6 +1529,32 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "mockall"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a"
+dependencies = [
+ "cfg-if",
+ "downcast",
+ "fragile",
+ "mockall_derive",
+ "predicates",
+ "predicates-tree",
+]
+
+[[package]]
+name = "mockall_derive"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -1739,6 +1777,32 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "predicates"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
+dependencies = [
+ "anstyle",
+ "predicates-core",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
+dependencies = [
+ "predicates-core",
+ "termtree",
+]
+
[[package]]
name = "pretty_assertions"
version = "1.4.1"
@@ -1771,6 +1835,7 @@ dependencies = [
"k8s-openapi",
"kube",
"kube-custom-resources-rs",
+ "mockall",
"pretty_assertions",
"schemars",
"serde",
@@ -1793,6 +1858,7 @@ dependencies = [
"k8s-openapi",
"kube",
"kube-custom-resources-rs",
+ "mockall",
"pretty_assertions",
"pvc-snapshotter",
"schemars",
@@ -2297,6 +2363,12 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "termtree"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
+
[[package]]
name = "thiserror"
version = "1.0.64"
diff --git a/Cargo.toml b/Cargo.toml
index 4abfc56..055e3d2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,6 +32,7 @@ tokio = { version = "1", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
pvc-snapshotter = { path = ".", version = "0.1" }
+mockall = "0.13"
[dependencies]
anyhow.workspace = true
@@ -50,6 +51,7 @@ serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
+mockall.workspace = true
[lib]
test = true
diff --git a/README.md b/README.md
index 05d1caa..3f28927 100644
--- a/README.md
+++ b/README.md
@@ -33,12 +33,6 @@ The tool supports three primary modes of operation:
Restore: Restore PVCs from existing snapshots.
Full: Run both backup and restore operations in a single process.
-
-
-> [!NOTE]
-> Average percentage of time saved by PVC-Snapshotter compared to [Velero](https://github.com/vmware-tanzu/velero): **X%**.
-
-
## Features
- **Backup**: Create Kubernetes VolumeSnapshots from existing PVCs
- **Restore**: Restore PVCs to any namespace from a VolumeSnapshot
diff --git a/pvc-snapshotter-client/Cargo.toml b/pvc-snapshotter-client/Cargo.toml
index a11dedf..14ac038 100644
--- a/pvc-snapshotter-client/Cargo.toml
+++ b/pvc-snapshotter-client/Cargo.toml
@@ -28,6 +28,7 @@ tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
pvc-snapshotter.workspace = true
+mockall = "0.13.0"
[features]
default = ["full"]
diff --git a/src/backup/backup_operator.rs b/src/backup/backup_operator.rs
index 11e805b..c936d2f 100644
--- a/src/backup/backup_operator.rs
+++ b/src/backup/backup_operator.rs
@@ -2,14 +2,15 @@ use super::backup_payload::BackupPayload;
use crate::{
aws_ops::ebs::create_ebs_client,
k8s_ops::{
- pvc::persistent_volume_claims::{check_if_pvc_exists, get_pvcs_available},
- vs::volume_snapshots::wait_untill_snapshot_is_ready,
- vs::volume_snapshots_operator::VolumeSnapshotOperator,
+ pvc::persistent_volume_claims::{check_if_pvc_exists, get_pvcs_available, KubePvcApi},
+ vs::{
+ volume_snapshots::wait_untill_snapshot_is_ready,
+ volume_snapshots_operator::VolumeSnapshotOperator,
+ },
vsc::retain_policy::VSCRetainPolicy,
},
};
use anyhow::{bail, Result};
-use k8s_openapi::api::core::v1::PersistentVolumeClaim;
use kube::{api::PostParams, Api, Client};
use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{
volumesnapshotcontents::VolumeSnapshotContent,
@@ -34,7 +35,9 @@ impl BackupOperator {
// Define the VolumeSnapshot and VolumeSnapshotContent APIs
let restore_k8s_apis_struct = BackupKubernetesApisStruct {
source_vs_api: Api::namespaced(k8s_client.clone(), backup_payload.source_ns()),
- source_pvcs_api: Api::namespaced(k8s_client.clone(), backup_payload.source_ns()),
+ source_pvcs_api: KubePvcApi {
+ api: Api::namespaced(k8s_client.clone(), backup_payload.source_ns()),
+ },
vsc_api: Api::all(k8s_client.clone()),
};
@@ -102,6 +105,6 @@ impl BackupOperator {
/// A struct for holding the Kubernetes APIs for the backup operation
struct BackupKubernetesApisStruct {
source_vs_api: Api,
- source_pvcs_api: Api,
+ source_pvcs_api: KubePvcApi,
vsc_api: Api,
}
diff --git a/src/k8s_ops/pvc/persistent_volume_claims.rs b/src/k8s_ops/pvc/persistent_volume_claims.rs
index 2b1fb9c..f2435b7 100644
--- a/src/k8s_ops/pvc/persistent_volume_claims.rs
+++ b/src/k8s_ops/pvc/persistent_volume_claims.rs
@@ -1,14 +1,51 @@
use anyhow::Result;
+use async_trait::async_trait;
use k8s_openapi::api::core::v1::PersistentVolumeClaim;
-use kube::api::ListParams;
+use kube::{api::ListParams, Api};
use tracing::info;
+#[cfg(test)]
+use mockall::automock;
+
+#[cfg_attr(test, automock)]
+#[async_trait]
+pub trait PvcApiTrait {
+ async fn list_pvcs(&self) -> Result>;
+ async fn get(&self, name: &str) -> Result;
+ async fn create(&self, pvc: PersistentVolumeClaim) -> Result;
+}
+
+pub struct KubePvcApi {
+ pub api: Api,
+}
+
+/// Implement the PvcApi trait for PVC Api
+/// This will allow us to call the functions defined in the PvcApi trait on an instance of KubePvcApi.
+/// This is useful for testing, as we can mock the KubePvcApi struct and implement the PvcApi trait
+/// to return mock data.
+/// This way, we can test the functions that use the KubePvcApi struct without actually making
+/// calls to the Kubernetes API.
+#[async_trait]
+impl PvcApiTrait for KubePvcApi {
+ async fn list_pvcs(&self) -> Result> {
+ let pvcs = self.api.list(&ListParams::default()).await?;
+ Ok(pvcs.items)
+ }
+
+ async fn get(&self, name: &str) -> Result {
+ let pvc = self.api.get(name).await?;
+ Ok(pvc)
+ }
+
+ async fn create(&self, pvc: PersistentVolumeClaim) -> Result {
+ let pvc = self.api.create(&Default::default(), &pvc).await?;
+ Ok(pvc)
+ }
+}
+
/// Get the list of PersistentVolumeClaims available
-pub async fn get_pvcs_available(
- target_pvc_api: &kube::Api,
-) -> Result> {
- let lp = ListParams::default();
- let pvc_list: Vec<_> = match target_pvc_api.list(&lp).await {
+pub async fn get_pvcs_available(pvc_api: &impl PvcApiTrait) -> Result> {
+ let pvc_list: Vec<_> = match pvc_api.list_pvcs().await {
Ok(pvc) => pvc,
Err(e) => panic!("Failed to list PVCs: {}", e),
}
@@ -20,7 +57,8 @@ pub async fn get_pvcs_available(
}
pub async fn check_if_pvc_exists(
- target_pvc_api: &kube::Api,
+ target_pvc_api: &impl PvcApiTrait,
+ //&kube::Api,
pvc_name: &str,
should_exist: bool,
) -> Result