Skip to content

Commit 323f791

Browse files
Dingane HlalukuGabrielBrascher
authored andcommitted
IP address acquired with associate ip address is marked as source nat (apache#3125)
* CLOUDSTACK-4045 added a check for network state when determining whether a new IP should be source NAT. this prevents associated IP's to be marked as source NAT when the network is in allocated state, causing disassociateIpAddress to fail later * Remove mock object that cause other tests to fail * Remove underscores from variable types and add documentation for the created method * Improve exception message to include network name * Include network UUID with the Exception message and fix failing marvin test * Rebase against latest master and format AssociateIPAddrCmd class
1 parent f967944 commit 323f791

File tree

6 files changed

+263
-78
lines changed

6 files changed

+263
-78
lines changed

api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@
5757
import com.cloud.projects.Project;
5858
import com.cloud.user.Account;
5959

60-
@APICommand(name = "associateIpAddress", description = "Acquires and associates a public IP to an account. Either of the parameters are required, i.e. either zoneId, or networkId, or vpcId ", responseObject = IPAddressResponse.class, responseView = ResponseView.Restricted,
61-
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
60+
@APICommand(name = "associateIpAddress",
61+
description = "Acquires and associates a public IP to an account. Either of the parameters are required, i.e. either zoneId, or networkId, or vpcId ",
62+
responseObject = IPAddressResponse.class,
63+
responseView = ResponseView.Restricted,
64+
requestHasSensitiveInfo = false,
65+
responseHasSensitiveInfo = false)
6266
public class AssociateIPAddrCmd extends BaseAsyncCreateCmd {
6367
public static final Logger s_logger = Logger.getLogger(AssociateIPAddrCmd.class.getName());
6468
private static final String s_name = "associateipaddressresponse";
@@ -67,46 +71,57 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd {
6771
//////////////// API parameters /////////////////////
6872
/////////////////////////////////////////////////////
6973

70-
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the account to associate with this IP address")
74+
@Parameter(name = ApiConstants.ACCOUNT,
75+
type = CommandType.STRING,
76+
description = "the account to associate with this IP address")
7177
private String accountName;
7278

7379
@Parameter(name = ApiConstants.DOMAIN_ID,
74-
type = CommandType.UUID,
75-
entityType = DomainResponse.class,
76-
description = "the ID of the domain to associate with this IP address")
80+
type = CommandType.UUID,
81+
entityType = DomainResponse.class,
82+
description = "the ID of the domain to associate with this IP address")
7783
private Long domainId;
7884

7985
@Parameter(name = ApiConstants.ZONE_ID,
80-
type = CommandType.UUID,
81-
entityType = ZoneResponse.class,
82-
description = "the ID of the availability zone you want to acquire an public IP address from")
86+
type = CommandType.UUID,
87+
entityType = ZoneResponse.class,
88+
description = "the ID of the availability zone you want to acquire an public IP address from")
8389
private Long zoneId;
8490

8591
@Parameter(name = ApiConstants.NETWORK_ID,
86-
type = CommandType.UUID,
87-
entityType = NetworkResponse.class,
88-
description = "The network this IP address should be associated to.")
92+
type = CommandType.UUID,
93+
entityType = NetworkResponse.class,
94+
description = "The network this IP address should be associated to.")
8995
private Long networkId;
9096

91-
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Deploy VM for the project")
97+
@Parameter(name = ApiConstants.PROJECT_ID,
98+
type = CommandType.UUID,
99+
entityType = ProjectResponse.class,
100+
description = "Deploy VM for the project")
92101
private Long projectId;
93102

94-
@Parameter(name = ApiConstants.VPC_ID, type = CommandType.UUID, entityType = VpcResponse.class, description = "the VPC you want the IP address to "
95-
+ "be associated with")
103+
@Parameter(name = ApiConstants.VPC_ID,
104+
type = CommandType.UUID,
105+
entityType = VpcResponse.class,
106+
description = "the VPC you want the IP address to be associated with")
96107
private Long vpcId;
97108

98-
@Parameter(name = ApiConstants.IS_PORTABLE, type = BaseCmd.CommandType.BOOLEAN, description = "should be set to true "
99-
+ "if public IP is required to be transferable across zones, if not specified defaults to false")
109+
@Parameter(name = ApiConstants.IS_PORTABLE,
110+
type = BaseCmd.CommandType.BOOLEAN,
111+
description = "should be set to true if public IP is required to be transferable across zones, if not specified defaults to false")
100112
private Boolean isPortable;
101113

102114
@Parameter(name = ApiConstants.REGION_ID,
103-
type = CommandType.INTEGER,
104-
entityType = RegionResponse.class,
105-
required = false,
106-
description = "region ID from where portable IP is to be associated.")
115+
type = CommandType.INTEGER,
116+
entityType = RegionResponse.class,
117+
required = false,
118+
description = "region ID from where portable IP is to be associated.")
107119
private Integer regionId;
108120

