This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch netris-integration-upstream in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 5d7f0649c7eebeb58558c0eaf590b393f18d353a Author: Nicolas Vazquez <nicovazque...@gmail.com> AuthorDate: Thu Nov 7 11:05:20 2024 -0300 Set up Netris Public range on new zone addition (#15) * Set up Netris Public range on new zone addition * Add dependency to calculate subnet containing a start and end IP * Remove unused import * Move dependency to the netris module * Rename Netris IP range * Refactor logic * Revert "Refactor logic" This reverts commit 7ec36a81320444c37e7bb914dd895060b663411b. * Fix setup range after adding Netris Provider * Fix VXLAN range adding on zone creation --- plugins/network-elements/netris/pom.xml | 5 + .../apache/cloudstack/agent/api/NetrisCommand.java | 10 +- .../agent/api/SetupNetrisPublicRangeCommand.java | 37 ++++++ .../apache/cloudstack/resource/NetrisResource.java | 11 ++ .../resource/NetrisResourceObjectUtils.java | 19 ++- .../apache/cloudstack/service/NetrisApiClient.java | 8 ++ .../cloudstack/service/NetrisApiClientImpl.java | 138 ++++++++++++++++----- .../service/NetrisProviderServiceImpl.java | 88 ++++++++++++- .../service/NetrisProviderServiceImplTest.java | 33 +++++ .../java/com/cloud/network/NetworkServiceImpl.java | 2 +- ui/public/locales/en.json | 2 +- 11 files changed, 305 insertions(+), 48 deletions(-) diff --git a/plugins/network-elements/netris/pom.xml b/plugins/network-elements/netris/pom.xml index 881d8195a93..21aeadec935 100644 --- a/plugins/network-elements/netris/pom.xml +++ b/plugins/network-elements/netris/pom.xml @@ -35,5 +35,10 @@ <artifactId>netris-java-sdk</artifactId> <version>1.0.0</version> </dependency> + <dependency> + <groupId>com.github.seancfoley</groupId> + <artifactId>ipaddress</artifactId> + <version>5.5.1</version> + </dependency> </dependencies> </project> diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java index 440892e6e87..463001452c1 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java @@ -20,13 +20,13 @@ import com.cloud.agent.api.Command; public class NetrisCommand extends Command { private final long zoneId; - private final long accountId; - private final long domainId; + private final Long accountId; + private final Long domainId; private final String name; private final long id; private final boolean isVpc; - public NetrisCommand(long zoneId, long accountId, long domainId, String name, long id, boolean isVpc) { + public NetrisCommand(long zoneId, Long accountId, Long domainId, String name, long id, boolean isVpc) { this.zoneId = zoneId; this.accountId = accountId; this.domainId = domainId; @@ -48,11 +48,11 @@ public class NetrisCommand extends Command { return zoneId; } - public long getAccountId() { + public Long getAccountId() { return accountId; } - public long getDomainId() { + public Long getDomainId() { return domainId; } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java new file mode 100644 index 00000000000..5cbdd96e76a --- /dev/null +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.agent.api; + +public class SetupNetrisPublicRangeCommand extends NetrisCommand { + + private final String superCidr; + private final String exactCidr; + + public SetupNetrisPublicRangeCommand(long zoneId, String superCidr, String exactCidr) { + super(zoneId, null, null, null, zoneId, false); + this.superCidr = superCidr; + this.exactCidr = exactCidr; + } + + public String getSuperCidr() { + return superCidr; + } + + public String getExactCidr() { + return exactCidr; + } +} diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java index 0fdb0913053..dc922aef963 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java @@ -34,6 +34,7 @@ import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; import org.apache.cloudstack.agent.api.NetrisAnswer; import org.apache.cloudstack.StartupNetrisCommand; +import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; import org.apache.cloudstack.service.NetrisApiClient; import org.apache.cloudstack.service.NetrisApiClientImpl; import org.apache.logging.log4j.LogManager; @@ -94,6 +95,8 @@ public class NetrisResource implements ServerResource { return executeRequest((CreateNetrisVnetCommand) cmd); } else if (cmd instanceof DeleteNetrisVnetCommand) { return executeRequest((DeleteNetrisVnetCommand) cmd); + } else if (cmd instanceof SetupNetrisPublicRangeCommand) { + return executeRequest((SetupNetrisPublicRangeCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -250,6 +253,14 @@ public class NetrisResource implements ServerResource { return new NetrisAnswer(cmd, true, "OK"); } + private Answer executeRequest(SetupNetrisPublicRangeCommand cmd) { + boolean result = netrisApiClient.setupZoneLevelPublicRange(cmd); + if (!result) { + return new NetrisAnswer(cmd, false, "Netris Setup for Public Range failed"); + } + return new NetrisAnswer(cmd, true, "OK"); + } + @Override public boolean start() { return true; diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java index 127636dd7d5..5b19aa43199 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java @@ -27,14 +27,19 @@ public class NetrisResourceObjectUtils { public static String retrieveNetrisResourceObjectName(NetrisCommand cmd, NetrisObjectType netrisObjectType, String... suffixes) { long zoneId = cmd.getZoneId(); - long accountId = cmd.getAccountId(); - long domainId = cmd.getDomainId(); + Long accountId = cmd.getAccountId(); + Long domainId = cmd.getDomainId(); long objectId = cmd.getId(); String objectName = cmd.getName(); boolean isVpc = cmd.isVpc(); + boolean isZoneLevel = accountId == null && domainId == null; StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(String.format("D%s-A%s-Z%s", domainId, accountId, zoneId)); + if (isZoneLevel) { + stringBuilder.append(String.format("Z%s", zoneId)); + } else { + stringBuilder.append(String.format("D%s-A%s-Z%s", domainId, accountId, zoneId)); + } String prefix = isVpc ? "-V" : "-N"; switch (netrisObjectType) { case VPC: @@ -46,10 +51,14 @@ public class NetrisResourceObjectUtils { } break; case IPAM_ALLOCATION: - stringBuilder.append(String.format("%s%s", prefix, objectId)); + if (!isZoneLevel) { + stringBuilder.append(String.format("%s%s", prefix, objectId)); + } break; case IPAM_SUBNET: - stringBuilder.append(String.format("-N%s", objectId)); + if (!isZoneLevel) { + stringBuilder.append(String.format("-N%s", objectId)); + } break; case VNET: break; diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java index 42cb9a772fc..0a00583df52 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; +import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; import java.util.List; @@ -50,4 +51,11 @@ public interface NetrisApiClient { boolean createVnet(CreateNetrisVnetCommand cmd); boolean deleteVnet(DeleteNetrisVnetCommand cmd); + + /** + * Check and create zone level Netris Public range in the following manner: + * - Check the IPAM allocation for the zone super CIDR. In case it doesn't exist, create it + * - Check the IPAM subnet for NAT purpose for the range start-end. In case it doesn't exist, create it + */ + boolean setupZoneLevelPublicRange(SetupNetrisPublicRangeCommand cmd); } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java index b2cc9d47bdf..b61c84e0110 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java @@ -33,6 +33,9 @@ import io.netris.model.FilterBySites; import io.netris.model.FilterByVpc; import io.netris.model.GetSiteBody; import io.netris.model.InlineResponse2004; +import io.netris.model.InlineResponse2004Data; +import io.netris.model.IpTree; +import io.netris.model.IpTreeAllocation; import io.netris.model.IpTreeAllocationTenant; import io.netris.model.IpTreeSubnet; import io.netris.model.IpTreeSubnetSites; @@ -63,6 +66,7 @@ import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; +import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; import org.apache.cloudstack.resource.NetrisResourceObjectUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.LogManager; @@ -196,25 +200,31 @@ public class NetrisApiClientImpl implements NetrisApiClient { return response; } - private InlineResponse2004 createVpcAllocationInternal(VPCResponseObjectOK createdVpc, String cidr, int adminTenantId, - String adminTenantName, String netrisIpamAllocationName) { - logger.debug(String.format("Creating Netris VPC Allocation %s for VPC %s", cidr, createdVpc.getData().getName())); + private InlineResponse2004Data createIpamAllocationInternal(String ipamName, String ipamPrefix, VPCListing vpc) { + logger.debug(String.format("Creating Netris IPAM Allocation %s for VPC %s", ipamPrefix, vpc.getName())); try { IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); AllocationBody body = new AllocationBody(); AllocationBodyVpc allocationBodyVpc = new AllocationBodyVpc(); - allocationBodyVpc.setId(createdVpc.getData().getId()); - allocationBodyVpc.setName(createdVpc.getData().getName()); + allocationBodyVpc.setId(vpc.getId()); + allocationBodyVpc.setName(vpc.getName()); body.setVpc(allocationBodyVpc); - body.setName(netrisIpamAllocationName); - body.setPrefix(cidr); + body.setName(ipamName); + body.setPrefix(ipamPrefix); IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); - allocationTenant.setId(new BigDecimal(adminTenantId)); - allocationTenant.setName(adminTenantName); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); body.setTenant(allocationTenant); - return ipamApi.apiV2IpamAllocationPost(body); + InlineResponse2004 ipamResponse = ipamApi.apiV2IpamAllocationPost(body); + if (ipamResponse == null || !ipamResponse.isIsSuccess()) { + String reason = ipamResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris Allocation {} for VPC {} creation failed: {}", ipamPrefix, vpc.getName(), reason); + return null; + } + logger.debug(String.format("Successfully created VPC %s and its IPAM Allocation %s on Netris", vpc.getName(), ipamPrefix)); + return ipamResponse.getData(); } catch (ApiException e) { - logAndThrowException(String.format("Error creating Netris Allocation %s for VPC %s", cidr, createdVpc.getData().getName()), e); + logAndThrowException(String.format("Error creating Netris IPAM Allocation %s for VPC %s", ipamPrefix, vpc.getName()), e); return null; } } @@ -231,14 +241,8 @@ public class NetrisApiClientImpl implements NetrisApiClient { String netrisIpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, cmd.getCidr()); String vpcCidr = cmd.getCidr(); - InlineResponse2004 ipamResponse = createVpcAllocationInternal(createdVpc, vpcCidr, tenantId, tenantName, netrisIpamAllocationName); - if (ipamResponse == null || !ipamResponse.isIsSuccess()) { - String reason = ipamResponse == null ? "Empty response" : "Operation failed on Netris"; - logger.debug("The Netris Allocation {} for VPC {} creation failed: {}", vpcCidr, cmd.getName(), reason); - return false; - } - logger.debug(String.format("Successfully created VPC %s and its IPAM Allocation %s on Netris", cmd.getName(), vpcCidr)); - return true; + InlineResponse2004Data createdIpamAllocation = createIpamAllocationInternal(netrisIpamAllocationName, vpcCidr, createdVpc.getData()); + return createdIpamAllocation != null; } private void deleteVpcIpamAllocationInternal(VPCListing vpcResource, String vpcCidr) { @@ -334,12 +338,7 @@ public class NetrisApiClientImpl implements NetrisApiClient { String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ; String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, vnetCidr) ; - InlineResponse2004 subnetResponse = createVpcSubnetInternal(associatedVpc, vNetName, vnetCidr, netrisSubnetName); - if (subnetResponse == null || !subnetResponse.isIsSuccess()) { - String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris"; - logger.debug("The Netris Subnet {} for network {} creation failed: {}", vnetCidr, networkName, reason); - return false; - } + createIpamSubnetInternal(netrisSubnetName, vnetCidr, SubnetBody.PurposeEnum.COMMON, associatedVpc); logger.debug("Successfully created IPAM Subnet {} for network {} on Netris", netrisSubnetName, networkName); VnetResAddBody vnetResponse = createVnetInternal(associatedVpc, netrisVnetName, vnetCidr); @@ -392,6 +391,71 @@ public class NetrisApiClientImpl implements NetrisApiClient { return true; } + protected VPCListing getSystemVpc() throws ApiException { + List<VPCListing> systemVpcList = listVPCs().stream().filter(VPCListing::isIsSystem).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(systemVpcList)) { + String msg = "Cannot find any system VPC"; + logger.error(msg); + throw new CloudRuntimeException(msg); + } + return systemVpcList.get(0); + } + + private BigDecimal getIpamAllocationIdByPrefixAndVpc(String superCidrPrefix, VPCListing vpc) throws ApiException { + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + FilterBySites filterBySites = new FilterBySites(); + filterBySites.add(siteId); + FilterByVpc filterByVpc = new FilterByVpc(); + filterByVpc.add(vpc.getId()); + IpTree ipamTree = ipamApi.apiV2IpamGet(filterBySites, filterByVpc); + List<IpTreeAllocation> superCidrList = ipamTree.getData().stream() + .filter(x -> x.getPrefix().equals(superCidrPrefix)) + .collect(Collectors.toList()); + return CollectionUtils.isEmpty(superCidrList) ? null : superCidrList.get(0).getId(); + } + + private IpTreeSubnet getIpamSubnetByAllocationAndPrefixAndPurposeAndVpc(BigDecimal ipamAllocationId, String exactCidr, IpTreeSubnet.PurposeEnum purpose, VPCListing vpc) throws ApiException { + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + FilterByVpc filterByVpc = new FilterByVpc(); + filterByVpc.add(vpc.getId()); + SubnetResBody subnetResBody = ipamApi.apiV2IpamSubnetsGet(filterByVpc); + List<IpTreeSubnet> exactSubnetList = subnetResBody.getData().stream() + .filter(x -> x.getAllocationID().equals(ipamAllocationId) && x.getPrefix().equals(exactCidr) && x.getPurpose() == purpose) + .collect(Collectors.toList()); + return CollectionUtils.isEmpty(exactSubnetList) ? null : exactSubnetList.get(0); + } + + @Override + public boolean setupZoneLevelPublicRange(SetupNetrisPublicRangeCommand cmd) { + String superCidr = cmd.getSuperCidr(); + String exactCidr = cmd.getExactCidr(); + try { + VPCListing systemVpc = getSystemVpc(); + logger.debug("Checking if the Netris Public Super CIDR {} exists", superCidr); + BigDecimal ipamAllocationId = getIpamAllocationIdByPrefixAndVpc(superCidr, systemVpc); + if (ipamAllocationId == null) { + String ipamName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, superCidr); + InlineResponse2004Data ipamAllocation = createIpamAllocationInternal(ipamName, superCidr, systemVpc); + if (ipamAllocation == null) { + String msg = String.format("Could not create the zone level super CIDR %s for the system VPC", superCidr); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + ipamAllocationId = new BigDecimal(ipamAllocation.getId()); + } + IpTreeSubnet exactSubnet = getIpamSubnetByAllocationAndPrefixAndPurposeAndVpc(ipamAllocationId, exactCidr, IpTreeSubnet.PurposeEnum.NAT, systemVpc); + if (exactSubnet == null) { + String ipamSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, exactCidr); + createIpamSubnetInternal(ipamSubnetName, exactCidr, SubnetBody.PurposeEnum.NAT, systemVpc); + } + } catch (ApiException e) { + String msg = String.format("Error setting up the Netris Public Range %s on super CIDR %s", exactCidr, superCidr); + logAndThrowException(msg, e); + return false; + } + return true; + } + private void deleteVnetInternal(VPCListing associatedVpc, FilterBySites siteFilter, FilterByVpc vpcFilter, String netrisVnetName, String vNetName) { try { VNetApi vNetApi = apiClient.getApiStubForMethod(VNetApi.class); @@ -433,16 +497,16 @@ public class NetrisApiClientImpl implements NetrisApiClient { } } - private InlineResponse2004 createVpcSubnetInternal(VPCListing associatedVpc, String vNetName, String vNetCidr, String netrisSubnetName) { - logger.debug("Creating Netris VPC Subnet {} for VPC {} for vNet {}", vNetCidr, associatedVpc.getName(), vNetName); + private InlineResponse2004Data createIpamSubnetInternal(String subnetName, String subnetPrefix, SubnetBody.PurposeEnum purpose, VPCListing vpc) { + logger.debug("Creating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName()); try { SubnetBody subnetBody = new SubnetBody(); - subnetBody.setName(netrisSubnetName); + subnetBody.setName(subnetName); AllocationBodyVpc vpcAllocationBody = new AllocationBodyVpc(); - vpcAllocationBody.setName(associatedVpc.getName()); - vpcAllocationBody.setId(associatedVpc.getId()); + vpcAllocationBody.setName(vpc.getName()); + vpcAllocationBody.setId(vpc.getId()); subnetBody.setVpc(vpcAllocationBody); IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); @@ -455,12 +519,18 @@ public class NetrisApiClientImpl implements NetrisApiClient { subnetSites.setName(siteName); subnetBody.setSites(List.of(subnetSites)); - subnetBody.setPurpose(SubnetBody.PurposeEnum.COMMON); - subnetBody.setPrefix(vNetCidr); + subnetBody.setPurpose(purpose); + subnetBody.setPrefix(subnetPrefix); IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); - return ipamApi.apiV2IpamSubnetPost(subnetBody); + InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetPost(subnetBody); + if (subnetResponse == null || !subnetResponse.isIsSuccess()) { + String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris IPAM Subnet {} creation failed: {}", subnetName, reason); + throw new CloudRuntimeException(reason); + } + return subnetResponse.getData(); } catch (ApiException e) { - logAndThrowException(String.format("Error creating Netris Subnet %s for VPC %s", vNetCidr, associatedVpc.getName()), e); + logAndThrowException(String.format("Error creating Netris IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e); return null; } } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisProviderServiceImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisProviderServiceImpl.java index ef4cd265d8c..2887e03a1f5 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisProviderServiceImpl.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisProviderServiceImpl.java @@ -16,15 +16,19 @@ // under the License. package org.apache.cloudstack.service; -import com.amazonaws.util.CollectionUtils; +import com.cloud.agent.api.Answer; import com.cloud.dc.DataCenterVO; +import com.cloud.dc.VlanVO; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.VlanDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.dao.HostDetailsDao; import com.cloud.network.Network; import com.cloud.network.Networks; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetrisProviderDao; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; @@ -36,7 +40,11 @@ import com.cloud.resource.ResourceManager; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; import com.google.common.annotations.VisibleForTesting; +import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddressString; +import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.command.AddNetrisProviderCmd; import org.apache.cloudstack.api.command.DeleteNetrisProviderCmd; @@ -44,6 +52,10 @@ import org.apache.cloudstack.api.command.ListNetrisProvidersCmd; import org.apache.cloudstack.api.response.NetrisProviderResponse; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.resource.NetrisResource; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -53,9 +65,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; public class NetrisProviderServiceImpl implements NetrisProviderService { + protected Logger logger = LogManager.getLogger(getClass()); + @Inject DataCenterDao dataCenterDao; @Inject @@ -68,6 +83,10 @@ public class NetrisProviderServiceImpl implements NetrisProviderService { PhysicalNetworkDao physicalNetworkDao; @Inject NetworkDao networkDao; + @Inject + private IPAddressDao ipAddressDao; + @Inject + private VlanDao vlanDao; @Override public NetrisProvider addProvider(AddNetrisProviderCmd cmd) { @@ -123,12 +142,77 @@ public class NetrisProviderServiceImpl implements NetrisProviderService { } else { throw new CloudRuntimeException("Failed to add Netris controller due to internal error."); } + createNetrisPublicIpRangesOnNetrisProvider(zoneId, netrisResource); } catch (ConfigurationException e) { throw new CloudRuntimeException(e.getMessage()); } return netrisProvider; } + /** + * Calculate the minimum CIDR subnet containing the IP range (using the library: <a href="https://github.com/seancfoley/IPAddress">IPAddress</a>) + * From: <a href="https://github.com/seancfoley/IPAddress/wiki/Code-Examples-3:-Subnetting-and-Other-Subnet-Operations#from-start-and-end-address-get-single-cidr-block-covering-both">Example</a> + * @param ipRange format: startIP-endIP + * @return the minimum CIDR containing the IP range + */ + protected String calculateSubnetCidrFromIpRange(String ipRange) { + if (StringUtils.isBlank(ipRange) || !ipRange.contains("-")) { + return null; + } + String[] rangeArray = ipRange.split("-"); + String startIp = rangeArray[0]; + String endIp = rangeArray[1]; + IPAddress startIpAddress = new IPAddressString(startIp).getAddress(); + IPAddress endIpAddress = new IPAddressString(endIp).getAddress(); + return startIpAddress.coverWithPrefixBlock(endIpAddress).toPrefixLengthString(); + } + + /** + * 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) { + String superCidr = NetUtils.getCidrFromGatewayAndNetmask(gateway, netmask); + String subnetNatCidr = calculateSubnetCidrFromIpRange(ipRange); + return new SetupNetrisPublicRangeCommand(zoneId, superCidr, subnetNatCidr); + } + + protected void createNetrisPublicIpRangesOnNetrisProvider(long zoneId, NetrisResource netrisResource) { + List<PhysicalNetworkVO> physicalNetworks = physicalNetworkDao.listByZoneAndTrafficType(zoneId, Networks.TrafficType.Public); + physicalNetworks = physicalNetworks.stream().filter(x -> x.getIsolationMethods().contains(Network.Provider.Netris.getName())).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(physicalNetworks)) { + return; + } + 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); + } + if (vlanDbIds.size() > 1) { + logger.warn("Expected one Netris Public IP range but found {}. Using the first one to create the Netris IPAM allocation and NAT subnet", vlanDbIds.size()); + } + VlanVO vlanRecord = vlanDao.findById(vlanDbIds.get(0)); + if (vlanRecord == null) { + logger.error("Cannot set up the Netris Public IP range as it cannot find the public range on database"); + return; + } + String gateway = vlanRecord.getVlanGateway(); + String netmask = vlanRecord.getVlanNetmask(); + String ipRange = vlanRecord.getIpRange(); + SetupNetrisPublicRangeCommand cmd = createSetupPublicRangeCommand(zoneId, gateway, netmask, ipRange); + Answer answer = netrisResource.executeRequest(cmd); + boolean result = answer != null && answer.getResult(); + if (!result) { + throw new CloudRuntimeException("Netris Public IP Range setup failed, please check the logs"); + } + } + } + @Override public List<BaseResponse> listNetrisProviders(Long zoneId) { List<BaseResponse> netrisControllersResponseList = new ArrayList<>(); @@ -158,7 +242,7 @@ public class NetrisProviderServiceImpl implements NetrisProviderService { List<PhysicalNetworkVO> physicalNetworks = physicalNetworkDao.listByZone(zoneId); for (PhysicalNetworkVO physicalNetwork : physicalNetworks) { List<NetworkVO> networkList = networkDao.listByPhysicalNetwork(physicalNetwork.getId()); - if (!CollectionUtils.isNullOrEmpty(networkList)) { + if (!CollectionUtils.isEmpty(networkList)) { validateNetworkState(networkList); } } diff --git a/plugins/network-elements/netris/src/test/java/org/apache/cloudstack/service/NetrisProviderServiceImplTest.java b/plugins/network-elements/netris/src/test/java/org/apache/cloudstack/service/NetrisProviderServiceImplTest.java new file mode 100644 index 00000000000..233787d9004 --- /dev/null +++ b/plugins/network-elements/netris/src/test/java/org/apache/cloudstack/service/NetrisProviderServiceImplTest.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.service; + +import org.junit.Assert; +import org.junit.Test; + +public class NetrisProviderServiceImplTest { + + private NetrisProviderServiceImpl service = new NetrisProviderServiceImpl(); + + @Test + public void testCalculateSubnetCidrFromIpRange() { + String ipRange = "100.99.18.140-100.99.18.159"; + String expectedCidr = "100.99.18.128/27"; + String cidr = service.calculateSubnetCidrFromIpRange(ipRange); + Assert.assertEquals(expectedCidr, cidr); + } +} diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 097c5c1addc..a430564dbc1 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -4458,7 +4458,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (network.getIsolationMethods().contains("GRE")) { minVnet = MIN_GRE_KEY; maxVnet = MAX_GRE_KEY; - } else if (network.getIsolationMethods().contains("VXLAN")) { + } else if (network.getIsolationMethods().contains("VXLAN") || network.getIsolationMethods().contains("Netris")) { minVnet = MIN_VXLAN_VNI; maxVnet = MAX_VXLAN_VNI; // fail if zone already contains VNI, need to be unique per zone. diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index ab74dc76bf3..1ed17a2ee11 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1791,7 +1791,7 @@ "label.public.ips": "Public IP addresses", "label.public.lb": "Public LB", "label.public.traffic": "Public traffic", -"label.public.traffic.netris": "Netris Public traffic", +"label.public.traffic.netris": "Netris Public IP Pool", "label.public.traffic.nsx": "NSX Public traffic", "label.publicinterface": "Public interface", "label.publicip": "IP address",