Skip to content

Commit f228c7a

Browse files
nvazquezPearl1594
authored andcommitted
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
1 parent 3579806 commit f228c7a

File tree

7 files changed

+140
-59
lines changed

7 files changed

+140
-59
lines changed

engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,8 @@ public long getResourceId() {
7979
public boolean isDisplay() {
8080
return display;
8181
}
82+
83+
public void setValue(String value) {
84+
this.value = value;
85+
}
8286
}

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import com.cloud.agent.api.Command;
2121

2222
public class NsxAnswer extends Answer {
23+
24+
private boolean objectExists;
25+
2326
public NsxAnswer(final Command command, final boolean success, final String details) {
2427
super(command, success, details);
2528
}
@@ -28,4 +31,11 @@ public NsxAnswer(final Command command, final Exception e) {
2831
super(command, e);
2932
}
3033

34+
public boolean isObjectExistent() {
35+
return objectExists;
36+
}
37+
38+
public void setObjectExists(boolean objectExisted) {
39+
this.objectExists = objectExisted;
40+
}
3141
}

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,16 +385,21 @@ private NsxAnswer executeRequest(CreateNsxPortForwardRuleCommand cmd) {
385385
cmd.getNetworkResourceId(), cmd.isResourceVpc());
386386
try {
387387
String privatePort = cmd.getPrivatePort();
388-
String service = privatePort.contains("-") ? nsxApiClient.getServicePath(ruleName, privatePort, cmd.getProtocol(), null, null) :
389-
nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol(), null, null);
388+
LOGGER.debug(String.format("Checking if rule %s exists on Tier 1 Gateway: %s", ruleName, tier1GatewayName));
390389
if (nsxApiClient.doesPfRuleExist(ruleName, tier1GatewayName)) {
391-
logger.debug(String.format("Port forward rule for port: %s exits on NSX, not adding it again", privatePort));
392-
return new NsxAnswer(cmd, true, null);
390+
String msg = String.format("Port forward rule for port: %s (%s) exits on NSX, not adding it again", ruleName, privatePort);
391+
LOGGER.debug(msg);
392+
NsxAnswer answer = new NsxAnswer(cmd, true, msg);
393+
answer.setObjectExists(true);
394+
return answer;
393395
}
396+
String service = privatePort.contains("-") ? nsxApiClient.getServicePath(ruleName, privatePort, cmd.getProtocol(), null, null) :
397+
nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol(), null, null);
394398
nsxApiClient.createPortForwardingRule(ruleName, tier1GatewayName, cmd.getNetworkResourceName(), cmd.getPublicIp(),
395399
cmd.getVmIp(), cmd.getPublicPort(), service);
396400
} catch (Exception e) {
397-
logger.error(String.format("Failed to add NSX port forward rule %s for network: %s", ruleName, cmd.getNetworkResourceName()));
401+
String msg = String.format("Failed to add NSX port forward rule %s for network: %s", ruleName, cmd.getNetworkResourceName());
402+
LOGGER.error(msg, e);
398403
return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage()));
399404
}
400405
return new NsxAnswer(cmd, true, null);
@@ -415,8 +420,9 @@ private NsxAnswer executeRequest(DeleteNsxNatRuleCommand cmd) {
415420
nsxApiClient.deleteNatRule(cmd.getService(), cmd.getPrivatePort(), cmd.getProtocol(),
416421
cmd.getNetworkResourceName(), tier1GatewayName, ruleName);
417422
} catch (Exception e) {
418-
logger.error(String.format("Failed to add NSX static NAT rule %s for network: %s", ruleName, cmd.getNetworkResourceName()));
419-
return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage()));
423+
String msg = String.format("Failed to delete NSX rule %s for network %s: due to %s", ruleName, cmd.getNetworkResourceName(), e.getMessage());
424+
LOGGER.error(msg, e);
425+
return new NsxAnswer(cmd, new CloudRuntimeException(msg));
420426
}
421427
return new NsxAnswer(cmd, true, null);
422428
}

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.apache.commons.collections.CollectionUtils;
8585
import org.apache.logging.log4j.LogManager;
8686
import org.apache.logging.log4j.Logger;
87+
import org.apache.commons.lang3.BooleanUtils;
8788

8889
import java.util.ArrayList;
8990
import java.util.List;
@@ -526,24 +527,37 @@ public void createStaticNatRule(String vpcName, String tier1GatewayName,
526527
}
527528
}
528529