109-
@Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the IP to the end user or not", since = "4.4", authorized = {RoleType.Admin})
121+
@Parameter(name = ApiConstants.FOR_DISPLAY,
122+
type = CommandType.BOOLEAN,
123+
description = "an optional field, whether to the display the IP to the end user or not", since = "4.4",
124+
authorized = {RoleType.Admin})
110125
private Boolean display;
111126

112127
/////////////////////////////////////////////////////
@@ -178,7 +193,7 @@ public Long getNetworkId() {
178193
if (networks.size() == 0) {
179194
String domain = _domainService.getDomain(getDomainId()).getName();
180195
throw new InvalidParameterValueException("Account name=" + getAccountName() + " domain=" + domain + " doesn't have virtual networks in zone=" +
181-
zone.getName());
196+
zone.getName());
182197
}
183198

184199
if (networks.size() < 1) {
@@ -205,7 +220,7 @@ public Boolean getDisplayIp() {
205220

206221
@Override
207222
public boolean isDisplay() {
208-
if(display == null)
223+
if (display == null)
209224
return true;
210225
else
211226
return display;
@@ -224,7 +239,7 @@ public long getEntityOwnerId() {
224239
return project.getProjectAccountId();
225240
} else {
226241
throw new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() +
227-
" as it's no longer active");
242+
" as it's no longer active");
228243
}
229244
} else {
230245
throw new InvalidParameterValueException("Unable to find project by ID");
@@ -268,7 +283,7 @@ public String getEventType() {
268283

269284
@Override
270285
public String getEventDescription() {
271-
return "associating IP to network ID: " + getNetworkId() + " in zone " + getZoneId();
286+
return "associating IP to network ID: " + getNetworkId() + " in zone " + getZoneId();
272287
}
273288

274289
/////////////////////////////////////////////////////

server/src/main/java/com/cloud/network/IpAddressManagerImpl.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,16 +1377,7 @@ public IPAddressVO associateIPToGuestNetwork(long ipId, long networkId, boolean
13771377
}
13781378
}
13791379

1380-
NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
1381-
boolean sharedSourceNat = offering.isSharedSourceNat();
1382-
boolean isSourceNat = false;
1383-
if (!sharedSourceNat) {
1384-
if (getExistingSourceNatInNetwork(owner.getId(), networkId) == null) {
1385-
if (network.getGuestType() == GuestType.Isolated && network.getVpcId() == null && !ipToAssoc.isPortable()) {
1386-
isSourceNat = true;
1387-
}
1388-
}
1389-
}
1380+
boolean isSourceNat = isSourceNatAvailableForNetwork(owner, ipToAssoc, network);
13901381

13911382
s_logger.debug("Associating ip " + ipToAssoc + " to network " + network);
13921383

@@ -1424,6 +1415,33 @@ public IPAddressVO associateIPToGuestNetwork(long ipId, long networkId, boolean
14241415
}
14251416
}
14261417

