New API endpoint to update pod management network IP range (#5458)

* Add UpdatePodManagementNetwork api endpoint

* Checkstyle changes and added a few methods

* Minor Checkstyle change

* Refactor UpdatePodManagementNetworkIpRangeCmd.java

* Added missing parameters

* Cleanup

* Addressed the review comments

Co-authored-by: kioie <kioieddy@google.com>
Co-authored-by: kioie <kioi@outlook.com>
This commit is contained in:
sureshanaparti 2021-09-21 02:49:05 +05:30 committed by GitHub
parent 121a72c4fa
commit 34bd92259a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 365 additions and 1 deletions

View File

@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -204,6 +205,14 @@ public interface ConfigurationService {
*/
void deletePodIpRange(DeleteManagementNetworkIpRangeCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException;
/**
* Updates a mutually exclusive IP range in the pod.
* @param cmd - The command specifying pod ID, current Start IP, current End IP, new Start IP, new End IP.
* @throws com.cloud.exception.ConcurrentOperationException
* @return Success
*/
void updatePodIpRange(UpdatePodManagementNetworkIpRangeCmd cmd) throws ConcurrentOperationException;
/**
* Edits a pod in the database. Will not allow you to edit pods that are being used anywhere in the system.
*

View File

@ -328,6 +328,7 @@ public class EventTypes {
public static final String EVENT_MANAGEMENT_IP_RANGE_CREATE = "MANAGEMENT.IP.RANGE.CREATE";
public static final String EVENT_MANAGEMENT_IP_RANGE_DELETE = "MANAGEMENT.IP.RANGE.DELETE";
public static final String EVENT_MANAGEMENT_IP_RANGE_UPDATE = "MANAGEMENT.IP.RANGE.UPDATE";
public static final String EVENT_STORAGE_IP_RANGE_CREATE = "STORAGE.IP.RANGE.CREATE";
public static final String EVENT_STORAGE_IP_RANGE_DELETE = "STORAGE.IP.RANGE.DELETE";

View File

@ -98,6 +98,8 @@ public class ApiConstants {
public static final String CUSTOMIZED_IOPS = "customizediops";
public static final String CUSTOM_ID = "customid";
public static final String CUSTOM_JOB_ID = "customjobid";
public static final String CURRENT_START_IP = "currentstartip";
public static final String CURRENT_END_IP = "currentendip";
public static final String MIN_IOPS = "miniops";
public static final String MAX_IOPS = "maxiops";
public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve";
@ -252,6 +254,8 @@ public class ApiConstants {
public static final String NIC = "nic";
public static final String NIC_NETWORK_LIST = "nicnetworklist";
public static final String NIC_IP_ADDRESS_LIST = "nicipaddresslist";
public static final String NEW_START_IP = "newstartip";
public static final String NEW_END_IP = "newendip";
public static final String NUM_RETRIES = "numretries";
public static final String OFFER_HA = "offerha";
public static final String IS_SYSTEM_OFFERING = "issystem";

View File

@ -0,0 +1,154 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.PodResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.log4j.Logger;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
@APICommand(name = UpdatePodManagementNetworkIpRangeCmd.APINAME,
description = "Updates a management network IP range. Only allowed when no IPs are allocated.",
responseObject = SuccessResponse.class,
since = "4.16.0.0",
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
authorized = {RoleType.Admin})
public class UpdatePodManagementNetworkIpRangeCmd extends BaseAsyncCmd {
public static final Logger s_logger = Logger.getLogger(UpdatePodManagementNetworkIpRangeCmd.class);
public static final String APINAME = "updatePodManagementNetworkIpRange";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.POD_ID,
type = CommandType.UUID,
entityType = PodResponse.class,
required = true,
description = "UUID of POD, where the IP range belongs to.",
validations = {ApiArgValidator.PositiveNumber})
private Long podId;
@Parameter(name = ApiConstants.CURRENT_START_IP,
type = CommandType.STRING,
entityType = PodResponse.class,
required = true,
description = "The current starting IP address.",
validations = {ApiArgValidator.NotNullOrEmpty})
private String currentStartIp;
@Parameter(name = ApiConstants.CURRENT_END_IP,
type = CommandType.STRING,
entityType = PodResponse.class,
required = true,
description = "The current ending IP address.",
validations = {ApiArgValidator.NotNullOrEmpty})
private String currentEndIp;
@Parameter(name = ApiConstants.NEW_START_IP,
type = CommandType.STRING,
description = "The new starting IP address.",
validations = {ApiArgValidator.NotNullOrEmpty})
private String newStartIp;
@Parameter(name = ApiConstants.NEW_END_IP,
type = CommandType.STRING,
description = "The new ending IP address.",
validations = {ApiArgValidator.NotNullOrEmpty})
private String newEndIp;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getPodId() {
return podId;
}
public String getCurrentStartIP() {
return currentStartIp;
}
public String getCurrentEndIP() {
return currentEndIp;
}
public String getNewStartIP() {
return newStartIp;
}
public String getNewEndIP() {
return newEndIp;
}
@Override
public String getEventType() {
return EventTypes.EVENT_MANAGEMENT_IP_RANGE_UPDATE;
}
@Override
public String getEventDescription() {
return "Updating pod management IP range " + getNewStartIP() + "-" + getNewEndIP() + " of Pod: " + getPodId();
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
if (getNewStartIP() == null && getNewEndIP() == null) {
throw new InvalidParameterValueException("Either new starting IP address or new ending IP address must be specified");
}
try {
_configService.updatePodIpRange(this);
SuccessResponse response = new SuccessResponse(getCommandName());
this.setResponseObject(response);
} catch (ConcurrentOperationException ex) {
s_logger.warn("Exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
} catch (Exception e) {
s_logger.warn("Failed to update pod management IP range " + getNewStartIP() + "-" + getNewEndIP() + " of Pod: " + getPodId(), e);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
}

View File

@ -41,6 +41,8 @@ public interface DataCenterIpAddressDao extends GenericDao<DataCenterIpAddressVO
List<DataCenterIpAddressVO> listByPodIdDcId(long podId, long dcId);
List<DataCenterIpAddressVO> listIpAddressUsage(final long podId, final long dcId, final boolean onlyListAllocated);
int countIPs(long podId, long dcId, boolean onlyCountAllocated);
int countIPs(long dcId, boolean onlyCountAllocated);

View File

@ -21,7 +21,6 @@ import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.log4j.Logger;
@ -238,6 +237,17 @@ public class DataCenterIpAddressDaoImpl extends GenericDaoBase<DataCenterIpAddre
return listBy(sc);
}
@Override
public List<DataCenterIpAddressVO> listIpAddressUsage(final long podId, final long dcId, final boolean onlyListAllocated) {
SearchCriteria<DataCenterIpAddressVO> sc = createSearchCriteria();
if(onlyListAllocated) {
sc.addAnd("takenAt", SearchCriteria.Op.NNULL);
}
sc.addAnd("podId", SearchCriteria.Op.EQ, podId);
sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, dcId);
return listBy(sc);
}
@Override
public List<DataCenterIpAddressVO> listByPodIdDcIdIpAddress(long podId, long dcId, String ipAddress) {
SearchCriteria<DataCenterIpAddressVO> sc = AllFieldsSearch.create();

View File

@ -57,6 +57,7 @@ import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -1530,6 +1531,178 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
@Override
@DB
public void updatePodIpRange(final UpdatePodManagementNetworkIpRangeCmd cmd) throws ConcurrentOperationException {
final long podId = cmd.getPodId();
final HostPodVO pod = _podDao.findById(podId);
if (pod == null) {
throw new InvalidParameterValueException("Unable to find pod by id: " + podId);
}
final String currentStartIP = cmd.getCurrentStartIP();
final String currentEndIP = cmd.getCurrentEndIP();
String newStartIP = cmd.getNewStartIP();
String newEndIP = cmd.getNewEndIP();
if (newStartIP == null) {
newStartIP = currentStartIP;
}
if (newEndIP == null) {
newEndIP = currentEndIP;
}
if (newStartIP.equals(currentStartIP) && newEndIP.equals(currentEndIP)) {
throw new InvalidParameterValueException("New starting and ending IP address are the same as current starting and ending IP address");
}
final String[] existingPodIpRanges = pod.getDescription().split(",");
if (existingPodIpRanges.length == 0) {
throw new InvalidParameterValueException("The IP range cannot be found in the pod: " + podId + " since the existing IP range is empty.");
}
verifyIpRangeParameters(currentStartIP,currentEndIP);
verifyIpRangeParameters(newStartIP,newEndIP);
checkIpRangeContainsTakenAddresses(pod,currentStartIP,currentEndIP,newStartIP,newEndIP);
String vlan = verifyPodIpRangeExists(podId,existingPodIpRanges,currentStartIP,currentEndIP,newStartIP,newEndIP);
List<Long> currentIpRange = listAllIPsWithintheRange(currentStartIP,currentEndIP);
List<Long> newIpRange = listAllIPsWithintheRange(newStartIP,newEndIP);
try {
final String finalNewEndIP = newEndIP;
final String finalNewStartIP = newStartIP;
final Integer vlanId = vlan.equals(Vlan.UNTAGGED) ? null : Integer.parseInt(vlan);
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(final TransactionStatus status) {
final long zoneId = pod.getDataCenterId();
pod.setDescription(pod.getDescription().replace(currentStartIP + "-",
finalNewStartIP + "-").replace(currentEndIP, finalNewEndIP));
updatePodIpRangeInDb(zoneId,podId,vlanId,pod,newIpRange,currentIpRange);
}
});
} catch (final Exception e) {
s_logger.error("Unable to update Pod " + podId + " IP range due to " + e.getMessage(), e);
throw new CloudRuntimeException("Failed to update Pod " + podId + " IP range. Please contact Cloud Support.");
}
}
private String verifyPodIpRangeExists(long podId,String[] existingPodIpRanges, String currentStartIP,
String currentEndIP, String newStartIP, String newEndIP) {
boolean foundRange = false;
String vlan = null;
for (String podIpRange: existingPodIpRanges) {
final String[] existingPodIpRange = podIpRange.split("-");
if (existingPodIpRange.length > 1) {
if (!NetUtils.isValidIp4(existingPodIpRange[0]) || !NetUtils.isValidIp4(existingPodIpRange[1])) {
continue;
}
if (currentStartIP.equals(existingPodIpRange[0]) && currentEndIP.equals(existingPodIpRange[1])) {
foundRange = true;
vlan = existingPodIpRange[3];
}
if (!foundRange && NetUtils.ipRangesOverlap(newStartIP, newEndIP, existingPodIpRange[0], existingPodIpRange[1])) {
throw new InvalidParameterValueException("The Start and End IP address range: (" + newStartIP + "-" + newEndIP + ") overlap with the pod IP range: " + podIpRange);
}
}
}
if (!foundRange) {
throw new InvalidParameterValueException("The input IP range: " + currentStartIP + "-" + currentEndIP + " of pod: " + podId + " is not present. Please input an existing range.");
}
return vlan;
}
private void updatePodIpRangeInDb (long zoneId, long podId, Integer vlanId, HostPodVO pod, List<Long> newIpRange, List<Long> currentIpRange) {
HostPodVO lock = null;
try {
lock = _podDao.acquireInLockTable(podId);
if (lock == null) {
String msg = "Unable to acquire lock on table to update the ip range of POD: " + pod.getName() + ", Update failed.";
s_logger.warn(msg);
throw new CloudRuntimeException(msg);
}
List<Long> iPaddressesToAdd = new ArrayList(newIpRange);
iPaddressesToAdd.removeAll(currentIpRange);
if (iPaddressesToAdd.size() > 0) {
for (Long startIP : iPaddressesToAdd) {
_zoneDao.addPrivateIpAddress(zoneId, podId, NetUtils.long2Ip(startIP), NetUtils.long2Ip(startIP), false, vlanId);
}
} else {
currentIpRange.removeAll(newIpRange);
if (currentIpRange.size() > 0) {
for (Long startIP: currentIpRange) {
if (!_privateIpAddressDao.deleteIpAddressByPodDc(NetUtils.long2Ip(startIP),podId,zoneId)) {
throw new CloudRuntimeException("Failed to remove private ip address: " + NetUtils.long2Ip(startIP) + " of Pod: " + podId + " DC: " + pod.getDataCenterId());
}
}
}
}
_podDao.update(podId, pod);
} catch (final Exception e) {
s_logger.error("Unable to update Pod " + podId + " IP range due to database error " + e.getMessage(), e);
throw new CloudRuntimeException("Failed to update Pod " + podId + " IP range. Please contact Cloud Support.");
} finally {
if (lock != null) {
_podDao.releaseFromLockTable(podId);
}
}
}
private List<Long> listAllIPsWithintheRange(String startIp, String endIP) {
verifyIpRangeParameters(startIp,endIP);
long startIpLong = NetUtils.ip2Long(startIp);
long endIpLong = NetUtils.ip2Long(endIP);
List<Long> listOfIpsinRange = new ArrayList<>();
while (startIpLong <= endIpLong) {
listOfIpsinRange.add(startIpLong);
startIpLong++;
}
return listOfIpsinRange;
}
private void verifyIpRangeParameters(String startIP, String endIp) {
if (!Strings.isNullOrEmpty(startIP) && !NetUtils.isValidIp4(startIP)) {
throw new InvalidParameterValueException("The current start address of the IP range " + startIP + " is not a valid IP address.");
}
if (!Strings.isNullOrEmpty(endIp) && !NetUtils.isValidIp4(endIp)) {
throw new InvalidParameterValueException("The current end address of the IP range " + endIp + " is not a valid IP address.");
}
if (NetUtils.ip2Long(startIP) > NetUtils.ip2Long(endIp)) {
throw new InvalidParameterValueException("The start IP address must have a lower value than the end IP address.");
}
}
private void checkIpRangeContainsTakenAddresses(final HostPodVO pod,final String currentStartIP,
final String currentEndIP,final String newStartIp, final String newEndIp) {
List<Long> newIpRange = listAllIPsWithintheRange(newStartIp,newEndIp);
List<Long> currentIpRange = listAllIPsWithintheRange(currentStartIP,currentEndIP);
List<Long> takenIpsList = new ArrayList<>();
final List<DataCenterIpAddressVO> takenIps = _privateIpAddressDao.listIpAddressUsage(pod.getId(),pod.getDataCenterId(),true);
for (DataCenterIpAddressVO takenIp : takenIps) {
takenIpsList.add(NetUtils.ip2Long(takenIp.getIpAddress()));
}
takenIpsList.retainAll(currentIpRange);
if (!newIpRange.containsAll(takenIpsList)) {
throw new InvalidParameterValueException("The IP range does not contain some IP addresses that have "
+ "already been taken. Please adjust your IP range to include all IP addresses already taken.");
}
}
@Override
public Pod editPod(final UpdatePodCmd cmd) {
return editPod(cmd.getId(), cmd.getPodName(), null, null, cmd.getGateway(), cmd.getNetmask(), cmd.getAllocationState());

View File

@ -148,6 +148,7 @@ import org.apache.cloudstack.api.command.admin.network.UpdateNetworkCmdByAdmin;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkServiceProviderCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePhysicalNetworkCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateStorageNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
@ -3052,6 +3053,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(UpdateRegionCmd.class);
cmdList.add(ListAlertsCmd.class);
cmdList.add(ListCapacityCmd.class);
cmdList.add(UpdatePodManagementNetworkIpRangeCmd.class);
cmdList.add(UploadCustomCertificateCmd.class);
cmdList.add(ConfigureVirtualRouterElementCmd.class);
cmdList.add(CreateVirtualRouterElementCmd.class);

View File

@ -29,6 +29,7 @@ import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -204,6 +205,14 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
return;
}
/* (non-Javadoc)
* @see com.cloud.configuration.ConfigurationService#updatePodIpRange(org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd)
*/
public void updatePodIpRange(final UpdatePodManagementNetworkIpRangeCmd cmd) throws ConcurrentOperationException {
// TODO Auto-generated method stub
return;
}
/* (non-Javadoc)
* @see com.cloud.configuration.ConfigurationService#editPod(org.apache.cloudstack.api.commands.UpdatePodCmd)
*/