530+
protected void deletePortForwardingNatRuleService(String ruleName, String privatePort, String protocol) {
531+
String svcName = getServiceName(ruleName, privatePort, protocol, null, null);
532+
try {
533+
Services services = (Services) nsxService.apply(Services.class);
534+
com.vmware.nsx_policy.model.Service servicePFRule = services.get(svcName);
535+
if (servicePFRule != null && !servicePFRule.getMarkedForDelete() && !BooleanUtils.toBoolean(servicePFRule.getIsDefault())) {
536+
services.delete(svcName);
537+
}
538+
} catch (Error error) {
539+
String msg = String.format("Cannot find service %s associated to rule %s, skipping its deletion: %s",
540+
svcName, ruleName, error.getMessage());
541+
logger.debug(msg);
542+
}
543+
}
544+
529545
public void deleteNatRule(Network.Service service, String privatePort, String protocol, String networkName, String tier1GatewayName, String ruleName) {
530546
try {
531547
NatRules natService = (NatRules) nsxService.apply(NatRules.class);
532-
logger.debug(String.format("Deleting NSX static NAT rule %s for tier-1 gateway %s (network: %s)", ruleName, tier1GatewayName, networkName));
533-
// delete NAT rule
534-
natService.delete(tier1GatewayName, NatId.USER.name(), ruleName);
535-
if (service == Network.Service.PortForwarding) {
536-
String svcName = getServiceName(ruleName, privatePort, protocol, null, null);
537-
// Delete service
538-
Services services = (Services) nsxService.apply(Services.class);
539-
services.delete(svcName);
548+
logger.debug(String.format("Deleting NSX NAT rule %s for tier-1 gateway %s (network: %s)", ruleName, tier1GatewayName, networkName));
549+
PolicyNatRule natRule = natService.get(tier1GatewayName, NatId.USER.name(), ruleName);
550+
if (natRule != null && !natRule.getMarkedForDelete()) {
551+
logger.debug(String.format("Deleting rule %s from Tier 1 Gateway %s", ruleName, tier1GatewayName));
552+
natService.delete(tier1GatewayName, NatId.USER.name(), ruleName);
540553
}
541554
} catch (Error error) {
542-
ApiError ae = error.getData()._convertTo(ApiError.class);
543-
String msg = String.format("Failed to delete NSX Static NAT rule %s for tier-1 gateway %s (VPC: %s), due to %s",
544-
ruleName, tier1GatewayName, networkName, ae.getErrorMessage());
545-
logger.error(msg);
546-
throw new CloudRuntimeException(msg);
555+
String msg = String.format("Cannot find NAT rule with name %s: %s, skipping deletion", ruleName, error.getMessage());
556+
logger.debug(msg);
557+
}
558+
559+
if (service == Network.Service.PortForwarding) {
560+
deletePortForwardingNatRuleService(ruleName, privatePort, protocol);
547561
}
548562
}
549563

@@ -577,9 +591,14 @@ public boolean doesPfRuleExist(String ruleName, String tier1GatewayName) {
577591
try {
578592
NatRules natService = (NatRules) nsxService.apply(NatRules.class);
579593
PolicyNatRule rule = natService.get(tier1GatewayName, NAT_ID, ruleName);
594+
logger.debug(String.format("Rule %s from Tier 1 GW %s: %s", ruleName, tier1GatewayName,
595+
rule == null ? "null" : rule.getId() + " " + rule.getPath()));
580596
return !Objects.isNull(rule);
581597
} catch (Error error) {
582-
logger.debug(String.format("Found a port forward rule named: %s on NSX", ruleName));
598+
String msg = String.format("Error checking if port forwarding rule %s exists on Tier 1 Gateway %s: %s",
599+
ruleName, tier1GatewayName, error.getMessage());
600+
Throwable throwable = error.getCause();
601+
logger.error(msg, throwable);
583602
return false;
584603
}
585604
}

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,18 @@
9191
import com.cloud.utils.component.AdapterBase;
9292
import com.cloud.utils.db.QueryBuilder;
9393
import com.cloud.utils.db.SearchCriteria;
94+
import com.cloud.utils.db.Transaction;
95+
import com.cloud.utils.db.TransactionCallback;
9496
import com.cloud.utils.exception.CloudRuntimeException;
9597
import com.cloud.vm.NicProfile;
9698
import com.cloud.vm.ReservationContext;
9799
import com.cloud.vm.VMInstanceVO;
98100
import com.cloud.vm.VirtualMachineProfile;
99101
import com.cloud.vm.dao.VMInstanceDao;
100102
import net.sf.ehcache.config.InvalidConfigurationException;
103+
import org.apache.cloudstack.NsxAnswer;
101104
import org.apache.cloudstack.StartupNsxCommand;
105+
import org.apache.cloudstack.api.ApiConstants;
102106
import org.apache.cloudstack.api.command.admin.internallb.ConfigureInternalLoadBalancerElementCmd;
103107
import org.apache.cloudstack.api.command.admin.internallb.CreateInternalLoadBalancerElementCmd;
104108
import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd;
@@ -108,6 +112,8 @@
108112
import org.apache.cloudstack.resource.NsxOpObject;
109113
import org.apache.logging.log4j.LogManager;
110114
import org.apache.logging.log4j.Logger;
115+
import org.apache.cloudstack.resourcedetail.FirewallRuleDetailVO;
116+
import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao;
111117
import org.springframework.stereotype.Component;
112118

113119
import javax.inject.Inject;
@@ -160,6 +166,8 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns
160166
VirtualRouterProviderDao vrProviderDao;
161167
@Inject
162168
PhysicalNetworkServiceProviderDao pNtwkSvcProviderDao;
169+
@Inject
170+
FirewallRuleDetailsDao firewallRuleDetailsDao;
163171

164172
protected Logger logger = LogManager.getLogger(getClass());
165173

@@ -527,45 +535,77 @@ public boolean applyStaticNats(Network config, List<? extends StaticNat> rules)
527535
return false;
528536
}
529537

