# Kubernetes Async/Await Guide

## Understanding Async in herolib-virt

All Kubernetes operations in `herolib-virt` are **asynchronous**. This guide explains why, how to use them, and best practices.

## Why Async?

### Problem It Solves

Traditional synchronous I/O blocks the entire thread:

```rust
// Synchronous (blocking) - thread is blocked waiting
let pod = km.pod_get("my-pod").unwrap();  // ← blocks here
// ... rest of code doesn't run until pod_get returns
```

With async:

```rust
// Asynchronous (non-blocking) - thread can do other work
let pod = km.pod_get("my-pod").await?;  // ← thread can switch to other tasks
// ... rest of code runs as soon as data is available
```

### Benefits

1. **Higher Concurrency**: Run 1000s of concurrent API calls without 1000s of threads
2. **Better Performance**: Fewer threads = less memory, less context switching
3. **Responsive Systems**: Thread isn't blocked waiting for network I/O
4. **Natural Fit**: Kubernetes API is inherently asynchronous

## Prerequisites

You need a tokio runtime. Add to Cargo.toml:

```toml
[dependencies]
tokio = { version = "1.0", features = ["full"] }
herolib-virt = { version = "...", features = ["kubernetes"] }
```

## Basic Usage Pattern

### Pattern 1: Single Async Function

```rust
use herolib_virt::kubernetes::KubernetesManager;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create manager in async context
    let km = KubernetesManager::new("default").await?;
    
    // Call async operations with `.await`
    let pods = km.pods_list().await?;
    
    for pod in pods {
        println!("Pod: {:?}", pod.metadata.name);
    }
    
    Ok(())
}
```

**Key points**:
- `#[tokio::main]` macro sets up tokio runtime
- All `.await` calls must be in async functions
- Function signature includes `async`

### Pattern 2: Async Function Called from Sync Code

```rust
// Your async function
async fn list_pods(namespace: &str) -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new(namespace).await?;
    let pods = km.pods_list().await?;
    println!("Found {} pods", pods.len());
    Ok(())
}

// Called from main
fn main() {
    // Create runtime explicitly
    let rt = tokio::runtime::Runtime::new().unwrap();
    
    // Run async code
    rt.block_on(list_pods("default")).unwrap();
}
```

### Pattern 3: Multiple Concurrent Operations

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    // Run multiple operations concurrently
    let (pods, services, deployments) = tokio::join!(
        km.pods_list(),
        km.services_list(),
        km.deployments_list()
    );
    
    println!("Pods: {}", pods?.len());
    println!("Services: {}", services?.len());
    println!("Deployments: {}", deployments?.len());
    
    Ok(())
}
```

**Benefits**: All three API calls run in parallel, not sequentially.

## Common Patterns

### Pattern: Processing Results

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    // Get list of deployments
    let deployments = km.deployments_list().await?;
    
    // Process each one
    for deployment in deployments {
        if let Some(name) = deployment.metadata.name {
            // Update each deployment
            println!("Checking deployment: {}", name);
            
            // Can await in loops
            let spec = km.deployment_get(&name).await?;
            println!("  Replicas: {:?}", 
                spec.spec.unwrap().replicas);
        }
    }
    
    Ok(())
}
```

### Pattern: Error Handling with Async

```rust
#[tokio::main]
async fn main() {
    let km = KubernetesManager::new("default").await.unwrap();
    
    // Handle errors explicitly
    match km.pod_get("nonexistent").await {
        Ok(pod) => println!("Found: {:?}", pod.metadata.name),
        Err(e) => eprintln!("Error: {}", e),  // Handle gracefully
    }
    
    // Or use ? operator in Result function
    if let Err(e) = create_and_delete_pod(&km).await {
        eprintln!("Operation failed: {}", e);
    }
}

async fn create_and_delete_pod(
    km: &KubernetesManager
) -> Result<(), Box<dyn std::error::Error>> {
    let labels = Default::default();
    
    km.pod_create("temp", "alpine:latest", labels).await?;
    println!("Pod created");
    
    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    
    km.pod_delete("temp").await?;
    println!("Pod deleted");
    
    Ok(())
}
```

