- Add Kubernetes cluster management and operations - Include pod, service, and deployment management - Implement pattern-based resource deletion - Support namespace creation and management - Provide Rhai scripting wrappers for all functions - Include production safety features (timeouts, retries, rate limiting)
386 lines
11 KiB
Rust
386 lines
11 KiB
Rust
//! Integration tests for SAL Kubernetes
|
|
//!
|
|
//! These tests require a running Kubernetes cluster and appropriate credentials.
|
|
//! Set KUBERNETES_TEST_ENABLED=1 to run these tests.
|
|
|
|
use sal_kubernetes::KubernetesManager;
|
|
|
|
/// Check if Kubernetes integration tests should run
|
|
fn should_run_k8s_tests() -> bool {
|
|
std::env::var("KUBERNETES_TEST_ENABLED").unwrap_or_default() == "1"
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_kubernetes_manager_creation() {
|
|
if !should_run_k8s_tests() {
|
|
println!("Skipping Kubernetes integration tests. Set KUBERNETES_TEST_ENABLED=1 to enable.");
|
|
return;
|
|
}
|
|
|
|
let result = KubernetesManager::new("default").await;
|
|
match result {
|
|
Ok(_) => println!("Successfully created KubernetesManager"),
|
|
Err(e) => println!("Failed to create KubernetesManager: {}", e),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_namespace_operations() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return, // Skip if can't connect
|
|
};
|
|
|
|
// Test namespace creation (should be idempotent)
|
|
let test_namespace = "sal-test-namespace";
|
|
let result = km.namespace_create(test_namespace).await;
|
|
assert!(result.is_ok(), "Failed to create namespace: {:?}", result);
|
|
|
|
// Test creating the same namespace again (should not error)
|
|
let result = km.namespace_create(test_namespace).await;
|
|
assert!(
|
|
result.is_ok(),
|
|
"Failed to create namespace idempotently: {:?}",
|
|
result
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_pods_list() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return, // Skip if can't connect
|
|
};
|
|
|
|
let result = km.pods_list().await;
|
|
match result {
|
|
Ok(pods) => {
|
|
println!("Found {} pods in default namespace", pods.len());
|
|
|
|
// Verify pod structure
|
|
for pod in pods.iter().take(3) {
|
|
// Check first 3 pods
|
|
assert!(pod.metadata.name.is_some());
|
|
assert!(pod.metadata.namespace.is_some());
|
|
println!(
|
|
"Pod: {} in namespace: {}",
|
|
pod.metadata.name.as_ref().unwrap(),
|
|
pod.metadata.namespace.as_ref().unwrap()
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list pods: {}", e);
|
|
// Don't fail the test if we can't list pods due to permissions
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_services_list() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let result = km.services_list().await;
|
|
match result {
|
|
Ok(services) => {
|
|
println!("Found {} services in default namespace", services.len());
|
|
|
|
// Verify service structure
|
|
for service in services.iter().take(3) {
|
|
assert!(service.metadata.name.is_some());
|
|
println!("Service: {}", service.metadata.name.as_ref().unwrap());
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list services: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deployments_list() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let result = km.deployments_list().await;
|
|
match result {
|
|
Ok(deployments) => {
|
|
println!(
|
|
"Found {} deployments in default namespace",
|
|
deployments.len()
|
|
);
|
|
|
|
// Verify deployment structure
|
|
for deployment in deployments.iter().take(3) {
|
|
assert!(deployment.metadata.name.is_some());
|
|
println!("Deployment: {}", deployment.metadata.name.as_ref().unwrap());
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list deployments: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_resource_counts() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let result = km.resource_counts().await;
|
|
match result {
|
|
Ok(counts) => {
|
|
println!("Resource counts: {:?}", counts);
|
|
|
|
// Verify expected resource types are present
|
|
assert!(counts.contains_key("pods"));
|
|
assert!(counts.contains_key("services"));
|
|
assert!(counts.contains_key("deployments"));
|
|
assert!(counts.contains_key("configmaps"));
|
|
assert!(counts.contains_key("secrets"));
|
|
|
|
// Verify counts are reasonable (counts are usize, so always non-negative)
|
|
for (resource_type, count) in counts {
|
|
// Verify we got a count for each resource type
|
|
println!("Resource type '{}' has {} items", resource_type, count);
|
|
// Counts should be reasonable (not impossibly large)
|
|
assert!(
|
|
count < 10000,
|
|
"Count for {} seems unreasonably high: {}",
|
|
resource_type,
|
|
count
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to get resource counts: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_namespaces_list() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let result = km.namespaces_list().await;
|
|
match result {
|
|
Ok(namespaces) => {
|
|
println!("Found {} namespaces", namespaces.len());
|
|
|
|
// Should have at least default namespace
|
|
let namespace_names: Vec<String> = namespaces
|
|
.iter()
|
|
.filter_map(|ns| ns.metadata.name.as_ref())
|
|
.cloned()
|
|
.collect();
|
|
|
|
println!("Namespaces: {:?}", namespace_names);
|
|
assert!(namespace_names.contains(&"default".to_string()));
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list namespaces: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_pattern_matching_dry_run() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Test pattern matching without actually deleting anything
|
|
// We'll just verify that the regex patterns work correctly
|
|
let test_patterns = vec![
|
|
"test-.*", // Should match anything starting with "test-"
|
|
".*-temp$", // Should match anything ending with "-temp"
|
|
"nonexistent-.*", // Should match nothing (hopefully)
|
|
];
|
|
|
|
for pattern in test_patterns {
|
|
println!("Testing pattern: {}", pattern);
|
|
|
|
// Get all pods first
|
|
if let Ok(pods) = km.pods_list().await {
|
|
let regex = regex::Regex::new(pattern).unwrap();
|
|
let matching_pods: Vec<_> = pods
|
|
.iter()
|
|
.filter_map(|pod| pod.metadata.name.as_ref())
|
|
.filter(|name| regex.is_match(name))
|
|
.collect();
|
|
|
|
println!(
|
|
"Pattern '{}' would match {} pods: {:?}",
|
|
pattern,
|
|
matching_pods.len(),
|
|
matching_pods
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_namespace_exists_functionality() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Test that default namespace exists
|
|
let result = km.namespace_exists("default").await;
|
|
match result {
|
|
Ok(exists) => {
|
|
assert!(exists, "Default namespace should exist");
|
|
println!("Default namespace exists: {}", exists);
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to check if default namespace exists: {}", e);
|
|
}
|
|
}
|
|
|
|
// Test that a non-existent namespace doesn't exist
|
|
let result = km.namespace_exists("definitely-does-not-exist-12345").await;
|
|
match result {
|
|
Ok(exists) => {
|
|
assert!(!exists, "Non-existent namespace should not exist");
|
|
println!("Non-existent namespace exists: {}", exists);
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to check if non-existent namespace exists: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_manager_namespace_property() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let test_namespace = "test-namespace";
|
|
let km = match KubernetesManager::new(test_namespace).await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Verify the manager knows its namespace
|
|
assert_eq!(km.namespace(), test_namespace);
|
|
println!("Manager namespace: {}", km.namespace());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_error_handling() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Test getting a non-existent pod
|
|
let result = km.pod_get("definitely-does-not-exist-12345").await;
|
|
assert!(result.is_err(), "Getting non-existent pod should fail");
|
|
|
|
if let Err(e) = result {
|
|
println!("Expected error for non-existent pod: {}", e);
|
|
// Verify it's the right kind of error
|
|
match e {
|
|
sal_kubernetes::KubernetesError::ApiError(_) => {
|
|
println!("Correctly got API error for non-existent resource");
|
|
}
|
|
_ => {
|
|
println!("Got unexpected error type: {:?}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_configmaps_and_secrets() {
|
|
if !should_run_k8s_tests() {
|
|
return;
|
|
}
|
|
|
|
let km = match KubernetesManager::new("default").await {
|
|
Ok(km) => km,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Test configmaps listing
|
|
let result = km.configmaps_list().await;
|
|
match result {
|
|
Ok(configmaps) => {
|
|
println!("Found {} configmaps in default namespace", configmaps.len());
|
|
for cm in configmaps.iter().take(3) {
|
|
if let Some(name) = &cm.metadata.name {
|
|
println!("ConfigMap: {}", name);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list configmaps: {}", e);
|
|
}
|
|
}
|
|
|
|
// Test secrets listing
|
|
let result = km.secrets_list().await;
|
|
match result {
|
|
Ok(secrets) => {
|
|
println!("Found {} secrets in default namespace", secrets.len());
|
|
for secret in secrets.iter().take(3) {
|
|
if let Some(name) = &secret.metadata.name {
|
|
println!("Secret: {}", name);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Failed to list secrets: {}", e);
|
|
}
|
|
}
|
|
}
|