538+
protected synchronized boolean applyPFRulesInternal(Network network, List<PortForwardingRule> rules) {
539+
return Transaction.execute((TransactionCallback<Boolean>) status -> {
540+
boolean result = true;
541+
for (PortForwardingRule rule : rules) {
542+
IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId());
543+
UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId());
544+
if (vm == null && rule.getState() != FirewallRule.State.Revoke) {
545+
continue;
546+
}
547+
NsxOpObject nsxObject = getNsxOpObject(network);
548+
String publicPort = getPublicPortRange(rule);
549+
550+
String privatePort = getPrivatePFPortRange(rule);
551+
552+
NsxNetworkRule networkRule = new NsxNetworkRule.Builder()
553+
.setDomainId(nsxObject.getDomainId())
554+
.setAccountId(nsxObject.getAccountId())
555+
.setZoneId(nsxObject.getZoneId())
556+
.setNetworkResourceId(nsxObject.getNetworkResourceId())
557+
.setNetworkResourceName(nsxObject.getNetworkResourceName())
558+
.setVpcResource(nsxObject.isVpcResource())
559+
.setVmId(Objects.nonNull(vm) ? vm.getId() : 0)
560+
.setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null)
561+
.setPublicIp(publicIp.getAddress().addr())
562+
.setPrivatePort(privatePort)
563+
.setPublicPort(publicPort)
564+
.setRuleId(rule.getId())
565+
.setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT))
566+
.build();
567+
FirewallRuleDetailVO ruleDetail = firewallRuleDetailsDao.findDetail(rule.getId(), ApiConstants.FOR_NSX);
568+
if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) {
569+
if ((ruleDetail == null && FirewallRule.State.Add == rule.getState()) || (ruleDetail != null && !ruleDetail.getValue().equalsIgnoreCase("true"))) {
570+
LOGGER.debug(String.format("Creating port forwarding rule on NSX for VM %s to ports %s - %s",
571+
vm.getUuid(), rule.getDestinationPortStart(), rule.getDestinationPortEnd()));
572+
NsxAnswer answer = nsxService.createPortForwardRule(networkRule);
573+
boolean pfRuleResult = answer.getResult();
574+
if (pfRuleResult && !answer.isObjectExistent()) {
575+
LOGGER.debug(String.format("Port forwarding rule %s created on NSX, adding detail on firewall rules details", rule.getId()));
576+
if (ruleDetail == null && FirewallRule.State.Add == rule.getState()) {
577+
LOGGER.debug(String.format("Adding new firewall detail for rule %s", rule.getId()));
578+
firewallRuleDetailsDao.addDetail(rule.getId(), ApiConstants.FOR_NSX, "true", false);
579+
} else {
580+
LOGGER.debug(String.format("Updating firewall detail for rule %s", rule.getId()));
581+
ruleDetail.setValue("true");
582+
firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
583+
}
584+
}
585+
result &= pfRuleResult;
586+
}
587+
} else if (rule.getState() == FirewallRule.State.Revoke) {
588+
if (ruleDetail != null && ruleDetail.getValue().equalsIgnoreCase("true")) {
589+
boolean pfRuleResult = nsxService.deletePortForwardRule(networkRule);
590+
if (pfRuleResult) {
591+
LOGGER.debug(String.format("Updating firewall rule detail %s for rule %s, set to false", ruleDetail.getId(), rule.getId()));
592+
ruleDetail.setValue("false");
593+
firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
594+
}
595+
result &= pfRuleResult;
596+
}
597+
}
598+
}
599+
return result;
600+
});
601+
}
602+
530603
@Override
531604
public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException {
532605
if (!canHandle(network, Network.Service.PortForwarding)) {
533606
return false;
534607
}
535-
boolean result = true;
536-
for (PortForwardingRule rule : rules) {
537-
IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId());
538-
UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId());
539-
if (vm == null && rule.getState() != FirewallRule.State.Revoke) {
540-
continue;
541-
}
542-
NsxOpObject nsxObject = getNsxOpObject(network);
543-
String publicPort = getPublicPortRange(rule);
544-
545-
String privatePort = getPrivatePFPortRange(rule);
546-
547-
NsxNetworkRule networkRule = new NsxNetworkRule.Builder()
548-
.setDomainId(nsxObject.getDomainId())
549-
.setAccountId(nsxObject.getAccountId())
550-
.setZoneId(nsxObject.getZoneId())
551-
.setNetworkResourceId(nsxObject.getNetworkResourceId())
552-
.setNetworkResourceName(nsxObject.getNetworkResourceName())
553-
.setVpcResource(nsxObject.isVpcResource())
554-
.setVmId(Objects.nonNull(vm) ? vm.getId() : 0)
555-
.setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null)
556-
.setPublicIp(publicIp.getAddress().addr())
557-
.setPrivatePort(privatePort)
558-
.setPublicPort(publicPort)
559-
.setRuleId(rule.getId())
560-
.setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT))
561-
.build();
562-
if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) {
563-
result &= nsxService.createPortForwardRule(networkRule);
564-
} else if (rule.getState() == FirewallRule.State.Revoke) {
565-
result &= nsxService.deletePortForwardRule(networkRule);
566-
}
567-
}
568-
return result;
608+
return applyPFRulesInternal(network, rules);
569609
}
570610