### Pattern: Batch Operations with Timeout

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    // Set timeout for operation
    let timeout_result = tokio::time::timeout(
        std::time::Duration::from_secs(30),
        km.pods_list()
    ).await;
    
    match timeout_result {
        Ok(Ok(pods)) => println!("Listed {} pods", pods.len()),
        Ok(Err(e)) => eprintln!("API error: {}", e),
        Err(_) => eprintln!("Operation timed out after 30s"),
    }
    
    Ok(())
}
```

### Pattern: Spawning Background Tasks

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = std::sync::Arc::new(
        KubernetesManager::new("default").await?
    );
    
    // Spawn a background task
    let km_clone = km.clone();
    let monitor_task = tokio::spawn(async move {
        loop {
            match km_clone.pods_list().await {
                Ok(pods) => println!("Currently {} pods", pods.len()),
                Err(e) => eprintln!("Error: {}", e),
            }
            
            tokio::time::sleep(
                std::time::Duration::from_secs(10)
            ).await;
        }
    });
    
    // Do other work in main task
    tokio::time::sleep(std::time::Duration::from_secs(30)).await;
    
    // Cancel background task
    monitor_task.abort();
    
    Ok(())
}
```

### Pattern: Parallel Processing

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    let pods = km.pods_list().await?;
    
    // Process multiple pods in parallel
    let handles: Vec<_> = pods.iter().map(|pod| {
        let name = pod.metadata.name.clone().unwrap_or_default();
        let km = &km;
        
        async move {
            // Each pod processed concurrently
            match km.pod_get(&name).await {
                Ok(info) => println!("Pod {}: Ready", name),
                Err(e) => println!("Pod {} error: {}", name, e),
            }
        }
    }).collect();
    
    // Wait for all to complete
    for handle in handles {
        handle.await;
    }
    
    Ok(())
}
```

**Better approach with join_all**:

```rust
use futures::future;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    let pods = km.pods_list().await?;
    
    let futures: Vec<_> = pods.iter().map(|pod| {
        let name = pod.metadata.name.clone().unwrap_or_default();
        km.pod_get(&name)
    }).collect();
    
    // Wait for all concurrently
    let results = future::join_all(futures).await;
    
    for result in results {
        match result {
            Ok(pod) => println!("Got: {:?}", pod.metadata.name),
            Err(e) => println!("Error: {}", e),
        }
    }
    
    Ok(())
}
```

## Common Mistakes

### Mistake 1: Missing `.await`

```rust
// ❌ WRONG - forgets .await
#[tokio::main]
async fn main() {
    let km = KubernetesManager::new("default").await.unwrap();
    let pods = km.pods_list();  // ← Missing .await!
    println!("{}", pods.len()); // ← This won't compile
}

// ✅ CORRECT
#[tokio::main]
async fn main() {
    let km = KubernetesManager::new("default").await.unwrap();
    let pods = km.pods_list().await;  // ← Has .await
    println!("{}", pods.len());
}
```

### Mistake 2: Forgetting to Mark Function as Async

```rust
// ❌ WRONG - function is not async
fn list_pods() {
    let km = KubernetesManager::new("default").await.unwrap();  // ← Error!
}

// ✅ CORRECT
async fn list_pods() {
    let km = KubernetesManager::new("default").await.unwrap();
}
```

### Mistake 3: Calling Async Function Without Await

```rust
// ❌ WRONG - function returns Future, not result
#[tokio::main]
async fn main() {
    let result = list_pods();  // ← Returns Future
    println!("{:?}", result);  // ← Won't print what you expect
}

// ✅ CORRECT
#[tokio::main]
async fn main() {
    let result = list_pods().await;  // ← Actually waits for result
    println!("{:?}", result);
}
```

### Mistake 4: No Runtime

```rust
// ❌ WRONG - no tokio runtime
fn main() {
    list_pods().await;  // ← Error: `await` outside async context
}