1418+
/**
1419+
* Prevents associating an IP address to an allocated (unimplemented network) network, throws an Exception otherwise
1420+
* @param owner Used to check if the user belongs to the Network
1421+
* @param ipToAssoc IP address to be associated to a Network, can only be associated to an implemented network for Source NAT
1422+
* @param network Network to which IP address is to be associated with, must not be in allocated state for Source NAT Network/IP association
1423+
* @return true if IP address can be successfully associated with Source NAT network
1424+
*/
1425+
protected boolean isSourceNatAvailableForNetwork(Account owner, IPAddressVO ipToAssoc, Network network) {
1426+
NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
1427+
boolean sharedSourceNat = offering.isSharedSourceNat();
1428+
boolean isSourceNat = false;
1429+
if (!sharedSourceNat) {
1430+
if (getExistingSourceNatInNetwork(owner.getId(), network.getId()) == null) {
1431+
if (network.getGuestType() == GuestType.Isolated && network.getVpcId() == null && !ipToAssoc.isPortable()) {
1432+
if (network.getState() == Network.State.Allocated) {
1433+
//prevent associating an ip address to an allocated (unimplemented network).
1434+
//it will cause the ip to become source nat, and it can't be disassociated later on.
1435+
String msg = String.format("Network with UUID:%s is in allocated and needs to be implemented first before acquiring an IP address", network.getUuid());
1436+
throw new InvalidParameterValueException(msg);
1437+
}
1438+
isSourceNat = true;
1439+
}
1440+
}
1441+
}
1442+
return isSourceNat;
1443+
}
1444+
14271445
protected boolean isSharedNetworkOfferingWithServices(long networkOfferingId) {
14281446
NetworkOfferingVO networkOffering = _networkOfferingDao.findById(networkOfferingId);
14291447
if ((networkOffering.getGuestType() == Network.GuestType.Shared)

server/src/test/java/com/cloud/network/IpAddressManagerTest.java

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
package com.cloud.network;
1919

20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
2022
import static org.mockito.Matchers.anyLong;
23+
import static org.mockito.Mockito.doReturn;
2124
import static org.mockito.Mockito.mock;
2225
import static org.mockito.Mockito.when;
2326

@@ -28,32 +31,63 @@
2831
import org.junit.Assert;
2932
import org.junit.Before;
3033
import org.junit.Test;
34+
import org.junit.runner.RunWith;
3135
import org.mockito.InjectMocks;
3236
import org.mockito.Mock;
3337
import org.mockito.Mockito;
34-
import org.mockito.MockitoAnnotations;
38+
import org.mockito.Spy;
3539

40+
import com.cloud.exception.InvalidParameterValueException;
41+
import com.cloud.exception.ResourceUnavailableException;
3642
import com.cloud.network.Network.Service;
3743
import com.cloud.network.dao.IPAddressDao;
3844
import com.cloud.network.dao.IPAddressVO;
45+
import com.cloud.network.dao.NetworkDao;
46+
import com.cloud.network.dao.NetworkVO;
3947
import com.cloud.network.rules.StaticNat;
4048
import com.cloud.network.rules.StaticNatImpl;
49+
import com.cloud.offerings.NetworkOfferingVO;
50+
import com.cloud.offerings.dao.NetworkOfferingDao;
51+
import com.cloud.user.AccountVO;
4152
import com.cloud.utils.net.Ip;
53+
import org.mockito.runners.MockitoJUnitRunner;
4254

55+
@RunWith(MockitoJUnitRunner.class)
4356
public class IpAddressManagerTest {
4457

4558
@Mock
46-
IPAddressDao _ipAddrDao;
59+
IPAddressDao ipAddressDao;
4760

61+
@Mock
62+
NetworkDao networkDao;
63+
64+
@Mock
65+
NetworkOfferingDao networkOfferingDao;
66+
67+
@Spy
4868
@InjectMocks
49-
IpAddressManagerImpl _ipManager;
69+
IpAddressManagerImpl ipAddressManager;
5070

5171
@InjectMocks
5272
NetworkModelImpl networkModel = Mockito.spy(new NetworkModelImpl());
5373

74+
IPAddressVO ipAddressVO;
75+
76+
AccountVO account;
77+
5478
@Before
55-
public void setup() {
56-
MockitoAnnotations.initMocks(this);
79+
public void setup() throws ResourceUnavailableException {
80+
81+
ipAddressVO = new IPAddressVO(new Ip("192.0.0.1"), 1L, 1L, 1L,false);
82+
ipAddressVO.setAllocatedToAccountId(1L);
83+
84+
account = new AccountVO("admin", 1L, null, (short) 1, 1L, "c65a73d5-ebbd-11e7-8f45-107b44277808");
85+
account.setId(1L);
86+
87+
NetworkOfferingVO networkOfferingVO = Mockito.mock(NetworkOfferingVO.class);
88+
networkOfferingVO.setSharedSourceNat(false);
89+
90+
Mockito.when(networkOfferingDao.findById(Mockito.anyLong())).thenReturn(networkOfferingVO);
5791
}
5892

5993
@Test
@@ -63,10 +97,10 @@ public void testGetStaticNatSourceIps() {
6397
when(vo.getAddress()).thenReturn(new Ip(publicIpAddress));
6498
when(vo.getId()).thenReturn(1l);
6599

66-
when(_ipAddrDao.findById(anyLong())).thenReturn(vo);
100+
when(ipAddressDao.findById(anyLong())).thenReturn(vo);
67101
StaticNat snat = new StaticNatImpl(1, 1, 1, 1, publicIpAddress, false);
68102

69-
List<IPAddressVO> ips = _ipManager.getStaticNatSourceIps(Collections.singletonList(snat));
103+
List<IPAddressVO> ips = ipAddressManager.getStaticNatSourceIps(Collections.singletonList(snat));
70104
Assert.assertNotNull(ips);
71105
Assert.assertEquals(1, ips.size());
72106

@@ -78,7 +112,7 @@ public void testGetStaticNatSourceIps() {
78112
public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestRequestedIpEqualsIp6Gateway() {
79113
Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>());
80114

81-
boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "ip6Gateway");
115+
boolean result = ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "ip6Gateway");
82116

83117
Mockito.verify(networkModel, Mockito.times(0)).listNetworkOfferingServices(Mockito.anyLong());
84118
Assert.assertTrue(result);
@@ -88,7 +122,7 @@ public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestRequestedIpEqualsIp6Gate
88122
public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestRequestedIpEqualsGateway() {
89123
Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>());
90124

91-
boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "gateway");
125+
boolean result = ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "gateway");
92126

93127
Mockito.verify(networkModel, Mockito.times(0)).listNetworkOfferingServices(Mockito.anyLong());
94128
Assert.assertTrue(result);
@@ -101,7 +135,7 @@ public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestExpectFalseServicesNotEm
101135
services.add(serviceGateway);
102136
Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, services);
103137

104-
boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
138+
boolean result = ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
105139

106140
Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong());
107141
Assert.assertFalse(result);
@@ -111,17 +145,75 @@ public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestExpectFalseServicesNotEm
111145
public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestExpectFalseServicesCidrNotNull() {
112146
Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", "cidr", new ArrayList<Service>());
113147

114-
boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
148+
boolean result = ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
115149

116150
Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong());
117151
Assert.assertFalse(result);
118152
}
119153