571611
public Pair<VpcVO, NetworkVO> getVpcOrNetwork(Long vpcId, long networkId) {

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,13 @@ public boolean deleteStaticNatRule(long zoneId, long domainId, long accountId, L
139139
return result.getResult();
140140
}
141141

142-
public boolean createPortForwardRule(NsxNetworkRule netRule) {
142+
public NsxAnswer createPortForwardRule(NsxNetworkRule netRule) {
143143
// TODO: if port doesn't exist in default list of services, create a service entry
144144
CreateNsxPortForwardRuleCommand createPortForwardCmd = new CreateNsxPortForwardRuleCommand(netRule.getDomainId(),
145145
netRule.getAccountId(), netRule.getZoneId(), netRule.getNetworkResourceId(),
146146
netRule.getNetworkResourceName(), netRule.isVpcResource(), netRule.getVmId(), netRule.getRuleId(),
147147
netRule.getPublicIp(), netRule.getVmIp(), netRule.getPublicPort(), netRule.getPrivatePort(), netRule.getProtocol());
148-
NsxAnswer result = nsxControllerUtils.sendNsxCommand(createPortForwardCmd, netRule.getZoneId());
149-
return result.getResult();
148+
return nsxControllerUtils.sendNsxCommand(createPortForwardCmd, netRule.getZoneId());
150149
}
151150

152151
public boolean deletePortForwardRule(NsxNetworkRule netRule) {

plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import com.cloud.vm.dao.VMInstanceDao;
6262
import org.apache.cloudstack.acl.ControlledEntity;
6363
import org.apache.cloudstack.resource.NsxNetworkRule;
64+
import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao;
6465
import org.junit.Assert;
6566
import org.junit.Before;
6667
import org.junit.Test;
@@ -124,6 +125,8 @@ public class NsxElementTest {
124125
private VpcOfferingServiceMapDao vpcOfferingServiceMapDao;
125126
@Mock
126127
LoadBalancerVMMapDao lbVmMapDao;
128+
@Mock
129+
FirewallRuleDetailsDao firewallRuleDetailsDao;
127130

128131
NsxElement nsxElement;
129132
ReservationContext reservationContext;
@@ -148,6 +151,7 @@ public void setup() throws NoSuchFieldException, IllegalAccessException {
148151
nsxElement.vmInstanceDao = vmInstanceDao;
149152
nsxElement.vpcDao = vpcDao;
150153
nsxElement.lbVmMapDao = lbVmMapDao;
154+
nsxElement.firewallRuleDetailsDao = firewallRuleDetailsDao;
151155

152156
Field field = ApiDBUtils.class.getDeclaredField("s_ipAddressDao");
153157
field.setAccessible(true);
@@ -279,7 +283,6 @@ public void testApplyPFRules_delete() throws ResourceUnavailableException {
279283
IPAddressVO ipAddress = new IPAddressVO(new Ip("10.1.13.10"), 1L, 1L, 1L,false);
280284
when(ApiDBUtils.findIpAddressById(anyLong())).thenReturn(ipAddress);
281285
when(nsxElement.canHandle(networkVO, service)).thenReturn(true);
282-
when(nsxService.deletePortForwardRule(any(NsxNetworkRule.class))).thenReturn(true);
283286
assertTrue(nsxElement.applyPFRules(networkVO, List.of(rule)));
284287
}
285288

0 commit comments

Comments
 (0)