diff --git a/examples/network_get_assignment_example.rs b/crates/store/examples/network_get_assignment_example.rs similarity index 91% rename from examples/network_get_assignment_example.rs rename to crates/store/examples/network_get_assignment_example.rs index cc5a544..c0438c5 100644 --- a/examples/network_get_assignment_example.rs +++ b/crates/store/examples/network_get_assignment_example.rs @@ -1,158 +1,162 @@ use store::{open, Result}; use store::network::{NetRange, BasicProvider, RangeAssigner}; -use std::net::IpAddr; + fn main() -> Result<()> { println!("=== Network Assignment Example ===\n"); // Open the store let store = open()?; // Create an IPv4 network with RangeAssigner let ipv4_netrange = NetRange::from_cidr("192.168.100.0/24")?; let provider = BasicProvider::new() .with_config("type", "docker") .with_config("driver", "bridge"); let assigner = RangeAssigner::new() .with_config("strategy", "sequential") .with_config("reserve_gateway", "true"); store.networks().create("docker-bridge", ipv4_netrange, provider, Some(assigner))?; println!("✓ Created IPv4 network: docker-bridge (192.168.100.0/24)"); // Create an IPv6 network let ipv6_netrange = NetRange::from_cidr("fd00:db8::/64")?; let provider6 = BasicProvider::new() .with_config("type", "kubernetes") .with_config("driver", "calico"); let assigner6 = RangeAssigner::new() .with_config("strategy", "sequential"); store.networks().create("k8s-pods", ipv6_netrange, provider6, Some(assigner6))?; println!("✓ Created IPv6 network: k8s-pods (fd00:db8::/64)"); // Create a network without an assigner let no_assigner_net = NetRange::from_cidr("10.50.0.0/16")?; let static_provider = BasicProvider::new() .with_config("type", "static") .with_config("manual", "true"); store.networks().create("static-network", no_assigner_net, static_provider, None::)?; println!("✓ Created static network: static-network (10.50.0.0/16) - no assigner\n"); // Get network instances let docker_network = store.networks().get("docker-bridge")?.unwrap(); let k8s_network = store.networks().get("k8s-pods")?.unwrap(); let static_network = store.networks().get("static-network")?.unwrap(); // Assign some IPs manually using RangeAssigner let range_assigner = RangeAssigner::new(); // Docker network assignments println!("--- Docker Bridge Network Assignments ---"); - if let Some(ref ranges) = store.ranges() { + let ranges = store.ranges(); + { // Assign IPs to containers let web_bit = range_assigner.assign_ip("docker-bridge", ranges, "web-container-1")?; let db_bit = range_assigner.assign_ip("docker-bridge", ranges, "database-container")?; let cache_bit = range_assigner.assign_ip("docker-bridge", ranges, "redis-cache")?; println!("Assigned bit positions:"); println!(" web-container-1: bit {}", web_bit); println!(" database-container: bit {}", db_bit); println!(" redis-cache: bit {}", cache_bit); } // Now use Network.get_assignment to retrieve IP addresses println!("\nUsing Network.get_assignment() to resolve IPs:"); let containers = ["web-container-1", "database-container", "redis-cache", "nonexistent-container"]; for container in &containers { - match docker_network.get_assignment(container, store.ranges())? { + match docker_network.get_assignment(container, Some(store.ranges()))? { Some(ip) => println!(" {} -> {}", container, ip), None => println!(" {} -> No assignment found", container), } } // K8s network assignments println!("\n--- Kubernetes Pods Network Assignments ---"); - if let Some(ref ranges) = store.ranges() { + { + let ranges = store.ranges(); range_assigner.assign_ip("k8s-pods", ranges, "nginx-pod-abc123")?; range_assigner.assign_ip("k8s-pods", ranges, "postgres-pod-def456")?; range_assigner.assign_ip("k8s-pods", ranges, "api-pod-ghi789")?; } let pods = ["nginx-pod-abc123", "postgres-pod-def456", "api-pod-ghi789"]; for pod in &pods { - if let Some(ip) = k8s_network.get_assignment(pod, store.ranges())? { + if let Some(ip) = k8s_network.get_assignment(pod, Some(store.ranges()))? { println!(" {} -> {}", pod, ip); } } // Test static network (should return None since no assigner) println!("\n--- Static Network (No Assigner) ---"); - match static_network.get_assignment("some-device", store.ranges())? { + match static_network.get_assignment("some-device", Some(store.ranges()))? { Some(ip) => println!(" Unexpected IP found: {}", ip), None => println!(" ✓ No assignment found (expected for static network)"), } // Demonstrate bit position to IP conversion println!("\n--- Direct Bit Position to IP Conversion ---"); for bit_pos in 0..5 { let ipv4_addr = docker_network.get_assignment_by_bit_position(bit_pos)?; let ipv6_addr = k8s_network.get_assignment_by_bit_position(bit_pos)?; println!(" Bit {}: IPv4={}, IPv6={}", bit_pos, ipv4_addr, ipv6_addr); } // Show practical usage: find all assigned IPs in a network println!("\n--- All Docker Network Assignments ---"); - if let Some(ref ranges) = store.ranges() { - let assignments = ranges.list_range("docker-bridge")?; + { + let ranges = store.ranges(); + let assignments: Vec<(u64, String)> = ranges.list_range("docker-bridge")?; for (bit_pos, identifier) in assignments { let ip = docker_network.get_assignment_by_bit_position(bit_pos)?; println!(" {}: {} (bit {})", identifier, ip, bit_pos); } } // Demonstrate unassignment and re-query println!("\n--- Assignment Lifecycle ---"); let test_container = "temporary-container"; // Assign - if let Some(ref ranges) = store.ranges() { + { + let ranges = store.ranges(); let bit_pos = range_assigner.assign_ip("docker-bridge", ranges, test_container)?; - let ip_before = docker_network.get_assignment(test_container, store.ranges())?.unwrap(); + let ip_before = docker_network.get_assignment(test_container, Some(store.ranges()))?.unwrap(); println!(" Assigned {} -> {} (bit {})", test_container, ip_before, bit_pos); // Unassign let unassigned = range_assigner.unassign_ip("docker-bridge", ranges, bit_pos)?; println!(" Unassigned {}: {}", test_container, unassigned); // Query after unassignment - let ip_after = docker_network.get_assignment(test_container, store.ranges())?; + let ip_after = docker_network.get_assignment(test_container, Some(store.ranges()))?; println!(" Query after unassignment: {:?}", ip_after); } // Summary println!("\n=== Summary ==="); let networks = store.networks().list()?; println!("Created {} networks:", networks.len()); for network_name in networks { let net = store.networks().get(&network_name)?.unwrap(); let assigner_info = match &net.assigner_type { Some(atype) => format!("with {} assigner", atype), None => "no assigner".to_string(), }; println!(" • {} ({}) - {}", network_name, net.netrange.to_cidr(), assigner_info); } println!("\nNetwork.get_assignment() provides a unified interface to:"); println!(" ✓ Query IP assignments by identifier"); println!(" ✓ Support different assigner implementations"); println!(" ✓ Handle both IPv4 and IPv6 networks"); println!(" ✓ Return None for networks without assigners"); println!(" ✓ Convert bit positions to actual IP addresses"); Ok(()) } \ No newline at end of file diff --git a/examples/range_assigner_example.rs b/crates/store/examples/range_assigner_example.rs similarity index 97% rename from examples/range_assigner_example.rs rename to crates/store/examples/range_assigner_example.rs index c90fdc8..311690a 100644 --- a/examples/range_assigner_example.rs +++ b/crates/store/examples/range_assigner_example.rs @@ -1,66 +1,66 @@ use store::{open, Result, RangeAssigner}; -use store::network::{NetRange, BasicProvider, NetworkStore}; +use store::network::{NetRange, BasicProvider}; fn main() -> Result<()> { // Open the store let store = open()?; // Create a network with RangeAssigner let netrange = NetRange::from_cidr("10.0.0.0/24")?; let provider = BasicProvider::new() .with_config("type", "docker") .with_config("subnet", "bridge"); let assigner = RangeAssigner::new() .with_config("strategy", "sequential") .with_config("reserve_gateway", "true"); // Create the network - this will automatically initialize the range store.networks().create("docker-network", netrange, provider, Some(assigner))?; println!("Created network 'docker-network' with range 10.0.0.0/24"); // Create another RangeAssigner instance to work with the range let range_assigner = RangeAssigner::new(); let range_name = "docker-network"; // Uses network name as range name // Assign IP addresses to devices let container1_ip = range_assigner.assign_ip(range_name, store.ranges(), "container-web-1")?; let container2_ip = range_assigner.assign_ip(range_name, store.ranges(), "container-db-1")?; let container3_ip = range_assigner.assign_ip(range_name, store.ranges(), "container-cache-1")?; println!("Assigned IP addresses:"); println!(" container-web-1: 10.0.0.{}", container1_ip + 1); // +1 to skip network address println!(" container-db-1: 10.0.0.{}", container2_ip + 1); println!(" container-cache-1: 10.0.0.{}", container3_ip + 1); // Retrieve assignment information let web_assignment = range_assigner.get_assignment_by_bit(range_name, store.ranges(), container1_ip)?; let db_assignment = range_assigner.get_assignment_by_bit(range_name, store.ranges(), container2_ip)?; println!("\nAssignment verification:"); println!(" Bit {} assigned to: {:?}", container1_ip, web_assignment); println!(" Bit {} assigned to: {:?}", container2_ip, db_assignment); // Unassign an IP (e.g., when container is removed) let unassigned = range_assigner.unassign_ip(range_name, store.ranges(), container2_ip)?; println!("\nUnassigned container-db-1: {}", unassigned); // Verify unassignment let db_assignment_after = range_assigner.get_assignment_by_bit(range_name, store.ranges(), container2_ip)?; println!("DB assignment after unassign: {:?}", db_assignment_after); // Assign a new container to the freed IP let new_container_ip = range_assigner.assign_ip(range_name, store.ranges(), "container-api-1")?; println!("New container assigned to bit: {}", new_container_ip); // List all networks let networks = store.networks().list()?; println!("\nAll networks: {:?}", networks); // Get network details let network = store.networks().get("docker-network")?.unwrap(); println!("Network details: {:#?}", network); Ok(()) } \ No newline at end of file diff --git a/examples/updated_range_assigner_example.rs b/crates/store/examples/updated_range_assigner_example.rs similarity index 98% rename from examples/updated_range_assigner_example.rs rename to crates/store/examples/updated_range_assigner_example.rs index 4f8bfa8..5491feb 100644 --- a/examples/updated_range_assigner_example.rs +++ b/crates/store/examples/updated_range_assigner_example.rs @@ -1,88 +1,88 @@ use store::{open, Result, RangeAssigner}; -use store::network::{NetRange, BasicProvider, NetworkStore}; +use store::network::{NetRange, BasicProvider}; fn main() -> Result<()> { // Open the store let store = open()?; // Create a network with RangeAssigner let netrange = NetRange::from_cidr("10.0.0.0/24")?; let provider = BasicProvider::new() .with_config("type", "docker") .with_config("subnet", "bridge"); // RangeAssigner now defaults to using the network name as the range name let assigner = RangeAssigner::new() .with_config("strategy", "sequential") .with_config("reserve_gateway", "true"); // Create the network - range is automatically initialized within the same transaction store.networks().create("docker-network", netrange, provider, Some(assigner))?; println!("Created network 'docker-network' with range 10.0.0.0/24"); println!("Range name automatically set to 'docker-network'"); // Create a RangeAssigner instance for IP operations let range_assigner = RangeAssigner::new(); let network_name = "docker-network"; // This is also the range name // Assign IP addresses to devices let container1_ip = range_assigner.assign_ip(network_name, store.ranges(), "container-web-1")?; let container2_ip = range_assigner.assign_ip(network_name, store.ranges(), "container-db-1")?; let container3_ip = range_assigner.assign_ip(network_name, store.ranges(), "container-cache-1")?; println!("\nAssigned IP addresses:"); println!(" container-web-1: 10.0.0.{}", container1_ip + 1); // +1 to skip network address println!(" container-db-1: 10.0.0.{}", container2_ip + 1); println!(" container-cache-1: 10.0.0.{}", container3_ip + 1); // Retrieve assignment information let web_assignment = range_assigner.get_assignment_by_bit(network_name, store.ranges(), container1_ip)?; let db_assignment = range_assigner.get_assignment_by_bit(network_name, store.ranges(), container2_ip)?; println!("\nAssignment verification:"); println!(" Bit {} assigned to: {:?}", container1_ip, web_assignment); println!(" Bit {} assigned to: {:?}", container2_ip, db_assignment); // Unassign an IP (e.g., when container is removed) let unassigned = range_assigner.unassign_ip(network_name, store.ranges(), container2_ip)?; println!("\nUnassigned container-db-1: {}", unassigned); // Verify unassignment let db_assignment_after = range_assigner.get_assignment_by_bit(network_name, store.ranges(), container2_ip)?; println!("DB assignment after unassign: {:?}", db_assignment_after); // Assign a new container to the freed IP let new_container_ip = range_assigner.assign_ip(network_name, store.ranges(), "container-api-1")?; println!("New container assigned to bit: {}", new_container_ip); // Example with custom range name println!("\n--- Example with custom range name ---"); let custom_assigner = RangeAssigner::with_range_name("custom-pool") .with_config("strategy", "sequential"); let netrange2 = NetRange::from_cidr("172.16.0.0/16")?; let provider2 = BasicProvider::new().with_config("type", "kubernetes"); store.networks().create("k8s-network", netrange2, provider2, Some(custom_assigner))?; println!("Created 'k8s-network' with custom range name 'custom-pool'"); // Assign from the custom range let k8s_assigner = RangeAssigner::new(); let pod_ip = k8s_assigner.assign_ip("custom-pool", store.ranges(), "pod-nginx-1")?; println!("Assigned pod IP bit position: {}", pod_ip); // List all networks let networks = store.networks().list()?; println!("\nAll networks: {:?}", networks); // Get network details let network = store.networks().get("docker-network")?.unwrap(); println!("\nDocker network details:"); println!(" Name: {}", network.name); println!(" Range: {}", network.netrange.to_cidr()); println!(" Assigner type: {:?}", network.assigner_type); Ok(()) } \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..336b482 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,245 @@ +# Troubleshooting Guide + +This guide covers common issues you might encounter when working with the collar project and how to resolve them. + +## Compilation Issues + +### 1. "use of unresolved module or unlinked crate `store`" + +**Problem**: Examples fail to compile with errors like: +``` +error[E0433]: failed to resolve: use of unresolved module or unlinked crate `store` +``` + +**Solution**: Make sure you're running examples from the correct package: +```bash +# Correct - run from store package +cargo run --package store --example network_get_assignment_example + +# Incorrect - running from workspace root without package specification +cargo run --example network_get_assignment_example +``` + +### 2. Type annotation errors in examples + +**Problem**: Examples fail with type inference errors like: +``` +error[E0282]: type annotations needed +``` + +**Solution**: This usually happens when the compiler can't infer the type for generic parameters. Check that: +- All method calls have the correct parameter types +- Optional parameters are properly wrapped (e.g., `Some(store.ranges())` instead of `store.ranges()`) + +### 3. Missing dependencies + +**Problem**: Compilation fails with missing crate errors. + +**Solution**: Ensure you're in the correct directory and all dependencies are installed: +```bash +cd collar +cargo check --package store +``` + +## Runtime Issues + +### 1. Database access errors + +**Problem**: Errors related to database file access or permissions. + +**Solution**: +- Ensure the current user has write permissions to the directory +- Check that no other processes are using the database +- Try running with elevated permissions if necessary + +### 2. Range assignment failures + +**Problem**: `RangeFull` errors when assigning IP addresses. + +**Solution**: +- Check the network size vs. number of assignments +- Use `list_range()` to see current assignments +- Consider using larger subnets (smaller prefix length) + +Example: +```rust +// Check how many IPs are available +let assignments = range_store.list_range("my-network")?; +println!("Current assignments: {}", assignments.len()); + +// For /24 network, maximum ~254 usable IPs +let netrange = NetRange::from_cidr("192.168.1.0/24")?; +``` + +### 3. Network assignment returns None + +**Problem**: `Network.get_assignment()` returns `None` unexpectedly. + +**Solution**: Check that: +- The network has an assigner configured +- The identifier exists in the range +- The RangeStore is properly passed to the method + +```rust +// Debug network configuration +let network = store.networks().get("my-network")?.unwrap(); +println!("Assigner type: {:?}", network.assigner_type); + +// Check if assignment exists +if let Some(ranges) = store.ranges() { + let assignments = ranges.list_range("my-network")?; + println!("All assignments: {:?}", assignments); +} +``` + +## Testing Issues + +### 1. Tests fail with database conflicts + +**Problem**: Tests fail intermittently with database access errors. + +**Solution**: Run tests with single thread to avoid conflicts: +```bash +cargo test --package store -- --test-threads 1 +``` + +### 2. Test database cleanup + +**Problem**: Previous test runs leave database state that affects new tests. + +**Solution**: Tests use temporary directories that should auto-cleanup, but you can manually clean: +```bash +# Clean all build artifacts and temporary files +cargo clean +rm -rf /tmp/test-* +``` + +## Performance Issues + +### 1. Slow identifier lookups + +**Problem**: `Network.get_assignment()` is slow for large networks. + +**Solution**: +- This is expected for large ranges as it uses O(n) scanning +- Consider using `get_assignment_by_bit_position()` if you know the bit position +- For frequently accessed assignments, consider caching + +### 2. Large memory usage + +**Problem**: High memory usage with large IPv6 networks. + +**Solution**: +- IPv6 ranges are automatically capped at 2^32 addresses +- Consider using smaller subnets for very large deployments +- Monitor actual usage vs. allocated range size + +## Common Patterns and Solutions + +### 1. Proper error handling + +```rust +// Good: Handle specific error types +match range_assigner.assign_ip("network", store.ranges(), "device") { + Ok(bit_pos) => println!("Assigned: {}", bit_pos), + Err(Error::RangeFull(range_name)) => { + eprintln!("Range '{}' is full", range_name); + // Handle gracefully - maybe use different network + } + Err(e) => eprintln!("Unexpected error: {:?}", e), +} +``` + +### 2. Network lifecycle management + +```rust +// Create network with proper cleanup +let network_name = "temp-network"; + +// Create +store.networks().create(network_name, netrange, provider, Some(assigner))?; + +// Use +let assignments = /* ... */; + +// Cleanup (manual - consider adding to NetworkStore) +// Note: Currently no automatic range cleanup when network is deleted +if let Some(ranges) = store.ranges() { + // Would need custom cleanup logic here +} +``` + +### 3. Checking network capacity + +```rust +fn check_network_capacity(network_name: &str, ranges: &RangeStore) -> Result<(usize, u64)> { + let assignments = ranges.list_range(network_name)?; + let used = assignments.len(); + + // Get network info to calculate total capacity + // This would require additional method to get range size + // For now, calculate from CIDR manually + + Ok((used, 256)) // Example for /24 network +} +``` + +## Getting Help + +### 1. Enable logging + +Add logging to see what's happening: +```rust +env_logger::init(); +log::info!("Creating network: {}", network_name); +``` + +### 2. Debug network state + +```rust +// Check network configuration +let network = store.networks().get("my-network")?.unwrap(); +println!("Network: {:#?}", network); + +// Check range assignments +if let Some(ranges) = store.ranges() { + let assignments = ranges.list_range("my-network")?; + for (bit, id) in assignments { + let ip = network.get_assignment_by_bit_position(bit)?; + println!(" {} -> {} (bit {})", id, ip, bit); + } +} +``` + +### 3. Validate network configuration + +```rust +// Check that network CIDR is valid +let netrange = NetRange::from_cidr("192.168.1.0/24")?; +println!("Network: {}", netrange.to_cidr()); + +// Verify assigner type matches expectations +if let Some(assigner_type) = &network.assigner_type { + println!("Using assigner: {}", assigner_type); +} else { + println!("No assigner configured - assignments will return None"); +} +``` + +## Best Practices + +1. **Always check return values** - Many methods return `Option` or `Result` +2. **Use appropriate network sizes** - Don't allocate huge ranges unless needed +3. **Test with small ranges first** - Easier to debug with /29 or /28 networks +4. **Clean up assignments** - Unassign IPs when devices are removed +5. **Handle range exhaustion gracefully** - Have fallback networks or error handling +6. **Use descriptive identifiers** - Makes debugging easier + +## Common Error Messages + +| Error | Meaning | Solution | +|-------|---------|----------| +| `RangeFull` | No free IPs in range | Use larger subnet or clean up unused assignments | +| `NamespaceKeyReserved` | Network name already exists | Use different name or delete existing network | +| `BitOutOfRange` | Bit position exceeds range size | Check range size and bit position validity | +| `InvalidCidr` | CIDR format is wrong | Use correct format like "192.168.1.0/24" | \ No newline at end of file