// ✅ CORRECT
#[tokio::main]
async fn main() {
    list_pods().await;
}
```

### Mistake 5: Holding Non-Async References Across Await

```rust
// ❌ WRONG - reference doesn't implement Send
#[tokio::main]
async fn main() {
    let name = String::from("pod-name");
    
    tokio::spawn(async {
        let km = KubernetesManager::new("default").await.unwrap();
        km.pod_get(&name).await;  // ← Error: `name` not Send
    });
}

// ✅ CORRECT - use owned values
#[tokio::main]
async fn main() {
    let name = String::from("pod-name");
    
    tokio::spawn(async move {  // ← move ownership
        let km = KubernetesManager::new("default").await.unwrap();
        km.pod_get(&name).await;  // ← Now OK
    });
}
```

## Performance Tips

### 1. Reuse KubernetesManager

```rust
// ❌ INEFFICIENT - creates new manager for each operation
for _ in 0..1000 {
    let km = KubernetesManager::new("default").await?;
    km.pods_list().await?;
}

// ✅ EFFICIENT - reuse manager
let km = KubernetesManager::new("default").await?;
for _ in 0..1000 {
    km.pods_list().await?;
}
```

### 2. Use Concurrency for Independent Operations

```rust
// ❌ SLOW - sequential
let pods = km.pods_list().await?;
let svcs = km.services_list().await?;
let deps = km.deployments_list().await?;

// ✅ FAST - parallel
let (pods, svcs, deps) = tokio::join!(
    km.pods_list(),
    km.services_list(),
    km.deployments_list()
);
```

### 3. Use select! for Race Conditions

```rust
use tokio::select;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    // Wait for whichever completes first
    select! {
        result = km.pods_list() => {
            println!("Pods arrived first: {:?}", result?);
        }
        result = km.services_list() => {
            println!("Services arrived first: {:?}", result?);
        }
    }
    
    Ok(())
}
```

### 4. Set Appropriate Timeouts

```rust
use tokio::time::timeout;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    // Short timeout for health checks
    let health = timeout(
        std::time::Duration::from_secs(5),
        km.pods_list()
    ).await??;
    
    // Longer timeout for full list operations
    let pods = timeout(
        std::time::Duration::from_secs(60),
        km.pods_list()
    ).await??;
    
    Ok(())
}
```

## Debugging Async Code

### Enable Tokio Console

```toml
[dev-dependencies]
tokio-console = "0.1"

[[example]]
name = "async_debug"
```

```rust
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() {
    // Your code here
}
```

Run with: `TOKIO_CONSOLE=1 cargo run --example async_debug`

### Add Tracing

```rust
use tracing::{debug, info};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();
    
    let km = KubernetesManager::new("default").await?;
    
    info!("Listing pods");
    let pods = km.pods_list().await?;
    debug!("Found {} pods", pods.len());
    
    Ok(())
}
```

### Use dbg! with Async

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("default").await?;
    
    let pods = dbg!(km.pods_list().await?);
    println!("Pods: {:?}", pods);
    
    Ok(())
}
```

## Real-World Example: Deployment Monitor

```rust
use herolib_virt::kubernetes::KubernetesManager;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let km = KubernetesManager::new("prod").await?;
    
    loop {
        // Get all deployments
        let deployments = km.deployments_list().await?;
        
        // Check each one
        for deployment in deployments {
            if let Some(name) = deployment.metadata.name {
                // Get detailed status
                match km.deployment_get(&name).await {
                    Ok(dep) => {
                        let spec = dep.spec.unwrap();
                        let status = dep.status.unwrap();
                        
                        println!(
                            "Deployment {}: {}/{} ready",
                            name,
                            status.ready_replicas.unwrap_or(0),
                            spec.replicas.unwrap_or(0)
                        );
                    }
                    Err(e) => println!("Error checking {}: {}", name, e),
                }
            }
        }
        
        // Wait before next check
        tokio::time::sleep(Duration::from_secs(10)).await;
    }
}
```

## See Also

- [Tokio Tutorial](https://tokio.rs/tokio/tutorial)
- [Async Rust Book](https://rust-lang.github.io/async-book/)
- [Futures Crate Docs](https://docs.rs/futures/)
- ARCHITECTURE.md - System design overview
