Repository navigation
Routed VPC / network CIDR uniqueness is enforced globally instead of per zone #14419
Description
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
- Enable routed networks/VPCs. Do not configure any zone IPv4 guest subnets.
- As root admin, create a routed VPC in zone A with cidr=10.0.0.0/8 -> succeeds
- 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:
- 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.
- Add a zone predicate to SubnetSearch / findBySubnet() and pass zoneId from the callers (already available in getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal).
- Apply the same scoping to the overlap check in getParentOfNetworkCidr(), which uses listAllNoParent() and is likewise installation-wide.
- 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
- Use a non-routed (NAT) VPC offering in the second zone — identical CIDRs work today.
- Use different CIDRs per zone.
Activity
network: list and report routed subnets by their own zone