diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxStaticNatCommand.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxStaticNatCommand.java new file mode 100644 index 00000000000..9768b9ecb93 --- /dev/null +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxStaticNatCommand.java @@ -0,0 +1,76 @@ +package org.apache.cloudstack.agent.api; + +import java.util.Objects; + +public class CreateNsxStaticNatCommand extends NsxCommand { + + private long vpcId; + private String vpcName; + private long vmId; + private String publicIp; + private String vmIp; + + public CreateNsxStaticNatCommand(long domainId, long accountId, long zoneId, long vpcId, String vpcName, + long vmId, String publicIp, String vmIp) { + super(domainId, accountId, zoneId); + this.vpcId = vpcId; + this.vpcName = vpcName; + this.vmId = vmId; + this.publicIp = publicIp; + this.vmIp = vmIp; + } + + public long getVpcId() { + return vpcId; + } + + public void setVpcId(long vpcId) { + this.vpcId = vpcId; + } + + public String getVpcName() { + return vpcName; + } + + public void setVpcName(String vpcName) { + this.vpcName = vpcName; + } + + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public String getPublicIp() { + return publicIp; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public String getVmIp() { + return vmIp; + } + + public void setVmIp(String vmIp) { + this.vmIp = vmIp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + CreateNsxStaticNatCommand that = (CreateNsxStaticNatCommand) o; + return vpcId == that.vpcId && Objects.equals(publicIp, that.publicIp) && Objects.equals(vmIp, that.vmIp); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), vpcId, publicIp, vmIp); + } +} diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeleteNsxStaticNatCommand.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeleteNsxStaticNatCommand.java new file mode 100644 index 00000000000..a4c30c1c701 --- /dev/null +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeleteNsxStaticNatCommand.java @@ -0,0 +1,43 @@ +package org.apache.cloudstack.agent.api; + +import java.util.Objects; + +public class DeleteNsxStaticNatCommand extends NsxCommand { + private long vpcId; + private String vpcName; + public DeleteNsxStaticNatCommand(long domainId, long accountId, long zoneId, long vpcId, String vpcName) { + super(domainId, accountId, zoneId); + this.vpcId = vpcId; + this.vpcName = vpcName; + } + + public long getVpcId() { + return vpcId; + } + + public void setVpcId(long vpcId) { + this.vpcId = vpcId; + } + + public String getVpcName() { + return vpcName; + } + + public void setVpcName(String vpcName) { + this.vpcName = vpcName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DeleteNsxStaticNatCommand that = (DeleteNsxStaticNatCommand) o; + return vpcId == that.vpcId && Objects.equals(vpcName, that.vpcName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), vpcId, vpcName); + } +} diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java index ad5ea2b7067..091a29f10c4 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java @@ -36,8 +36,10 @@ import org.apache.cloudstack.NsxAnswer; import org.apache.cloudstack.StartupNsxCommand; import org.apache.cloudstack.agent.api.CreateNsxDhcpRelayConfigCommand; import org.apache.cloudstack.agent.api.CreateNsxSegmentCommand; +import org.apache.cloudstack.agent.api.CreateNsxStaticNatCommand; import org.apache.cloudstack.agent.api.CreateNsxTier1GatewayCommand; import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand; +import org.apache.cloudstack.agent.api.DeleteNsxStaticNatCommand; import org.apache.cloudstack.agent.api.DeleteNsxTier1GatewayCommand; import org.apache.cloudstack.service.NsxApiClient; import org.apache.cloudstack.utils.NsxControllerUtils; @@ -103,6 +105,10 @@ public class NsxResource implements ServerResource { return executeRequest((CreateNsxTier1GatewayCommand) cmd); } else if (cmd instanceof CreateNsxDhcpRelayConfigCommand) { return executeRequest((CreateNsxDhcpRelayConfigCommand) cmd); + } else if (cmd instanceof CreateNsxStaticNatCommand) { + return executeRequest((CreateNsxStaticNatCommand) cmd); + } else if (cmd instanceof DeleteNsxStaticNatCommand) { + return executeRequest((DeleteNsxStaticNatCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -331,6 +337,34 @@ public class NsxResource implements ServerResource { return new NsxAnswer(cmd, true, null); } + private NsxAnswer executeRequest(CreateNsxStaticNatCommand cmd) { + String staticNatRuleName = NsxControllerUtils.getStaticNatRuleName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), + cmd.getVpcId()); + String tier1GatewayName = NsxControllerUtils.getTier1GatewayName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), + cmd.getVpcId()); + try { + nsxApiClient.createStaticNatRule(cmd.getVpcName(), tier1GatewayName, staticNatRuleName, cmd.getPublicIp(), cmd.getVmIp()); + } catch (Exception e) { + LOGGER.error(String.format("Failed to add NSX static NAT rule %s for network: %s", staticNatRuleName, cmd.getVpcName())); + return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); + } + return new NsxAnswer(cmd, true, null); + } + + private NsxAnswer executeRequest(DeleteNsxStaticNatCommand cmd) { + String staticNatRuleName = NsxControllerUtils.getStaticNatRuleName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), + cmd.getVpcId()); + String tier1GatewayName = NsxControllerUtils.getTier1GatewayName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), + cmd.getVpcId()); + try { + nsxApiClient.deleteStaticNatRule(cmd.getVpcName(), tier1GatewayName, staticNatRuleName); + } catch (Exception e) { + LOGGER.error(String.format("Failed to add NSX static NAT rule %s for network: %s", staticNatRuleName, cmd.getVpcName())); + return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); + } + return new NsxAnswer(cmd, true, null); + } + @Override public boolean start() { return true; diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java index 973650db09c..b1a555140fd 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java @@ -26,10 +26,14 @@ import com.vmware.nsx_policy.infra.Sites; import com.vmware.nsx_policy.infra.Tier1s; import com.vmware.nsx_policy.infra.sites.EnforcementPoints; import com.vmware.nsx_policy.infra.tier_0s.LocaleServices; +import com.vmware.nsx_policy.infra.tier_1s.Nat; +import com.vmware.nsx_policy.infra.tier_1s.nat.NatRules; import com.vmware.nsx_policy.model.ApiError; import com.vmware.nsx_policy.model.DhcpRelayConfig; import com.vmware.nsx_policy.model.EnforcementPointListResult; import com.vmware.nsx_policy.model.LocaleServicesListResult; +import com.vmware.nsx_policy.model.PolicyNat; +import com.vmware.nsx_policy.model.PolicyNatRule; import com.vmware.nsx_policy.model.Segment; import com.vmware.nsx_policy.model.SegmentSubnet; import com.vmware.nsx_policy.model.SiteListResult; @@ -79,6 +83,16 @@ public class NsxApiClient { private enum TransportType { OVERLAY, VLAN } + private enum NatId { USER, INTERNAL, DEFAULT } + + private enum NatAction {SNAT, DNAT, REFLEXIVE} + + private enum FirewallMatch { + MATCH_INTERNAL_ADDRESS, + MATCH_EXTERNAL_ADDRESS, + BYPASS + } + public enum RouteAdvertisementType { TIER1_STATIC_ROUTES, TIER1_CONNECTED, TIER1_NAT, TIER1_LB_VIP, TIER1_LB_SNAT, TIER1_DNS_FORWARDER_IP, TIER1_IPSEC_LOCAL_ENDPOINT } @@ -290,4 +304,43 @@ public class NsxApiClient { throw new CloudRuntimeException(msg); } } + + public void createStaticNatRule(String vpcName, String tier1GatewayName, + String ruleName, String publicIp, String vmIp) { + try { + NatRules natService = (NatRules) nsxService.apply(NatRules.class); + PolicyNatRule rule = new PolicyNatRule.Builder() + .setId(ruleName) + .setDisplayName(ruleName) + .setAction(NatAction.DNAT.name()) + .setFirewallMatch(FirewallMatch.MATCH_INTERNAL_ADDRESS.name()) + .setDestinationNetwork(publicIp) + .setTranslatedNetwork(vmIp) + .setEnabled(true) + .build(); + + LOGGER.debug(String.format("Creating NSX static NAT rule %s for tier-1 gateway %s (VPC: %s)", ruleName, tier1GatewayName, vpcName)); + natService.patch(tier1GatewayName, NatId.USER.name(), ruleName, rule); + } catch (Error error) { + ApiError ae = error.getData()._convertTo(ApiError.class); + String msg = String.format("Error creating NSX Static NAT rule %s for tier-1 gateway %s (VPC: %s), due to %s", + ruleName, tier1GatewayName, vpcName, ae.getErrorMessage()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + } + + public void deleteStaticNatRule(String vpcName, String tier1GatewayName, String ruleName) { + try { + NatRules natService = (NatRules) nsxService.apply(NatRules.class); + LOGGER.debug(String.format("Deleting NSX static NAT rule %s for tier-1 gateway %s (VPC: %s)", ruleName, tier1GatewayName, vpcName)); + natService.delete(tier1GatewayName, NatId.USER.name(), ruleName); + } catch (Error error) { + ApiError ae = error.getData()._convertTo(ApiError.class); + String msg = String.format("Failed to delete NSX Static NAT rule %s for tier-1 gateway %s (VPC: %s), due to %s", + ruleName, tier1GatewayName, vpcName, ae.getErrorMessage()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java index edbc2eebffe..3613e817a1d 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java @@ -41,13 +41,19 @@ import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.PublicIpAddress; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.element.DhcpServiceProvider; import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.IpDeployer; +import com.cloud.network.element.StaticNatServiceProvider; import com.cloud.network.element.VpcProvider; +import com.cloud.network.rules.StaticNat; import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.PrivateGateway; import com.cloud.network.vpc.StaticRouteProfile; @@ -62,9 +68,12 @@ import com.cloud.user.AccountManager; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.ReservationContext; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.VMInstanceDao; import net.sf.ehcache.config.InvalidConfigurationException; import org.apache.cloudstack.StartupNsxCommand; import org.apache.log4j.Logger; @@ -81,7 +90,7 @@ import java.util.function.LongFunction; @Component public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsServiceProvider, VpcProvider, - ResourceStateAdapter, Listener { + StaticNatServiceProvider, IpDeployer, ResourceStateAdapter, Listener { @Inject AccountManager accountMgr; @@ -101,6 +110,10 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsS NetworkModel networkModel; @Inject DomainDao domainDao; + @Inject + IPAddressDao ipAddressDao; + @Inject + VMInstanceDao vmInstanceDao; private static final Logger LOGGER = Logger.getLogger(NsxElement.class); @@ -172,6 +185,11 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsS return capabilities; } + @Override + public boolean applyIps(Network network, List ipAddress, Set services) throws ResourceUnavailableException { + return true; + } + @Override public Network.Provider getProvider() { return Network.Provider.Nsx; @@ -412,4 +430,32 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsS } private final LongFunction zoneFunction = zoneId -> dataCenterDao.findById(zoneId); + + @Override + public IpDeployer getIpDeployer(Network network) { + return this; + } + + @Override + public boolean applyStaticNats(Network config, List rules) throws ResourceUnavailableException { + for(StaticNat staticNat : rules) { + long sourceIpAddressId = staticNat.getSourceIpAddressId(); + IPAddressVO ipAddressVO = ipAddressDao.findByIdIncludingRemoved(sourceIpAddressId); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(ipAddressVO.getAssociatedWithVmId()); + // floating ip is released when nic was deleted + if (vm == null || networkModel.getNicInNetworkIncludingRemoved(vm.getId(), config.getId()) == null) { + continue; + } + Nic nic = networkModel.getNicInNetworkIncludingRemoved(vm.getId(), config.getId()); + Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(config.getDataCenterId(), Networks.TrafficType.Public); + if (!staticNat.isForRevoke()) { + return nsxService.createStaticNatRule(config.getDataCenterId(), config.getDomainId(), config.getAccountId(), + config.getVpcId(), vm.getId(), ipAddressVO.getAddress().addr(), staticNat.getDestIpAddress()); + } else { + return nsxService.deleteStaticNatRule(config.getDataCenterId(), config.getDomainId(), config.getAccountId(), + config.getVpcId()); + } + } + return false; + } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java index 999f28bde25..df667890f8c 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java @@ -19,9 +19,12 @@ package org.apache.cloudstack.service; import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.NsxAnswer; +import org.apache.cloudstack.agent.api.CreateNsxStaticNatCommand; import org.apache.cloudstack.agent.api.CreateNsxTier1GatewayCommand; import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand; +import org.apache.cloudstack.agent.api.DeleteNsxStaticNatCommand; import org.apache.cloudstack.agent.api.DeleteNsxTier1GatewayCommand; import org.apache.cloudstack.utils.NsxControllerUtils; @@ -59,4 +62,27 @@ public class NsxServiceImpl implements NsxService { NsxAnswer result = nsxControllerUtils.sendNsxCommand(deleteNsxSegmentCommand, network.getDataCenterId()); return result.getResult(); } + + public boolean createStaticNatRule(long zoneId, long accountId, long domainId, long vpcId, + long vmId, String publicIp, String vmIp) { + VpcVO vpc = vpcDao.findById(vpcId); + if (Objects.isNull(vpc)) { + throw new CloudRuntimeException(String.format("Failed to find VPC with id: %s", vpcId)); + } + CreateNsxStaticNatCommand createNsxStaticNatCommand = new CreateNsxStaticNatCommand(domainId, accountId, zoneId, + vpcId, vpc.getName(), vmId, publicIp, vmIp); + NsxAnswer result = nsxControllerUtils.sendNsxCommand(createNsxStaticNatCommand, zoneId); + return result.getResult(); + } + + public boolean deleteStaticNatRule(long zoneId, long accountId, long domainId, long vpcId) { + VpcVO vpc = vpcDao.findById(vpcId); + if (Objects.isNull(vpc)) { + throw new CloudRuntimeException(String.format("Failed to find VPC with id: %s", vpcId)); + } + DeleteNsxStaticNatCommand deleteNsxStaticNatCommand = new DeleteNsxStaticNatCommand(domainId, accountId, zoneId, + vpcId, vpc.getName()); + NsxAnswer result = nsxControllerUtils.sendNsxCommand(deleteNsxStaticNatCommand, zoneId); + return result.getResult(); + } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java index bef09bb0d55..6ed428d6a49 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java @@ -75,4 +75,9 @@ public class NsxControllerUtils { } return String.format("D%s-A%s-Z%s-V%s-S%s-%s", domainId, accountId, zoneId, vpcId, networkId, suffix); } + + public static String getStaticNatRuleName(long zoneId, long domainId, long accountId, Long vpcId) { + String suffix = "-STATICNAT"; + return getTier1GatewayName(domainId, accountId, zoneId, vpcId) + suffix; + } }