- Created T026-practical-test task.yaml for MVP smoke testing - Added k8shost-server to flake.nix (packages, apps, overlays) - Staged all workspace directories for nix flake build - Updated flake.nix shellHook to include k8shost Resolves: T026.S1 blocker (R8 - nix submodule visibility)
534 lines
18 KiB
Rust
534 lines
18 KiB
Rust
//! Integration tests for NovaNET control-plane
|
|
//!
|
|
//! These tests validate the full E2E flow from VPC creation through
|
|
//! DHCP, ACL enforcement, Gateway Router, and SNAT configuration.
|
|
|
|
use novanet_server::ovn::{build_acl_match, calculate_priority, OvnClient};
|
|
use novanet_types::{
|
|
DhcpOptions, IpProtocol, Port, RuleDirection, SecurityGroup, SecurityGroupId,
|
|
SecurityGroupRule, SubnetId, Vpc,
|
|
};
|
|
|
|
/// Test Scenario 1: Full Control-Plane Flow
|
|
///
|
|
/// Validates the complete lifecycle:
|
|
/// VPC → Subnet+DHCP → Port → SecurityGroup+ACL → Router+SNAT
|
|
#[tokio::test]
|
|
async fn test_full_control_plane_flow() {
|
|
// Setup: Create mock OvnClient
|
|
let ovn = OvnClient::new_mock();
|
|
|
|
// 1. Create VPC (logical switch)
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 2. Create Subnet with DHCP options
|
|
let dhcp_opts = DhcpOptions {
|
|
cidr: "10.0.0.0/24".to_string(),
|
|
router: Some("10.0.0.1".to_string()),
|
|
dns_servers: vec!["8.8.8.8".to_string(), "8.8.4.4".to_string()],
|
|
lease_time: 86400,
|
|
domain_name: Some("cloud.local".to_string()),
|
|
};
|
|
let dhcp_uuid = ovn
|
|
.create_dhcp_options("10.0.0.0/24", &dhcp_opts)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 3. Create Port attached to Subnet
|
|
let mut port = Port::new("test-port", SubnetId::new());
|
|
port.ip_address = Some("10.0.0.5".to_string());
|
|
ovn.create_logical_switch_port(&port, &vpc.id, port.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
// 4. Bind DHCP to port
|
|
let lsp_name = format!("port-{}", port.id);
|
|
ovn.set_lsp_dhcp_options(&lsp_name, &dhcp_uuid)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 5. Create SecurityGroup with rules
|
|
let sg = SecurityGroup::new("web-sg", "org-1", "proj-1");
|
|
|
|
// SSH rule (ingress, TCP/22 from anywhere)
|
|
let mut ssh_rule = SecurityGroupRule::new(sg.id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
ssh_rule.port_range_min = Some(22);
|
|
ssh_rule.port_range_max = Some(22);
|
|
ssh_rule.remote_cidr = Some("0.0.0.0/0".to_string());
|
|
|
|
// HTTP rule (ingress, TCP/80)
|
|
let mut http_rule = SecurityGroupRule::new(sg.id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
http_rule.port_range_min = Some(80);
|
|
http_rule.port_range_max = Some(80);
|
|
http_rule.remote_cidr = Some("0.0.0.0/0".to_string());
|
|
|
|
// 6. Apply SecurityGroup → create ACLs
|
|
let ssh_match = build_acl_match(&ssh_rule, Some(&lsp_name));
|
|
let ssh_priority = calculate_priority(&ssh_rule);
|
|
let ssh_acl_key = ovn
|
|
.create_acl(&sg.id, &ssh_rule, &vpc.id, &ssh_match, ssh_priority)
|
|
.await
|
|
.unwrap();
|
|
|
|
let http_match = build_acl_match(&http_rule, Some(&lsp_name));
|
|
let http_priority = calculate_priority(&http_rule);
|
|
let http_acl_key = ovn
|
|
.create_acl(&sg.id, &http_rule, &vpc.id, &http_match, http_priority)
|
|
.await
|
|
.unwrap();
|
|
|
|
// 7. Create Gateway Router
|
|
let router_id = ovn.create_logical_router("vpc-router").await.unwrap();
|
|
|
|
// 8. Attach router to VPC
|
|
let router_port_id = ovn
|
|
.add_router_port(&router_id, &vpc.id, "10.0.0.1/24", "02:00:00:00:00:01")
|
|
.await
|
|
.unwrap();
|
|
|
|
// 9. Configure SNAT
|
|
ovn.configure_snat(&router_id, "203.0.113.10", "10.0.0.0/24")
|
|
.await
|
|
.unwrap();
|
|
|
|
// 10. ASSERTIONS: Verify mock state
|
|
let state = ovn.mock_state().unwrap();
|
|
let guard = state.lock().await;
|
|
|
|
// Verify VPC exists
|
|
assert!(guard.has_logical_switch(&vpc.id));
|
|
|
|
// Verify DHCP options exist
|
|
assert!(guard.dhcp_options_exists(&dhcp_uuid));
|
|
|
|
// Verify port has DHCP binding
|
|
assert!(guard.port_has_dhcp(&lsp_name));
|
|
|
|
// Verify port is attached
|
|
assert!(guard.port_attached(&port.id));
|
|
|
|
// Verify ACLs exist with correct match expressions
|
|
assert!(guard.acl_exists(&ssh_acl_key));
|
|
assert!(guard.acl_exists(&http_acl_key));
|
|
|
|
let ssh_acl_match = guard.get_acl_match(&ssh_acl_key).unwrap();
|
|
assert!(ssh_acl_match.contains(&format!("inport == \"{}\"", lsp_name)));
|
|
assert!(ssh_acl_match.contains("tcp.dst == 22"));
|
|
assert!(ssh_acl_match.contains("ip4.src == 0.0.0.0/0"));
|
|
|
|
let http_acl_match = guard.get_acl_match(&http_acl_key).unwrap();
|
|
assert!(http_acl_match.contains("tcp.dst == 80"));
|
|
|
|
// Verify router exists
|
|
assert!(guard.router_exists(&router_id));
|
|
|
|
// Verify router port attached
|
|
assert!(guard.router_port_exists(&router_port_id));
|
|
assert_eq!(guard.get_router_port_count(&router_id), 1);
|
|
|
|
// Verify SNAT rule configured
|
|
assert!(guard.snat_rule_exists(&router_id, "203.0.113.10"));
|
|
}
|
|
|
|
/// Test Scenario 2: Multi-Tenant Isolation
|
|
///
|
|
/// Ensures that two VPCs are properly isolated from each other
|
|
#[tokio::test]
|
|
async fn test_multi_tenant_isolation() {
|
|
let ovn = OvnClient::new_mock();
|
|
|
|
// Tenant A
|
|
let vpc_a = Vpc::new("tenant-a-vpc", "org-a", "proj-a", "10.0.0.0/16");
|
|
ovn.create_logical_switch(&vpc_a.id, &vpc_a.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut port_a = Port::new("tenant-a-port", SubnetId::new());
|
|
port_a.ip_address = Some("10.0.0.10".to_string());
|
|
ovn.create_logical_switch_port(&port_a, &vpc_a.id, port_a.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
// Tenant B
|
|
let vpc_b = Vpc::new("tenant-b-vpc", "org-b", "proj-b", "10.1.0.0/16");
|
|
ovn.create_logical_switch(&vpc_b.id, &vpc_b.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut port_b = Port::new("tenant-b-port", SubnetId::new());
|
|
port_b.ip_address = Some("10.1.0.10".to_string());
|
|
ovn.create_logical_switch_port(&port_b, &vpc_b.id, port_b.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify: Each VPC has separate logical switch
|
|
let state = ovn.mock_state().unwrap();
|
|
let guard = state.lock().await;
|
|
|
|
assert!(guard.has_logical_switch(&vpc_a.id));
|
|
assert!(guard.has_logical_switch(&vpc_b.id));
|
|
|
|
// Verify: Ports isolated to their VPCs
|
|
assert!(guard.port_attached(&port_a.id));
|
|
assert!(guard.port_attached(&port_b.id));
|
|
|
|
// Verify ports are in the correct VPCs
|
|
let port_a_state = guard.logical_ports.get(&port_a.id).unwrap();
|
|
assert_eq!(port_a_state.logical_switch, vpc_a.id);
|
|
assert_eq!(port_a_state.ip, "10.0.0.10");
|
|
|
|
let port_b_state = guard.logical_ports.get(&port_b.id).unwrap();
|
|
assert_eq!(port_b_state.logical_switch, vpc_b.id);
|
|
assert_eq!(port_b_state.ip, "10.1.0.10");
|
|
}
|
|
|
|
/// Test Scenario 3: ACL Priority Ordering
|
|
///
|
|
/// Validates that more specific ACL rules get higher priority
|
|
#[tokio::test]
|
|
async fn test_acl_priority_ordering() {
|
|
let ovn = OvnClient::new_mock();
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
let sg_id = SecurityGroupId::new();
|
|
|
|
// Rule 1: Protocol only (priority 700)
|
|
let rule_protocol_only = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
let priority_protocol = calculate_priority(&rule_protocol_only);
|
|
assert_eq!(priority_protocol, 700);
|
|
|
|
// Rule 2: Protocol + port (priority 800)
|
|
let mut rule_with_port = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
rule_with_port.port_range_min = Some(80);
|
|
rule_with_port.port_range_max = Some(80);
|
|
let priority_port = calculate_priority(&rule_with_port);
|
|
assert_eq!(priority_port, 800);
|
|
|
|
// Rule 3: Protocol + CIDR (priority 800)
|
|
let mut rule_with_cidr = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
rule_with_cidr.remote_cidr = Some("10.0.0.0/8".to_string());
|
|
let priority_cidr = calculate_priority(&rule_with_cidr);
|
|
assert_eq!(priority_cidr, 800);
|
|
|
|
// Rule 4: Protocol + port + CIDR (priority 1000 - most specific)
|
|
let mut rule_full = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
rule_full.port_range_min = Some(443);
|
|
rule_full.port_range_max = Some(443);
|
|
rule_full.remote_cidr = Some("192.168.0.0/16".to_string());
|
|
let priority_full = calculate_priority(&rule_full);
|
|
assert_eq!(priority_full, 1000);
|
|
|
|
// Rule 5: Any protocol (priority 600 - least specific)
|
|
let rule_any = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Any);
|
|
let priority_any = calculate_priority(&rule_any);
|
|
assert_eq!(priority_any, 600);
|
|
|
|
// Verify ordering: full > port/cidr > protocol > any
|
|
assert!(priority_full > priority_port);
|
|
assert!(priority_port > priority_protocol);
|
|
assert!(priority_protocol > priority_any);
|
|
}
|
|
|
|
/// Test Scenario 4: Router Cascade Deletion
|
|
///
|
|
/// Validates that deleting a router also removes associated router ports and SNAT rules
|
|
#[tokio::test]
|
|
async fn test_router_cascade_deletion() {
|
|
let ovn = OvnClient::new_mock();
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
|
|
// Create VPC
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create router
|
|
let router_id = ovn.create_logical_router("test-router").await.unwrap();
|
|
|
|
// Add router port
|
|
let port_id = ovn
|
|
.add_router_port(&router_id, &vpc.id, "10.0.0.1/24", "02:00:00:00:00:01")
|
|
.await
|
|
.unwrap();
|
|
|
|
// Configure SNAT
|
|
ovn.configure_snat(&router_id, "203.0.113.10", "10.0.0.0/24")
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify everything exists
|
|
let state = ovn.mock_state().unwrap();
|
|
{
|
|
let guard = state.lock().await;
|
|
assert!(guard.router_exists(&router_id));
|
|
assert!(guard.router_port_exists(&port_id));
|
|
assert!(guard.snat_rule_exists(&router_id, "203.0.113.10"));
|
|
}
|
|
|
|
// Delete router
|
|
ovn.delete_logical_router(&router_id).await.unwrap();
|
|
|
|
// Verify cascade deletion
|
|
let guard = state.lock().await;
|
|
assert!(!guard.router_exists(&router_id));
|
|
assert!(!guard.router_port_exists(&port_id));
|
|
assert!(!guard.snat_rule_exists(&router_id, "203.0.113.10"));
|
|
}
|
|
|
|
/// Test Scenario 5: DHCP Option Updates
|
|
///
|
|
/// Validates that DHCP options can be created, bound to ports, and deleted
|
|
#[tokio::test]
|
|
async fn test_dhcp_options_lifecycle() {
|
|
let ovn = OvnClient::new_mock();
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
|
|
// Create VPC
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create DHCP options
|
|
let dhcp_opts = DhcpOptions {
|
|
cidr: "10.0.0.0/24".to_string(),
|
|
router: Some("10.0.0.1".to_string()),
|
|
dns_servers: vec!["8.8.8.8".to_string()],
|
|
lease_time: 3600,
|
|
domain_name: Some("test.local".to_string()),
|
|
};
|
|
|
|
let dhcp_uuid = ovn
|
|
.create_dhcp_options("10.0.0.0/24", &dhcp_opts)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create port
|
|
let mut port = Port::new("test-port", SubnetId::new());
|
|
port.ip_address = Some("10.0.0.5".to_string());
|
|
ovn.create_logical_switch_port(&port, &vpc.id, port.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
// Bind DHCP to port
|
|
let lsp_name = format!("port-{}", port.id);
|
|
ovn.set_lsp_dhcp_options(&lsp_name, &dhcp_uuid)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify DHCP options exist and are bound
|
|
let state = ovn.mock_state().unwrap();
|
|
{
|
|
let guard = state.lock().await;
|
|
assert!(guard.dhcp_options_exists(&dhcp_uuid));
|
|
assert!(guard.port_has_dhcp(&lsp_name));
|
|
}
|
|
|
|
// Delete DHCP options
|
|
ovn.delete_dhcp_options(&dhcp_uuid).await.unwrap();
|
|
|
|
// Verify deletion
|
|
let guard = state.lock().await;
|
|
assert!(!guard.dhcp_options_exists(&dhcp_uuid));
|
|
}
|
|
|
|
/// Test Scenario 6: SecurityGroup Rule Lifecycle
|
|
///
|
|
/// Validates adding and removing ACL rules
|
|
#[tokio::test]
|
|
async fn test_security_group_rule_lifecycle() {
|
|
let ovn = OvnClient::new_mock();
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
let sg = SecurityGroup::new("test-sg", "org-1", "proj-1");
|
|
|
|
// Add SSH rule
|
|
let ssh_rule = SecurityGroupRule::tcp_port(sg.id, RuleDirection::Ingress, 22, "0.0.0.0/0");
|
|
let ssh_match = build_acl_match(&ssh_rule, None);
|
|
let ssh_priority = calculate_priority(&ssh_rule);
|
|
|
|
let acl_key = ovn
|
|
.create_acl(&sg.id, &ssh_rule, &vpc.id, &ssh_match, ssh_priority)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify ACL exists
|
|
let state = ovn.mock_state().unwrap();
|
|
{
|
|
let guard = state.lock().await;
|
|
assert!(guard.acl_exists(&acl_key));
|
|
let match_expr = guard.get_acl_match(&acl_key).unwrap();
|
|
assert!(match_expr.contains("tcp"));
|
|
assert!(match_expr.contains("tcp.dst == 22"));
|
|
}
|
|
|
|
// Remove ACL
|
|
ovn.delete_acl(&ssh_rule.id).await.unwrap();
|
|
|
|
// Verify deletion
|
|
let guard = state.lock().await;
|
|
assert!(!guard.acl_exists(&acl_key));
|
|
}
|
|
|
|
/// Test Scenario 7: VPC Deletion Cascades
|
|
///
|
|
/// Validates that deleting a VPC removes all associated ports and ACLs
|
|
#[tokio::test]
|
|
async fn test_vpc_deletion_cascades() {
|
|
let ovn = OvnClient::new_mock();
|
|
let vpc = Vpc::new("test-vpc", "org-1", "proj-1", "10.0.0.0/16");
|
|
|
|
// Create VPC
|
|
ovn.create_logical_switch(&vpc.id, &vpc.cidr_block)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create ports
|
|
let mut port1 = Port::new("port1", SubnetId::new());
|
|
port1.ip_address = Some("10.0.0.5".to_string());
|
|
ovn.create_logical_switch_port(&port1, &vpc.id, port1.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut port2 = Port::new("port2", SubnetId::new());
|
|
port2.ip_address = Some("10.0.0.6".to_string());
|
|
ovn.create_logical_switch_port(&port2, &vpc.id, port2.ip_address.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create ACL
|
|
let sg_id = SecurityGroupId::new();
|
|
let rule = SecurityGroupRule::tcp_port(sg_id, RuleDirection::Ingress, 80, "0.0.0.0/0");
|
|
let match_expr = build_acl_match(&rule, None);
|
|
let priority = calculate_priority(&rule);
|
|
|
|
let acl_key = ovn
|
|
.create_acl(&sg_id, &rule, &vpc.id, &match_expr, priority)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify everything exists
|
|
let state = ovn.mock_state().unwrap();
|
|
{
|
|
let guard = state.lock().await;
|
|
assert!(guard.has_logical_switch(&vpc.id));
|
|
assert!(guard.port_attached(&port1.id));
|
|
assert!(guard.port_attached(&port2.id));
|
|
assert!(guard.acl_exists(&acl_key));
|
|
}
|
|
|
|
// Delete VPC
|
|
ovn.delete_logical_switch(&vpc.id).await.unwrap();
|
|
|
|
// Verify cascade deletion
|
|
let guard = state.lock().await;
|
|
assert!(!guard.has_logical_switch(&vpc.id));
|
|
assert!(!guard.port_attached(&port1.id));
|
|
assert!(!guard.port_attached(&port2.id));
|
|
assert!(!guard.acl_exists(&acl_key));
|
|
}
|
|
|
|
/// Test Scenario 8: Multiple Routers and SNAT Rules
|
|
///
|
|
/// Validates that a single router can have multiple SNAT rules
|
|
#[tokio::test]
|
|
async fn test_multiple_snat_rules() {
|
|
let ovn = OvnClient::new_mock();
|
|
|
|
// Create router
|
|
let router_id = ovn.create_logical_router("multi-snat-router").await.unwrap();
|
|
|
|
// Add multiple SNAT rules for different subnets
|
|
ovn.configure_snat(&router_id, "203.0.113.10", "10.0.0.0/24")
|
|
.await
|
|
.unwrap();
|
|
|
|
ovn.configure_snat(&router_id, "203.0.113.11", "10.1.0.0/24")
|
|
.await
|
|
.unwrap();
|
|
|
|
ovn.configure_snat(&router_id, "203.0.113.12", "10.2.0.0/24")
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify all SNAT rules exist
|
|
let state = ovn.mock_state().unwrap();
|
|
let guard = state.lock().await;
|
|
|
|
assert!(guard.snat_rule_exists(&router_id, "203.0.113.10"));
|
|
assert!(guard.snat_rule_exists(&router_id, "203.0.113.11"));
|
|
assert!(guard.snat_rule_exists(&router_id, "203.0.113.12"));
|
|
|
|
// Verify total SNAT rule count
|
|
let snat_count = guard
|
|
.snat_rules
|
|
.iter()
|
|
.filter(|rule| rule.router_id == router_id)
|
|
.count();
|
|
assert_eq!(snat_count, 3);
|
|
}
|
|
|
|
/// Test Scenario 9: ACL Match Expression Validation
|
|
///
|
|
/// Validates that ACL match expressions are correctly built for different scenarios
|
|
#[tokio::test]
|
|
async fn test_acl_match_expression_validation() {
|
|
let sg_id = SecurityGroupId::new();
|
|
|
|
// Test 1: TCP with port range
|
|
let mut tcp_range_rule = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Tcp);
|
|
tcp_range_rule.port_range_min = Some(8000);
|
|
tcp_range_rule.port_range_max = Some(9000);
|
|
tcp_range_rule.remote_cidr = Some("192.168.0.0/16".to_string());
|
|
|
|
let match_expr = build_acl_match(&tcp_range_rule, Some("port-123"));
|
|
assert!(match_expr.contains("inport == \"port-123\""));
|
|
assert!(match_expr.contains("tcp"));
|
|
assert!(match_expr.contains("tcp.dst >= 8000"));
|
|
assert!(match_expr.contains("tcp.dst <= 9000"));
|
|
assert!(match_expr.contains("ip4.src == 192.168.0.0/16"));
|
|
|
|
// Test 2: UDP single port
|
|
let mut udp_rule = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Udp);
|
|
udp_rule.port_range_min = Some(53);
|
|
udp_rule.port_range_max = Some(53);
|
|
|
|
let match_expr = build_acl_match(&udp_rule, None);
|
|
assert!(match_expr.contains("udp"));
|
|
assert!(match_expr.contains("udp.dst == 53"));
|
|
assert!(!match_expr.contains("inport"));
|
|
|
|
// Test 3: ICMP (no port)
|
|
let icmp_rule = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Icmp);
|
|
let match_expr = build_acl_match(&icmp_rule, None);
|
|
assert!(match_expr.contains("icmp4"));
|
|
assert!(!match_expr.contains("tcp"));
|
|
assert!(!match_expr.contains("udp"));
|
|
|
|
// Test 4: Egress direction (different port field)
|
|
let mut egress_rule = SecurityGroupRule::new(sg_id, RuleDirection::Egress, IpProtocol::Tcp);
|
|
egress_rule.port_range_min = Some(443);
|
|
egress_rule.port_range_max = Some(443);
|
|
egress_rule.remote_cidr = Some("0.0.0.0/0".to_string());
|
|
|
|
let match_expr = build_acl_match(&egress_rule, Some("port-456"));
|
|
assert!(match_expr.contains("outport == \"port-456\""));
|
|
assert!(match_expr.contains("ip4.dst == 0.0.0.0/0")); // dst for egress
|
|
|
|
// Test 5: Any protocol
|
|
let any_rule = SecurityGroupRule::new(sg_id, RuleDirection::Ingress, IpProtocol::Any);
|
|
let match_expr = build_acl_match(&any_rule, None);
|
|
assert!(match_expr.contains("ip4"));
|
|
assert!(!match_expr.contains("tcp"));
|
|
assert!(!match_expr.contains("udp"));
|
|
assert!(!match_expr.contains("icmp"));
|
|
}
|