Add support to add IPv6 Public IP range as IPAM Allocation / Subnet on Netris (#36)

* Add support to add IPv6 Public IP range as IPAM Allocation / Subnet on Netris

* Add ipam alloc and subnet for the ipv6 subnet associated to the vpc tier network

* remove commented code
This commit is contained in:
Pearl Dsilva 2025-01-11 21:12:32 -05:00 committed by GitHub
parent f54cb50d17
commit 57609c79c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 121 additions and 50 deletions

View File

@ -43,6 +43,7 @@ import com.cloud.bgp.BGPService;
import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
import com.cloud.network.dao.NetrisProviderDao;
import com.cloud.network.dao.NsxProviderDao;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
@ -361,6 +362,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
private ASNumberDao asNumberDao;
@Inject
private BGPService bgpService;
@Inject
private Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao;
@Override
public List<NetworkGuru> getNetworkGurus() {
@ -824,6 +827,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
if (domainId != null && aclType == ACLType.Domain) {
_networksDao.addDomainToNetwork(id, domainId, subdomainAccess == null || subdomainAccess);
}
String ipv6Cidr = network.getIp6Cidr();
String ipv6Gateway = network.getIp6Gateway();
if (StringUtils.isNoneBlank(ipv6Cidr, ipv6Gateway)) {
ipv6Service.assignIpv6SubnetToNetwork(ipv6Cidr, networkPersisted.getId());
}
}
});
guru.setup(network, relatedFile);

View File

@ -64,4 +64,6 @@ public interface VlanDao extends GenericDao<VlanVO, Long> {
List<VlanVO> listIpv6RangeByZoneIdAndVlanId(long zoneId, String vlanId);
List<VlanVO> listIpv6SupportingVlansByZone(long zoneId);
List<VlanVO> listVlansForExternalNetworkProvider(long zoneId, String detailKey);
}

View File

