This is an automated email from the ASF dual-hosted git repository. nvazquez pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push: new f8d8a9c7b38 NSX Integration fixes (#8906) f8d8a9c7b38 is described below commit f8d8a9c7b389ea77a3559b5d69560f8ac5b0e355 Author: Pearl Dsilva <pearl1...@gmail.com> AuthorDate: Fri Sep 6 15:56:50 2024 -0400 NSX Integration fixes (#8906) * Prevent addition of duplicate PF rules on scale up and no rules left behind on scale down (#32) * fix missing dependency injection * NSX: Fix concurrency issues on port forwarding rules deletion (#37) * Fix concurrency issues on port forwarding rules deletion * Refactor objectExists * Fix unit test * Fix test * Small fixes * CKS: Externalize control and worker node setup wait time and installation attempts (#38) * NSX: Add shared network support (#41) * NSX: Fix number of physical networks for Guest traffic checks and leftover rules on CKS cluster deletion (#45) * Fix pf rules removal on CKS cluster deletion * Fix check for number of physical networks for guest traffic * Fix unit test * fix logger * NSX: Handle CheckHealthCommand to avoid host disconnection and errors on APIs * NSX: Handle CheckHealthCommand to avoid host disconnection and errors on APIs * Remove unused string * fix logger * Update UDP active monitor to ICMP * Fix NPE on restarting VPC with additional public IPs * NSX / VPC: Reuse Source NAT IP from systemVM range on restarts * CKS: Public IP not found for VPC networks * Externalize retries and inverval for NSX segment deletion (#67) * remove unused import * remove duplicate imports * remove unused import * revert externalizing cks settings * fix test * Refactor log messages * Address comments * Fix issue caused due to forward merge: 90fe1d --------- Co-authored-by: Nicolas Vazquez <nicovazque...@gmail.com> Co-authored-by: Rohit Yadav <rohit.ya...@shapeblue.com> --- .../java/com/cloud/network/nsx/NsxService.java | 10 ++ .../java/com/cloud/network/vpc/VpcManager.java | 5 +- .../engine/orchestration/NetworkOrchestrator.java | 2 +- .../resourcedetail/FirewallRuleDetailVO.java | 4 + .../cloudstack/affinity/HostAffinityProcessor.java | 8 +- .../affinity/HostAntiAffinityProcessor.java | 8 +- .../KubernetesClusterActionWorker.java | 2 +- ...ernetesClusterResourceModifierActionWorker.java | 65 ++++++----- .../KubernetesClusterStartWorker.java | 2 + .../main/java/org/apache/cloudstack/NsxAnswer.java | 10 ++ .../apache/cloudstack/resource/NsxResource.java | 28 +++-- .../apache/cloudstack/service/NsxApiClient.java | 106 ++++++++++++----- .../org/apache/cloudstack/service/NsxElement.java | 125 ++++++++++++++------- .../apache/cloudstack/service/NsxServiceImpl.java | 24 ++-- .../cloudstack/utils/NsxControllerUtils.java | 3 + .../cloudstack/service/NsxApiClientTest.java | 15 +++ .../apache/cloudstack/service/NsxElementTest.java | 4 + .../configuration/ConfigurationManagerImpl.java | 2 +- .../java/com/cloud/network/NetworkServiceImpl.java | 2 +- .../cloud/network/router/VpcNetworkHelperImpl.java | 1 + .../java/com/cloud/network/vpc/VpcManagerImpl.java | 33 ++++-- .../deployment/VpcRouterDeploymentDefinition.java | 6 +- .../VpcRouterDeploymentDefinitionTest.java | 2 +- ui/src/views/network/CreateSharedNetworkForm.vue | 11 +- 24 files changed, 338 insertions(+), 140 deletions(-) diff --git a/api/src/main/java/com/cloud/network/nsx/NsxService.java b/api/src/main/java/com/cloud/network/nsx/NsxService.java index 79ad9547c73..bc4e6aafbfe 100644 --- a/api/src/main/java/com/cloud/network/nsx/NsxService.java +++ b/api/src/main/java/com/cloud/network/nsx/NsxService.java @@ -18,9 +18,19 @@ package com.cloud.network.nsx; import com.cloud.network.IpAddress; import com.cloud.network.vpc.Vpc; +import org.apache.cloudstack.framework.config.ConfigKey; public interface NsxService { + ConfigKey<Integer> NSX_API_FAILURE_RETRIES = new ConfigKey<>("Advanced", Integer.class, + "nsx.api.failure.retries", "30", + "Number of retries for NSX API operations in case of failures", + true, ConfigKey.Scope.Zone); + ConfigKey<Integer> NSX_API_FAILURE_INTERVAL = new ConfigKey<>("Advanced", Integer.class, + "nsx.api.failure.interval", "60", + "Waiting time (in seconds) before retrying an NSX API operation in case of failure", + true, ConfigKey.Scope.Zone); + boolean createVpcNetwork(Long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled); boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address); } diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java index c6279e08012..a340f49c13f 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java @@ -115,7 +115,8 @@ public interface VpcManager { throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; /** - * Assigns source nat public IP address to VPC + * Assigns source nat public IP address to VPC. + * In case of NSX backed VPCs: CloudStack deploys VRs with Public NIC IP different to the VPC source NAT IP, the source NAT IP is on the NSX Public range * * @param owner * @param vpc @@ -123,7 +124,7 @@ public interface VpcManager { * @throws InsufficientAddressCapacityException * @throws ConcurrentOperationException */ - PublicIp assignSourceNatIpAddressToVpc(Account owner, Vpc vpc) throws InsufficientAddressCapacityException, ConcurrentOperationException; + PublicIp assignSourceNatIpAddressToVpc(Account owner, Vpc vpc, Long podId) throws InsufficientAddressCapacityException, ConcurrentOperationException; /** * Validates network offering to find if it can be used for network creation in VPC diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index c8a0ce9e7e9..e1b798d16d6 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -1645,7 +1645,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (ips.isEmpty()) { final Vpc vpc = _vpcMgr.getActiveVpc(network.getVpcId()); logger.debug("Creating a source nat ip for vpc {}", vpc); - _vpcMgr.assignSourceNatIpAddressToVpc(owner, vpc); + _vpcMgr.assignSourceNatIpAddressToVpc(owner, vpc, null); } } else { ips = _ipAddressDao.listByAssociatedNetwork(network.getId(), true); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java index 636d889fafe..1149d0b13e7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java @@ -79,4 +79,8 @@ public class FirewallRuleDetailVO implements ResourceDetail { public boolean isDisplay() { return display; } + + public void setValue(String value) { + this.value = value; + } } diff --git a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java index 4a5bbe8e787..b94cf49e4d9 100644 --- a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java @@ -63,7 +63,9 @@ public class HostAffinityProcessor extends AffinityProcessorBase implements Affi Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { - _affinityGroupDao.listByIds(affinityGroupIdList, true); + if (!affinityGroupIdList.isEmpty()) { + _affinityGroupDao.listByIds(affinityGroupIdList, true); + } for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { processAffinityGroup(vmGroupMapping, plan, vm, vmList); } @@ -149,7 +151,9 @@ public class HostAffinityProcessor extends AffinityProcessorBase implements Affi return Transaction.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus status) { - _affinityGroupDao.listByIds(affinityGroupIds, true); + if (!affinityGroupIds.isEmpty()) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + } for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { return false; diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java index e724f02fe98..4681ce4321e 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -78,7 +78,9 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { - _affinityGroupDao.listByIds(affinityGroupIds, true); + if (!affinityGroupIds.isEmpty()) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + } for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { processAffinityGroup(vmGroupMapping, avoid, vm); } @@ -165,7 +167,9 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements return Transaction.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus status) { - _affinityGroupDao.listByIds(affinityGroupIds, true); + if (!affinityGroupIds.isEmpty()) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + } for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { // if more than 1 VM's are present in the group then check for // conflict due to parallel deployment diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java index 2d993ac3cb6..743962a1f00 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java @@ -360,7 +360,7 @@ public class KubernetesClusterActionWorker { return null; } IpAddress address = ipAddressDao.findByUuid(detailsVO.getValue()); - if (address == null || network.getVpcId() != address.getVpcId()) { + if (address == null || !Objects.equals(network.getVpcId(), address.getVpcId())) { logger.warn(String.format("Public IP with ID: %s linked to the Kubernetes cluster: %s is not usable", detailsVO.getValue(), kubernetesCluster.getName())); return null; } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 92a0315efb5..ec461610e32 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -17,6 +17,31 @@ package com.cloud.kubernetes.cluster.actionworkers; +import static com.cloud.utils.NumbersUtil.toHumanReadableSize; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import com.cloud.network.rules.FirewallManager; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd; +import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + import com.cloud.capacity.CapacityManager; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; @@ -61,9 +86,7 @@ import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.NetworkACLItemDao; import com.cloud.network.vpc.NetworkACLItemVO; import com.cloud.network.vpc.NetworkACLService; -import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; -import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.resource.ResourceManager; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; @@ -88,29 +111,9 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd; -import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; -import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.context.CallContext; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Level; -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import static com.cloud.utils.NumbersUtil.toHumanReadableSize; - public class KubernetesClusterResourceModifierActionWorker extends KubernetesClusterActionWorker { @Inject @@ -134,6 +137,8 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu @Inject protected RulesService rulesService; @Inject + protected FirewallManager firewallManager; + @Inject protected PortForwardingRulesDao portForwardingRulesDao; @Inject protected ResourceManager resourceManager; @@ -169,6 +174,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu final String joinIpKey = "{{ k8s_control_node.join_ip }}"; final String clusterTokenKey = "{{ k8s_control_node.cluster.token }}"; final String ejectIsoKey = "{{ k8s.eject.iso }}"; + String pubKey = "- \"" + configurationDao.getValue("ssh.publickey") + "\""; String sshKeyPair = kubernetesCluster.getKeyPair(); if (StringUtils.isNotEmpty(sshKeyPair)) { @@ -181,7 +187,6 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu k8sNodeConfig = k8sNodeConfig.replace(joinIpKey, joinIp); k8sNodeConfig = k8sNodeConfig.replace(clusterTokenKey, KubernetesClusterUtil.generateClusterToken(kubernetesCluster)); k8sNodeConfig = k8sNodeConfig.replace(ejectIsoKey, String.valueOf(ejectIso)); - k8sNodeConfig = updateKubeConfigWithRegistryDetails(k8sNodeConfig); return k8sNodeConfig; @@ -522,17 +527,22 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu protected void removePortForwardingRules(final IpAddress publicIp, final Network network, final Account account, final List<Long> removedVMIds) throws ResourceUnavailableException { if (!CollectionUtils.isEmpty(removedVMIds)) { + List<PortForwardingRuleVO> pfRules = new ArrayList<>(); + List<PortForwardingRuleVO> revokedRules = new ArrayList<>(); for (Long vmId : removedVMIds) { - List<PortForwardingRuleVO> pfRules = portForwardingRulesDao.listByNetwork(network.getId()); + pfRules.addAll(portForwardingRulesDao.listByNetwork(network.getId())); for (PortForwardingRuleVO pfRule : pfRules) { if (pfRule.getVirtualMachineId() == vmId) { portForwardingRulesDao.remove(pfRule.getId()); + logger.trace("Marking PF rule {} with Revoke state", pfRule); + pfRule.setState(FirewallRule.State.Revoke); + revokedRules.add(pfRule); logger.debug("The Port forwarding rule [%s] with the id [%s] was removed.", pfRule.getName(), pfRule.getId()); break; } } } - rulesService.applyPortForwardingRules(publicIp.getId(), account); + firewallManager.applyRules(revokedRules, false, true); } } @@ -542,10 +552,11 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu for (PortForwardingRuleVO pfRule : pfRules) { if (startPort <= pfRule.getSourcePortStart() && pfRule.getSourcePortStart() <= endPort) { portForwardingRulesDao.remove(pfRule.getId()); - logger.debug("The Port forwarding rule [{}] with the id [{}] was removed.", pfRule.getName(), pfRule.getId()); + logger.debug("The Port forwarding rule [{}] with the id [{}] was mark as revoked.", pfRule.getName(), pfRule.getId()); + pfRule.setState(FirewallRule.State.Revoke); } } - rulesService.applyPortForwardingRules(publicIp.getId(), account); + firewallManager.applyRules(pfRules, false, true); } protected void removeLoadBalancingRule(final IpAddress publicIp, final Network network, diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java index fb963a13044..c3b81a6ee1f 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java @@ -139,6 +139,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif final String clusterToken = "{{ k8s_control_node.cluster.token }}"; final String clusterInitArgsKey = "{{ k8s_control_node.cluster.initargs }}"; final String ejectIsoKey = "{{ k8s.eject.iso }}"; + final List<String> addresses = new ArrayList<>(); addresses.add(controlNodeIp); if (!serverIp.equals(controlNodeIp)) { @@ -243,6 +244,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif final String sshPubKey = "{{ k8s.ssh.pub.key }}"; final String clusterHACertificateKey = "{{ k8s_control_node.cluster.ha.certificate.key }}"; final String ejectIsoKey = "{{ k8s.eject.iso }}"; + String pubKey = "- \"" + configurationDao.getValue("ssh.publickey") + "\""; String sshKeyPair = kubernetesCluster.getKeyPair(); if (StringUtils.isNotEmpty(sshKeyPair)) { diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java index 0820465a6b6..a667adda794 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java @@ -20,6 +20,9 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; public class NsxAnswer extends Answer { + + private boolean objectExists; + public NsxAnswer(final Command command, final boolean success, final String details) { super(command, success, details); } @@ -28,4 +31,11 @@ public class NsxAnswer extends Answer { super(command, e); } + public boolean isObjectExistent() { + return objectExists; + } + + public void setObjectExists(boolean objectExisted) { + this.objectExists = objectExisted; + } } 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 cd1d481b9f8..76815b0deeb 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 @@ -18,6 +18,8 @@ package org.apache.cloudstack.resource; import com.cloud.agent.IAgentControl; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckHealthAnswer; +import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.ReadyAnswer; @@ -102,6 +104,8 @@ public class NsxResource implements ServerResource { public Answer executeRequest(Command cmd) { if (cmd instanceof ReadyCommand) { return executeRequest((ReadyCommand) cmd); + } else if (cmd instanceof CheckHealthCommand) { + return executeRequest((CheckHealthCommand) cmd); } else if (cmd instanceof DeleteNsxTier1GatewayCommand) { return executeRequest((DeleteNsxTier1GatewayCommand) cmd); } else if (cmd instanceof DeleteNsxSegmentCommand) { @@ -293,6 +297,10 @@ public class NsxResource implements ServerResource { return new ReadyAnswer(cmd); } + private Answer executeRequest(CheckHealthCommand cmd) { + return new CheckHealthAnswer(cmd, nsxApiClient.isNsxControllerActive()); + } + private Answer executeRequest(CreateNsxTier1GatewayCommand cmd) { String tier1GatewayName = NsxControllerUtils.getTier1GatewayName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), cmd.getNetworkResourceId(), cmd.isResourceVpc()); boolean sourceNatEnabled = cmd.isSourceNatEnabled(); @@ -385,16 +393,21 @@ public class NsxResource implements ServerResource { cmd.getNetworkResourceId(), cmd.isResourceVpc()); try { String privatePort = cmd.getPrivatePort(); - String service = privatePort.contains("-") ? nsxApiClient.getServicePath(ruleName, privatePort, cmd.getProtocol(), null, null) : - nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol(), null, null); + logger.debug("Checking if the rule {} exists on Tier 1 Gateway: {}", ruleName, tier1GatewayName); if (nsxApiClient.doesPfRuleExist(ruleName, tier1GatewayName)) { - logger.debug(String.format("Port forward rule for port: %s exits on NSX, not adding it again", privatePort)); - return new NsxAnswer(cmd, true, null); + String msg = String.format("Port forward rule for port: %s (%s) exits on NSX, not adding it again", ruleName, privatePort); + logger.debug(msg); + NsxAnswer answer = new NsxAnswer(cmd, true, msg); + answer.setObjectExists(true); + return answer; } + String service = privatePort.contains("-") ? nsxApiClient.getServicePath(ruleName, privatePort, cmd.getProtocol(), null, null) : + nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol(), null, null); nsxApiClient.createPortForwardingRule(ruleName, tier1GatewayName, cmd.getNetworkResourceName(), cmd.getPublicIp(), cmd.getVmIp(), cmd.getPublicPort(), service); } catch (Exception e) { - logger.error(String.format("Failed to add NSX port forward rule %s for network: %s", ruleName, cmd.getNetworkResourceName())); + String msg = String.format("Failed to add NSX port forward rule %s for network: %s", ruleName, cmd.getNetworkResourceName()); + logger.error(msg, e); return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); } return new NsxAnswer(cmd, true, null); @@ -415,8 +428,9 @@ public class NsxResource implements ServerResource { nsxApiClient.deleteNatRule(cmd.getService(), cmd.getPrivatePort(), cmd.getProtocol(), cmd.getNetworkResourceName(), tier1GatewayName, ruleName); } catch (Exception e) { - logger.error(String.format("Failed to add NSX static NAT rule %s for network: %s", ruleName, cmd.getNetworkResourceName())); - return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); + String msg = String.format("Failed to delete NSX rule %s for network %s: due to %s", ruleName, cmd.getNetworkResourceName(), e.getMessage()); + logger.error(msg, e); + return new NsxAnswer(cmd, new CloudRuntimeException(msg)); } return new NsxAnswer(cmd, true, null); } 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 d443b0e14e7..1ba1cc0fcc3 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 @@ -17,7 +17,11 @@ package org.apache.cloudstack.service; import com.cloud.network.Network; +import com.cloud.network.nsx.NsxService; import com.cloud.utils.exception.CloudRuntimeException; +import com.vmware.nsx.cluster.Status; +import com.vmware.nsx.model.ClusterStatus; +import com.vmware.nsx.model.ControllerClusterStatus; import com.vmware.nsx.model.TransportZone; import com.vmware.nsx.model.TransportZoneListResult; import com.vmware.nsx_policy.infra.DhcpRelayConfigs; @@ -45,13 +49,13 @@ import com.vmware.nsx_policy.model.GroupListResult; import com.vmware.nsx_policy.model.ICMPTypeServiceEntry; import com.vmware.nsx_policy.model.L4PortSetServiceEntry; import com.vmware.nsx_policy.model.LBAppProfileListResult; +import com.vmware.nsx_policy.model.LBIcmpMonitorProfile; import com.vmware.nsx_policy.model.LBMonitorProfileListResult; import com.vmware.nsx_policy.model.LBPool; import com.vmware.nsx_policy.model.LBPoolListResult; import com.vmware.nsx_policy.model.LBPoolMember; import com.vmware.nsx_policy.model.LBService; import com.vmware.nsx_policy.model.LBTcpMonitorProfile; -import com.vmware.nsx_policy.model.LBUdpMonitorProfile; import com.vmware.nsx_policy.model.LBVirtualServer; import com.vmware.nsx_policy.model.LBVirtualServerListResult; import com.vmware.nsx_policy.model.LocaleServicesListResult; @@ -84,6 +88,7 @@ import org.apache.cloudstack.utils.NsxControllerUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.commons.lang3.BooleanUtils; import java.util.ArrayList; import java.util.List; @@ -112,6 +117,7 @@ public class NsxApiClient { protected Logger logger = LogManager.getLogger(getClass()); // Constants + private static final String CLUSTER_STATUS_STABLE = "STABLE"; private static final String TIER_1_RESOURCE_TYPE = "Tier1"; private static final String TIER_1_LOCALE_SERVICE_ID = "default"; private static final String SEGMENT_RESOURCE_TYPE = "Segment"; @@ -123,7 +129,7 @@ public class NsxApiClient { // TODO: Pass as global / zone-level setting? protected static final String NSX_LB_PASSIVE_MONITOR = "/infra/lb-monitor-profiles/default-passive-lb-monitor"; protected static final String TCP_MONITOR_PROFILE = "LBTcpMonitorProfile"; - protected static final String UDP_MONITOR_PROFILE = "LBUdpMonitorProfile"; + protected static final String ICMP_MONITOR_PROFILE = "LBIcmpMonitorProfile"; protected static final String NAT_ID = "USER"; private enum PoolAllocation { ROUTING, LB_SMALL, LB_MEDIUM, LB_LARGE, LB_XLARGE } @@ -199,6 +205,26 @@ public class NsxApiClient { nsxService = apiClient::createStub; } + public boolean isNsxControllerActive() { + try { + Status statusService = (Status) nsxService.apply(Status.class); + ClusterStatus clusterStatus = statusService.get(); + if (clusterStatus == null) { + logger.error("Cannot get NSX Cluster Status"); + return false; + } + ControllerClusterStatus status = clusterStatus.getControlClusterStatus(); + if (status == null) { + logger.error("Cannot get NSX Controller Cluster Status"); + return false; + } + return CLUSTER_STATUS_STABLE.equalsIgnoreCase(status.getStatus()); + } catch (Error error) { + logger.error("Error checking NSX Controller Health: {}", error.getMessage()); + return false; + } + } + public void createTier1NatRule(String tier1GatewayName, String natId, String natRuleId, String action, String translatedIp) { NatRules natRulesService = (NatRules) nsxService.apply(NatRules.class); @@ -435,7 +461,7 @@ public class NsxApiClient { String t1GatewayName = getTier1GatewayName(domainId, accountId, zoneId, networkId, false); deleteLoadBalancer(getLoadBalancerName(t1GatewayName)); } - removeSegment(segmentName); + removeSegment(segmentName, zoneId); DhcpRelayConfigs dhcpRelayConfig = (DhcpRelayConfigs) nsxService.apply(DhcpRelayConfigs.class); String dhcpRelayConfigId = NsxControllerUtils.getNsxDhcpRelayConfigId(zoneId, domainId, accountId, vpcId, networkId); logger.debug(String.format("Removing the DHCP relay config with ID %s", dhcpRelayConfigId)); @@ -448,7 +474,8 @@ public class NsxApiClient { } } - protected void removeSegment(String segmentName) { + + protected void removeSegment(String segmentName, long zoneId) { logger.debug(String.format("Removing the segment with ID %s", segmentName)); Segments segmentService = (Segments) nsxService.apply(Segments.class); String errMsg = String.format("The segment with ID %s is not found, skipping removal", segmentName); @@ -467,15 +494,16 @@ public class NsxApiClient { SegmentPorts segmentPortsService = (SegmentPorts) nsxService.apply(SegmentPorts.class); PolicyGroupMembersListResult segmentPortsList = getSegmentPortList(segmentPortsService, segmentName, enforcementPointPath); Long portCount = segmentPortsList.getResultCount(); - portCount = retrySegmentDeletion(segmentPortsService, portCount, segmentName, enforcementPointPath); - logger.info("Port count: " + portCount); + if (portCount > 0L) { + portCount = retrySegmentDeletion(segmentPortsService, segmentName, enforcementPointPath, zoneId); + } if (portCount == 0L) { logger.debug(String.format("Removing the segment with ID %s", segmentName)); removeGroupForSegment(segmentName); segmentService.delete(segmentName); } else { String msg = String.format("Cannot remove the NSX segment %s because there are still %s port group(s) attached to it", segmentName, portCount); - logger.debug(msg); + logger.error(msg); throw new CloudRuntimeException(msg); } } @@ -485,13 +513,16 @@ public class NsxApiClient { false, null, 50L, false, null); } - private Long retrySegmentDeletion(SegmentPorts segmentPortsService, Long portCount, String segmentName, String enforcementPointPath) { - int retries = 20; + private Long retrySegmentDeletion(SegmentPorts segmentPortsService, String segmentName, String enforcementPointPath, long zoneId) { + int retries = NsxService.NSX_API_FAILURE_RETRIES.valueIn(zoneId); + int waitingSecs = NsxService.NSX_API_FAILURE_INTERVAL.valueIn(zoneId); int count = 1; + Long portCount; do { try { - logger.info("Waiting for all port groups to be unlinked from the segment - Attempt: " + count++ + " Waiting for 5 secs"); - Thread.sleep(5000); + logger.info("Waiting for all port groups to be unlinked from the segment {} - " + + "Attempt: {}. Waiting for {} secs", segmentName, count++, waitingSecs); + Thread.sleep(waitingSecs * 1000L); portCount = getSegmentPortList(segmentPortsService, segmentName, enforcementPointPath).getResultCount(); retries--; } catch (InterruptedException e) { @@ -526,24 +557,37 @@ public class NsxApiClient { } } + protected void deletePortForwardingNatRuleService(String ruleName, String privatePort, String protocol) { + String svcName = getServiceName(ruleName, privatePort, protocol, null, null); + try { + Services services = (Services) nsxService.apply(Services.class); + com.vmware.nsx_policy.model.Service servicePFRule = services.get(svcName); + if (servicePFRule != null && !servicePFRule.getMarkedForDelete() && !BooleanUtils.toBoolean(servicePFRule.getIsDefault())) { + services.delete(svcName); + } + } catch (Error error) { + String msg = String.format("Cannot find service %s associated to rule %s, skipping its deletion: %s", + svcName, ruleName, error.getMessage()); + logger.debug(msg); + } + } + public void deleteNatRule(Network.Service service, String privatePort, String protocol, String networkName, 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 (network: %s)", ruleName, tier1GatewayName, networkName)); - // delete NAT rule - natService.delete(tier1GatewayName, NatId.USER.name(), ruleName); - if (service == Network.Service.PortForwarding) { - String svcName = getServiceName(ruleName, privatePort, protocol, null, null); - // Delete service - Services services = (Services) nsxService.apply(Services.class); - services.delete(svcName); + logger.debug("Deleting NSX NAT rule {} for tier-1 gateway {} (network: {})", ruleName, tier1GatewayName, networkName); + PolicyNatRule natRule = natService.get(tier1GatewayName, NatId.USER.name(), ruleName); + if (natRule != null && !natRule.getMarkedForDelete()) { + logger.debug("Deleting rule {} from Tier 1 Gateway {}", ruleName, tier1GatewayName); + 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, networkName, ae.getErrorMessage()); - logger.error(msg); - throw new CloudRuntimeException(msg); + String msg = String.format("Cannot find NAT rule with name %s: %s, skipping deletion", ruleName, error.getMessage()); + logger.debug(msg); + } + + if (service == Network.Service.PortForwarding) { + deletePortForwardingNatRuleService(ruleName, privatePort, protocol); } } @@ -577,9 +621,14 @@ public class NsxApiClient { try { NatRules natService = (NatRules) nsxService.apply(NatRules.class); PolicyNatRule rule = natService.get(tier1GatewayName, NAT_ID, ruleName); + logger.debug("Rule {} from Tier 1 GW {}: {}", ruleName, tier1GatewayName, + rule == null ? "null" : rule.getId() + " " + rule.getPath()); return !Objects.isNull(rule); } catch (Error error) { - logger.debug(String.format("Found a port forward rule named: %s on NSX", ruleName)); + String msg = String.format("Error checking if port forwarding rule %s exists on Tier 1 Gateway %s: %s", + ruleName, tier1GatewayName, error.getMessage()); + Throwable throwable = error.getCause(); + logger.error(msg, throwable); return false; } } @@ -637,13 +686,10 @@ public class NsxApiClient { .build(); lbActiveMonitor.patch(lbMonitorProfileId, lbTcpMonitorProfile); } else if ("UDP".equals(protocol.toUpperCase(Locale.ROOT))) { - LBUdpMonitorProfile lbUdpMonitorProfile = new LBUdpMonitorProfile.Builder(UDP_MONITOR_PROFILE) + LBIcmpMonitorProfile icmpMonitorProfile = new LBIcmpMonitorProfile.Builder(ICMP_MONITOR_PROFILE) .setDisplayName(lbMonitorProfileId) - .setMonitorPort(Long.parseLong(port)) - .setSend("") - .setReceive("") .build(); - lbActiveMonitor.patch(lbMonitorProfileId, lbUdpMonitorProfile); + lbActiveMonitor.patch(lbMonitorProfileId, icmpMonitorProfile); } LBMonitorProfileListResult listResult = listLBActiveMonitors(lbActiveMonitor); 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 d09049700e5..7673e5a6038 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 @@ -91,6 +91,8 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.NicProfile; import com.cloud.vm.ReservationContext; @@ -98,7 +100,9 @@ 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.NsxAnswer; import org.apache.cloudstack.StartupNsxCommand; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.internallb.ConfigureInternalLoadBalancerElementCmd; import org.apache.cloudstack.api.command.admin.internallb.CreateInternalLoadBalancerElementCmd; import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd; @@ -108,6 +112,8 @@ import org.apache.cloudstack.resource.NsxNetworkRule; import org.apache.cloudstack.resource.NsxOpObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.cloudstack.resourcedetail.FirewallRuleDetailVO; +import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao; import org.springframework.stereotype.Component; import javax.inject.Inject; @@ -121,6 +127,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.LongFunction; +import java.util.stream.Collectors; @Component public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsServiceProvider, VpcProvider, @@ -160,6 +167,8 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns VirtualRouterProviderDao vrProviderDao; @Inject PhysicalNetworkServiceProviderDao pNtwkSvcProviderDao; + @Inject + FirewallRuleDetailsDao firewallRuleDetailsDao; protected Logger logger = LogManager.getLogger(getClass()); @@ -395,10 +404,18 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns Account account = null; boolean forNsx = false; List<PhysicalNetworkVO> physicalNetworks = physicalNetworkDao.listByZoneAndTrafficType(zone.getId(), Networks.TrafficType.Guest); - if (CollectionUtils.isNullOrEmpty(physicalNetworks) || physicalNetworks.size() > 1 ) { - throw new InvalidConfigurationException(String.format("Desired number of physical networks is not present in the zone %s for traffic type %s. ", zone.getName(), Networks.TrafficType.Guest.name())); - } - if (physicalNetworks.get(0).getIsolationMethods().contains("NSX")) { + if (CollectionUtils.isNullOrEmpty(physicalNetworks)) { + String err = String.format("Desired physical network is not present in the zone %s for traffic type %s. ", zone.getName(), Networks.TrafficType.Guest.name()); + logger.error(err); + throw new InvalidConfigurationException(err); + } + List<PhysicalNetworkVO> filteredPhysicalNetworks = physicalNetworks.stream().filter(x -> x.getIsolationMethods().contains("NSX")).collect(Collectors.toList()); + if (CollectionUtils.isNullOrEmpty(filteredPhysicalNetworks)) { + String err = String.format("No physical network with NSX isolation type for traffic type %s is present in the zone %s.", Networks.TrafficType.Guest.name(), zone.getName()); + logger.error(err); + throw new InvalidConfigurationException(err); + } + if (filteredPhysicalNetworks.get(0).getIsolationMethods().contains("NSX")) { account = accountMgr.getAccount(vpc.getAccountId()); forNsx = true; } @@ -527,45 +544,77 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns return false; } + protected synchronized boolean applyPFRulesInternal(Network network, List<PortForwardingRule> rules) { + return Transaction.execute((TransactionCallback<Boolean>) status -> { + boolean result = true; + for (PortForwardingRule rule : rules) { + IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId()); + UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId()); + if (vm == null && rule.getState() != FirewallRule.State.Revoke) { + continue; + } + NsxOpObject nsxObject = getNsxOpObject(network); + String publicPort = getPublicPortRange(rule); + + String privatePort = getPrivatePFPortRange(rule); + + NsxNetworkRule networkRule = new NsxNetworkRule.Builder() + .setDomainId(nsxObject.getDomainId()) + .setAccountId(nsxObject.getAccountId()) + .setZoneId(nsxObject.getZoneId()) + .setNetworkResourceId(nsxObject.getNetworkResourceId()) + .setNetworkResourceName(nsxObject.getNetworkResourceName()) + .setVpcResource(nsxObject.isVpcResource()) + .setVmId(Objects.nonNull(vm) ? vm.getId() : 0) + .setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null) + .setPublicIp(publicIp.getAddress().addr()) + .setPrivatePort(privatePort) + .setPublicPort(publicPort) + .setRuleId(rule.getId()) + .setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT)) + .build(); + FirewallRuleDetailVO ruleDetail = firewallRuleDetailsDao.findDetail(rule.getId(), ApiConstants.FOR_NSX); + if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) { + if ((ruleDetail == null && FirewallRule.State.Add == rule.getState()) || (ruleDetail != null && !ruleDetail.getValue().equalsIgnoreCase("true"))) { + logger.debug("Creating port forwarding rule on NSX for VM {} to ports {} - {}", + vm.getUuid(), rule.getDestinationPortStart(), rule.getDestinationPortEnd()); + NsxAnswer answer = nsxService.createPortForwardRule(networkRule); + boolean pfRuleResult = answer.getResult(); + if (pfRuleResult && !answer.isObjectExistent()) { + logger.debug("Port forwarding rule {} created on NSX, adding detail on firewall rules details", rule.getId()); + if (ruleDetail == null && FirewallRule.State.Add == rule.getState()) { + logger.debug("Adding new firewall detail for rule {}", rule.getId()); + firewallRuleDetailsDao.addDetail(rule.getId(), ApiConstants.FOR_NSX, "true", false); + } else { + logger.debug("Updating firewall detail for rule {}", rule.getId()); + ruleDetail.setValue("true"); + firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail); + } + } + result &= pfRuleResult; + } + } else if (rule.getState() == FirewallRule.State.Revoke) { + if (ruleDetail == null || (ruleDetail != null && ruleDetail.getValue().equalsIgnoreCase("true"))) { + boolean pfRuleResult = nsxService.deletePortForwardRule(networkRule); + if (pfRuleResult && ruleDetail != null) { + logger.debug("Updating firewall rule detail {} for rule {}, set to false", ruleDetail.getId(), rule.getId()); + ruleDetail.setValue("false"); + firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail); + } + result &= pfRuleResult; + } + } + } + return result; + }); + } + @Override public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException { if (!canHandle(network, Network.Service.PortForwarding)) { return false; } - boolean result = true; - for (PortForwardingRule rule : rules) { - IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId()); - UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId()); - if (vm == null && rule.getState() != FirewallRule.State.Revoke) { - continue; - } - NsxOpObject nsxObject = getNsxOpObject(network); - String publicPort = getPublicPortRange(rule); - - String privatePort = getPrivatePFPortRange(rule); - - NsxNetworkRule networkRule = new NsxNetworkRule.Builder() - .setDomainId(nsxObject.getDomainId()) - .setAccountId(nsxObject.getAccountId()) - .setZoneId(nsxObject.getZoneId()) - .setNetworkResourceId(nsxObject.getNetworkResourceId()) - .setNetworkResourceName(nsxObject.getNetworkResourceName()) - .setVpcResource(nsxObject.isVpcResource()) - .setVmId(Objects.nonNull(vm) ? vm.getId() : 0) - .setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null) - .setPublicIp(publicIp.getAddress().addr()) - .setPrivatePort(privatePort) - .setPublicPort(publicPort) - .setRuleId(rule.getId()) - .setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT)) - .build(); - if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) { - result &= nsxService.createPortForwardRule(networkRule); - } else if (rule.getState() == FirewallRule.State.Revoke) { - result &= nsxService.deletePortForwardRule(networkRule); - } - } - return result; + return applyPFRulesInternal(network, rules); } public Pair<VpcVO, NetworkVO> getVpcOrNetwork(Long vpcId, long networkId) { 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 f8880826a3f..139d8a55e59 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,7 +19,6 @@ package org.apache.cloudstack.service; import com.cloud.network.IpAddress; import com.cloud.network.Network; import com.cloud.network.nsx.NsxService; -import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcVO; @@ -37,6 +36,8 @@ import org.apache.cloudstack.agent.api.DeleteNsxLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand; import org.apache.cloudstack.agent.api.DeleteNsxNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxTier1GatewayCommand; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.resource.NsxNetworkRule; import org.apache.cloudstack.utils.NsxControllerUtils; import org.apache.cloudstack.utils.NsxHelper; @@ -47,13 +48,11 @@ import javax.inject.Inject; import java.util.List; import java.util.Objects; -public class NsxServiceImpl implements NsxService { +public class NsxServiceImpl implements NsxService, Configurable { @Inject NsxControllerUtils nsxControllerUtils; @Inject VpcDao vpcDao; - @Inject - NetworkDao networkDao; protected Logger logger = LogManager.getLogger(getClass()); @@ -139,14 +138,13 @@ public class NsxServiceImpl implements NsxService { return result.getResult(); } - public boolean createPortForwardRule(NsxNetworkRule netRule) { + public NsxAnswer createPortForwardRule(NsxNetworkRule netRule) { // TODO: if port doesn't exist in default list of services, create a service entry CreateNsxPortForwardRuleCommand createPortForwardCmd = new CreateNsxPortForwardRuleCommand(netRule.getDomainId(), netRule.getAccountId(), netRule.getZoneId(), netRule.getNetworkResourceId(), netRule.getNetworkResourceName(), netRule.isVpcResource(), netRule.getVmId(), netRule.getRuleId(), netRule.getPublicIp(), netRule.getVmIp(), netRule.getPublicPort(), netRule.getPrivatePort(), netRule.getProtocol()); - NsxAnswer result = nsxControllerUtils.sendNsxCommand(createPortForwardCmd, netRule.getZoneId()); - return result.getResult(); + return nsxControllerUtils.sendNsxCommand(createPortForwardCmd, netRule.getZoneId()); } public boolean deletePortForwardRule(NsxNetworkRule netRule) { @@ -190,4 +188,16 @@ public class NsxServiceImpl implements NsxService { NsxAnswer result = nsxControllerUtils.sendNsxCommand(command, network.getDataCenterId()); return result.getResult(); } + + @Override + public String getConfigComponentName() { + return NsxApiClient.class.getSimpleName(); + } + + @Override + public ConfigKey<?>[] getConfigKeys() { + return new ConfigKey<?>[] { + NSX_API_FAILURE_RETRIES, NSX_API_FAILURE_INTERVAL + }; + } } 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 e064a6b6291..f44364f34c8 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 @@ -124,6 +124,9 @@ public class NsxControllerUtils { } public static String getActiveMonitorProfileName(String lbServerPoolName, String port, String protocol) { + if (protocol.equalsIgnoreCase("udp")) { + protocol = "ICMP"; + } return lbServerPoolName + "-" + protocol + "-" + port + "-AM"; } diff --git a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java index a0fde08ade8..fbcca86a28b 100644 --- a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java +++ b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.service; import com.cloud.network.Network; +import com.vmware.nsx.cluster.Status; +import com.vmware.nsx.model.ClusterStatus; +import com.vmware.nsx.model.ControllerClusterStatus; import com.vmware.nsx_policy.infra.domains.Groups; import com.vmware.nsx_policy.model.Group; import com.vmware.nsx_policy.model.PathExpression; @@ -93,4 +96,16 @@ public class NsxApiClientTest { Assert.assertEquals(List.of(String.format("%s/%s", NsxApiClient.GROUPS_PATH_PREFIX, segmentName)), sourceGroups); Assert.assertEquals(List.of("ANY"), destinationGroups); } + + @Test + public void testIsNsxControllerActive() { + Status statusService = Mockito.mock(Status.class); + Mockito.when(nsxService.apply(Status.class)).thenReturn(statusService); + ClusterStatus clusterStatus = Mockito.mock(ClusterStatus.class); + ControllerClusterStatus status = Mockito.mock(ControllerClusterStatus.class); + Mockito.when(status.getStatus()).thenReturn("stable"); + Mockito.when(statusService.get()).thenReturn(clusterStatus); + Mockito.when(clusterStatus.getControlClusterStatus()).thenReturn(status); + Assert.assertTrue(client.isNsxControllerActive()); + } } diff --git a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java index ff7fa5427ee..7c44a7324fb 100644 --- a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java +++ b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java @@ -61,6 +61,7 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.resource.NsxNetworkRule; +import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -124,6 +125,8 @@ public class NsxElementTest { private VpcOfferingServiceMapDao vpcOfferingServiceMapDao; @Mock LoadBalancerVMMapDao lbVmMapDao; + @Mock + FirewallRuleDetailsDao firewallRuleDetailsDao; NsxElement nsxElement; ReservationContext reservationContext; @@ -148,6 +151,7 @@ public class NsxElementTest { nsxElement.vmInstanceDao = vmInstanceDao; nsxElement.vpcDao = vpcDao; nsxElement.lbVmMapDao = lbVmMapDao; + nsxElement.firewallRuleDetailsDao = firewallRuleDetailsDao; Field field = ApiDBUtils.class.getDeclaredField("s_ipAddressDao"); field.setAccessible(true); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 7926deb9065..25cbd10de0a 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -8002,7 +8002,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, VM_SERVICE_OFFERING_MAX_CPU_CORES, VM_SERVICE_OFFERING_MAX_RAM_SIZE, MIGRATE_VM_ACROSS_CLUSTERS, ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN, ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN, - ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, DELETE_QUERY_BATCH_SIZE + ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs }; } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 126800e0af3..d8d7eb20057 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -1832,7 +1832,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C private void validateNetworkCreationSupported(long zoneId, String zoneName, GuestType guestType) { NsxProviderVO nsxProviderVO = nsxProviderDao.findByZoneId(zoneId); - if (Objects.nonNull(nsxProviderVO) && List.of(GuestType.L2, GuestType.Shared).contains(guestType)) { + if (Objects.nonNull(nsxProviderVO) && GuestType.L2.equals(guestType)) { throw new InvalidParameterValueException( String.format("Creation of %s networks is not supported in NSX enabled zone %s", guestType.name(), zoneName) ); diff --git a/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java index fa2f2aba8ff..13e118349b0 100644 --- a/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java @@ -134,6 +134,7 @@ public class VpcNetworkHelperImpl extends NetworkHelperImpl { final PublicIp publicIp = PublicIp.createFromAddrAndVlan(ip, _vlanDao.findById(ip.getVlanId())); if ((ip.getState() == IpAddress.State.Allocated || ip.getState() == IpAddress.State.Allocating) && vpcMgr.isIpAllocatedToVpc(ip) + && Objects.nonNull(publicIp.getVlanTag()) && !publicVlans.contains(publicIp.getVlanTag())) { logger.debug("Allocating nic for router in vlan " + publicIp.getVlanTag()); final NicProfile publicNic = new NicProfile(); diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 91fceb2cbb3..d21e2bf8ca4 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -43,6 +43,9 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.Vlan; +import com.cloud.network.dao.NsxProviderDao; +import com.cloud.network.element.NsxProviderVO; import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.dc.ASNumberVO; import com.cloud.bgp.BGPService; @@ -282,6 +285,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Inject private VpcPrivateGatewayTransactionCallable vpcTxCallable; @Inject + private NsxProviderDao nsxProviderDao; + @Inject RoutedIpv4Manager routedIpv4Manager; private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpcChecker")); @@ -3169,7 +3174,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis logger.debug("Associating ip " + ipToAssoc + " to vpc " + vpc); - final boolean isSourceNatFinal = isSrcNatIpRequired(vpc.getVpcOfferingId()) && getExistingSourceNatInVpc(vpc.getAccountId(), vpcId) == null; + final boolean isSourceNatFinal = isSrcNatIpRequired(vpc.getVpcOfferingId()) && getExistingSourceNatInVpc(vpc.getAccountId(), vpcId, false) == null; Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) { @@ -3266,7 +3271,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis return guestNetwork; } - protected IPAddressVO getExistingSourceNatInVpc(final long ownerId, final long vpcId) { + protected IPAddressVO getExistingSourceNatInVpc(final long ownerId, final long vpcId, final boolean forNsx) { final List<IPAddressVO> addrs = listPublicIpsAssignedToVpc(ownerId, true, vpcId); @@ -3277,8 +3282,16 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis // Account already has ip addresses for (final IPAddressVO addr : addrs) { if (addr.isSourceNat()) { - sourceNatIp = addr; - return sourceNatIp; + if (!forNsx) { + sourceNatIp = addr; + } else { + if (addr.isForSystemVms()) { + sourceNatIp = addr; + } + } + if (Objects.nonNull(sourceNatIp)) { + return sourceNatIp; + } } } @@ -3302,17 +3315,23 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis } @Override - public PublicIp assignSourceNatIpAddressToVpc(final Account owner, final Vpc vpc) throws InsufficientAddressCapacityException, ConcurrentOperationException { + public PublicIp assignSourceNatIpAddressToVpc(final Account owner, final Vpc vpc, final Long podId) throws InsufficientAddressCapacityException, ConcurrentOperationException { final long dcId = vpc.getZoneId(); + NsxProviderVO nsxProvider = nsxProviderDao.findByZoneId(dcId); + boolean forNsx = nsxProvider != null; - final IPAddressVO sourceNatIp = getExistingSourceNatInVpc(owner.getId(), vpc.getId()); + final IPAddressVO sourceNatIp = getExistingSourceNatInVpc(owner.getId(), vpc.getId(), forNsx); PublicIp ipToReturn = null; if (sourceNatIp != null) { ipToReturn = PublicIp.createFromAddrAndVlan(sourceNatIp, _vlanDao.findById(sourceNatIp.getVlanId())); } else { - ipToReturn = _ipAddrMgr.assignDedicateIpAddress(owner, null, vpc.getId(), dcId, true); + if (forNsx) { + ipToReturn = _ipAddrMgr.assignPublicIpAddress(dcId, podId, owner, Vlan.VlanType.VirtualNetwork, null, null, false, true); + } else { + ipToReturn = _ipAddrMgr.assignDedicateIpAddress(owner, null, vpc.getId(), dcId, true); + } } return ipToReturn; diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java index aa44f29efcd..405575c65b1 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.Objects; import com.cloud.dc.DataCenter; -import com.cloud.dc.Vlan; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.element.NsxProviderVO; @@ -132,10 +131,9 @@ public class VpcRouterDeploymentDefinition extends RouterDeploymentDefinition { if (isPublicNetwork) { if (Objects.isNull(nsxProvider)) { - sourceNatIp = vpcMgr.assignSourceNatIpAddressToVpc(owner, vpc); + sourceNatIp = vpcMgr.assignSourceNatIpAddressToVpc(owner, vpc, null); } else { - // NSX deploys VRs with Public NIC != to the source NAT, the source NAT IP is on the NSX Public range - sourceNatIp = ipAddrMgr.assignPublicIpAddress(zoneId, getPodId(), owner, Vlan.VlanType.VirtualNetwork, null, null, false, true); + sourceNatIp = vpcMgr.assignSourceNatIpAddressToVpc(owner, vpc, getPodId()); if (vpc != null) { IPAddressVO routerPublicIp = ipAddressDao.findByIp(sourceNatIp.getAddress().toString()); routerPublicIp.setVpcId(vpc.getId()); diff --git a/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java b/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java index a355ad21f2b..d3ab6d8904b 100644 --- a/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java +++ b/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java @@ -269,7 +269,7 @@ public class VpcRouterDeploymentDefinitionTest extends RouterDeploymentDefinitio public void testFindSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { // Prepare final PublicIp publicIp = mock(PublicIp.class); - when(vpcMgr.assignSourceNatIpAddressToVpc(mockOwner, mockVpc)).thenReturn(publicIp); + when(vpcMgr.assignSourceNatIpAddressToVpc(mockOwner, mockVpc, null)).thenReturn(publicIp); deployment.isPublicNetwork = true; // Execute diff --git a/ui/src/views/network/CreateSharedNetworkForm.vue b/ui/src/views/network/CreateSharedNetworkForm.vue index 4fd42370946..b0c7203f833 100644 --- a/ui/src/views/network/CreateSharedNetworkForm.vue +++ b/ui/src/views/network/CreateSharedNetworkForm.vue @@ -18,14 +18,7 @@ <template> <a-spin :spinning="loading"> <div class="form-layout" v-ctrl-enter="handleSubmit"> - <div v-if="isNsxEnabled"> - <a-alert type="warning"> - <template #message> - <span v-html="$t('message.shared.network.unsupported.for.nsx')" /> - </template> - </a-alert> - </div> - <div v-else class="form"> + <div class="form"> <a-form :ref="formRef" :model="form" @@ -725,7 +718,7 @@ export default { var trafficTypes = json.listtraffictypesresponse.traffictype if (this.arrayHasItems(trafficTypes)) { for (const type of trafficTypes) { - if (type.traffictype === 'Guest') { + if (type.traffictype === 'Guest' && physicalNetwork.isolationmethods !== 'NSX') { this.formPhysicalNetworks.push(physicalNetwork) break }