sureshanaparti commented on code in PR #10458: URL: https://github.com/apache/cloudstack/pull/10458#discussion_r2222826604
########## 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; + } + + + private VnetResAddBody updateVnetInternal(VPCListing associatedVpc, String netrisVnetName, String prevVnetName) { + logger.debug("Updating Netris vNet name from {} to {} ", netrisVnetName, prevVnetName); + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(associatedVpc.getId()); + FilterBySites siteFilter = new FilterBySites(); + siteFilter.add(siteId); + List<VnetsBody> vnetsList = getVnets(associatedVpc, prevVnetName, siteFilter, vpcFilter); + if (CollectionUtils.isEmpty(vnetsList)) { + String errorMsg = String.format("Could not find vNet with name: %s", prevVnetName); + logger.error(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + VnetsBody vnetsBody = vnetsList.get(0); + + VnetEditBody vnetBody = new VnetEditBody(); + + vnetBody.setCustomAnycastMac(vnetBody.getCustomAnycastMac()); + + VnetEditBodyGateways gatewayV4 = new VnetEditBodyGateways(); + gatewayV4.prefix(vnetsBody.getGateways().get(0).getPrefix()); + gatewayV4.setDhcpEnabled(false); + VnetEditBodyDhcp dhcp = new VnetEditBodyDhcp(); + dhcp.setEnd(""); + dhcp.setStart(""); + dhcp.setOptionSet(new VnetAddBodyDhcpOptionSet()); + gatewayV4.setDhcp(dhcp); + List<VnetEditBodyGateways> gatewaysList = new ArrayList<>(); + gatewaysList.add(gatewayV4); + + + if (vnetsBody.getGateways().size() > 1 && Objects.nonNull(vnetsBody.getGateways().get(1))) { + String netrisV6Gateway = vnetsBody.getGateways().get(1).getPrefix(); + VnetEditBodyGateways gatewayV6 = new VnetEditBodyGateways(); + gatewayV6.prefix(netrisV6Gateway); + gatewayV6.setDhcpEnabled(false); + gatewayV6.setDhcp(dhcp); + gatewaysList.add(gatewayV6); + } + + vnetBody.setGateways(gatewaysList); + vnetBody.setGuestTenants(new ArrayList<>()); + vnetBody.setL3vpn(false); + vnetBody.setName(netrisVnetName); + vnetBody.setNativeVlan(0); + vnetBody.setVxlanID(vnetsBody.getVxlanID()); + vnetBody.setPorts(new ArrayList<>()); + + IpTreeSubnetSites subnetSites = new IpTreeSubnetSites(); + subnetSites.setId(new BigDecimal(siteId)); + subnetSites.setName(siteName); + List<IpTreeSubnetSites> subnetSitesList = new ArrayList<>(); + subnetSitesList.add(subnetSites); + vnetBody.setSites(subnetSitesList); + + vnetBody.setState(VnetEditBody.StateEnum.ACTIVE); + + vnetBody.setTags(new ArrayList<>()); + + IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); + vnetBody.setTenant(allocationTenant); + + vnetBody.setVlan(0); + vnetBody.setVlanAware(false); + vnetBody.setVlans(""); + + VnetAddBodyVpc vpc = new VnetAddBodyVpc(); + vpc.setName(associatedVpc.getName()); + vpc.setId(associatedVpc.getId()); + vnetBody.setVpc(vpc); + + vnetBody.setTags(vnetsBody.getTags()); + + VNetApi vnetApi = apiClient.getApiStubForMethod(VNetApi.class); + return vnetApi.apiV2VnetIdPut(vnetBody, vnetsBody.getId().intValue()); + } catch (ApiException e) { + logAndThrowException(String.format("Error creating Netris vNet %s for VPC %s", netrisVnetName, associatedVpc.getName()), e); + return null; + } + } + + @Override + public boolean deleteVnet(DeleteNetrisVnetCommand cmd) { + String vpcName = cmd.getVpcName(); + Long vpcId = cmd.getVpcId(); + String networkName = cmd.getName(); + Long networkId = cmd.getId(); + boolean isVpc = cmd.isVpc(); + String vnetCidr = cmd.getVNetCidr(); + String vnetV6Cidr = cmd.getvNetV6Cidr(); + 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); + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(associatedVpc.getId()); + FilterBySites siteFilter = new FilterBySites(); + siteFilter.add(siteId); + deleteVnetInternal(associatedVpc, siteFilter, vpcFilter, netrisVnetName, vNetName); + + logger.debug("Successfully deleted vNet {}", vNetName); + deleteSubnetInternal(vpcFilter, netrisVnetName, netrisSubnetName); + if (Objects.nonNull(vnetV6Cidr)) { + String netrisV6IpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, vnetV6Cidr); + String netrisV6SubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), vnetV6Cidr); + deleteSubnetInternal(vpcFilter, netrisVnetName, netrisV6SubnetName); + deleteVpcIpamAllocationInternal(associatedVpc, netrisV6IpamAllocationName); + } + + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Failed to delete Netris vNet %s", networkName), e); + } + 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) || isAllocationPartOfBiggerAllocation(x.getPrefix(), superCidrPrefix)) + .collect(Collectors.toList()); + return CollectionUtils.isEmpty(superCidrList) ? null : superCidrList.get(0).getId(); + } + + private boolean isAllocationPartOfBiggerAllocation(String netrisAllocation, String providedAllocation) { + IPAddress biggerAllocation = new IPAddressString(netrisAllocation).getAddress(); + IPAddress smallerAllocation = new IPAddressString(providedAllocation).getAddress(); + + return biggerAllocation.contains(smallerAllocation); + + } + + 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 -> ipamAllocationId != null ? + x.getAllocationID().equals(ipamAllocationId) && x.getPrefix().equals(exactCidr) && x.getPurpose() == purpose : + 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.COMMON, systemVpc); + if (exactSubnet == null) { + String ipamSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, exactCidr); + createIpamSubnetInternal(ipamSubnetName, exactCidr, SubnetBody.PurposeEnum.COMMON, systemVpc, null); + } + } 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 boolean createOrUpdateNatRuleInternal(CreateOrUpdateNetrisNatCommand cmd) { + String ruleName = cmd.getNatRuleName(); + long vpcId = cmd.getVpcId(); + Long networkId = cmd.getId(); + String networkName = cmd.getName(); + String vpcName = cmd.getVpcName(); + String vpcCidr = cmd.getVpcCidr(); + boolean isVpc = cmd.isVpc(); + NatPostBody.ActionEnum action = getNatActionFromRuleType(cmd.getNatRuleType()); + NatPostBody.ProtocolEnum protocol = getProtocolFromString(cmd.getProtocol()); + NatPostBody.StateEnum state = getStateFromString(cmd.getState()); + + String vNetName = isVpc ? + String.format("V%s-N%s-%s", vpcId, networkId, networkName) : + String.format("N%s-%s", networkId, networkName); + String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName); + VPCListing vpcResource = getNetrisVpcResource(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + + String targetIpSubnet = null; + if (NatPostBody.ActionEnum.SNAT == action) { + targetIpSubnet = cmd.getNatIp() + "/32"; + } else if (NatPostBody.ActionEnum.DNAT == action) { + targetIpSubnet = cmd.getDestinationAddress() + "/32"; + } + + if (StringUtils.isNotBlank(targetIpSubnet) && existsDestinationSubnet(targetIpSubnet)) { + logger.debug("Creating subnet with NAT purpose for {}}", targetIpSubnet); + createNatSubnet(cmd, targetIpSubnet, vpcResource.getId()); + } + + NatGetBody existingNatRule = netrisNatRuleExists(ruleName); + boolean ruleExists = Objects.nonNull(existingNatRule); + if (!ruleExists) { + String destinationAddress = action == NatPostBody.ActionEnum.SNAT ? ANY_IP : cmd.getDestinationAddress() + "/32"; + String destinationPort = cmd.getDestinationPort(); + String sourceAddress = action == NatPostBody.ActionEnum.SNAT ? vpcCidr : ANY_IP; + String sourcePort = "1-65535"; + String snatToIp = action == NatPostBody.ActionEnum.SNAT ? targetIpSubnet : null; + String dnatToIp = action == NatPostBody.ActionEnum.DNAT ? cmd.getSourceAddress() + "/32" : null; + String dnatToPort = action == NatPostBody.ActionEnum.DNAT ? cmd.getSourcePort() : null; + return createNatRuleInternal(ruleName, action, protocol, state, destinationAddress, destinationPort, + sourceAddress, sourcePort, snatToIp, dnatToIp, dnatToPort, netrisVpcName, networkName, vNetName); + } else if (NatPostBody.ActionEnum.SNAT == action) { + return updateSnatRuleInternal(ruleName, targetIpSubnet, netrisVpcName, networkName, vNetName, existingNatRule.getId(), vpcCidr); + } + return true; + } + + private NatPostBody.StateEnum getStateFromString(String state) { + return NatPostBody.StateEnum.fromValue(state); + } + + private NatPostBody.ActionEnum getNatActionFromRuleType(String natRuleType) { + return NatPostBody.ActionEnum.fromValue(natRuleType); + } + + @Override + public boolean createOrUpdateSNATRule(CreateOrUpdateNetrisNatCommand cmd) { + return createOrUpdateNatRuleInternal(cmd); + } + + private boolean existsDestinationSubnet(String destinationSubnet) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(getSystemVpc().getId()); + List<IpTreeSubnet> targetSubnetList = getSubnet(vpcFilter, destinationSubnet); + return targetSubnetList != null; + } catch (ApiException e) { + logAndThrowException(String.format("Error checking if subnet %s exists: %s", destinationSubnet, e.getMessage()), e); + return false; + } + } + + @Override + public boolean createStaticNatRule(CreateOrUpdateNetrisNatCommand cmd) { + String staticNatRuleName = cmd.getNatRuleName(); + String natIP = cmd.getNatIp() + "/32"; + String vmIp = cmd.getVmIp() + "/32"; + String vpcName = cmd.getVpcName(); + String vpcCidr = cmd.getVpcCidr(); + Long vpcId = cmd.getVpcId(); + Long networkId = cmd.getId(); + String networkName = cmd.getName(); + boolean isVpc = cmd.isVpc(); + + try { + String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName); + VPCListing vpcResource = getNetrisVpcResource(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + // Create a /32 subnet for the DNAT IP + createNatSubnet(cmd, natIP, vpcResource.getId()); + NatApi natApi = apiClient.getApiStubForMethod(NatApi.class); + NatPostBody natBody = new NatPostBody(); + natBody.setAction(NatPostBody.ActionEnum.DNAT); + natBody.setDestinationAddress(natIP); + natBody.setName(staticNatRuleName); + natBody.setProtocol(NatPostBody.ProtocolEnum.ALL); + natBody.setState(NatPostBody.StateEnum.ENABLED); + natBody.setComment(String.format("Static NAT rule for %s", netrisVpcName)); + + NatBodySiteSite site = new NatBodySiteSite(); + site.setId(siteId); + site.setName(siteName); + natBody.setSite(site); + natBody.setSourceAddress(ANY_IP); + natBody.setDnatToIP(vmIp); + + NatBodyVpcVpc vpc = new NatBodyVpcVpc(); + vpc.setId(vpcResource.getId()); + vpc.setName(vpcResource.getName()); + natBody.setVpc(vpc); + + InlineResponse20015 natResponse = natApi.apiV2NatPost(natBody); + if (natResponse == null || !natResponse.isIsSuccess()) { + String reason = natResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris static NAT (DNAT) rule creation failed for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException(String.format("Failed to create Static NAT (DNAT) rule for network : %s", Objects.nonNull(vpcName) ? vpcName : networkName), e); + } + return true; + } + + private void createNatSubnet(NetrisCommand cmd, String natIp, Integer netrisVpcId) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(netrisVpcId); + String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(((CreateOrUpdateNetrisNatCommand)cmd).getVpcId()), natIp); + List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName); + if (matchedSubnets.isEmpty()) { + VPCListing systemVpc = getSystemVpc(); + createIpamSubnetInternal(netrisSubnetName, natIp, SubnetBody.PurposeEnum.NAT, systemVpc, null); + } else { + IpTreeSubnet existingSubnet = matchedSubnets.stream().filter(x -> x.getPrefix().equals(natIp)).collect(Collectors.toList()).get(0); + if (existingSubnet.getPurpose() != IpTreeSubnet.PurposeEnum.NAT) { + VPCListing systemVpc = getSystemVpc(); + logger.debug("Subnet: {} already exists, but purpose is not NAT, updating its purpose to 'nat'", natIp); + updateIpamSubnetInternal(existingSubnet.getId().intValue(), netrisSubnetName, natIp, SubnetBody.PurposeEnum.NAT, systemVpc, null); + } + } + } catch (ApiException e) { + throw new CloudRuntimeException(String.format("Failed to create subnet for %s with NAT purpose", natIp)); + } + } + + private void createLBSubnet(NetrisCommand cmd, String lbIp, Integer netrisVpcId) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(netrisVpcId); + String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, + NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, + String.valueOf(cmd.getId()), lbIp); + List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName); + VPCListing systemVpc = getSystemVpc(); + if (matchedSubnets.isEmpty()) { + createIpamSubnetInternal(netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null); + } else if (IpTreeSubnet.PurposeEnum.LOAD_BALANCER != matchedSubnets.get(0).getPurpose()){ + logger.debug("Updating existing NAT subnet {} to have load balancer purpose", netrisSubnetName); + updateIpamSubnetInternal(matchedSubnets.get(0).getId().intValue(), netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null); + } + logger.debug("LB subnet: {} already exists", netrisSubnetName); + } catch (ApiException e) { + throw new CloudRuntimeException(String.format("Failed to create subnet for %s with LB purpose", lbIp)); + } + } + + private NatPostBody.ProtocolEnum getProtocolFromString(String protocol) { + return NatPostBody.ProtocolEnum.fromValue(protocol); + } + + private NatPostBody createNatRulePostBody(String ruleName, NatPostBody.ActionEnum action, NatPostBody.ProtocolEnum protocol, NatPostBody.StateEnum state, + String destinationAddress, String destinationPort, + String sourceAddress, String sourcePort, + String dnatToIp, String dnatToPort, + String netrisVpcName, String snatIP, String comment) { + NatPostBody natBody = new NatPostBody(); + natBody.setAction(action); + natBody.setName(ruleName); + natBody.setProtocol(protocol); + natBody.setState(state); + if (StringUtils.isNotBlank(comment)) { + natBody.setComment(comment); + } + + natBody.setDestinationAddress(destinationAddress); + if (StringUtils.isNotBlank(destinationPort)) { + natBody.setDestinationPort(destinationPort); + } + + if (StringUtils.isNotBlank(sourceAddress)) { + natBody.setSourceAddress(sourceAddress); + } + if (StringUtils.isNotBlank(sourcePort)) { + natBody.setSourcePort(sourcePort); + } + + NatBodySiteSite site = new NatBodySiteSite(); + site.setId(siteId); + site.setName(siteName); + natBody.setSite(site); + + if (StringUtils.isNotBlank(snatIP)) { + natBody.setSnatToIP(snatIP); + } + + if (StringUtils.isNotBlank(dnatToIp)) { + natBody.setDnatToIP(dnatToIp); + } + if (StringUtils.isNotBlank(dnatToPort)) { + natBody.setDnatToPort(Integer.valueOf(dnatToPort)); + } + + NatBodyVpcVpc vpc = new NatBodyVpcVpc(); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return null; + } + vpc.setId(vpcResource.getId()); + vpc.setName(vpcResource.getName()); + natBody.setVpc(vpc); + return natBody; + } + + @Override + public boolean createOrUpdateDNATRule(CreateOrUpdateNetrisNatCommand cmd) { + return createOrUpdateNatRuleInternal(cmd); + } + + private boolean createNatRuleInternal(String ruleName, NatPostBody.ActionEnum action, NatPostBody.ProtocolEnum protocol, NatPostBody.StateEnum state, + String destinationAddress, String destinationPort, String sourceAddress, String sourcePort, + String sNatToIp, String dNatToIp, String dNatToPort, + String netrisVpcName, String networkName, String vNetName) { + try { + NatApi natApi = apiClient.getApiStubForMethod(NatApi.class); + String comment = String.format("NAT rule for %s with action %s", netrisVpcName, action.name()); + NatPostBody natBody = createNatRulePostBody(ruleName, action, protocol, state, + destinationAddress, destinationPort, sourceAddress, sourcePort, + dNatToIp, dNatToPort, netrisVpcName, sNatToIp, comment); + if (natBody == null) { + return false; + } + InlineResponse20015 natResponse = natApi.apiV2NatPost(natBody); + if (natResponse == null || !natResponse.isIsSuccess()) { + String reason = natResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris NAT rule {} creation failed for network(vNet) - {}({}): {}", action.name(), networkName, vNetName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException(String.format("Failed to create NAT rule %s for network(vNet): %s(%s)", action.name(), networkName, vNetName), e); + } + return true; + } + + private boolean updateSnatRuleInternal(String snatRuleName, String snatIP, String netrisVpcName, String networkName, + String vNetName, Integer netisSnatId, String vpcCidr) { + try { + NatApi natApi = apiClient.getApiStubForMethod(NatApi.class); + NatPutBody natBody = new NatPutBody(); + natBody.setAction(NatPutBody.ActionEnum.SNAT); + natBody.setDestinationAddress(ANY_IP); + natBody.setName(snatRuleName); + natBody.setProtocol(NatPutBody.ProtocolEnum.ALL); + + NatBodySiteSite site = new NatBodySiteSite(); + site.setId(siteId); + site.setName(siteName); + natBody.setSite(site); + natBody.setSourceAddress(vpcCidr); + natBody.setSnatToIP(snatIP); + + NatBodyVpcVpc vpc = new NatBodyVpcVpc(); + 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; + } + vpc.setId(vpcResource.getId()); + vpc.setName(vpcResource.getName()); + natBody.setVpc(vpc); + + InlineResponse20016 natUpdateResponse = natApi.apiV2NatIdPut(natBody, netisSnatId); + if (natUpdateResponse == null || !natUpdateResponse.isIsSuccess()) { + String reason = natUpdateResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("Update of Netris SNAT rule failed for network(vNet) - {}({}): {}", networkName, vNetName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException(String.format("Failed to create SNAT rule for network(vNet): %s(%s)", networkName, vNetName), e); + } + return true; + } + + private List<VnetsBody> getVnets(VPCListing associatedVpc, String netrisVnetName, FilterBySites siteFilter, FilterByVpc vpcFilter) { + try { + VNetApi vNetApi = apiClient.getApiStubForMethod(VNetApi.class); + VnetResListBody vnetList = vNetApi.apiV2VnetGet(siteFilter, vpcFilter); + if (vnetList == null || !vnetList.isIsSuccess()) { + throw new CloudRuntimeException(String.format("Failed to list vNets for the given VPC: %s and site: %s", associatedVpc.getName(), siteName)); + } + return vnetList.getData().stream().filter(vnet -> vnet.getName().equals(netrisVnetName)).collect(Collectors.toList()); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to get vNets: %s", netrisVnetName), e); + } + return Collections.emptyList(); + } + + private void deleteVnetInternal(VPCListing associatedVpc, FilterBySites siteFilter, FilterByVpc vpcFilter, String netrisVnetName, String vNetName) { + try { + VNetApi vNetApi = apiClient.getApiStubForMethod(VNetApi.class); + List<VnetsBody> vnetsList = getVnets(associatedVpc, netrisVnetName, siteFilter, vpcFilter); + if (CollectionUtils.isEmpty(vnetsList)) { + logger.debug("vNet: {} for the given VPC: {} appears to already be deleted on Netris", vNetName, associatedVpc.getName()); + return; + } + VnetsBody vnetsBody = vnetsList.get(0); + + VnetResDeleteBody deleteVnetResponse = vNetApi.apiV2VnetIdDelete(vnetsBody.getId().intValue()); + if (deleteVnetResponse == null || !deleteVnetResponse.isIsSuccess()) { + throw new CloudRuntimeException(String.format("Failed to delete vNet: %s", vNetName)); + } + } catch (ApiException e) { + logAndThrowException(String.format("Failed to delete vNet: %s", netrisVnetName), e); + } + } + + private List<IpTreeSubnet> getSubnet(FilterByVpc vpcFilter, String netrisSubnetName) { + try { + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + SubnetResBody subnetsResponse = ipamApi.apiV2IpamSubnetsGet(vpcFilter); + List<IpTreeSubnet> subnets = subnetsResponse.getData(); + return subnets.stream().filter(subnet -> subnet.getName().equals(netrisSubnetName)).collect(Collectors.toList()); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to get IPAM subnet: %s", netrisSubnetName), e); + } + return new ArrayList<>(); + } + + private void deleteSubnetInternal(FilterByVpc vpcFilter, String netrisVnetName, String netrisSubnetName) { + try { + String logString = ""; + if (Objects.nonNull(netrisVnetName)) { + logString = String.format("for vNet: %s ", netrisVnetName); + } + logger.debug("Deleting Netris VPC IPAM Subnet {} {}", netrisSubnetName, logString); + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName); + if (CollectionUtils.isEmpty(matchedSubnets)) { + logger.debug("IPAM subnet: {} {} appears to already be deleted on Netris", netrisSubnetName, logString); + return; + } + + ipamApi.apiV2IpamTypeIdDelete("subnet", matchedSubnets.get(0).getId().intValue()); + } catch (ApiException e) { + logAndThrowException(String.format("Failed to delete subnet: %s", netrisSubnetName), e); + } + } + + private SubnetBody getIpamSubnetBody(VPCListing vpc, SubnetBody.PurposeEnum purpose, String subnetName, String subnetPrefix, Boolean isGlobalRouting) { + SubnetBody subnetBody = new SubnetBody(); + subnetBody.setName(subnetName); + + AllocationBodyVpc vpcAllocationBody = new AllocationBodyVpc(); + vpcAllocationBody.setName(vpc.getName()); + vpcAllocationBody.setId(vpc.getId()); + subnetBody.setVpc(vpcAllocationBody); + + IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); + subnetBody.setTenant(allocationTenant); + + IpTreeSubnetSites subnetSites = new IpTreeSubnetSites(); + subnetSites.setId(new BigDecimal(siteId)); + subnetSites.setName(siteName); + subnetBody.setSites(List.of(subnetSites)); + + subnetBody.setPurpose(purpose); + subnetBody.setPrefix(subnetPrefix); + if (isGlobalRouting != null) { + subnetBody.setGlobalRouting(isGlobalRouting); + } + return subnetBody; + } + + private InlineResponse2004Data createIpamSubnetInternal(String subnetName, String subnetPrefix, SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) { + logger.debug("Creating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName()); + try { + + SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting); + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + 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 IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e); + return null; + } + } + + private InlineResponse2004Data updateIpamSubnetInternal(Integer netrisSubnetId, String subnetName, String subnetPrefix, + SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) { + logger.debug("Updating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName()); + try { + + SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting); + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetIdPut(subnetBody, netrisSubnetId); + if (subnetResponse == null || !subnetResponse.isIsSuccess()) { + String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris IPAM Subnet {} update failed: {}", subnetName, reason); + throw new CloudRuntimeException(reason); + } + return subnetResponse.getData(); + } catch (ApiException e) { + logAndThrowException(String.format("Error Updating Netris IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e); + return null; + } + } + + VnetResAddBody createVnetInternal(VPCListing associatedVpc, String netrisVnetName, String netrisGateway, String netrisV6Cidr, Integer vxlanId, String netrisTag) { + logger.debug("Creating Netris VPC vNet {} for CIDR {}", netrisVnetName, netrisGateway); + try { + VnetAddBody vnetBody = new VnetAddBody(); + + vnetBody.setCustomAnycastMac(""); + + VnetAddBodyGateways gatewayV4 = new VnetAddBodyGateways(); + gatewayV4.prefix(netrisGateway); + gatewayV4.setDhcpEnabled(false); + VnetAddBodyDhcp dhcp = new VnetAddBodyDhcp(); + dhcp.setEnd(""); + dhcp.setStart(""); + dhcp.setOptionSet(new VnetAddBodyDhcpOptionSet()); + gatewayV4.setDhcp(dhcp); + List<VnetAddBodyGateways> gatewaysList = new ArrayList<>(); + gatewaysList.add(gatewayV4); + + if (Objects.nonNull(netrisV6Cidr)) { + VnetAddBodyGateways gatewayV6 = new VnetAddBodyGateways(); + gatewayV6.prefix(NetUtils.getIpv6Gateway(netrisV6Cidr)); + gatewayV6.setDhcpEnabled(false); + gatewayV6.setDhcp(dhcp); + gatewaysList.add(gatewayV6); + } + + vnetBody.setGateways(gatewaysList); + vnetBody.setGuestTenants(new ArrayList<>()); + vnetBody.setL3vpn(false); + vnetBody.setName(netrisVnetName); + vnetBody.setNativeVlan(0); + vnetBody.setVxlanID(vxlanId); + vnetBody.setPorts(new ArrayList<>()); + + IpTreeSubnetSites subnetSites = new IpTreeSubnetSites(); + subnetSites.setId(new BigDecimal(siteId)); + subnetSites.setName(siteName); + List<IpTreeSubnetSites> subnetSitesList = new ArrayList<>(); + subnetSitesList.add(subnetSites); + vnetBody.setSites(subnetSitesList); + + vnetBody.setState(VnetAddBody.StateEnum.ACTIVE); + + vnetBody.setTags(new ArrayList<>()); + + IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); + vnetBody.setTenant(allocationTenant); + + vnetBody.setVlan(0); + vnetBody.setVlanAware(false); + vnetBody.setVlans(""); + + VnetAddBodyVpc vpc = new VnetAddBodyVpc(); + vpc.setName(associatedVpc.getName()); + vpc.setId(associatedVpc.getId()); + vnetBody.setVpc(vpc); + + vnetBody.setTags(Collections.singletonList(netrisTag)); + + VNetApi vnetApi = apiClient.getApiStubForMethod(VNetApi.class); + return vnetApi.apiV2VnetPost(vnetBody); + } catch (ApiException e) { + logAndThrowException(String.format("Error creating Netris vNet %s for VPC %s", netrisVnetName, associatedVpc.getName()), e); + return null; + } + } + 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