Skip to content

Commit 6a53517

Browse files
New feature: Reserve and release Public IPs (#6046)
* Reserve and release a public IP * Update #6046: show orange color for Reserved public ip * Update #6046 reserve IP: fix ui conflicts * Update #6046: fix resource count * Update #6046: associate Reserved public IP to network * Update #6046: fix unit tests * Update #6046: fix ui bugs * Update #6046: make api/ui available for domain admin and users
1 parent 15e3a10 commit 6a53517

File tree

19 files changed

+819
-14
lines changed

19 files changed

+819
-14
lines changed

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ public class EventTypes {
133133
// Network Events
134134
public static final String EVENT_NET_IP_ASSIGN = "NET.IPASSIGN";
135135
public static final String EVENT_NET_IP_RELEASE = "NET.IPRELEASE";
136+
public static final String EVENT_NET_IP_RESERVE = "NET.IPRESERVE";
136137
public static final String EVENT_NET_IP_UPDATE = "NET.IPUPDATE";
137138
public static final String EVENT_PORTABLE_IP_ASSIGN = "PORTABLE.IPASSIGN";
138139
public static final String EVENT_PORTABLE_IP_RELEASE = "PORTABLE.IPRELEASE";

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ enum State {
4141
Allocating, // The IP Address is being propagated to other network elements and is not ready for use yet.
4242
Allocated, // The IP address is in used.
4343
Releasing, // The IP address is being released for other network elements and is not ready for allocation.
44+
Reserved, // The IP address is reserved and is not ready for allocation.
4445
Free // The IP address is ready to be allocated.
4546
}
4647

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ public interface NetworkService {
5959
IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException,
6060
ConcurrentOperationException;
6161

62+
IpAddress reserveIpAddress(Account account, Boolean displayIp, Long ipAddressId) throws ResourceAllocationException;
63+
64+
boolean releaseReservedIpAddress(long ipAddressId) throws InsufficientAddressCapacityException;
65+
6266
boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException;
6367

6468
IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.api.command.user.address;
18+
19+
import org.apache.log4j.Logger;
20+
21+
import org.apache.cloudstack.acl.RoleType;
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiConstants;
24+
import org.apache.cloudstack.api.ApiErrorCode;
25+
import org.apache.cloudstack.api.BaseCmd;
26+
import org.apache.cloudstack.api.Parameter;
27+
import org.apache.cloudstack.api.ServerApiException;
28+
import org.apache.cloudstack.api.response.IPAddressResponse;
29+
import org.apache.cloudstack.api.response.SuccessResponse;
30+
import org.apache.cloudstack.context.CallContext;
31+
32+
import com.cloud.exception.InsufficientAddressCapacityException;
33+
import com.cloud.exception.InvalidParameterValueException;
34+
import com.cloud.network.IpAddress;
35+
36+
@APICommand(name = "releaseIpAddress",
37+
description = "Releases an IP address from the account.",
38+
since = "4.17",
39+
responseObject = SuccessResponse.class,
40+
requestHasSensitiveInfo = false,
41+
responseHasSensitiveInfo = false,
42+
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
43+
public class ReleaseIPAddrCmd extends BaseCmd {
44+
public static final Logger s_logger = Logger.getLogger(ReleaseIPAddrCmd.class.getName());
45+
46+
private static final String s_name = "releaseipaddressresponse";
47+
48+
/////////////////////////////////////////////////////
49+
//////////////// API parameters /////////////////////
50+
/////////////////////////////////////////////////////
51+
52+
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, required = true, description = "the ID of the public IP address"
53+
+ " to release")
54+
private Long id;
55+
56+
/////////////////////////////////////////////////////
57+
/////////////////// Accessors ///////////////////////
58+
/////////////////////////////////////////////////////
59+
60+
public Long getIpAddressId() {
61+
return id;
62+
}
63+
64+
/////////////////////////////////////////////////////
65+
/////////////// API Implementation///////////////////
66+
/////////////////////////////////////////////////////
67+
68+
@Override
69+
public String getCommandName() {
70+
return s_name;
71+
}
72+
73+
@Override
74+
public long getEntityOwnerId() {
75+
IpAddress ip = getIpAddress(id);
76+
if (ip == null) {
77+
throw new InvalidParameterValueException("Unable to find IP address by ID=" + id);
78+
}
79+
return ip.getAccountId();
80+
}
81+
82+
@Override
83+
public void execute() throws InsufficientAddressCapacityException {
84+
CallContext.current().setEventDetails("IP ID: " + getIpAddressId());
85+
boolean result = _networkService.releaseReservedIpAddress(getIpAddressId());
86+
if (result) {
87+
SuccessResponse response = new SuccessResponse(getCommandName());
88+
this.setResponseObject(response);
89+
} else {
90+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release IP address");
91+
}
92+
}
93+
94+
private IpAddress getIpAddress(long id) {
95+
IpAddress ip = _entityMgr.findById(IpAddress.class, id);
96+
97+
if (ip == null) {
98+
throw new InvalidParameterValueException("Unable to find IP address by ID=" + id);
99+
} else {
100+
return ip;
101+
}
102+
}
103+
104+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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.api.command.user.address;
18+
19+
import org.apache.log4j.Logger;
20+
21+
import org.apache.cloudstack.acl.RoleType;
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiConstants;
24+
import org.apache.cloudstack.api.ApiErrorCode;
25+
import org.apache.cloudstack.api.BaseCmd;
26+
import org.apache.cloudstack.api.Parameter;
27+
import org.apache.cloudstack.api.ResponseObject.ResponseView;
28+
import org.apache.cloudstack.api.ServerApiException;
29+
import org.apache.cloudstack.api.command.user.UserCmd;
30+
import org.apache.cloudstack.api.response.DomainResponse;
31+
import org.apache.cloudstack.api.response.IPAddressResponse;
32+
import org.apache.cloudstack.api.response.ProjectResponse;
33+
import org.apache.cloudstack.context.CallContext;
34+
35+
import com.cloud.exception.ConcurrentOperationException;
36+
import com.cloud.exception.InvalidParameterValueException;
37+
import com.cloud.exception.PermissionDeniedException;
38+
import com.cloud.exception.ResourceAllocationException;
39+
import com.cloud.exception.ResourceUnavailableException;
40+
import com.cloud.network.IpAddress;
41+
import com.cloud.projects.Project;
42+
import com.cloud.user.Account;
43+
44+
@APICommand(name = "reserveIpAddress",
45+
description = "Reserve a public IP to an account.",
46+
since = "4.17",
47+
responseObject = IPAddressResponse.class,
48+
responseView = ResponseView.Restricted,
49+
requestHasSensitiveInfo = false,
50+
responseHasSensitiveInfo = false,
51+
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
52+
public class ReserveIPAddrCmd extends BaseCmd implements UserCmd {
53+
public static final Logger s_logger = Logger.getLogger(ReserveIPAddrCmd.class.getName());
54+
private static final String s_name = "reserveipaddressresponse";
55+
56+
/////////////////////////////////////////////////////
57+
//////////////// API parameters /////////////////////
58+
/////////////////////////////////////////////////////
59+
60+
@Parameter(name = ApiConstants.ACCOUNT,
61+
type = CommandType.STRING,
62+
description = "the account to reserve with this IP address")
63+
private String accountName;
64+
65+
@Parameter(name = ApiConstants.DOMAIN_ID,
66+
type = CommandType.UUID,
67+
entityType = DomainResponse.class,
68+
description = "the ID of the domain to reserve with this IP address")
69+
private Long domainId;
70+
71+
@Parameter(name = ApiConstants.PROJECT_ID,
72+
type = CommandType.UUID,
73+
entityType = ProjectResponse.class,
74+
description = "the ID of the project to reserve with this IP address")
75+
private Long projectId;
76+
77+
@Parameter(name = ApiConstants.FOR_DISPLAY,
78+
type = CommandType.BOOLEAN,
79+
description = "an optional field, whether to the display the IP to the end user or not",
80+
authorized = {RoleType.Admin})
81+
private Boolean display;
82+
83+
@Parameter(name = ApiConstants.ID,
84+
type = CommandType.UUID,
85+
entityType = IPAddressResponse.class,
86+
required = true,
87+
description = "the ID of the public IP address to reserve")
88+
private Long id;
89+
90+
/////////////////////////////////////////////////////
91+
/////////////////// Accessors ///////////////////////
92+
/////////////////////////////////////////////////////
93+
94+
public String getAccountName() {
95+
if (accountName != null) {
96+
return accountName;
97+
}
98+
return CallContext.current().getCallingAccount().getAccountName();
99+
}
100+
101+
public long getDomainId() {
102+
if (domainId != null) {
103+
return domainId;
104+
}
105+
return CallContext.current().getCallingAccount().getDomainId();
106+
}
107+
108+
public Long getIpAddressId() {
109+
return id;
110+
}
111+
112+
@Override
113+
public boolean isDisplay() {
114+
if (display == null)
115+
return true;
116+
else
117+
return display;
118+
}
119+
120+
@Override
121+
public long getEntityOwnerId() {
122+
Account caller = CallContext.current().getCallingAccount();
123+
if (accountName != null && domainId != null) {
124+
Account account = _accountService.finalizeOwner(caller, accountName, domainId, projectId);
125+
return account.getId();
126+
} else if (projectId != null) {
127+
Project project = _projectService.getProject(projectId);
128+
if (project != null) {
129+
if (project.getState() == Project.State.Active) {
130+
return project.getProjectAccountId();
131+
} else {
132+
throw new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() +
133+
" as it's no longer active");
134+
}
135+
} else {
136+
throw new InvalidParameterValueException("Unable to find project by ID");
137+
}
138+
}
139+
140+
return caller.getAccountId();
141+
}
142+
143+
/////////////////////////////////////////////////////
144+
/////////////// API Implementation///////////////////
145+
/////////////////////////////////////////////////////
146+
147+
@Override
148+
public String getCommandName() {
149+
return s_name;
150+
}
151+
152+
@Override
153+
public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException {
154+
IpAddress result = _networkService.reserveIpAddress(_accountService.getAccount(getEntityOwnerId()), isDisplay(), getIpAddressId());
155+
if (result != null) {
156+
IPAddressResponse ipResponse = _responseGenerator.createIPAddressResponse(getResponseView(), result);
157+
ipResponse.setResponseName(getCommandName());
158+
setResponseObject(ipResponse);
159+
} else {
160+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to reserve IP address");
161+
}
162+
}
163+
164+
}

engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,6 @@ public interface IPAddressDao extends GenericDao<IPAddressVO, Long> {
9292
List<IPAddressVO> listByAssociatedVmId(long vmId);
9393

9494
IPAddressVO findByVmIdAndNetworkId(long networkId, long vmId);
95+
96+
IPAddressVO findByAccountIdAndZoneIdAndStateAndIpAddress(long accountId, long dcId, State state, String ipAddress);
9597
}

engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen
6262
@Inject
6363
protected VlanDao _vlanDao;
6464
protected GenericSearchBuilder<IPAddressVO, Long> CountFreePublicIps;
65+
protected SearchBuilder<IPAddressVO> PublicIpSearchByAccountAndState;
6566
@Inject
6667
ResourceTagDao _tagsDao;
6768
@Inject
@@ -138,6 +139,7 @@ public void init() {
138139
AllocatedIpCountForAccount.and("allocated", AllocatedIpCountForAccount.entity().getAllocatedTime(), Op.NNULL);
139140
AllocatedIpCountForAccount.and().op("network", AllocatedIpCountForAccount.entity().getAssociatedWithNetworkId(), Op.NNULL);
140141
AllocatedIpCountForAccount.or("vpc", AllocatedIpCountForAccount.entity().getVpcId(), Op.NNULL);
142+
AllocatedIpCountForAccount.or("state", AllocatedIpCountForAccount.entity().getState(), Op.EQ);
141143
AllocatedIpCountForAccount.cp();AllocatedIpCountForAccount.done();
142144

143145
CountFreePublicIps = createSearchBuilder(Long.class);
@@ -152,6 +154,13 @@ public void init() {
152154
DeleteAllExceptGivenIp = createSearchBuilder();
153155
DeleteAllExceptGivenIp.and("vlanDbId", DeleteAllExceptGivenIp.entity().getVlanId(), Op.EQ);
154156
DeleteAllExceptGivenIp.and("ip", DeleteAllExceptGivenIp.entity().getAddress(), Op.NEQ);
157+
158+
PublicIpSearchByAccountAndState = createSearchBuilder();
159+
PublicIpSearchByAccountAndState.and("accountId", PublicIpSearchByAccountAndState.entity().getAllocatedToAccountId(), Op.EQ);
160+
PublicIpSearchByAccountAndState.and("dcId", PublicIpSearchByAccountAndState.entity().getDataCenterId(), Op.EQ);
161+
PublicIpSearchByAccountAndState.and("state", PublicIpSearchByAccountAndState.entity().getState(), Op.EQ);
162+
PublicIpSearchByAccountAndState.and("allocated", PublicIpSearchByAccountAndState.entity().getAllocatedTime(), Op.NNULL);
163+
PublicIpSearchByAccountAndState.and("ipAddress", PublicIpSearchByAccountAndState.entity().getAddress(), Op.EQ);
155164
}
156165

157166
@Override
@@ -368,6 +377,7 @@ public IPAddressVO markAsUnavailable(long ipAddressId) {
368377
public long countAllocatedIPsForAccount(long accountId) {
369378
SearchCriteria<Long> sc = AllocatedIpCountForAccount.create();
370379
sc.setParameters("account", accountId);
380+
sc.setParameters("state", State.Reserved);
371381
return customSearch(sc, null).get(0);
372382
}
373383

@@ -480,4 +490,14 @@ public List<IPAddressVO> listByVlanIdAndState(long vlanId, State state) {
480490
sc.setParameters("state", state);
481491
return listBy(sc);
482492
}
493+
494+
@Override
495+
public IPAddressVO findByAccountIdAndZoneIdAndStateAndIpAddress(long accountId, long dcId, State state, String ipAddress) {
496+
SearchCriteria<IPAddressVO> sc = PublicIpSearchByAccountAndState.create();
497+
sc.setParameters("accountId", accountId);
498+
sc.setParameters("dcId", dcId);
499+
sc.setParameters("state", state);
500+
sc.setParameters("ipAddress", ipAddress);
501+
return findOneBy(sc);
502+
}
483503
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,13 @@ public IpAddress allocateIp(final Account ipOwner, final boolean isSystem, Accou
12391239
s_logger.debug("Associate IP address lock acquired");
12401240
}
12411241

1242+
if (ipaddress != null) {
1243+
IPAddressVO ipAddr = _ipAddressDao.findByAccountIdAndZoneIdAndStateAndIpAddress(ipOwner.getId(), zone.getId(), State.Reserved, ipaddress);
1244+
if (ipAddr != null) {
1245+
return PublicIp.createFromAddrAndVlan(ipAddr, _vlanDao.findById(ipAddr.getVlanId()));
1246+
}
1247+
}
1248+
12421249
ip = Transaction.execute(new TransactionCallbackWithException<PublicIp, InsufficientAddressCapacityException>() {
12431250
@Override
12441251
public PublicIp doInTransaction(TransactionStatus status) throws InsufficientAddressCapacityException {
@@ -1401,7 +1408,7 @@ public IPAddressVO associateIPToGuestNetwork(long ipId, long networkId, boolean
14011408
}
14021409

14031410
if (ipToAssoc.getAssociatedWithNetworkId() != null) {
1404-
s_logger.debug("IP " + ipToAssoc + " is already associated with network id" + networkId);
1411+
s_logger.debug("IP " + ipToAssoc + " is already associated with network id=" + networkId);
14051412
return ipToAssoc;
14061413
}
14071414

@@ -1459,6 +1466,7 @@ public IPAddressVO associateIPToGuestNetwork(long ipId, long networkId, boolean
14591466

14601467
IPAddressVO ip = _ipAddressDao.findById(ipId);
14611468
//update ip address with networkId
1469+
ip.setState(State.Allocated);
14621470
ip.setAssociatedWithNetworkId(networkId);
14631471
ip.setSourceNat(isSourceNat);
14641472
_ipAddressDao.update(ipId, ip);

0 commit comments

Comments
 (0)