@ -26,6 +26,7 @@ import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.dc.VlanDetailsVO;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@ -67,6 +68,8 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
protected SearchBuilder<VlanVO> ZoneVlanIp6Search;
protected SearchBuilder<VlanVO> ZoneIp6Search;
protected SearchBuilder<VlanVO> ZoneVlansSearch;
protected SearchBuilder<VlanVO> ProviderVlanSearch;
protected SearchBuilder<VlanDetailsVO> VlanDetailsProviderSearch;
protected SearchBuilder<AccountVlanMapVO> AccountVlanMapSearch;
protected SearchBuilder<DomainVlanMapVO> DomainVlanMapSearch;
@ -79,6 +82,8 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
protected DomainVlanMapDao _domainVlanMapDao;
@Inject
protected IPAddressDao _ipAddressDao;
@Inject
protected VlanDetailsDao vlanDetailsDao;
@Override
public VlanVO findByZoneAndVlanId(long zoneId, String vlanId) {
@ -277,6 +282,19 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
ZoneVlansSearch.and("zoneId", ZoneVlansSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
ZoneVlansSearch.and("vlan", ZoneVlansSearch.entity().getVlanTag(), SearchCriteria.Op.IN);
ZoneVlansSearch.done();
ProviderVlanSearch = createSearchBuilder();
ProviderVlanSearch.and("removed", ProviderVlanSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
ProviderVlanSearch.and("dataCenterId", ProviderVlanSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
VlanDetailsProviderSearch = vlanDetailsDao.createSearchBuilder();
VlanDetailsProviderSearch.and("name", VlanDetailsProviderSearch.entity().getName(), SearchCriteria.Op.EQ);
VlanDetailsProviderSearch.and("value", VlanDetailsProviderSearch.entity().getValue(), SearchCriteria.Op.EQ);
ProviderVlanSearch.join("VlanDetailsProviderSearch", VlanDetailsProviderSearch, ProviderVlanSearch.entity().getId(),
VlanDetailsProviderSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
VlanDetailsProviderSearch.done();
ProviderVlanSearch.done();
return result;
}
@ -434,4 +452,13 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
return listBy(sc);
}
@Override
public List<VlanVO> listVlansForExternalNetworkProvider(long zoneId, String detailKey) {
SearchCriteria<VlanVO> sc = ProviderVlanSearch.create();
sc.setParameters("dataCenterId", zoneId);
sc.setJoinParameters("VlanDetailsProviderSearch", "name", detailKey);
sc.setJoinParameters("VlanDetailsProviderSearch", "value", "true");
return search(sc, null);
}
}

View File

@ -23,6 +23,7 @@ public class CreateNetrisVnetCommand extends NetrisCommand {
private Integer vxlanId;
private String gateway;
private String netrisTag;
private String ipv6Cidr;
public CreateNetrisVnetCommand(Long zoneId, Long accountId, Long domainId, String vpcName, Long vpcId, String vNetName, Long networkId, String cidr, String gateway, boolean isVpc) {
super(zoneId, accountId, domainId, vNetName, networkId, isVpc);
@ -64,4 +65,12 @@ public class CreateNetrisVnetCommand extends NetrisCommand {
public void setNetrisTag(String netrisTag) {
this.netrisTag = netrisTag;
}
public String getIpv6Cidr() {
return ipv6Cidr;
}
public void setIpv6Cidr(String ipv6Cidr) {
this.ipv6Cidr = ipv6Cidr;
}
}

View File

@ -594,6 +594,7 @@ public class NetrisApiClientImpl implements NetrisApiClient {
String netrisTag = cmd.getNetrisTag();
String netmask = vnetCidr.split("/")[1];
String netrisGateway = cmd.getGateway() + "/" + netmask;
String netrisV6Cidr = cmd.getIpv6Cidr();
boolean isVpc = cmd.isVpc();
String suffix = getNetrisVpcNameSuffix(vpcId, vpcName, networkId, networkName, isVpc);
@ -614,9 +615,18 @@ public class NetrisApiClientImpl implements NetrisApiClient {
String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, vnetCidr) ;
createIpamSubnetInternal(netrisSubnetName, vnetCidr, SubnetBody.PurposeEnum.COMMON, associatedVpc);
if (Objects.nonNull(netrisV6Cidr)) {
String netrisV6IpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, netrisV6Cidr);
String netrisV6SubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, netrisV6Cidr) ;
InlineResponse2004Data createdIpamAllocation = createIpamAllocationInternal(netrisV6IpamAllocationName, netrisV6Cidr, associatedVpc);
if (Objects.isNull(createdIpamAllocation)) {
throw new CloudRuntimeException(String.format("Failed to create Netris IPAM Allocation %s for VPC %s", netrisV6IpamAllocationName, netrisVpcName));
}
createIpamSubnetInternal(netrisV6SubnetName, netrisV6Cidr, SubnetBody.PurposeEnum.COMMON, associatedVpc);
}
logger.debug("Successfully created IPAM Subnet {} for network {} on Netris", netrisSubnetName, networkName);
VnetResAddBody vnetResponse = createVnetInternal(associatedVpc, netrisVnetName, netrisGateway, vxlanId, netrisTag);
VnetResAddBody vnetResponse = createVnetInternal(associatedVpc, netrisVnetName, netrisGateway, netrisV6Cidr, vxlanId, netrisTag);
if (vnetResponse == null || !vnetResponse.isIsSuccess()) {
String reason = vnetResponse == null ? "Empty response" : "Operation failed on Netris";
logger.debug("The Netris vNet creation {} failed: {}", vNetName, reason);
@ -1107,25 +1117,33 @@ public class NetrisApiClientImpl implements NetrisApiClient {
}
}
VnetResAddBody createVnetInternal(VPCListing associatedVpc, String netrisVnetName, String netrisGateway, Integer vxlanId, String netrisTag) {
VnetResAddBody createVnetInternal(VPCListing associatedVpc, String netrisVnetName, String netrisGateway, String netrisV6Gateway, Integer vxlanId, String netrisTag) {
logger.debug("Creating Netris VPC vNet {} for CIDR {}", netrisVnetName, netrisGateway);
try {
VnetAddBody vnetBody = new VnetAddBody();
vnetBody.setCustomAnycastMac("");
VnetAddBodyGateways gateways = new VnetAddBodyGateways();
gateways.prefix(netrisGateway);
gateways.setDhcpEnabled(false);
VnetAddBodyGateways gatewayV4 = new VnetAddBodyGateways();
gatewayV4.prefix(netrisGateway);
gatewayV4.setDhcpEnabled(false);
VnetAddBodyDhcp dhcp = new VnetAddBodyDhcp();
dhcp.setEnd("");
dhcp.setStart("");
dhcp.setOptionSet(new VnetAddBodyDhcpOptionSet());
gateways.setDhcp(dhcp);
gatewayV4.setDhcp(dhcp);
List<VnetAddBodyGateways> gatewaysList = new ArrayList<>();
gatewaysList.add(gateways);
vnetBody.setGateways(gatewaysList);
gatewaysList.add(gatewayV4);
if (Objects.nonNull(netrisV6Gateway)) {
VnetAddBodyGateways gatewayV6 = new VnetAddBodyGateways();
gatewayV6.prefix(netrisV6Gateway);
gatewayV6.setDhcpEnabled(false);
gatewayV6.setDhcp(dhcp);
gatewaysList.add(gatewayV6);
}
vnetBody.setGateways(gatewaysList);
vnetBody.setGuestTenants(new ArrayList<>());
vnetBody.setL3vpn(false);
vnetBody.setName(netrisVnetName);

View File

@ -18,17 +18,15 @@ package org.apache.cloudstack.service;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.VlanDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.IpAddress;
import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMapVO;
import com.cloud.network.Network;
import com.cloud.network.Networks;
import com.cloud.network.SDNProviderNetworkRule;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
import com.cloud.network.dao.NetrisProviderDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
@ -37,6 +35,7 @@ import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.element.NetrisProviderVO;
import com.cloud.network.netris.NetrisService;
import com.cloud.network.vpc.Vpc;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import inet.ipaddr.IPAddress;
@ -82,11 +81,9 @@ public class NetrisServiceImpl implements NetrisService, Configurable {
@Inject
private PhysicalNetworkDao physicalNetworkDao;
@Inject
private IPAddressDao ipAddressDao;
@Inject
private VlanDao vlanDao;
@Inject
private VlanDetailsDao vlanDetailsDao;
private Ipv6GuestPrefixSubnetNetworkMapDao ipv6PrefixNetworkMapDao;
@Override
public String getConfigComponentName() {
@ -138,10 +135,10 @@ public class NetrisServiceImpl implements NetrisService, Configurable {
/**
* Prepare the Netris Public Range to be used by CloudStack after the zone is created and the Netris provider is added
*/
public SetupNetrisPublicRangeCommand createSetupPublicRangeCommand(long zoneId, String gateway, String netmask, String ipRange) {
private Pair<String, String> getAllocationAndSubnet(String gateway, String netmask, String ipRange) {
String superCidr = NetUtils.getCidrFromGatewayAndNetmask(gateway, netmask);
String subnetNatCidr = calculateSubnetCidrFromIpRange(ipRange);
return new SetupNetrisPublicRangeCommand(zoneId, superCidr, subnetNatCidr);
return new Pair<>(superCidr, subnetNatCidr);
}
@Override
@ -151,38 +148,38 @@ public class NetrisServiceImpl implements NetrisService, Configurable {
if (CollectionUtils.isEmpty(physicalNetworks)) {
return false;
}
for (PhysicalNetworkVO physicalNetwork : physicalNetworks) {
List<IPAddressVO> publicIps = ipAddressDao.listByPhysicalNetworkId(physicalNetwork.getId());
List<Long> vlanDbIds = publicIps.stream()
.filter(x -> !x.isForSystemVms())
.map(IPAddressVO::getVlanId)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(vlanDbIds)) {
String msg = "Cannot find a public IP range VLAN range for the Netris Public traffic";
logger.error(msg);
throw new CloudRuntimeException(msg);
List<VlanVO> providerVlanIds = vlanDao.listVlansForExternalNetworkProvider(zoneId, ApiConstants.NETRIS_DETAIL_KEY);
List<Long> vlanDbIds = providerVlanIds.stream().map(VlanVO::getId).collect(Collectors.toList());
if (CollectionUtils.isEmpty(vlanDbIds)) {
String msg = "Cannot find a public IP range VLAN range for the Netris Public traffic";
logger.error(msg);
throw new CloudRuntimeException(msg);
}
for (Long vlanDbId : vlanDbIds) {
VlanVO vlanRecord = vlanDao.findById(vlanDbId);
String gateway = Objects.nonNull(vlanRecord.getVlanGateway()) ? vlanRecord.getVlanGateway() : vlanRecord.getIp6Gateway();
String netmask = vlanRecord.getVlanNetmask();
String ipRange = vlanRecord.getIpRange();
String ip6Cidr = vlanRecord.getIp6Cidr();
SetupNetrisPublicRangeCommand cmd = null;
if (NetUtils.isValidIp4(gateway)) {
Pair<String, String> allocationAndSubnet = getAllocationAndSubnet(gateway, netmask, ipRange);
cmd = new SetupNetrisPublicRangeCommand(zoneId, allocationAndSubnet.first(), allocationAndSubnet.second());
} else if (NetUtils.isValidIp6(gateway)) {
cmd = new SetupNetrisPublicRangeCommand(zoneId, ip6Cidr, ip6Cidr);
}
for (Long vlanDbId : vlanDbIds) {
VlanVO vlanRecord = vlanDao.findById(vlanDbId);
if (vlanRecord == null) {
logger.error("Cannot set up the Netris Public IP range as it cannot find the public range on database");
return false;
}
VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(vlanDbId, ApiConstants.NETRIS_DETAIL_KEY);
if (vlanDetail == null) {
logger.debug("Skipping the Public IP range {} creation on Netris as it does not belong to the Netris Public IP Pool", vlanRecord.getIpRange());
continue;
}
String gateway = vlanRecord.getVlanGateway();
String netmask = vlanRecord.getVlanNetmask();
String ipRange = vlanRecord.getIpRange();
SetupNetrisPublicRangeCommand cmd = createSetupPublicRangeCommand(zoneId, gateway, netmask, ipRange);
NetrisAnswer answer = sendNetrisCommand(cmd, zoneId);
if (!answer.getResult()) {
throw new CloudRuntimeException("Netris Public IP Range setup failed, please check the logs");
}
if (cmd == null) {
throw new CloudRuntimeException("Incorrect gateway and netmask details provided for the Netris Public IP range setup");
}
NetrisAnswer answer = sendNetrisCommand(cmd, zoneId);
if (!answer.getResult()) {
throw new CloudRuntimeException("Netris Public IP Range setup failed, please check the logs");
}
}
return true;
}
@ -208,6 +205,10 @@ public class NetrisServiceImpl implements NetrisService, Configurable {
cmd.setVxlanId(Integer.parseInt(vxlanId));
NetrisProviderVO netrisProvider = netrisProviderDao.findByZoneId(zoneId);
cmd.setNetrisTag(netrisProvider.getNetrisTag());
if (Objects.nonNull(networkId)) {
Ipv6GuestPrefixSubnetNetworkMapVO ipv6PrefixNetworkMapVO = ipv6PrefixNetworkMapDao.findByNetworkId(networkId);
cmd.setIpv6Cidr(ipv6PrefixNetworkMapVO.getSubnet());
}
NetrisAnswer answer = sendNetrisCommand(cmd, zoneId);
return answer.getResult();
}

View File

@ -1800,10 +1800,6 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
_networkDetailsDao.persist(new NetworkDetailVO(network.getId(), Network.hideIpAddressUsage, String.valueOf(hideIpAddressUsage), false));
}
if (ip6GatewayCidr != null) {
ipv6Service.assignIpv6SubnetToNetwork(ip6Cidr, network.getId());
}
// assign to network
if (NetworkOffering.NetworkMode.ROUTED.equals(ntwkOff.getNetworkMode())) {
routedIpv4Manager.assignIpv4SubnetToNetwork(network);

View File

@ -230,6 +230,16 @@
<a-form-item name="ip6cidr" ref="ip6cidr" :label="$t('label.cidr')" class="form__item">
<a-input v-model:value="form.ip6cidr" />
</a-form-item>
<a-form-item name="provider" ref="provider">
<template #label>
<tooltip-label :title="$t('label.provider')"/>
</template>
<a-select v-model:value="form.provider">
<a-select-option value=""></a-select-option>
<a-select-option value="NSX">{{ $t('label.nsx') }}</a-select-option>
<a-select-option value="Netris">{{ $t('label.netris') }}</a-select-option>
</a-select>
</a-form-item>
</div>
<div v-else>
<a-form-item name="gateway" ref="gateway" :label="$t('label.gateway')" class="form__item">