154+
@Test
155+
public void assertSourceNatImplementedNetwork() {
156+
157+
NetworkVO networkImplemented = Mockito.mock(NetworkVO.class);
158+
when(networkImplemented.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
159+
when(networkImplemented.getNetworkOfferingId()).thenReturn(8L);
160+
when(networkImplemented.getState()).thenReturn(Network.State.Implemented);
161+
when(networkImplemented.getGuestType()).thenReturn(Network.GuestType.Isolated);
162+
when(networkImplemented.getVpcId()).thenReturn(null);
163+
when(networkImplemented.getId()).thenReturn(1L);
164+
165+
Mockito.when(networkDao.findById(1L)).thenReturn(networkImplemented);
166+
doReturn(null).when(ipAddressManager).getExistingSourceNatInNetwork(1L, 1L);
167+
168+
boolean isSourceNat = ipAddressManager.isSourceNatAvailableForNetwork(account, ipAddressVO, networkImplemented);
169+
170+
assertTrue("Source NAT should be true", isSourceNat);
171+
}
172+
173+
@Test(expected = InvalidParameterValueException.class)
174+
public void assertSourceNatAllocatedNetwork() {
175+
176+
NetworkVO networkAllocated = Mockito.mock(NetworkVO.class);
177+
when(networkAllocated.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
178+
when(networkAllocated.getNetworkOfferingId()).thenReturn(8L);
179+
when(networkAllocated.getState()).thenReturn(Network.State.Allocated);
180+
when(networkAllocated.getGuestType()).thenReturn(Network.GuestType.Isolated);
181+
when(networkAllocated.getVpcId()).thenReturn(null);
182+
when(networkAllocated.getId()).thenReturn(2L);
183+
184+
Mockito.when(networkDao.findById(2L)).thenReturn(networkAllocated);
185+
doReturn(null).when(ipAddressManager).getExistingSourceNatInNetwork(1L, 2L);
186+
187+
ipAddressManager.isSourceNatAvailableForNetwork(account, ipAddressVO, networkAllocated);
188+
}
189+
190+
@Test
191+
public void assertExistingSourceNatAllocatedNetwork() {
192+
193+
NetworkVO networkNat = Mockito.mock(NetworkVO.class);
194+
when(networkNat.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
195+
when(networkNat.getNetworkOfferingId()).thenReturn(8L);
196+
when(networkNat.getState()).thenReturn(Network.State.Implemented);
197+
when(networkNat.getGuestType()).thenReturn(Network.GuestType.Isolated);
198+
when(networkNat.getId()).thenReturn(3L);
199+
when(networkNat.getVpcId()).thenReturn(null);
200+
when(networkNat.getId()).thenReturn(3L);
201+
202+
IPAddressVO sourceNat = new IPAddressVO(new Ip("192.0.0.2"), 1L, 1L, 1L,true);
203+
204+
Mockito.when(networkDao.findById(3L)).thenReturn(networkNat);
205+
doReturn(sourceNat).when(ipAddressManager).getExistingSourceNatInNetwork(1L, 3L);
206+
207+
boolean isSourceNat = ipAddressManager.isSourceNatAvailableForNetwork(account, ipAddressVO, networkNat);
208+
209+
assertFalse("Source NAT should be false", isSourceNat);
210+
}
211+
120212
@Test
121213
public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestNetworkOfferingsEmptyAndCidrNull() {
122214
Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>());
123215

124-
boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
216+
boolean result = ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp");
125217

126218
Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong());
127219
Assert.assertTrue(result);

0 commit comments

Comments
 (0)