sureshanaparti commented on code in PR #10458: URL: https://github.com/apache/cloudstack/pull/10458#discussion_r2222821998
########## plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java: ########## @@ -0,0 +1,2004 @@ +// 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 com.cloud.domain.Domain; +import com.cloud.network.netris.NetrisLbBackend; +import com.cloud.network.netris.NetrisNetworkRule; +import com.cloud.network.vpc.StaticRoute; +import com.cloud.network.vpc.StaticRouteVO; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddressString; +import io.netris.ApiClient; +import io.netris.ApiException; +import io.netris.ApiResponse; +import io.netris.api.v1.AclApi; +import io.netris.api.v1.AuthenticationApi; +import io.netris.api.v1.RoutesApi; +import io.netris.api.v1.SitesApi; +import io.netris.api.v1.TenantsApi; +import io.netris.api.v2.IpamApi; +import io.netris.api.v2.L4LoadBalancerApi; +import io.netris.api.v2.NatApi; +import io.netris.api.v2.VNetApi; +import io.netris.api.v2.VpcApi; +import io.netris.model.AclAddItem; +import io.netris.model.AclBodyVpc; +import io.netris.model.AclDeleteItem; +import io.netris.model.AclEditItem; +import io.netris.model.AclGetBody; +import io.netris.model.AclResponseGetOk; +import io.netris.model.AllocationBody; +import io.netris.model.AllocationBodyVpc; +import io.netris.model.FilterBySites; +import io.netris.model.FilterByVpc; +import io.netris.model.GetSiteBody; + +import io.netris.model.InlineResponse20015; +import io.netris.model.InlineResponse20016; +import io.netris.model.InlineResponse2003; +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; +import io.netris.model.L4LBSite; +import io.netris.model.L4LbAddItem; +import io.netris.model.L4LbEditItem; +import io.netris.model.L4LbTenant; +import io.netris.model.L4LbVpc; +import io.netris.model.L4LoadBalancerBackendItem; +import io.netris.model.L4LoadBalancerItem; +import io.netris.model.L4lbAddOrUpdateItem; +import io.netris.model.L4lbresBody; +import io.netris.model.NatBodySiteSite; +import io.netris.model.NatBodyVpcVpc; +import io.netris.model.NatGetBody; +import io.netris.model.NatPostBody; +import io.netris.model.NatPutBody; +import io.netris.model.NatResponseGetOk; +import io.netris.model.ResAddEditBody; +import io.netris.model.RoutesBody; +import io.netris.model.RoutesBodyId; +import io.netris.model.RoutesBodyVpcVpc; +import io.netris.model.RoutesGetBody; +import io.netris.model.RoutesPostBody; +import io.netris.model.RoutesPutBody; +import io.netris.model.RoutesResponseGetOk; +import io.netris.model.SitesResponseOK; +import io.netris.model.SubnetBody; +import io.netris.model.SubnetResBody; +import io.netris.model.VPCAdminTenant; +import io.netris.model.VPCCreate; +import io.netris.model.VPCListing; +import io.netris.model.VPCResource; +import io.netris.model.VPCResourceIpam; +import io.netris.model.VPCResponseOK; +import io.netris.model.VPCResponseObjectOK; +import io.netris.model.VPCResponseResourceOK; +import io.netris.model.VnetAddBody; +import io.netris.model.VnetAddBodyDhcp; +import io.netris.model.VnetAddBodyDhcpOptionSet; +import io.netris.model.VnetAddBodyGateways; +import io.netris.model.VnetAddBodyVpc; +import io.netris.model.VnetEditBody; +import io.netris.model.VnetEditBodyDhcp; +import io.netris.model.VnetEditBodyGateways; +import io.netris.model.VnetResAddBody; +import io.netris.model.VnetResDeleteBody; +import io.netris.model.VnetResListBody; +import io.netris.model.VnetsBody; +import io.netris.model.VpcEditResponseOK; +import io.netris.model.VpcVpcIdBody; +import io.netris.model.response.AuthResponse; +import io.netris.model.response.L4LbEditResponse; +import io.netris.model.response.TenantResponse; +import io.netris.model.response.TenantsResponse; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisACLCommand; +import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand; +import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; +import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; +import org.apache.cloudstack.agent.api.ListNetrisStaticRoutesCommand; +import org.apache.cloudstack.agent.api.NetrisCommand; +import org.apache.cloudstack.agent.api.ReleaseNatIpCommand; +import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; +import org.apache.cloudstack.agent.api.UpdateNetrisVnetCommand; +import org.apache.cloudstack.agent.api.UpdateNetrisVpcCommand; +import org.apache.cloudstack.resource.NetrisResourceObjectUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class NetrisApiClientImpl implements NetrisApiClient { + + private final Logger logger = LogManager.getLogger(getClass()); + private static final String ANY_IP = "0.0.0.0/0"; + private static final String[] PROTOCOL_LIST = new String[]{"TCP", "UDP", "ICMP", "ALL"}; + + protected ApiClient apiClient; + + protected final int siteId; + private final String siteName; + protected final int tenantId; + private final String tenantName; + + public NetrisApiClientImpl(String endpointBaseUrl, String username, String password, String siteName, String adminTenantName) { + try { + apiClient = new ApiClient(endpointBaseUrl, username, password, 1L); + } catch (ApiException e) { + logAndThrowException(String.format("Error creating the Netris API Client for %s", endpointBaseUrl), e); + } + Pair<Integer, String> sitePair = getNetrisSitePair(siteName); + Pair<Integer, String> tenantPair = getNetrisTenantPair(adminTenantName); + this.siteId = sitePair.first(); + this.siteName = sitePair.second(); + this.tenantId = tenantPair.first(); + this.tenantName = tenantPair.second(); + } + + private Pair<Integer, String> getNetrisSitePair(String siteName) { + List<GetSiteBody> sites = listSites(); + if (CollectionUtils.isEmpty(sites)) { + throw new CloudRuntimeException("There are no Netris sites, please check the Netris endpoint"); + } + List<GetSiteBody> filteredSites = sites.stream().filter(x -> x.getName().equals(siteName)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(filteredSites)) { + throw new CloudRuntimeException(String.format("Cannot find a site matching name %s on Netris, please check the Netris endpoint", siteName)); + } + return new Pair<>(filteredSites.get(0).getId(), siteName); + } + + private Pair<Integer, String> getNetrisTenantPair(String adminTenantName) { + List<TenantResponse> tenants = listTenants(); + if (CollectionUtils.isEmpty(tenants)) { + throw new CloudRuntimeException("There are no Netris tenants, please check the Netris endpoint"); + } + List<TenantResponse> filteredTenants = tenants.stream().filter(x -> x.getName().equals(adminTenantName)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(filteredTenants)) { + throw new CloudRuntimeException(String.format("Cannot find a site matching name %s on Netris, please check the Netris endpoint", adminTenantName)); + } + return new Pair<>(filteredTenants.get(0).getId().intValue(), adminTenantName); + } + + protected void logAndThrowException(String prefix, ApiException e) throws CloudRuntimeException { + String msg = String.format("%s: (%s, %s, %s)", prefix, e.getCode(), e.getMessage(), e.getResponseBody()); + logger.error(msg, e); + throw new CloudRuntimeException(msg); + } + + @Override + public boolean isSessionAlive() { + ApiResponse<AuthResponse> response = null; + try { + AuthenticationApi api = apiClient.getApiStubForMethod(AuthenticationApi.class); + response = api.apiAuthGet(); + } catch (ApiException e) { + logAndThrowException("Error checking the Netris API session is alive", e); + } + return response != null && response.getStatusCode() == 200; + } + + @Override + public List<GetSiteBody> listSites() { + SitesResponseOK response = null; + try { + SitesApi api = apiClient.getApiStubForMethod(SitesApi.class); + response = api.apiSitesGet(); + } catch (ApiException e) { + logAndThrowException("Error listing Netris Sites", e); + } + return response != null ? response.getData() : null; + } + + @Override + public List<VPCListing> listVPCs() { + VPCResponseOK response = null; + try { + VpcApi api = apiClient.getApiStubForMethod(VpcApi.class); + response = api.apiV2VpcGet(); + } catch (ApiException e) { + logAndThrowException("Error listing Netris VPCs", e); + } + return response != null ? response.getData() : null; + } + + @Override + public List<TenantResponse> listTenants() { + ApiResponse<TenantsResponse> response = null; + try { + TenantsApi api = apiClient.getApiStubForMethod(TenantsApi.class); + response = api.apiTenantsGet(); + } catch (ApiException e) { + logAndThrowException("Error listing Netris Tenants", e); + } + return (response != null && response.getData() != null) ? response.getData().getData() : null; + } + + private VPCResponseObjectOK createVpcInternal(String vpcName, int adminTenantId, String adminTenantName) { + VPCResponseObjectOK response; + logger.debug(String.format("Creating Netris VPC %s", vpcName)); + try { + VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class); + VPCCreate body = new VPCCreate(); + body.setName(vpcName); + VPCAdminTenant vpcAdminTenant = new VPCAdminTenant(); + vpcAdminTenant.setId(adminTenantId); + vpcAdminTenant.name(adminTenantName); + body.setAdminTenant(vpcAdminTenant); + response = vpcApi.apiV2VpcPost(body); + } catch (ApiException e) { + logAndThrowException("Error creating Netris VPC", e); + return null; + } + return response; + } + + private VpcEditResponseOK updateVpcInternal(String vpcName, String prevVpcName, int adminTenantId, String adminTenantName) { + VpcEditResponseOK response; + logger.debug(String.format("Updating Netris VPC name from %s to %s", prevVpcName, vpcName)); + try { + VPCListing vpcResource = getVpcByNameAndTenant(prevVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", prevVpcName, tenantId); + return null; + } + VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class); + VpcVpcIdBody body = new VpcVpcIdBody(); + body.setName(vpcName); + VPCAdminTenant vpcAdminTenant = new VPCAdminTenant(); + vpcAdminTenant.setId(adminTenantId); + vpcAdminTenant.name(adminTenantName); + body.setAdminTenant(vpcAdminTenant); + response = vpcApi.apiV2VpcVpcIdPut(body, vpcResource.getId()); + } catch (ApiException e) { + logAndThrowException("Error updating Netris VPC", e); + return null; + } + return response; + } + + 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(vpc.getId()); + allocationBodyVpc.setName(vpc.getName()); + body.setVpc(allocationBodyVpc); + body.setName(ipamName); + body.setPrefix(ipamPrefix); + IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); + body.setTenant(allocationTenant); + 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 IPAM Allocation %s for VPC %s", ipamPrefix, vpc.getName()), e); + return null; + } + } + + @Override + public boolean createVpc(CreateNetrisVpcCommand cmd) { + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC); + VPCResponseObjectOK createdVpc = createVpcInternal(netrisVpcName, tenantId, tenantName); + if (createdVpc == null || !createdVpc.isIsSuccess()) { + String reason = createdVpc == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris VPC {} creation failed: {}", cmd.getName(), reason); + return false; + } + + String netrisIpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, cmd.getCidr()); + String vpcCidr = cmd.getCidr(); + InlineResponse2004Data createdIpamAllocation = createIpamAllocationInternal(netrisIpamAllocationName, vpcCidr, createdVpc.getData()); + return createdIpamAllocation != null; + } + + @Override + public boolean updateVpc(UpdateNetrisVpcCommand cmd) { + Long domainId = cmd.getDomainId(); + Long zoneId = cmd.getZoneId(); + Long accountId = cmd.getAccountId(); + Long vpcId = cmd.getId(); + String prevVpcName = cmd.getPreviousVpcName(); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC); + String netrisPrevVpcName = String.format("D%s-A%s-Z%s-V%s-%s", domainId, accountId, zoneId, vpcId, prevVpcName); + VpcEditResponseOK updatedVpc = updateVpcInternal(netrisVpcName, netrisPrevVpcName, tenantId, tenantName); + if (updatedVpc == null || !updatedVpc.isIsSuccess()) { + String reason = updatedVpc == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The update of Netris VPC {} failed: {}", cmd.getPreviousVpcName(), reason); + return false; + } + return true; + } + + @Override + public boolean deleteNatRule(DeleteNetrisNatRuleCommand cmd) { + try { + String suffix = getNetrisVpcNameSuffix(cmd.getVpcId(), cmd.getVpcName(), cmd.getId(), cmd.getName(), cmd.isVpc()); + String vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix); + VPCListing vpcResource = getVpcByNameAndTenant(vpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return false; + } + String natRuleName = cmd.getNatRuleName(); + NatGetBody existingNatRule = netrisNatRuleExists(natRuleName); + boolean ruleExists = Objects.nonNull(existingNatRule); + if (ruleExists) { + deleteNatRule(natRuleName, existingNatRule.getId(), vpcResource.getName()); + if (cmd.getNatRuleType().equals("STATICNAT")) { + String natIp = cmd.getNatIp(); + String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), natIp); + deleteNatSubnet(netrisSubnetName, vpcResource.getId(), natIp); + } + } + } catch (Exception e) { + throw new CloudRuntimeException("Error deleting Netris NAT Rule", e); + } + return true; + } + + @Override + public boolean addOrUpdateAclRule(CreateOrUpdateNetrisACLCommand cmd, boolean forLb) { + String aclName = cmd.getNetrisAclName(); + try { + AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class); + VPCListing vpcResource; + String netrisVpcName; + if (forLb) { + vpcResource = getSystemVpc(); + netrisVpcName = vpcResource.getName(); + + } else { + netrisVpcName = getNetrisVpcName(cmd, cmd.getVpcId(), cmd.getVpcName()); + vpcResource = getNetrisVpcResource(netrisVpcName); + if (Objects.isNull(vpcResource)) { + return false; + } + } + AclBodyVpc vpc = new AclBodyVpc().id(vpcResource.getId()); + List<String> aclNames = List.of(aclName); + Pair<Boolean, List<BigDecimal>> resultAndMatchingAclIds = getMatchingAclIds(aclNames, netrisVpcName); + List<BigDecimal> aclIdList = resultAndMatchingAclIds.second(); + if (!aclIdList.isEmpty()) { + logger.debug("Netris ACL rule: {} already exists, updating it...", aclName); + AclEditItem aclEditItem = getAclEditItem(cmd, aclName, aclIdList.get(0)); + aclEditItem.setVpc(vpc); + try { + aclApi.apiAclPut(aclEditItem); + } catch (ApiException e) { + if (e.getResponseBody().contains("This kind of acl already exists")) { + logger.info("Netris ACL rule: {} already exists and doesn't need to be updated", aclName); + return true; + } + throw new CloudRuntimeException("Error updating Netris ACL rule", e); + } + return true; + } + AclAddItem aclAddItem = getAclAddItem(cmd, aclName); + aclAddItem.setVpc(vpc); + aclApi.apiAclPost(aclAddItem); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to create Netris ACL: %s", cmd.getNetrisAclName()), e); + } + return true; + } + + AclAddItem getAclAddItem(CreateOrUpdateNetrisACLCommand cmd, String aclName) throws ApiException { + AclAddItem aclAddItem = new AclAddItem(); + aclAddItem.setAction(cmd.getAction()); + aclAddItem.setComment(String.format("ACL rule: %s. %s", cmd.getNetrisAclName(), cmd.getReason())); + aclAddItem.setName(aclName); + String protocol = cmd.getProtocol(); + if ("TCP".equals(protocol)) { + aclAddItem.setEstablished(new BigDecimal(1)); + } else { + aclAddItem.setReverse("yes"); + } + if (!Arrays.asList(PROTOCOL_LIST).contains(protocol)) { + aclAddItem.setProto("ip"); + aclAddItem.setSrcPortTo(cmd.getIcmpType()); + // TODO: set proto number: where should the protocol number be set - API sets the protocol number to Src-from & to and Dest-from & to fields + } else if ("ICMP".equals(protocol)) { + aclAddItem.setProto("icmp"); + if (cmd.getIcmpType() != -1) { + aclAddItem.setIcmpType(cmd.getIcmpType()); + } + } else { + aclAddItem.setProto(protocol.toLowerCase(Locale.ROOT)); + } + + aclAddItem.setDstPortFrom(cmd.getDestPortStart()); + aclAddItem.setDstPortTo(cmd.getDestPortEnd()); + aclAddItem.setDstPrefix(cmd.getDestPrefix()); + aclAddItem.setSrcPrefix(cmd.getSourcePrefix()); + aclAddItem.setSrcPortFrom(1); + aclAddItem.setSrcPortTo(65535); + if (NatPutBody.ProtocolEnum.ICMP.name().equalsIgnoreCase(protocol)) { + aclAddItem.setIcmpType(cmd.getIcmpType()); + } + + return aclAddItem; + } + + AclEditItem getAclEditItem(CreateOrUpdateNetrisACLCommand cmd, String aclName, BigDecimal aclId) throws ApiException { + AclEditItem aclEditItem = new AclEditItem(); + aclEditItem.setId(aclId); + aclEditItem.setAction(cmd.getAction()); + aclEditItem.setComment(String.format("ACL rule: %s. %s", cmd.getNetrisAclName(), cmd.getReason())); + aclEditItem.setName(aclName); + String protocol = cmd.getProtocol(); + if ("TCP".equals(protocol)) { + aclEditItem.setEstablished(new BigDecimal(1)); + } else { + aclEditItem.setReverse("yes"); + } + if (!Arrays.asList(PROTOCOL_LIST).contains(protocol)) { + aclEditItem.setProto("ip"); + aclEditItem.setSrcPortTo(cmd.getIcmpType()); + // TODO: set proto number: where should the protocol number be set - API sets the protocol number to Src-from & to and Dest-from & to fields + } else if ("ICMP".equals(protocol)) { + aclEditItem.setProto("icmp"); + if (cmd.getIcmpType() != -1) { + aclEditItem.setIcmpType(cmd.getIcmpType()); + } + } else { + aclEditItem.setProto(protocol.toLowerCase(Locale.ROOT)); + } + + aclEditItem.setDstPortFrom(cmd.getDestPortStart()); + aclEditItem.setDstPortTo(cmd.getDestPortEnd()); + aclEditItem.setDstPrefix(cmd.getDestPrefix()); + aclEditItem.setSrcPrefix(cmd.getSourcePrefix()); + aclEditItem.setSrcPortFrom(1); + aclEditItem.setSrcPortTo(65535); + if (NatPutBody.ProtocolEnum.ICMP.name().equalsIgnoreCase(protocol)) { + aclEditItem.setIcmpType(cmd.getIcmpType()); + } + + return aclEditItem; + } + + @Override + public boolean deleteAclRule(DeleteNetrisACLCommand cmd, boolean forLb) { + List<String> aclNames = cmd.getAclRuleNames(); + try { + AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class); + + String vpcName; + if (!forLb) { + String suffix = getNetrisVpcNameSuffix(cmd.getVpcId(), cmd.getVpcName(), cmd.getId(), cmd.getName(), cmd.isVpc()); + vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix); + } else { + VPCListing vpcResource = getSystemVpc(); + vpcName = vpcResource.getName(); + } + Pair<Boolean, List<BigDecimal>> resultAndMatchingAclIds = getMatchingAclIds(aclNames, vpcName); + Boolean result = resultAndMatchingAclIds.first(); + List<BigDecimal> matchingAclIds = resultAndMatchingAclIds.second(); + if (!result) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return false; + } + if (matchingAclIds.isEmpty()) { + logger.warn("There doesn't seem to be any ACLs on Netris matching {}", aclNames.size() > 1 ? String.join(",", aclNames) : aclNames); + return true; + } + AclDeleteItem aclDeleteItem = new AclDeleteItem(); + aclDeleteItem.setId(matchingAclIds); + aclDeleteItem.setTenantsID(String.valueOf(tenantId)); + aclApi.apiAclDelete(aclDeleteItem); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to delete Netris ACLs: %s", String.join(",", cmd.getAclRuleNames())), e); + } + return true; + } + + Pair<Boolean, List<BigDecimal>> getMatchingAclIds(List<String> aclNames, String vpcName) { + try { + AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class); + VPCListing vpcResource = getVpcByNameAndTenant(vpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return new Pair<>(false, Collections.emptyList()); + } + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(vpcResource.getId()); + FilterBySites siteFilter = new FilterBySites(); + siteFilter.add(siteId); + AclResponseGetOk aclGetResponse = aclApi.apiAclGet(siteFilter, vpcFilter); + if (aclGetResponse == null || !aclGetResponse.isIsSuccess()) { + logger.warn("No ACLs were found to be present for the specific Netris VPC resource {}." + + " Netris ACLs may have been deleted out of band.", vpcName); + return new Pair<>(true, Collections.emptyList()); + } + List<AclGetBody> aclList = aclGetResponse.getData(); + return new Pair<>(true, aclList.stream() + .filter(acl -> aclNames.contains(acl.getName())) + .map(acl -> BigDecimal.valueOf(acl.getId())) + .collect(Collectors.toList())); + } catch (ApiException e) { + logAndThrowException("Failed to retrieve Netris ACLs", e); + } + return new Pair<>(true, Collections.emptyList()); + } + + public boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd) { + String prefix = cmd.getPrefix(); + String nextHop = cmd.getNextHop(); + Long vpcId = cmd.getId(); + String vpcName = cmd.getName(); + boolean updateRoute = cmd.isUpdateRoute(); + try { + String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + + String[] suffixes = new String[2]; + suffixes[0] = vpcId.toString(); + suffixes[1] = cmd.getRouteId().toString(); + String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes); + + Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, null, staticRouteId); + if (updateRoute) { + if (!existingStaticRoute.first()) { + logger.error("The Netris static route {} does not exist for VPC {}, adding it", prefix, netrisVpcName); + return addStaticRouteInternal(vpcResource, netrisVpcName, prefix, nextHop, staticRouteId); + } + return updateStaticRouteInternal(existingStaticRoute.second().getId(), netrisVpcName, prefix, nextHop, staticRouteId); + } else { + if (existingStaticRoute.first()) { + String existingNextHop = existingStaticRoute.second().getNextHop(); + if (existingNextHop != null && existingNextHop.equals(nextHop)) { + logger.debug("The Netris static route {} already exists for VPC {}", prefix, netrisVpcName); + return true; + } else { + logger.debug("The Netris static route {} already exists but has different next hop {} for VPC {}", prefix, nextHop, netrisVpcName); + return false; + } + } + return addStaticRouteInternal(vpcResource, netrisVpcName, prefix, nextHop, staticRouteId); + } + } catch (Exception e) { + throw new CloudRuntimeException("Error adding Netris static route", e); + } + } + + private boolean addStaticRouteInternal(VPCListing vpcResource, String netrisVpcName, String prefix, String nextHop, String staticRouteId) { + try { + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesPostBody routesPostBody = new RoutesPostBody(); + routesPostBody.setPrefix(prefix); + routesPostBody.setNextHop(nextHop); + routesPostBody.setSiteId(new BigDecimal(siteId)); + routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE); + + RoutesBodyVpcVpc vpcBody = new RoutesBodyVpcVpc(); + vpcBody.setId(vpcResource.getId()); + vpcBody.setName(vpcResource.getName()); + vpcBody.setIsDefault(vpcResource.isIsDefault()); + vpcBody.setIsSystem(vpcResource.isIsSystem()); + routesPostBody.setVpc(vpcBody); + + routesPostBody.setDescription(staticRouteId); + routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE); + routesPostBody.setSwitches(Collections.emptyList()); + + InlineResponse2004 routeResponse = routesApi.apiRoutesPost(routesPostBody); + if (routeResponse == null || !routeResponse.isIsSuccess()) { + String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris static route creation failed for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException("Error adding Netris static route", e); + return false; + } + return true; + } + + private boolean updateStaticRouteInternal(Integer id, String netrisVpcName, String prefix, String nextHop, String staticRouteId) { + try { + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesPutBody routesPutBody = new RoutesPutBody(); + routesPutBody.setId(id); + routesPutBody.setPrefix(prefix); + routesPutBody.setNextHop(nextHop); + routesPutBody.setSiteId(new BigDecimal(siteId)); + routesPutBody.setStateStatus(RoutesPutBody.StateStatusEnum.ACTIVE); + + routesPutBody.setDescription(staticRouteId); + routesPutBody.setSwitches(Collections.emptyList()); + + InlineResponse2003 routeResponse = routesApi.apiRoutesPut(routesPutBody); + if (routeResponse == null || !routeResponse.isIsSuccess()) { + String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("Failed to update Netris static route for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException("Error updating Netris static route", e); + return false; + } + return true; + } + + @Override + public boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand cmd) { + Long vpcId = cmd.getId(); + String vpcName = cmd.getName(); + String prefix = cmd.getPrefix(); + String nextHop = cmd.getNextHop(); + try { + String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + + String[] suffixes = new String[2]; + suffixes[0] = vpcId.toString(); + suffixes[1] = cmd.getRouteId().toString(); + String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes); + Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, nextHop, staticRouteId); + + if (Boolean.FALSE.equals(existingStaticRoute.first())) { + logger.debug("The Netris static route {} does not exist for VPC {}", prefix, netrisVpcName); + return true; + } + RoutesGetBody existingRoute = existingStaticRoute.second(); + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesBodyId id = new RoutesBodyId(); + id.setId(existingRoute.getId()); + InlineResponse2003 routeDeleteResponse = routesApi.apiRoutesDelete(id); + if (routeDeleteResponse == null || !routeDeleteResponse.isIsSuccess()) { + String reason = routeDeleteResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris static route deletion failed for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + return true; + } catch (ApiException e) { + logAndThrowException("Error deleting Netris static route", e); + } + return false; + } + + @Override + public List<StaticRoute> listStaticRoutes(ListNetrisStaticRoutesCommand cmd) { + Long vpcId = cmd.getId(); + String vpcName = cmd.getName(); + String prefix = cmd.getPrefix(); + String nextHop = cmd.getNextHop(); + String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return new ArrayList<>(); + } + + List<RoutesGetBody> staticRoutes = listStaticRoutes(vpcResource.getId(), prefix, nextHop); + if (CollectionUtils.isEmpty(staticRoutes)) { + return new ArrayList<>(); + } + List<StaticRoute> result = new ArrayList<>(); + for (RoutesGetBody staticRoute : staticRoutes) { + // All static routes belong the SYSTEM account, which does not matter + result.add(new StaticRouteVO(null, staticRoute.getName(), vpcId, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, staticRoute.getNextHop())); + } + return result; + } + + @Override + public boolean releaseNatIp(ReleaseNatIpCommand cmd) { + String natIp = cmd.getNatIp() + "/32"; + try { + VPCListing systemVpc = getSystemVpc(); + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(systemVpc.getId()); + SubnetResBody subnetResponse = ipamApi.apiV2IpamSubnetsGet(vpcFilter); + if (subnetResponse == null || !subnetResponse.isIsSuccess()) { + String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("Failed to retrieve Netris Public NAT IPs due to {}", reason); + throw new CloudRuntimeException(reason); + } + List<IpTreeSubnet> natIps = subnetResponse.getData().stream().filter(ip -> ip.getPrefix().equals(natIp)).collect(Collectors.toList()); + if (!natIps.isEmpty()) { + ipamApi.apiV2IpamTypeIdDelete("subnet", natIps.get(0).getId().intValue()); + } + + } catch (ApiException e) { + logAndThrowException("Failed to release Netris IP", e); + } + return true; + } + + @Override + public boolean createOrUpdateLbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd) { + boolean isVpc = cmd.isVpc(); + Long networkResourceId = cmd.getId(); + String networkResourceName = cmd.getName(); + Long domainId = cmd.getDomainId(); + Long accountId = cmd.getAccountId(); + Long zoneId = cmd.getZoneId(); + Long lbId = cmd.getLbId(); + String publicIp = cmd.getPublicIp(); + List<NetrisLbBackend> lbBackends = cmd.getLbBackends(); + + try { + String resourcePrefix = isVpc ? "V" : "N"; + String netrisResourceName = String.format("D%s-A%s-Z%s-%s%s-%s", domainId, accountId, zoneId, resourcePrefix, networkResourceId, networkResourceName); + VPCListing vpcResource = getNetrisVpcResource(netrisResourceName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisResourceName, tenantId); + return false; + } + createLBSubnet(cmd, publicIp + "/32", vpcResource.getId()); + + String suffix = String.format("LB%s", lbId); + String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix); + Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, vpcResource.getName()); + Boolean result = resultAndMatchingLbId.first(); + List<BigDecimal> matchingLbId = resultAndMatchingLbId.second(); + if (Boolean.FALSE.equals(result)) { + logger.warn("Could not find the Netris LB rule with name {}", lbName); + } + boolean updateRule = !matchingLbId.isEmpty(); + L4lbAddOrUpdateItem l4lbAddItem = getL4LbRule(cmd, vpcResource, lbName, publicIp, lbBackends, updateRule); + L4LoadBalancerApi loadBalancerApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + boolean success; + L4LbEditResponse editResponse = null; + ResAddEditBody createResponse = null; + if (updateRule) { + editResponse = loadBalancerApi.apiV2L4lbIdPut((L4LbEditItem) l4lbAddItem, matchingLbId.get(0).intValue()); + success = editResponse.isIsSuccess(); + } else { + createResponse = loadBalancerApi.apiV2L4lbPost((L4LbAddItem) l4lbAddItem); + success = createResponse.isIsSuccess(); + } + if (ObjectUtils.allNull(editResponse, createResponse) || Boolean.FALSE.equals(success)) { + throw new CloudRuntimeException(String.format("Failed to %s Netris LB rule", updateRule ? "update" : "create")); + } + if (Objects.nonNull(cmd.getCidrList()) && !cmd.getCidrList().isEmpty()) { + applyAclRulesForLb(cmd, lbName); + } + } catch (ApiException e) { + logAndThrowException("Failed to create Netris load balancer rule", e); + } + return true; + } + + private void applyAclRulesForLb(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, String lbName) { + // Add deny all rule first + addOrUpdateAclRule(createNetrisACLRuleCommand(cmd, lbName, "ANY", + NetrisNetworkRule.NetrisRuleAction.DENY.name().toLowerCase(Locale.ROOT), 0), true); + AtomicInteger cidrIndex = new AtomicInteger(1); + for (String cidr : cmd.getCidrList().split(" ")) { + try { + addOrUpdateAclRule(createNetrisACLRuleCommand(cmd, lbName, cidr, + NetrisNetworkRule.NetrisRuleAction.PERMIT.name().toLowerCase(Locale.ROOT), + cidrIndex.getAndIncrement()), true); + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Failed to add Netris ACL rule for LB CIDR %s", cidr), e); + } + } + } + + private CreateOrUpdateNetrisACLCommand createNetrisACLRuleCommand(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, String netrisLbName, String cidr, String action, int index) { + Long zoneId = cmd.getZoneId(); + Long accountId = cmd.getAccountId(); + Long domainId = cmd.getDomainId(); + String networkName = null; + Long networkId = null; + String vpcName = null; + Long vpcId = null; + boolean isVpc = cmd.isVpc(); + if (isVpc) { + vpcId = cmd.getId(); + vpcName = cmd.getName(); + } else { + networkName = cmd.getName(); + networkId = cmd.getId(); + } + String destinationPrefix = cmd.getPublicIp() + "/32"; + String srcPort = cmd.getPublicPort(); + String dstPort = cmd.getPublicPort(); + CreateOrUpdateNetrisACLCommand aclCommand = new CreateOrUpdateNetrisACLCommand(zoneId, accountId, domainId, networkName, networkId, + vpcName, vpcId, Objects.nonNull(vpcId), action, NetrisServiceImpl.getPrefix(cidr), NetrisServiceImpl.getPrefix(destinationPrefix), + Integer.parseInt(srcPort), Integer.parseInt(dstPort), cmd.getProtocol()); + String aclName; + if (isVpc) { + aclName = String.format("V%s-LBACL%s-%s", vpcId, index, cmd.getRuleName()); + } else { + aclName = String.format("N%s-LBACL%s-%s", networkId, index, cmd.getRuleName()); + } + String netrisAclName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.ACL, aclName); + aclCommand.setNetrisAclName(netrisAclName); + aclCommand.setReason(String.format("ACL Rule for CIDR %s of LB %s ", aclName, netrisLbName)); + return aclCommand; + } + + private L4lbAddOrUpdateItem getL4LbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, VPCListing vpcResource, String lbName, + String publicIp, List<NetrisLbBackend> lbBackends, boolean updateRule) { + L4lbAddOrUpdateItem l4lbAddItem = updateRule ? new L4LbEditItem() : new L4LbAddItem(); + try { + l4lbAddItem.setName(lbName); + + String protocol = cmd.getProtocol().toUpperCase(Locale.ROOT); + if (!Arrays.asList("TCP", "UDP").contains(protocol)) { + throw new CloudRuntimeException("Invalid protocol " + protocol); + } + l4lbAddItem.setProtocol(cmd.getProtocol().toUpperCase(Locale.ROOT)); + L4LBSite site = new L4LBSite(); + site.setId(siteId); + site.setName(siteName); + l4lbAddItem.setSite(site); + l4lbAddItem.setSiteID(new BigDecimal(siteId)); + + L4LbTenant tenant = new L4LbTenant(); + tenant.setId(tenantId); + tenant.setName(tenantName); + l4lbAddItem.setTenant(tenant); + + L4LbVpc vpc = new L4LbVpc(); + vpc.setId(vpcResource.getId()); + l4lbAddItem.setVpc(vpc); + + l4lbAddItem.setAutomatic(false); + l4lbAddItem.setIpFamily(NetUtils.isIpv4(publicIp) ? L4lbAddOrUpdateItem.IpFamilyEnum.IPv4 : L4lbAddOrUpdateItem.IpFamilyEnum.IPv6); + l4lbAddItem.setIp(publicIp); + l4lbAddItem.setStatus("enable"); + + List<L4LoadBalancerBackendItem> backends = new ArrayList<>(); + for (NetrisLbBackend backend : lbBackends) { + L4LoadBalancerBackendItem backendItem = new L4LoadBalancerBackendItem(); + backendItem.setIp(backend.getVmIp()); + backendItem.setPort(backend.getPort()); + backends.add(backendItem); + } + l4lbAddItem.setBackend(backends); + l4lbAddItem.setPort(Integer.valueOf(cmd.getPublicPort())); + l4lbAddItem.setHealthCheck(L4lbAddOrUpdateItem.HealthCheckEnum.NONE); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to create Netris load balancer rule", e); + } + return l4lbAddItem; + } + + @Override + public boolean deleteLbRule(DeleteNetrisLoadBalancerRuleCommand cmd) { + boolean isVpc = cmd.isVpc(); + String vpcName = null; + String networkName = null; + Long vpcId = null; + Long networkId = null; + if (isVpc) { + vpcName = cmd.getName(); + vpcId = cmd.getId(); + } else { + networkName = cmd.getName(); + networkId = cmd.getId(); + } + Long lbId = cmd.getLbId(); + String cidrList = cmd.getCidrList(); + try { + String suffix = getNetrisVpcNameSuffix(vpcId, vpcName, networkId, networkName, isVpc); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix); + suffix = String.format("LB%s", lbId); + String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix); + Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, netrisVpcName); + Boolean result = resultAndMatchingLbId.first(); + List<BigDecimal> matchingLbId = resultAndMatchingLbId.second(); + if (!result) { + logger.error("Could not find the Netris LB rule with name {}", lbName); + return false; + } + if (matchingLbId.isEmpty()) { + logger.warn("There doesn't seem to be any LB rule on Netris matching {}", lbName); + return true; + } + + L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + lbApi.apiV2L4lbIdDelete(matchingLbId.get(0).intValue()); + if (Objects.nonNull(cidrList)) { + deleteAclRulesForLb(cmd); + } + } catch (ApiException e) { + logAndThrowException("Failed to delete Netris load balancer rule", e); + } + return true; + } + + private void deleteAclRulesForLb(DeleteNetrisLoadBalancerRuleCommand cmd) { + // delete the deny rule + deleteAclRule(deleteNetrisACLCommand(cmd, 0), true); + AtomicInteger cidrIndex = new AtomicInteger(1); + for (String cidr : cmd.getCidrList().split(" ")) { + try { + deleteAclRule(deleteNetrisACLCommand(cmd, cidrIndex.getAndIncrement()), true); + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Failed to delete Netris ACL rule for LB CIDR %s", cidr), e); + } + } + } + + private DeleteNetrisACLCommand deleteNetrisACLCommand(DeleteNetrisLoadBalancerRuleCommand cmd, int index) { + Long zoneId = cmd.getZoneId(); + Long accountId = cmd.getAccountId(); + Long domainId = cmd.getDomainId(); + String networkName = null; + Long networkId = null; + String vpcName = null; + Long vpcId = null; + boolean isVpc = cmd.isVpc(); + if (isVpc) { + vpcId = cmd.getId(); + vpcName = cmd.getName(); + } else { + networkName = cmd.getName(); + networkId = cmd.getId(); + } + DeleteNetrisACLCommand deleteAclCommand = new DeleteNetrisACLCommand(zoneId, accountId, domainId, networkName, networkId, isVpc, vpcId, vpcName); + String aclName; + if (isVpc) { + aclName = String.format("V%s-LBACL%s-%s", vpcId, index, cmd.getRuleName()); + } else { + aclName = String.format("N%s-LBACL%s-%s", networkId, index, cmd.getRuleName()); + } + String netrisAclName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.ACL, aclName); + deleteAclCommand.setAclRuleNames(Collections.singletonList(netrisAclName)); + return deleteAclCommand; + } + + private Pair<Boolean, List<BigDecimal>> getMatchingLbRule(String lbName, String vpcName) { + try { + VPCListing vpcResource = getVpcByNameAndTenant(vpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return new Pair<>(false, Collections.emptyList()); + } + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(vpcResource.getId()); + FilterBySites siteFilter = new FilterBySites(); + siteFilter.add(siteId); + L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + L4lbresBody lbGetResponse = lbApi.apiV2L4lbGet(siteFilter, vpcFilter); + if (lbGetResponse == null || !lbGetResponse.isIsSuccess()) { + logger.warn("No LB rules were found to be present for the specific Netris VPC resource {}." + + " Netris LB rules may have been deleted out of band.", vpcName); + return new Pair<>(true, Collections.emptyList()); + } + List<L4LoadBalancerItem> lbList = lbGetResponse.getData(); + return new Pair<>(true, lbList.stream() + .filter(lb -> lbName.equals(lb.getName())) + .map(acl -> BigDecimal.valueOf(acl.getId())) + .collect(Collectors.toList())); + } catch (ApiException e) { + logAndThrowException("Failed to retrieve Netris LB rules", e); + } + return new Pair<>(true, Collections.emptyList()); + } + + private List<RoutesGetBody> listStaticRoutes(Integer netrisVpcId, String prefix, String nextHop) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(netrisVpcId); + FilterBySites sitesFilter = new FilterBySites(); + sitesFilter.add(siteId); + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesResponseGetOk routesResponseGetOk = routesApi.apiRoutesGet(sitesFilter, vpcFilter); + if (Objects.isNull(routesResponseGetOk) || Boolean.FALSE.equals(routesResponseGetOk.isIsSuccess())) { + logger.warn("Failed to retrieve static routes"); + return null; + } + List<RoutesGetBody> routesList = routesResponseGetOk.getData(); + return routesList.stream() + .filter(x -> (Objects.isNull(prefix) || x.getName().equals(prefix)) && + (Objects.isNull(nextHop) || x.getNextHop().equals(nextHop))) + .collect(Collectors.toList()); + } catch (ApiException e) { + logAndThrowException("Error listing Netris static routes", e); + } + return null; + } + + private Pair<Boolean, RoutesGetBody> staticRouteExists(Integer netrisVpcId, String prefix, String nextHop, String description) { + List<RoutesGetBody> staticRoutes = listStaticRoutes(netrisVpcId, prefix, nextHop); + if (staticRoutes == null) { + return new Pair<>(false, null); + } + return new Pair<>(!staticRoutes.isEmpty(), staticRoutes.isEmpty() ? null : staticRoutes.get(0)); + } + + public void deleteNatRule(String natRuleName, Integer snatRuleId, String netrisVpcName) { + logger.debug("Deleting NAT rule on Netris: {} for VPC {}", natRuleName, netrisVpcName); + try { + NatApi natApi = apiClient.getApiStubForMethod(NatApi.class); + natApi.apiV2NatIdDelete(snatRuleId); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to delete NAT rule: %s for VPC: %s", natRuleName, netrisVpcName), e); + } + } + + private void deleteVpcIpamAllocationInternal(VPCListing vpcResource, String allocationName) { + logger.debug("Deleting Netris VPC IPAM Allocation {} for VPC {}", allocationName, vpcResource.getName()); + try { + VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class); + VPCResponseResourceOK vpcResourcesResponse = vpcApi.apiV2VpcVpcIdResourcesGet(vpcResource.getId()); + VPCResourceIpam vpcAllocationResource = getVpcAllocationResource(vpcResourcesResponse, allocationName); + if (Objects.isNull(vpcAllocationResource)) { + logger.info("No VPC IPAM Allocation found for VPC {}", allocationName); + return; + } + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + logger.debug("Removing the IPAM allocation {} with ID {}", vpcAllocationResource.getName(), vpcAllocationResource.getId()); + ipamApi.apiV2IpamTypeIdDelete("allocation", vpcAllocationResource.getId()); + } catch (ApiException e) { + logAndThrowException(String.format("Error removing IPAM Allocation %s for VPC %s", allocationName, vpcResource.getName()), e); + } + } + + private VPCResourceIpam getVpcAllocationResource(VPCResponseResourceOK vpcResourcesResponse, String allocationName) { + VPCResource resource = vpcResourcesResponse.getData().get(0); + List<VPCResourceIpam> vpcAllocations = resource.getAllocation(); + if (CollectionUtils.isNotEmpty(vpcAllocations)) { + vpcAllocations = vpcAllocations.stream().filter(x -> x.getName().equalsIgnoreCase(allocationName)).collect(Collectors.toList()); + return CollectionUtils.isNotEmpty(vpcAllocations) ? vpcAllocations.get(0) : null; + } + return null; + } + + private VPCListing getVpcByNameAndTenant(String vpcName) { + try { + List<VPCListing> vpcListings = listVPCs(); + List<VPCListing> vpcs = vpcListings.stream() + .filter(x -> x.getName().equals(vpcName) && x.getAdminTenant().getId().equals(tenantId)) + .collect(Collectors.toList()); + return vpcs.isEmpty() ? null : vpcs.get(0); + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Error getting VPC %s information: %s", vpcName, e.getMessage()), e); + } + } + + private VPCResponseObjectOK deleteVpcInternal(VPCListing vpcResource) { + try { + VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class); + logger.debug("Removing the VPC {} with ID {}", vpcResource.getName(), vpcResource.getId()); + return vpcApi.apiV2VpcVpcIdDelete(vpcResource.getId()); + } catch (ApiException e) { + logAndThrowException(String.format("Error deleting VPC %s: %s", vpcResource.getName(), e.getResponseBody()), e); + return null; + } + } + + @Override + public boolean deleteVpc(DeleteNetrisVpcCommand cmd) { + String suffix = String.valueOf(cmd.getId()); + String vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC); + VPCListing vpcResource = getVpcByNameAndTenant(vpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return false; + } + String snatRuleName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.SNAT, suffix); + NatGetBody existingNatRule = netrisNatRuleExists(snatRuleName); + boolean ruleExists = Objects.nonNull(existingNatRule); + if (ruleExists) { + deleteNatRule(snatRuleName, existingNatRule.getId(), vpcResource.getName()); + } + + String vpcAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, cmd.getCidr()); + deleteVpcIpamAllocationInternal(vpcResource, vpcAllocationName); + VPCResponseObjectOK response = deleteVpcInternal(vpcResource); + return response != null && response.isIsSuccess(); + } + + @Override + public boolean createVnet(CreateNetrisVnetCommand cmd) { + String vpcName = cmd.getVpcName(); + Long vpcId = cmd.getVpcId(); + String networkName = cmd.getName(); + Long networkId = cmd.getId(); + String vnetCidr = cmd.getCidr(); + Integer vxlanId = cmd.getVxlanId(); + String netrisTag = cmd.getNetrisTag(); + String netmask = vnetCidr.split("/")[1]; + String netrisGateway = cmd.getGateway() + "/" + netmask; + String netrisV6Cidr = cmd.getIpv6Cidr(); + boolean isVpc = cmd.isVpc(); + Boolean isGlobalRouting = cmd.isGlobalRouting(); + + try { + String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName); + VPCListing associatedVpc = getNetrisVpcResource(netrisVpcName); + if (associatedVpc == null) { + logger.error("Failed to find Netris VPC with name: {}, to create the corresponding vNet for network {}", netrisVpcName, networkName); + return false; + } + + String vNetName; + if (isVpc) { + vNetName = String.format("V%s-N%s-%s", vpcId, networkId, networkName); + } else { + vNetName = String.format("N%s-%s", networkId, networkName); + } + String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ; + String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), vnetCidr) ; + + createIpamSubnetInternal(netrisSubnetName, vnetCidr, SubnetBody.PurposeEnum.COMMON, associatedVpc, isGlobalRouting); + if (Objects.nonNull(netrisV6Cidr)) { + String netrisV6IpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, netrisV6Cidr); + String netrisV6SubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), netrisV6Cidr) ; + BigDecimal ipamAllocationId = getIpamAllocationIdByPrefixAndVpc(netrisV6Cidr, associatedVpc); + if (ipamAllocationId == null) { + 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, isGlobalRouting); + } + logger.debug("Successfully created IPAM Subnet {} for network {} on Netris", netrisSubnetName, networkName); + + 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); + return false; + } + } catch (ApiException e) { + throw new CloudRuntimeException(String.format("Failed to create Netris vNet %s", networkName), e); + } + return true; + } + + @Override + public boolean updateVnet(UpdateNetrisVnetCommand cmd) { + String networkName = cmd.getName(); + Long networkId = cmd.getId(); + String prevNetworkName = cmd.getPrevNetworkName(); + String vpcName = cmd.getVpcName(); + Long vpcId = cmd.getVpcId(); + boolean isVpc = cmd.isVpc(); + + String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName); + VPCListing associatedVpc = getNetrisVpcResource(netrisVpcName); + if (associatedVpc == null) { + logger.error("Failed to find Netris VPC with name: {}, to create the corresponding vNet for network {}", netrisVpcName, networkName); + return false; + } + + String vNetName; + String prevVnetName; + if (isVpc) { + vNetName = String.format("V%s-N%s-%s", vpcId, networkId, networkName); + prevVnetName = String.format("V%s-N%s-%s", vpcId, networkId, prevNetworkName); + } else { + vNetName = String.format("N%s-%s", networkId, networkName); + prevVnetName = String.format("N%s-%s", networkId, prevNetworkName); + } + String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ; + String prevNetrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, prevVnetName) ; + + VnetResAddBody response = updateVnetInternal(associatedVpc, netrisVnetName, prevNetrisVnetName); + if (response == null || !response.isIsSuccess()) { + String reason = response == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("Netris vNet: {} update failed: {}", vNetName, reason); + return false; + } + return true; + } + Review Comment: ```suggestion ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@cloudstack.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org