From 86827f871d24def41c5c8cf6d8b7bad910979a55 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 29 Jul 2025 14:00:14 +0530 Subject: [PATCH] Update CIDR/Gateway of the Shared Networks from Guest IP ranges (#11249) --- .../META-INF/db/schema-42010to42100.sql | 5 ++ .../network/element/BigSwitchBcfElement.java | 54 +++++++-------- .../java/com/cloud/api/ApiResponseHelper.java | 17 ++--- .../ConfigurationManagerImpl.java | 68 +++++++++++++++++++ .../java/com/cloud/utils/StringUtils.java | 52 ++++++++++++++ 5 files changed, 161 insertions(+), 35 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 103b9363d6c..4c7fe74cbcd 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -659,3 +659,8 @@ CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( 'Resume', '[]' ); + +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `cidr` varchar(255) DEFAULT NULL COMMENT 'CloudStack managed vms get IP address from cidr.In general this cidr also serves as the network CIDR. But in case IP reservation feature is being used by a Guest network, networkcidr is the Effective network CIDR for that network'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `gateway` varchar(255) DEFAULT NULL COMMENT 'gateway(s) for this network configuration'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `ip6_cidr` varchar(1024) DEFAULT NULL COMMENT 'IPv6 cidr(s) for this network'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `ip6_gateway` varchar(1024) DEFAULT NULL COMMENT 'IPv6 gateway(s) for this network'; diff --git a/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java b/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java index adb957c3b5e..db0efd71b1d 100644 --- a/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java +++ b/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java @@ -237,7 +237,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { // get arguments for CreateBcfAttachmentCommand // determine whether this is VPC network or stand-alone network Vpc vpc = null; - if(network.getVpcId()!=null){ + if (network.getVpcId() != null) { vpc = _vpcDao.acquireInLockTable(network.getVpcId()); } @@ -264,7 +264,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { String vmwareVswitchLabel = _networkModel.getDefaultGuestTrafficLabel(zoneId, HypervisorType.VMware); String[] labelArray = null; String vswitchName = null; - if(vmwareVswitchLabel!=null){ + if (vmwareVswitchLabel != null) { labelArray=vmwareVswitchLabel.split(","); vswitchName = labelArray[0]; } @@ -273,9 +273,9 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { // kvm: ivs port name // vmware: specific portgroup naming convention String pgName = ""; - if (dest.getHost().getHypervisorType() == HypervisorType.KVM){ + if (dest.getHost().getHypervisorType() == HypervisorType.KVM) { pgName = hostname; - } else if (dest.getHost().getHypervisorType() == HypervisorType.VMware){ + } else if (dest.getHost().getHypervisorType() == HypervisorType.VMware) { pgName = hostname + "-" + vswitchName; } @@ -306,7 +306,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { String nicId = nic.getUuid(); String tenantId; - if(network.getVpcId()!=null) { + if (network.getVpcId() != null) { tenantId = network.getNetworkDomain(); } else { tenantId = networkId; @@ -439,16 +439,16 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { DataCenterVO zone = _zoneDao.findById(physicalNetwork.getDataCenterId()); String zoneName; - if(zone!= null){ + if (zone != null) { zoneName = zone.getName(); } else { zoneName = String.valueOf(zoneId); } Boolean natNow = _bcfUtils.isNatEnabled(); - if (!nat && natNow){ + if (!nat && natNow) { throw new CloudRuntimeException("NAT is enabled in existing controller. Enable NAT for new controller or remove existing controller first."); - } else if (nat && !natNow){ + } else if (nat && !natNow) { throw new CloudRuntimeException("NAT is disabled in existing controller. Disable NAT for new controller or remove existing controller first."); } @@ -582,7 +582,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { BigSwitchBcfResource bcfResource = (BigSwitchBcfResource) resource; bcfUtilsInit(); - if(_bcfUtils.getTopology()!=null){ + if (_bcfUtils.getTopology() != null) { bcfResource.setTopology(_bcfUtils.getTopology()); } @@ -621,7 +621,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { _bcfUtils.listACLbyNetwork(network); Vpc vpc = null; - if(network.getVpcId()!=null){ + if (network.getVpcId() != null) { vpc = _vpcDao.acquireInLockTable(network.getVpcId()); } @@ -635,11 +635,11 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { tenantId = network.getUuid(); } - for (StaticNat rule: rules){ + for (StaticNat rule: rules) { String srcIp = _ipAddressDao.findById(rule.getSourceIpAddressId()).getAddress().addr(); String dstIp = rule.getDestIpAddress(); String mac = rule.getSourceMacAddress(); - if(!rule.isForRevoke()) { + if (!rule.isForRevoke()) { logger.debug("BCF enables static NAT for public IP: " + srcIp + " private IP " + dstIp + " mac " + mac); CreateBcfStaticNatCommand cmd = new CreateBcfStaticNatCommand( @@ -671,13 +671,13 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { SubnetUtils utils; String cidr = null; List cidrList; - for(NetworkACLItem r: rules){ - if(r.getState()==NetworkACLItem.State.Revoke){ + for (NetworkACLItem r: rules) { + if (r.getState() == NetworkACLItem.State.Revoke) { continue; } cidrList = r.getSourceCidrList(); - if(cidrList != null){ - if(cidrList.size()>1 || !r.getSourcePortEnd().equals(r.getSourcePortStart())){ + if (cidrList != null) { + if (cidrList.size() > 1 || !r.getSourcePortEnd().equals(r.getSourcePortStart())) { throw new ResourceUnavailableException("One CIDR and one port only please.", Network.class, network.getId()); } else { @@ -688,7 +688,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { cidr = ""; } else { utils = new SubnetUtils(cidr); - if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){ + if (!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())) { throw new ResourceUnavailableException("Invalid CIDR in Network ACL rule.", Network.class, network.getId()); } @@ -710,13 +710,13 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { SubnetUtils utils; String cidr = null; List cidrList; - for(FirewallRule r: rules){ - if(r.getState()==FirewallRule.State.Revoke){ + for (FirewallRule r: rules) { + if (r.getState() == FirewallRule.State.Revoke) { continue; } cidrList = r.getSourceCidrList(); - if(cidrList != null){ - if(cidrList.size()>1 || !r.getSourcePortEnd().equals(r.getSourcePortStart())){ + if (cidrList != null) { + if (cidrList.size()>1 || !r.getSourcePortEnd().equals(r.getSourcePortStart())) { throw new ResourceUnavailableException("One CIDR and one port only please.", Network.class, network.getId()); } else { @@ -727,7 +727,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { cidr = ""; } else { utils = new SubnetUtils(cidr); - if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){ + if (!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())) { throw new ResourceUnavailableException("Invalid CIDR in Firewall rule.", Network.class, network.getId()); } @@ -741,7 +741,7 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { bcfUtilsInit(); Vpc vpc = null; - if(network.getVpcId()!=null){ + if (network.getVpcId() != null) { vpc = _vpcDao.acquireInLockTable(network.getVpcId()); } @@ -756,23 +756,23 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter { UpdateBcfRouterCommand cmd = new UpdateBcfRouterCommand(tenantId); List aclList = _bcfUtils.listACLbyNetwork(network); - for(AclData acl: aclList){ + for (AclData acl: aclList) { cmd.addAcl(acl); } - if(vpc != null){ + if (vpc != null) { cmd.setPublicIp(_bcfUtils.getPublicIpByVpc(vpc)); } else { cmd.setPublicIp(_bcfUtils.getPublicIpByNetwork(network)); } BcfAnswer answer = _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd, network); - if(answer != null && !answer.getResult()){ + if (answer != null && !answer.getResult()) { throw new IllegalArgumentException("Illegal router update arguments"); } } - private void bcfUtilsInit(){ + private void bcfUtilsInit() { if (_bcfUtils == null) { _bcfUtils = new BigSwitchBcfUtils(_networkDao, _nicDao, _vmDao, _hostDao, _vpcDao, _bigswitchBcfDao, diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index a4dafd60562..0293e0d08fb 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2539,10 +2539,11 @@ public class ApiResponseHelper implements ResponseGenerator { response.setType(network.getGuestType().toString()); } - response.setGateway(network.getGateway()); + response.setGateway(com.cloud.utils.StringUtils.getFirstValueFromCommaSeparatedString(network.getGateway())); + String cidr = com.cloud.utils.StringUtils.getFirstValueFromCommaSeparatedString(network.getCidr()); // FIXME - either set netmask or cidr - response.setCidr(network.getCidr()); + response.setCidr(cidr); if (network.getNetworkCidr() != null) { response.setNetworkCidr((network.getNetworkCidr())); } @@ -2553,18 +2554,18 @@ public class ApiResponseHelper implements ResponseGenerator { if (network.getNetworkCidr() != null) { response.setNetmask(NetUtils.cidr2Netmask(network.getNetworkCidr())); } - if (((network.getCidr()) != null) && (network.getNetworkCidr() == null)) { - response.setNetmask(NetUtils.cidr2Netmask(network.getCidr())); + if ((cidr != null) && (network.getNetworkCidr() == null)) { + response.setNetmask(NetUtils.cidr2Netmask(cidr)); } - response.setIp6Gateway(network.getIp6Gateway()); - response.setIp6Cidr(network.getIp6Cidr()); + response.setIp6Gateway(com.cloud.utils.StringUtils.getFirstValueFromCommaSeparatedString(network.getIp6Gateway())); + response.setIp6Cidr(com.cloud.utils.StringUtils.getFirstValueFromCommaSeparatedString(network.getIp6Cidr())); // create response for reserved IP ranges that can be used for // non-cloudstack purposes String reservation = null; - if ((network.getCidr() != null) && (NetUtils.isNetworkAWithinNetworkB(network.getCidr(), network.getNetworkCidr()))) { - String[] guestVmCidrPair = network.getCidr().split("\\/"); + if ((cidr != null) && (NetUtils.isNetworkAWithinNetworkB(cidr, network.getNetworkCidr()))) { + String[] guestVmCidrPair = cidr.split("\\/"); String[] guestCidrPair = network.getNetworkCidr().split("\\/"); Long guestVmCidrSize = Long.valueOf(guestVmCidrPair[1]); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index a60b84bf2b6..03b97af4e10 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -5397,9 +5397,42 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final VlanVO vlan = commitVlanAndIpRange(zoneId, networkId, physicalNetworkId, podId, startIP, endIP, vlanGateway, vlanNetmask, vlanId, domain, vlanOwner, vlanIp6Gateway, vlanIp6Cidr, ipv4, zone, vlanType, ipv6Range, ipRange, forSystemVms, provider); + if (vlan != null) { + if (ipv4) { + addCidrAndGatewayForIpv4(networkId, vlanGateway, vlanNetmask); + } else if (ipv6) { + addCidrAndGatewayForIpv6(networkId, vlanIp6Gateway, vlanIp6Cidr); + } + } + return vlan; } + private void addCidrAndGatewayForIpv4(final long networkId, final String vlanGateway, final String vlanNetmask) { + final NetworkVO networkVO = _networkDao.findById(networkId); + String networkCidr = networkVO.getCidr(); + String newCidr = NetUtils.getCidrFromGatewayAndNetmask(vlanGateway, vlanNetmask); + String newNetworkCidr = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkCidr, newCidr, true); + networkVO.setCidr(newNetworkCidr); + + String networkGateway = networkVO.getGateway(); + String newNetworkGateway = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkGateway, vlanGateway, true); + networkVO.setGateway(newNetworkGateway); + _networkDao.update(networkId, networkVO); + } + + private void addCidrAndGatewayForIpv6(final long networkId, final String vlanIp6Gateway, final String vlanIp6Cidr) { + final NetworkVO networkVO = _networkDao.findById(networkId); + String networkIp6Cidr = networkVO.getIp6Cidr(); + String newNetworkIp6Cidr = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkIp6Cidr, vlanIp6Cidr, true); + networkVO.setIp6Cidr(newNetworkIp6Cidr); + + String networkIp6Gateway = networkVO.getIp6Gateway(); + String newNetworkIp6Gateway = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkIp6Gateway, vlanIp6Gateway, true); + networkVO.setIp6Gateway(newNetworkIp6Gateway); + _networkDao.update(networkId, networkVO); + } + private boolean isConnectivityWithoutVlan(Network network) { boolean connectivityWithoutVlan = false; if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Connectivity)) { @@ -6440,12 +6473,47 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati private boolean deleteAndPublishVlanAndPublicIpRange(final long userId, final long vlanDbId, final Account caller) { VlanVO deletedVlan = deleteVlanAndPublicIpRange(userId, vlanDbId, caller); if (deletedVlan != null) { + final boolean ipv4 = deletedVlan.getVlanGateway() != null; + final boolean ipv6 = deletedVlan.getIp6Gateway() != null; + final long networkId = deletedVlan.getNetworkId(); + + if (ipv4) { + removeCidrAndGatewayForIpv4(networkId, deletedVlan); + } else if (ipv6) { + removeCidrAndGatewayForIpv6(networkId, deletedVlan); + } + messageBus.publish(_name, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, deletedVlan); return true; } return false; } + private void removeCidrAndGatewayForIpv4(final long networkId, VlanVO deletedVlan) { + final NetworkVO networkVO = _networkDao.findById(networkId); + String networkCidr = networkVO.getCidr(); + String cidrToRemove = NetUtils.getCidrFromGatewayAndNetmask(deletedVlan.getVlanGateway(), deletedVlan.getVlanNetmask()); + String newNetworkCidr = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkCidr, cidrToRemove, false); + networkVO.setCidr(newNetworkCidr); + + String networkGateway = networkVO.getGateway(); + String newNetworkGateway = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkGateway, deletedVlan.getVlanGateway(), false); + networkVO.setGateway(newNetworkGateway); + _networkDao.update(networkId, networkVO); + } + + private void removeCidrAndGatewayForIpv6(final long networkId, VlanVO deletedVlan) { + final NetworkVO networkVO = _networkDao.findById(networkId); + String networkIp6Cidr = networkVO.getIp6Cidr(); + String newNetworkIp6Cidr = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkIp6Cidr, deletedVlan.getIp6Cidr(), false); + networkVO.setIp6Cidr(newNetworkIp6Cidr); + + String networkIp6Gateway = networkVO.getIp6Gateway(); + String newNetworkIp6Gateway = com.cloud.utils.StringUtils.updateCommaSeparatedStringWithValue(networkIp6Gateway, deletedVlan.getIp6Gateway(), false); + networkVO.setIp6Gateway(newNetworkIp6Gateway); + _networkDao.update(networkId, networkVO); + } + @Override public void checkDiskOfferingAccess(final Account caller, final DiskOffering dof, DataCenter zone) { for (final SecurityChecker checker : _secChecker) { diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java index 3e4e970af2e..94295e3bb2e 100644 --- a/utils/src/main/java/com/cloud/utils/StringUtils.java +++ b/utils/src/main/java/com/cloud/utils/StringUtils.java @@ -24,14 +24,17 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class StringUtils extends org.apache.commons.lang3.StringUtils { private static final char[] hexChar = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; @@ -409,4 +412,53 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { String[] finalMergedTagsArray = appendedTags.split(","); return finalMergedTagsArray; } + + + /** + * Converts the comma separated numbers and ranges to numbers + * @param originalString the original string (can be null or empty) containing list of comma separated values that has to be updated + * @param value the value to add to, or remove from the original string + * @param add if true, adds the input value; if false, removes it + * @return String containing the modified original string (or null if empty) + */ + public static String updateCommaSeparatedStringWithValue(String originalString, String value, boolean add) { + if (org.apache.commons.lang3.StringUtils.isEmpty(value)) { + return originalString; + } + + Set values = new LinkedHashSet<>(); + + if (org.apache.commons.lang3.StringUtils.isNotEmpty(originalString)) { + values.addAll(Arrays.stream(originalString.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList())); + } + + if (add) { + values.add(value); + } else { + values.remove(value); + } + + return values.isEmpty() ? null : String.join(",", values); + } + + /** + * Returns the first value from a comma-separated string. + * @param inputString the input string (can be null or empty) containing list of comma separated values + * @return the first value, or null if none found + */ + public static String getFirstValueFromCommaSeparatedString(String inputString) { + if (org.apache.commons.lang3.StringUtils.isEmpty(inputString)) { + return inputString; + } + + String[] values = inputString.split(","); + if (values.length > 0) { + return values[0].trim(); + } + + return null; + } }