Skip to content

Commit a4be3eb

Browse files
nvazquezPearl1594
andauthored
[NSX] Add SNAT support (#8100)
* In progress add source NAT * Fix after merge * Fix tests * Fix NPE on isolated network deletion * Reserve source NAT IP when its not passed for NSX VPC * Create source NAT rule on VR NIC allocation * Fix update VPC and remove VPC to update and remove SNAT rule * Fix packaging * Address review comment * Fix build * fix build - unused import * Add defensive checks * Add missing design to NSX public guru --------- Co-authored-by: Pearl Dsilva <pearl1594@gmail.com>
1 parent 72bdc38 commit a4be3eb

File tree

20 files changed

+417
-19
lines changed

20 files changed

+417
-19
lines changed

api/src/main/java/com/cloud/network/NetworkService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020
import java.util.Map;
2121

22+
import com.cloud.dc.DataCenter;
2223
import org.apache.cloudstack.api.command.admin.address.ReleasePodIpCmdByAdmin;
2324
import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd;
2425
import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd;
@@ -85,6 +86,8 @@ IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displ
8586

8687
IpAddress reserveIpAddress(Account account, Boolean displayIp, Long ipAddressId) throws ResourceAllocationException;
8788

89+
IpAddress reserveIpAddressWithVlanDetail(Account account, DataCenter zone, Boolean displayIp, String vlanDetailKey) throws ResourceAllocationException;
90+
8891
boolean releaseReservedIpAddress(long ipAddressId) throws InsufficientAddressCapacityException;
8992

9093
boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException;

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import javax.inject.Inject;
4040
import javax.naming.ConfigurationException;
4141

42+
import com.cloud.dc.VlanDetailsVO;
43+
import com.cloud.dc.dao.VlanDetailsDao;
4244
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
4345
import org.apache.cloudstack.annotation.AnnotationService;
4446
import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -57,6 +59,7 @@
5759
import org.apache.cloudstack.network.dao.NetworkPermissionDao;
5860
import org.apache.commons.collections.CollectionUtils;
5961
import org.apache.commons.lang3.BooleanUtils;
62+
import org.apache.commons.lang3.ObjectUtils;
6063
import org.apache.commons.lang3.StringUtils;
6164
import org.apache.log4j.Logger;
6265

@@ -340,6 +343,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
340343
Ipv6Service ipv6Service;
341344
@Inject
342345
RouterNetworkDao routerNetworkDao;
346+
@Inject
347+
private VlanDetailsDao vlanDetailsDao;
343348

344349
List<NetworkGuru> networkGurus;
345350

@@ -1029,6 +1034,12 @@ public Pair<NicProfile, Integer> allocateNic(final NicProfile requested, final N
10291034
if (profile == null) {
10301035
return null;
10311036
}
1037+
// TODO: Fix with the public network PR
1038+
if (isNicAllocatedForNsxPublicNetworkOnVR(network, profile, vm)) {
1039+
String guruName = "NsxPublicNetworkGuru";
1040+
NetworkGuru nsxGuru = AdapterBase.getAdapterByName(networkGurus, guruName);
1041+
nsxGuru.allocate(network, profile, vm);
1042+
}
10321043

10331044
if (isDefaultNic != null) {
10341045
profile.setDefaultNic(isDefaultNic);
@@ -1062,6 +1073,27 @@ public Pair<NicProfile, Integer> allocateNic(final NicProfile requested, final N
10621073
return new Pair<NicProfile, Integer>(vmNic, Integer.valueOf(deviceId));
10631074
}
10641075

1076+
private boolean isNicAllocatedForNsxPublicNetworkOnVR(Network network, NicProfile requested, VirtualMachineProfile vm) {
1077+
if (ObjectUtils.anyNull(network, requested, vm)) {
1078+
return false;
1079+
}
1080+
boolean isVirtualRouter = vm.getType() == Type.DomainRouter;
1081+
boolean isPublicTraffic = network.getTrafficType() == TrafficType.Public;
1082+
if (!isVirtualRouter || !isPublicTraffic || requested.getIPv4Address() == null) {
1083+
return false;
1084+
}
1085+
IPAddressVO ip = _ipAddressDao.findByIp(requested.getIPv4Address());
1086+
if (ip == null) {
1087+
return false;
1088+
}
1089+
VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(ip.getVlanId(), ApiConstants.NSX_DETAIL_KEY);
1090+
if (vlanDetail == null) {
1091+
return false;
1092+
}
1093+
boolean isForNsx = vlanDetail.getValue().equalsIgnoreCase("true");
1094+
return isForNsx && ip.isSourceNat() && !ip.isForSystemVms();
1095+
}
1096+
10651097
private void setMtuDetailsInVRNic(final Pair<NetworkVO, VpcVO> networks, Network network, NicVO vo) {
10661098
if (TrafficType.Public == network.getTrafficType()) {
10671099
if (networks == null) {

plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxTier1GatewayCommand.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919
import java.util.Objects;
2020

2121
public class CreateNsxTier1GatewayCommand extends NsxCommand {
22+
2223
private Long networkResourceId;
2324
private String networkResourceName;
2425
private boolean isResourceVpc;
26+
private boolean sourceNatEnabled;
2527

2628
public CreateNsxTier1GatewayCommand(long domainId, long accountId, long zoneId,
27-
Long networkResourceId, String networkResourceName, boolean isResourceVpc) {
29+
Long networkResourceId, String networkResourceName, boolean isResourceVpc,
30+
boolean sourceNatEnabled) {
2831
super(domainId, accountId, zoneId);
2932
this.networkResourceId = networkResourceId;
3033
this.networkResourceName = networkResourceName;
3134
this.isResourceVpc = isResourceVpc;
35+
this.sourceNatEnabled = sourceNatEnabled;
3236
}
3337

3438
public Long getNetworkResourceId() {
@@ -43,6 +47,10 @@ public String getNetworkResourceName() {
4347
return networkResourceName;
4448
}
4549

50+
public boolean isSourceNatEnabled() {
51+
return sourceNatEnabled;
52+
}
53+
4654
@Override
4755
public boolean equals(Object o) {
4856
if (this == o) return true;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.agent.api;
18+
19+
public class CreateNsxTier1NatRuleCommand extends NsxCommand {
20+
21+
private String tier1GatewayName;
22+
private String action;
23+
private String translatedIpAddress;
24+
private String natRuleId;
25+
26+
public CreateNsxTier1NatRuleCommand(long domainId, long accountId, long zoneId,
27+
String tier1GatewayName, String action, String translatedIpAddress, String natRuleId) {
28+
super(domainId, accountId, zoneId);
29+
this.tier1GatewayName = tier1GatewayName;
30+
this.action = action;
31+
this.translatedIpAddress = translatedIpAddress;
32+
this.natRuleId = natRuleId;
33+
}
34+
35+
public String getTier1GatewayName() {
36+
return tier1GatewayName;
37+
}
38+
39+
public String getAction() {
40+
return action;
41+
}
42+
43+
public String getTranslatedIpAddress() {
44+
return translatedIpAddress;
45+
}
46+
47+
public String getNatRuleId() {
48+
return natRuleId;
49+
}
50+
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.apache.cloudstack.agent.api.CreateNsxSegmentCommand;
4242
import org.apache.cloudstack.agent.api.CreateNsxStaticNatCommand;
4343
import org.apache.cloudstack.agent.api.CreateNsxTier1GatewayCommand;
44+
import org.apache.cloudstack.agent.api.CreateNsxTier1NatRuleCommand;
4445
import org.apache.cloudstack.agent.api.DeleteNsxLoadBalancerRuleCommand;
4546
import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand;
4647
import org.apache.cloudstack.agent.api.DeleteNsxNatRuleCommand;
@@ -110,6 +111,8 @@ public Answer executeRequest(Command cmd) {
110111
return executeRequest((CreateNsxTier1GatewayCommand) cmd);
111112
} else if (cmd instanceof CreateNsxDhcpRelayConfigCommand) {
112113
return executeRequest((CreateNsxDhcpRelayConfigCommand) cmd);
114+
} else if (cmd instanceof CreateNsxTier1NatRuleCommand) {
115+
return executeRequest((CreateNsxTier1NatRuleCommand) cmd);
113116
} else if (cmd instanceof CreateNsxStaticNatCommand) {
114117
return executeRequest((CreateNsxStaticNatCommand) cmd);
115118
} else if (cmd instanceof DeleteNsxNatRuleCommand) {
@@ -226,6 +229,22 @@ public boolean configure(String name, Map<String, Object> params) throws Configu
226229
return true;
227230
}
228231

232+
private Answer executeRequest(CreateNsxTier1NatRuleCommand cmd) {
233+
String tier1GatewayName = cmd.getTier1GatewayName();
234+
String action = cmd.getAction();
235+
String translatedIpAddress = cmd.getTranslatedIpAddress();
236+
String natRuleId = cmd.getNatRuleId();
237+
String natId = "USER";
238+
try {
239+
nsxApiClient.createTier1NatRule(tier1GatewayName, natId, natRuleId, action, translatedIpAddress);
240+
} catch (CloudRuntimeException e) {
241+
String msg = String.format("Error creating the NAT rule with ID %s on Tier1 Gateway %s: %s", natRuleId, tier1GatewayName, e.getMessage());
242+
LOGGER.error(msg, e);
243+
return new NsxAnswer(cmd, e);
244+
}
245+
return new NsxAnswer(cmd, true, "");
246+
}
247+
229248
private Answer executeRequest(CreateNsxDhcpRelayConfigCommand cmd) {
230249
long zoneId = cmd.getZoneId();
231250
long domainId = cmd.getDomainId();
@@ -271,8 +290,9 @@ private Answer executeRequest(ReadyCommand cmd) {
271290

272291
private Answer executeRequest(CreateNsxTier1GatewayCommand cmd) {
273292
String name = NsxControllerUtils.getTier1GatewayName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), cmd.getNetworkResourceId(), cmd.isResourceVpc());
293+
boolean sourceNatEnabled = cmd.isSourceNatEnabled();
274294
try {
275-
nsxApiClient.createTier1Gateway(name, tier0Gateway, edgeCluster);
295+
nsxApiClient.createTier1Gateway(name, tier0Gateway, edgeCluster, sourceNatEnabled);
276296
return new NsxAnswer(cmd, true, "");
277297
} catch (CloudRuntimeException e) {
278298
String msg = String.format("Cannot create tier 1 gateway %s (%s: %s): %s", name,

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
// under the License.
1717
package org.apache.cloudstack.service;
1818

19-
import com.cloud.exception.InvalidParameterValueException;
2019
import com.cloud.network.Network;
2120
import com.cloud.utils.exception.CloudRuntimeException;
2221
import com.vmware.nsx.model.TransportZone;
@@ -46,6 +45,7 @@
4645
import com.vmware.nsx_policy.model.LBVirtualServerListResult;
4746
import com.vmware.nsx_policy.model.LocaleServicesListResult;
4847
import com.vmware.nsx_policy.model.PolicyNatRule;
48+
import com.vmware.nsx_policy.model.PolicyNatRuleListResult;
4949
import com.vmware.nsx_policy.model.Segment;
5050
import com.vmware.nsx_policy.model.SegmentSubnet;
5151
import com.vmware.nsx_policy.model.ServiceListResult;
@@ -68,6 +68,7 @@
6868
import org.apache.commons.collections.CollectionUtils;
6969
import org.apache.log4j.Logger;
7070

71+
import java.util.ArrayList;
7172
import java.util.List;
7273
import java.util.Locale;
7374
import java.util.Objects;
@@ -162,6 +163,16 @@ public NsxApiClient(String hostname, String port, String username, char[] passwo
162163
nsxService = apiClient::createStub;
163164
}
164165

166+
public void createTier1NatRule(String tier1GatewayName, String natId, String natRuleId,
167+
String action, String translatedIp) {
168+
NatRules natRulesService = (NatRules) nsxService.apply(NatRules.class);
169+
PolicyNatRule natPolicy = new PolicyNatRule.Builder()
170+
.setAction(action)
171+
.setTranslatedNetwork(translatedIp)
172+
.build();
173+
natRulesService.patch(tier1GatewayName, natId, natRuleId, natPolicy);
174+
}
175+
165176
public void createDhcpRelayConfig(String dhcpRelayConfigName, List<String> addresses) {
166177
try {
167178
DhcpRelayConfigs service = (DhcpRelayConfigs) nsxService.apply(DhcpRelayConfigs.class);
@@ -238,21 +249,34 @@ private void createTier1LocaleServices(String tier1Id, String edgeCluster, Strin
238249
}
239250
}
240251

241-
public void createTier1Gateway(String name, String tier0Gateway, String edgeCluster) {
252+
private List<String> getRouterAdvertisementTypeList(boolean sourceNatEnabled) {
253+
List<String> types = new ArrayList<>();
254+
types.add(RouteAdvertisementType.TIER1_IPSEC_LOCAL_ENDPOINT.name());
255+
types.add(RouteAdvertisementType.TIER1_NAT.name());
256+
if (!sourceNatEnabled) {
257+
types.add(RouteAdvertisementType.TIER1_CONNECTED.name());
258+
}
259+
return types;
260+
}
261+
262+
public void createTier1Gateway(String name, String tier0Gateway, String edgeCluster, boolean sourceNatEnabled) {
242263
String tier0GatewayPath = TIER_0_GATEWAY_PATH_PREFIX + tier0Gateway;
243264
Tier1 tier1 = getTier1Gateway(name);
244265
if (tier1 != null) {
245-
throw new InvalidParameterValueException(String.format("VPC network with name %s exists in NSX zone", name));
266+
LOGGER.info(String.format("VPC network with name %s exists in NSX zone", name));
267+
return;
246268
}
247269

270+
List<String> routeAdvertisementTypes = getRouterAdvertisementTypeList(sourceNatEnabled);
271+
248272
Tier1s tier1service = (Tier1s) nsxService.apply(Tier1s.class);
249273
tier1 = new Tier1.Builder()
250274
.setTier0Path(tier0GatewayPath)
251275
.setResourceType(TIER_1_RESOURCE_TYPE)
252276
.setPoolAllocation(PoolAllocation.ROUTING.name())
253277
.setHaMode(HAMode.ACTIVE_STANDBY.name())
254278
.setFailoverMode(FailoverMode.PREEMPTIVE.name())
255-
.setRouteAdvertisementTypes(List.of(RouteAdvertisementType.TIER1_CONNECTED.name(), RouteAdvertisementType.TIER1_IPSEC_LOCAL_ENDPOINT.name()))
279+
.setRouteAdvertisementTypes(routeAdvertisementTypes)
256280
.setId(name)
257281
.setDisplayName(name)
258282
.build();
@@ -270,11 +294,28 @@ public void createTier1Gateway(String name, String tier0Gateway, String edgeClus
270294
public void deleteTier1Gateway(String tier1Id) {
271295
com.vmware.nsx_policy.infra.tier_1s.LocaleServices localeService = (com.vmware.nsx_policy.infra.tier_1s.LocaleServices)
272296
nsxService.apply(com.vmware.nsx_policy.infra.tier_1s.LocaleServices.class);
297+
removeTier1GatewayNatRules(tier1Id);
273298
localeService.delete(tier1Id, Tier_1_LOCALE_SERVICE_ID);
274299
Tier1s tier1service = (Tier1s) nsxService.apply(Tier1s.class);
275300
tier1service.delete(tier1Id);
276301
}
277302

303+
private void removeTier1GatewayNatRules(String tier1Id) {
304+
NatRules natRulesService = (NatRules) nsxService.apply(NatRules.class);
305+
String natId = "USER";
306+
PolicyNatRuleListResult result = natRulesService.list(tier1Id, natId, null, false, null, null, null, null);
307+
List<PolicyNatRule> natRules = result.getResults();
308+
if (CollectionUtils.isEmpty(natRules)) {
309+
LOGGER.debug(String.format("Didn't find any NAT rule to remove on the Tier 1 Gateway %s", tier1Id));
310+
} else {
311+
for (PolicyNatRule natRule : natRules) {
312+
LOGGER.debug(String.format("Removing NAT rule %s from Tier 1 Gateway %s", natRule.getId(), tier1Id));
313+
natRulesService.delete(tier1Id, natId, natRule.getId());
314+
}
315+
}
316+
317+
}
318+
278319
public SiteListResult getSites() {
279320
try {
280321
Sites sites = (Sites) nsxService.apply(Sites.class);
@@ -327,7 +368,7 @@ public void createSegment(String segmentName, String tier1GatewayName, String ga
327368
}
328369
}
329370

330-
public void deleteSegment(long zoneId, long domainId, long accountId, long vpcId, long networkId, String segmentName) {
371+
public void deleteSegment(long zoneId, long domainId, long accountId, Long vpcId, long networkId, String segmentName) {
331372
try {
332373
Segments segmentService = (Segments) nsxService.apply(Segments.class);
333374
LOGGER.debug(String.format("Removing the segment with ID %s", segmentName));

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import com.cloud.network.vpc.PrivateGateway;
6868
import com.cloud.network.vpc.StaticRouteProfile;
6969
import com.cloud.network.vpc.Vpc;
70+
import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao;
7071
import com.cloud.network.vpc.VpcVO;
7172
import com.cloud.network.vpc.dao.VpcDao;
7273
import com.cloud.offering.NetworkOffering;
@@ -129,6 +130,7 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns
129130
@Inject
130131
DomainDao domainDao;
131132
@Inject
133+
protected VpcOfferingServiceMapDao vpcOfferingServiceMapDao;
132134
IPAddressDao ipAddressDao;
133135
@Inject
134136
VMInstanceDao vmInstanceDao;
@@ -331,9 +333,7 @@ public boolean implementVpc(Vpc vpc, DeployDestination dest, ReservationContext
331333
if (Boolean.TRUE.equals(isNsxAndAccount.first()) && Objects.isNull(isNsxAndAccount.second())) {
332334
throw new InvalidParameterValueException(String.format("Failed to find account with id %s", vpc.getAccountId()));
333335
}
334-
Account account = isNsxAndAccount.second();
335-
DomainVO domain = getDomainFromAccount(account);
336-
return nsxService.createVpcNetwork(vpc.getZoneId(), account.getId(), domain.getId(), vpc.getId(), vpc.getName());
336+
return true;
337337
}
338338

339339
@Override

0 commit comments

Comments
 (0)