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",


Reply via email to