prashanthr2 opened a new issue, #14419:
URL: https://github.com/apache/cloudstack/issues/14419

   ### problem
   
   For routed networks and routed VPCs, CloudStack treats a guest CIDR as 
unique across the entire installation rather than per zone. Creating a routed 
VPC with CIDR 10.0.0.0/8 in zone A and then the same CIDR in zone B fails with 
The subnet is in use, even though the two zones are independent L3 domains with 
separate fabrics.
   
   Routed networks are inherently zone-scoped ,  they are tied to that zone’s 
routing fabric, and the parent table dc_ip4_guest_subnets is explicitly 
per-zone via data_center_id. The uniqueness check therefore looks over-strict 
rather than intentional, and it blocks a common active/passive DR pattern where 
the standby zone deliberately uses the same addressing as the primary.
   
   ### versions
   
   4.22.1
   
   ### The steps to reproduce the bug
   
   1. Enable routed networks/VPCs. Do not configure any zone IPv4 guest subnets.
   2. As root admin, create a routed VPC in zone A with cidr=10.0.0.0/8  -> 
succeeds
   3. As root admin, create a routed VPC in zone B with cidr=10.0.0.0/8  -> 
fails (Request failed (431): The subnet is in use)
   
   ### What to do about it?
   
   
RoutedIpv4ManagerImpl.getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(String 
networkCidr, ...) (line 652 in 4.22.1.0) performs an exact-match lookup with no 
zone qualifier:
   
   ```
   Ipv4GuestSubnetNetworkMapVO subnetMap = 
ipv4GuestSubnetNetworkMapDao.findBySubnet(networkCidr);
   if (subnetMap != null) {
       // check if the subnet is in use
       if (subnetMap.getNetworkId() != null || subnetMap.getVpcId() != null) {
           throw new InvalidParameterValueException("The subnet is in use");
       }
   ```
   
   Ipv4GuestSubnetNetworkMapDaoImpl.findBySubnet() (line 145) uses a search 
builder with only a subnet predicate (line 71):
   ```
   SubnetSearch = createSearchBuilder();
   SubnetSearch.and("subnet", SubnetSearch.entity().getSubnet(), 
SearchCriteria.Op.EQ);
   SubnetSearch.done();
   ```
   
   The underlying table has no zone column at all — Ipv4GuestSubnetNetworkMapVO 
(@Table(name = "ip4_guest_subnet_network_map")) exposes only id, uuid, 
parent_id, subnet, vpc_id, network_id, state, allocated, created, removed. Zone 
is reachable only indirectly through parent_id → 
dc_ip4_guest_subnets.data_center_id.
   
   This makes the registry internally inconsistent: rows with a parent are 
implicitly zone-scoped, while parentless rows carry no zone information 
whatsoever and therefore block their CIDR installation-wide. Parentless rows 
are the normal case when no zone subnets are configured — 
getParentOfNetworkCidr() returns null and the row is created with parentId = 
null.
   
   Both entry points funnel into the same method, so routed VPCs and plain 
routed isolated networks are equally affected:
   * getOrCreateIpv4SubnetForVpc(Vpc, String)
   * getOrCreateIpv4SubnetForGuestNetwork(Network, String)
   
   Evidence from a reproduction — one parentless row blocking the CIDR globally:
   ```
   mysql> SELECT m.id, m.subnet, m.state, m.parent_id, m.network_id, m.vpc_id,
                 v.name AS vpc_name, v.zone_id AS vpc_zone
          FROM cloud.ip4_guest_subnet_network_map m
          LEFT JOIN cloud.vpc v ON v.id = m.vpc_id
          WHERE m.subnet = '10.0.0.0/8';
   
+----+------------+-----------+-----------+------------+--------+-----------+----------+
   | id | subnet     | state     | parent_id | network_id | vpc_id | vpc_name  
| vpc_zone |
   
+----+------------+-----------+-----------+------------+--------+-----------+----------+
   |  3 | 10.0.0.0/8 | Allocated |      NULL |       NULL |      5 | RouteVPC2 
|        2 |
   
+----+------------+-----------+-----------+------------+--------+-----------+----------+
   ```
   
   For contrast, non-routed VPCs have no CIDR uniqueness check at all — 
VpcManagerImpl.createVpc() validates only that the CIDR is a valid IPv4 CIDR 
and RFC1918-compliant — so the same CIDR in two zones works there. The 
restriction is specific to the routed path.
   
   **SUGGESTED FIX**
   
   Scope the lookup to the zone:
   
   1. Add a data_center_id column to ip4_guest_subnet_network_map, populated on 
creation. This is required because parentless rows have no other path to a zone.
   2. Add a zone predicate to SubnetSearch / findBySubnet() and pass zoneId 
from the callers (already available in 
getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal).
   3. Apply the same scoping to the overlap check in getParentOfNetworkCidr(), 
which uses listAllNoParent() and is likewise installation-wide.
   4. Upgrade: backfill data_center_id from vpc.zone_id / 
networks.data_center_id for allocated rows, and from 
dc_ip4_guest_subnets.data_center_id where a parent exists.
   
   **WORKAROUNDS**
   1. Use a non-routed (NAT) VPC offering in the second zone — identical CIDRs 
work today.
   2. Use different CIDRs per zone.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to