Skip to content

Commit 5c7e4b7

Browse files
authored
api: add ipaddress argument to disassociateIPAddress (#8222)
This PR adds argument 'ipadress' to the disassociateIpAddress api. IP address can be disassociated by directly giving the address instead of ID. Fixes: #8125
1 parent 98d643e commit 5c7e4b7

6 files changed

Lines changed: 137 additions & 18 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long ne
114114

115115
IpAddress getIp(long id);
116116

117+
IpAddress getIp(String ipAddress);
118+
117119
Network updateGuestNetwork(final UpdateNetworkCmd cmd);
118120

119121
/**

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

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,14 @@ public class DisassociateIPAddrCmd extends BaseAsyncCmd {
4646
//////////////// API parameters /////////////////////
4747
/////////////////////////////////////////////////////
4848

49-
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, required = true, description = "the ID of the public IP address"
50-
+ " to disassociate")
49+
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, description = "the ID of the public IP address"
50+
+ " to disassociate. Mutually exclusive with the ipaddress parameter")
5151
private Long id;
5252

53+
@Parameter(name=ApiConstants.IP_ADDRESS, type=CommandType.STRING, since="4.19.0", description="IP Address to be disassociated."
54+
+ " Mutually exclusive with the id parameter")
55+
private String ipAddress;
56+
5357
// unexposed parameter needed for events logging
5458
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, expose = false)
5559
private Long ownerId;
@@ -59,7 +63,18 @@ public class DisassociateIPAddrCmd extends BaseAsyncCmd {
5963
/////////////////////////////////////////////////////
6064

6165
public Long getIpAddressId() {
62-
return id;
66+
if (id != null & ipAddress != null) {
67+
throw new InvalidParameterValueException("id parameter is mutually exclusive with ipaddress parameter");
68+
}
69+
70+
if (id != null) {
71+
return id;
72+
} else if (ipAddress != null) {
73+
IpAddress ip = getIpAddressByIp(ipAddress);
74+
return ip.getId();
75+
}
76+
77+
throw new InvalidParameterValueException("Please specify either IP address or IP address ID");
6378
}
6479

6580
/////////////////////////////////////////////////////
@@ -68,12 +83,13 @@ public Long getIpAddressId() {
6883

6984
@Override
7085
public void execute() throws InsufficientAddressCapacityException {
71-
CallContext.current().setEventDetails("IP ID: " + getIpAddressId());
86+
Long ipAddressId = getIpAddressId();
87+
CallContext.current().setEventDetails("IP ID: " + ipAddressId);
7288
boolean result = false;
73-
if (!isPortable(id)) {
74-
result = _networkService.releaseIpAddress(getIpAddressId());
89+
if (!isPortable()) {
90+
result = _networkService.releaseIpAddress(ipAddressId);
7591
} else {
76-
result = _networkService.releasePortableIpAddress(getIpAddressId());
92+
result = _networkService.releasePortableIpAddress(ipAddressId);
7793
}
7894
if (result) {
7995
SuccessResponse response = new SuccessResponse(getCommandName());
@@ -85,7 +101,7 @@ public void execute() throws InsufficientAddressCapacityException {
85101

86102
@Override
87103
public String getEventType() {
88-
if (!isPortable(id)) {
104+
if (!isPortable()) {
89105
return EventTypes.EVENT_NET_IP_RELEASE;
90106
} else {
91107
return EventTypes.EVENT_PORTABLE_IP_RELEASE;
@@ -100,10 +116,7 @@ public String getEventDescription() {
100116
@Override
101117
public long getEntityOwnerId() {
102118
if (ownerId == null) {
103-
IpAddress ip = getIpAddress(id);
104-
if (ip == null) {
105-
throw new InvalidParameterValueException("Unable to find IP address by ID=" + id);
106-
}
119+
IpAddress ip = getIpAddress();
107120
ownerId = ip.getAccountId();
108121
}
109122

@@ -120,11 +133,11 @@ public String getSyncObjType() {
120133

121134
@Override
122135
public Long getSyncObjId() {
123-
IpAddress ip = getIpAddress(id);
136+
IpAddress ip = getIpAddress();
124137
return ip.getAssociatedWithNetworkId();
125138
}
126139

127-
private IpAddress getIpAddress(long id) {
140+
private IpAddress getIpAddressById(Long id) {
128141
IpAddress ip = _entityMgr.findById(IpAddress.class, id);
129142

130143
if (ip == null) {
@@ -134,6 +147,29 @@ private IpAddress getIpAddress(long id) {
134147
}
135148
}
136149

150+
private IpAddress getIpAddressByIp(String ipAddress) {
151+
IpAddress ip = _networkService.getIp(ipAddress);
152+
if (ip == null) {
153+
throw new InvalidParameterValueException("Unable to find IP address by IP address=" + ipAddress);
154+
} else {
155+
return ip;
156+
}
157+
}
158+
159+
private IpAddress getIpAddress() {
160+
if (id != null & ipAddress != null) {
161+
throw new InvalidParameterValueException("id parameter is mutually exclusive with ipaddress parameter");
162+
}
163+
164+
if (id != null) {
165+
return getIpAddressById(id);
166+
} else if (ipAddress != null){
167+
return getIpAddressByIp(ipAddress);
168+
}
169+
170+
throw new InvalidParameterValueException("Please specify either IP address or IP address ID");
171+
}
172+
137173
@Override
138174
public ApiCommandResourceType getApiResourceType() {
139175
return ApiCommandResourceType.IpAddress;
@@ -144,8 +180,8 @@ public Long getApiResourceId() {
144180
return getIpAddressId();
145181
}
146182

147-
private boolean isPortable(long id) {
148-
IpAddress ip = getIpAddress(id);
183+
private boolean isPortable() {
184+
IpAddress ip = getIpAddress();
149185
return ip.isPortable();
150186
}
151187
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,6 +2798,11 @@ public IpAddress getIp(long ipAddressId) {
27982798
return _ipAddressDao.findById(ipAddressId);
27992799
}
28002800

2801+
@Override
2802+
public IpAddress getIp(String ipAddress) {
2803+
return _ipAddressDao.findByIp(ipAddress);
2804+
}
2805+
28012806
protected boolean providersConfiguredForExternalNetworking(Collection<String> providers) {
28022807
for (String providerStr : providers) {
28032808
Provider provider = Network.Provider.getProvider(providerStr);

server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ public IpAddress getIp(long id) {
275275
return null;
276276
}
277277

278+
/* (non-Javadoc)
279+
* @see com.cloud.network.NetworkService#getIp(String)
280+
*/
281+
@Override
282+
public IpAddress getIp(String ipAddress) {
283+
// TODO Auto-generated method stub
284+
return null;
285+
}
286+
278287
@Override
279288
public Network updateGuestNetwork(final UpdateNetworkCmd cmd) {
280289
// TODO Auto-generated method stub

test/integration/smoke/test_network.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,7 @@ def tearDown(self):
871871

872872
@attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
873873
def test_releaseIP(self):
874-
"""Test for release public IP address"""
874+
"""Test for release public IP address using the ID"""
875875

876876
logger.debug("Deleting Public IP : %s" % self.ip_addr.id)
877877

@@ -930,6 +930,66 @@ def test_releaseIP(self):
930930
)
931931
return
932932

933+
@attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
934+
def test_releaseIP_using_IP(self):
935+
"""Test for release public IP address using the address"""
936+
937+
logger.debug("Deleting Public IP : %s" % self.ip_addr.ipaddress)
938+
self.ip_address.delete_by_ip(self.apiclient)
939+
940+
retriesCount = 10
941+
isIpAddressDisassociated = False
942+
while retriesCount > 0:
943+
listResponse = list_publicIP(
944+
self.apiclient,
945+
id=self.ip_addr.id,
946+
state="Allocated"
947+
)
948+
if listResponse is None:
949+
isIpAddressDisassociated = True
950+
break
951+
retriesCount -= 1
952+
time.sleep(60)
953+
# End while
954+
955+
self.assertTrue(
956+
isIpAddressDisassociated,
957+
"Failed to disassociate IP address")
958+
959+
# ListPortForwardingRules should not list
960+
# associated rules with Public IP address
961+
try:
962+
list_nat_rule = list_nat_rules(
963+
self.apiclient,
964+
id=self.nat_rule.id
965+
)
966+
logger.debug("List NAT Rule response" + str(list_nat_rule))
967+
except CloudstackAPIException:
968+
logger.debug("Port Forwarding Rule is deleted")
969+
970+
# listLoadBalancerRules should not list
971+
# associated rules with Public IP address
972+
try:
973+
list_lb_rule = list_lb_rules(
974+
self.apiclient,
975+
id=self.lb_rule.id
976+
)
977+
logger.debug("List LB Rule response" + str(list_lb_rule))
978+
except CloudstackAPIException:
979+
logger.debug("Port Forwarding Rule is deleted")
980+
981+
# SSH Attempt though public IP should fail
982+
with self.assertRaises(Exception):
983+
SshClient(
984+
self.ip_addr.ipaddress,
985+
self.services["natrule"]["publicport"],
986+
self.virtual_machine.username,
987+
self.virtual_machine.password,
988+
retries=2,
989+
delay=0
990+
)
991+
return
992+
933993

934994
class TestDeleteAccount(cloudstackTestCase):
935995

tools/marvin/marvin/lib/base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,12 +1879,19 @@ def create(cls, apiclient, accountid=None, zoneid=None, domainid=None,
18791879
return PublicIPAddress(apiclient.associateIpAddress(cmd).__dict__)
18801880

18811881
def delete(self, apiclient):
1882-
"""Dissociate Public IP address"""
1882+
"""Dissociate Public IP address using the given ID"""
18831883
cmd = disassociateIpAddress.disassociateIpAddressCmd()
18841884
cmd.id = self.ipaddress.id
18851885
apiclient.disassociateIpAddress(cmd)
18861886
return
18871887

1888+
def delete_by_ip(self, apiclient):
1889+
"""Dissociate Public IP address using the given IP address"""
1890+
cmd = disassociateIpAddress.disassociateIpAddressCmd()
1891+
cmd.ipaddress = self.ipaddress.ipaddress
1892+
apiclient.disassociateIpAddress(cmd)
1893+
return
1894+
18881895
@classmethod
18891896
def list(cls, apiclient, **kwargs):
18901897
"""List all Public IPs matching criteria"""

0 commit comments

Comments
 (0)