Add New API endpoint: UpdateVlanIpRange (#5411)

* Added Logic to update the user_ip_address table

* Edited ConfigurationManagerImpl

* Refactor UpdateVlanIpRangeCmd location

* Checkstyle corrections

* Mock updateVlanAndPublicIpRange

* Changes:
- UpdateVlanIpRangeCmd - changed since to 4.15.0
- ConfigurationService - Updated Javadoc
- ConfigurationManager - Updated Javadoc
- Added Unit tests
- Added license
- Update server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
- fix some bugs in #5411 and add support for ipv6 and forsystemvms
- fix #5411: disallow forsystemvms if ip range is dedicated
- update #5411: ui changes
- update #5411: support gateway/netmask change
- update #5411: change to sync call and fix bugs

Co-authored-by: kioie <kioieddy@google.com>
Co-authored-by: kioie <kioi@outlook.com>
Co-authored-by: dahn <daan.hoogland@gmail.com>
This commit is contained in:
Wei Zhou 2021-09-20 07:26:29 +02:00 committed by GitHub
parent 3b4523f22a
commit 747608f75f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 771 additions and 29 deletions

View File

@ -39,6 +39,7 @@ import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.UpdateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
@ -272,6 +273,12 @@ public interface ConfigurationService {
Vlan createVlanAndPublicIpRange(CreateVlanIpRangeCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException,
ResourceAllocationException;
/**
* Updates the IP address Range for the VLAN on the database
* @return The Updated Vlan Object
*/
Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOperationException,
ResourceUnavailableException, ResourceAllocationException;
/**
* Marks the the account with the default zone-id.
*

View File

@ -324,6 +324,7 @@ public class EventTypes {
public static final String EVENT_VLAN_IP_RANGE_DELETE = "VLAN.IP.RANGE.DELETE";
public static final String EVENT_VLAN_IP_RANGE_DEDICATE = "VLAN.IP.RANGE.DEDICATE";
public static final String EVENT_VLAN_IP_RANGE_RELEASE = "VLAN.IP.RANGE.RELEASE";
public static final String EVENT_VLAN_IP_RANGE_UPDATE = "VLAN.IP.RANGE.UPDATE";
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";

View File

@ -0,0 +1,167 @@
// 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.vlan;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VlanIpRangeResponse;
import org.apache.log4j.Logger;
import com.cloud.dc.Vlan;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.utils.net.NetUtils;
@APICommand(name = UpdateVlanIpRangeCmd.APINAME, description = "Updates a VLAN IP range.", responseObject =
VlanIpRangeResponse.class, since = "4.16.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
authorized = {RoleType.Admin})
public class UpdateVlanIpRangeCmd extends BaseCmd {
public static final String APINAME = "updateVlanIpRange";
public static final Logger s_logger = Logger.getLogger(UpdateVlanIpRangeCmd.class.getName());
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VlanIpRangeResponse.class, required = true,
description = "the UUID of the VLAN IP range")
private Long id;
@Parameter(name = ApiConstants.GATEWAY, type = CommandType.STRING, description = "the gateway of the VLAN IP range")
private String gateway;
@Parameter(name = ApiConstants.NETMASK, type = CommandType.STRING, description = "the netmask of the VLAN IP range")
private String netmask;
@Parameter(name = ApiConstants.START_IP, type = CommandType.STRING, description = "the beginning IP address in the VLAN IP range")
private String startIp;
@Parameter(name = ApiConstants.END_IP, type = CommandType.STRING,
description = "the ending IP address in the VLAN IP range")
private String endIp;
@Parameter(name = ApiConstants.START_IPV6, type = CommandType.STRING, description = "the beginning IPv6 address in the IPv6 network range")
private String startIpv6;
@Parameter(name = ApiConstants.END_IPV6, type = CommandType.STRING, description = "the ending IPv6 address in the IPv6 network range")
private String endIpv6;
@Parameter(name = ApiConstants.IP6_GATEWAY, type = CommandType.STRING, description = "the gateway of the IPv6 network")
private String ip6Gateway;
@Parameter(name = ApiConstants.IP6_CIDR, type = CommandType.STRING, description = "the CIDR of IPv6 network, must be at least /64")
private String ip6Cidr;
@Parameter(name = ApiConstants.FOR_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "true if IP range is set to system vms, false if not")
private Boolean forSystemVms;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getGateway() {
return gateway;
}
public String getEndIp() {
return endIp;
}
public String getNetmask() {
return netmask;
}
public String getStartIp() {
return startIp;
}
public String getStartIpv6() {
if (startIpv6 == null) {
return null;
}
return NetUtils.standardizeIp6Address(startIpv6);
}
public String getEndIpv6() {
if (endIpv6 == null) {
return null;
}
return NetUtils.standardizeIp6Address(endIpv6);
}
public String getIp6Gateway() {
if (ip6Gateway == null) {
return null;
}
return NetUtils.standardizeIp6Address(ip6Gateway);
}
public String getIp6Cidr() {
if (ip6Cidr == null) {
return null;
}
return NetUtils.standardizeIp6Cidr(ip6Cidr);
}
public Boolean isForSystemVms() {
return forSystemVms;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, ResourceAllocationException {
try {
Vlan result = _configService.updateVlanAndPublicIpRange(this);
if (result != null) {
VlanIpRangeResponse response = _responseGenerator.createVlanIpRangeResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to Update vlan ip range");
}
} catch (ConcurrentOperationException ex) {
s_logger.warn("Exception: ", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
}

View File

@ -0,0 +1,78 @@
// 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.vlan;
import junit.framework.TestCase;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VlanIpRangeResponse;
import org.junit.Test;
import org.mockito.Mockito;
import com.cloud.configuration.ConfigurationService;
import com.cloud.dc.Vlan;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
public class UpdateVlanIpRangeCmdTest extends TestCase {
private UpdateVlanIpRangeCmd updateVlanIpRangeCmd;
private ResponseGenerator responseGenerator;
@Test
public void testUpdateSuccess() throws Exception {
ConfigurationService configService = Mockito.mock(ConfigurationService.class);
Vlan result = Mockito.mock(Vlan.class);
responseGenerator = Mockito.mock(ResponseGenerator.class);
updateVlanIpRangeCmd = new UpdateVlanIpRangeCmd();
Mockito.when(configService.updateVlanAndPublicIpRange(updateVlanIpRangeCmd)).thenReturn(result);
updateVlanIpRangeCmd._configService = configService;
VlanIpRangeResponse ipRes = Mockito.mock(VlanIpRangeResponse.class);
Mockito.when(responseGenerator.createVlanIpRangeResponse(result)).thenReturn(ipRes);
updateVlanIpRangeCmd._responseGenerator = responseGenerator;
try {
updateVlanIpRangeCmd.execute();
} catch (ServerApiException ex) {
assertEquals("Failed to Update vlan ip range", ex.getMessage());
}
}
@Test
public void testUpdateFailure() throws ResourceAllocationException, ResourceUnavailableException {
ConfigurationService configService = Mockito.mock(ConfigurationService.class);
responseGenerator = Mockito.mock(ResponseGenerator.class);
updateVlanIpRangeCmd = new UpdateVlanIpRangeCmd();
updateVlanIpRangeCmd._configService = configService;
Mockito.when(configService.updateVlanAndPublicIpRange(updateVlanIpRangeCmd)).thenReturn(null);
try {
updateVlanIpRangeCmd.execute();
} catch (ServerApiException ex) {
assertEquals("Failed to Update vlan ip range", ex.getMessage());
}
}
}

View File

@ -118,11 +118,19 @@ public class VlanVO implements Vlan {
return vlanGateway;
}
public void setVlanGateway(String vlanGateway) {
this.vlanGateway = vlanGateway;
}
@Override
public String getVlanNetmask() {
return vlanNetmask;
}
public void setVlanNetmask(String vlanNetmask) {
this.vlanNetmask = vlanNetmask;
}
@Override
public long getDataCenterId() {
return dataCenterId;
@ -234,6 +242,6 @@ public class VlanVO implements Vlan {
}
public void setIpRange(String ipRange) {
this.ip6Range = ipRange;
this.ipRange = ipRange;
}
}

View File

@ -19,6 +19,7 @@ package com.cloud.network.dao;
import java.util.List;
import com.cloud.dc.Vlan.VlanType;
import com.cloud.network.IpAddress.State;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.net.Ip;
@ -32,6 +33,8 @@ public interface IPAddressDao extends GenericDao<IPAddressVO, Long> {
List<IPAddressVO> listByVlanId(long vlanId);
List<IPAddressVO> listByVlanIdAndState(long vlanId, State state);
List<IPAddressVO> listByDcIdIpAddress(long dcId, String ipAddress);
List<IPAddressVO> listByDcId(long dcId);

View File

@ -78,6 +78,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen
AllFieldsSearch.and("dataCenterId", AllFieldsSearch.entity().getDataCenterId(), Op.EQ);
AllFieldsSearch.and("ipAddress", AllFieldsSearch.entity().getAddress(), Op.EQ);
AllFieldsSearch.and("vlan", AllFieldsSearch.entity().getVlanId(), Op.EQ);
AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), Op.EQ);
AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAllocatedToAccountId(), Op.EQ);
AllFieldsSearch.and("sourceNat", AllFieldsSearch.entity().isSourceNat(), Op.EQ);
AllFieldsSearch.and("network", AllFieldsSearch.entity().getAssociatedWithNetworkId(), Op.EQ);
@ -471,4 +472,12 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen
sc.setParameters("vlan", vlandbId);
lockRows(sc, null, true);
}
@Override
public List<IPAddressVO> listByVlanIdAndState(long vlanId, State state) {
SearchCriteria<IPAddressVO> sc = AllFieldsSearch.create();
sc.setParameters("vlan", vlanId);
sc.setParameters("state", state);
return listBy(sc);
}
}

View File

@ -18,6 +18,7 @@ package com.cloud.network.dao;
import java.util.List;
import com.cloud.network.IpAddress;
import com.cloud.network.UserIpv6AddressVO;
import com.cloud.utils.db.GenericDao;
@ -26,6 +27,8 @@ public interface UserIpv6AddressDao extends GenericDao<UserIpv6AddressVO, Long>
List<UserIpv6AddressVO> listByVlanId(long vlanId);
List<UserIpv6AddressVO> listByVlanIdAndState(long vlanId, IpAddress.State state);
List<UserIpv6AddressVO> listByDcId(long dcId);
List<UserIpv6AddressVO> listByNetwork(long networkId);

View File

@ -19,6 +19,7 @@ package com.cloud.network.dao;
import java.util.List;
import com.cloud.network.IpAddress;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -43,6 +44,7 @@ public class UserIpv6AddressDaoImpl extends GenericDaoBase<UserIpv6AddressVO, Lo
AllFieldsSearch.and("dataCenterId", AllFieldsSearch.entity().getDataCenterId(), Op.EQ);
AllFieldsSearch.and("ipAddress", AllFieldsSearch.entity().getAddress(), Op.EQ);
AllFieldsSearch.and("vlan", AllFieldsSearch.entity().getVlanId(), Op.EQ);
AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), Op.EQ);
AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), Op.EQ);
AllFieldsSearch.and("network", AllFieldsSearch.entity().getNetworkId(), Op.EQ);
AllFieldsSearch.and("physicalNetworkId", AllFieldsSearch.entity().getPhysicalNetworkId(), Op.EQ);
@ -69,6 +71,14 @@ public class UserIpv6AddressDaoImpl extends GenericDaoBase<UserIpv6AddressVO, Lo
return listBy(sc);
}
@Override
public List<UserIpv6AddressVO> listByVlanIdAndState(long vlanId, IpAddress.State state) {
SearchCriteria<UserIpv6AddressVO> sc = AllFieldsSearch.create();
sc.setParameters("vlan", vlanId);
sc.setParameters("state", state);
return listBy(sc);
}
@Override
public List<UserIpv6AddressVO> listByDcId(long dcId) {
SearchCriteria<UserIpv6AddressVO> sc = AllFieldsSearch.create();

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -35,10 +36,12 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.googlecode.ipv6.IPv6Address;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupService;
@ -69,6 +72,7 @@ import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.UpdateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
@ -167,6 +171,7 @@ import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostTagsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.IpAddress;
import com.cloud.network.IpAddressManager;
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
@ -178,6 +183,7 @@ import com.cloud.network.NetworkService;
import com.cloud.network.Networks.BroadcastDomainType;
import com.cloud.network.Networks.TrafficType;
import com.cloud.network.PhysicalNetwork;
import com.cloud.network.UserIpv6AddressVO;
import com.cloud.network.dao.FirewallRulesDao;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
@ -187,6 +193,7 @@ import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao;
import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.dao.UserIpv6AddressDao;
import com.cloud.network.rules.LoadBalancerContainer.Scheme;
import com.cloud.network.vpc.VpcManager;
import com.cloud.offering.DiskOffering;
@ -403,7 +410,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
StoragePoolTagsDao storagePoolTagDao;
@Inject
private AnnotationDao annotationDao;
@Inject
UserIpv6AddressDao _ipv6Dao;
// FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao?
@Inject
@ -4239,6 +4247,260 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
@Override
public Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOperationException,
ResourceUnavailableException,ResourceAllocationException {
return updateVlanAndPublicIpRange(cmd.getId(), cmd.getStartIp(),cmd.getEndIp(), cmd.getGateway(),cmd.getNetmask(),
cmd.getStartIpv6(), cmd.getEndIpv6(), cmd.getIp6Gateway(), cmd.getIp6Cidr(), cmd.isForSystemVms());
}
@DB
@ActionEvent(eventType = EventTypes.EVENT_VLAN_IP_RANGE_UPDATE, eventDescription = "update vlan ip Range", async
= false)
public Vlan updateVlanAndPublicIpRange(final long id, String startIp,
String endIp,
String gateway,
String netmask,
String startIpv6,
String endIpv6,
String ip6Gateway,
String ip6Cidr,
Boolean forSystemVms) throws ConcurrentOperationException {
VlanVO vlanRange = _vlanDao.findById(id);
if (vlanRange == null) {
throw new InvalidParameterValueException("Please specify a valid IP range id.");
}
final boolean ipv4 = vlanRange.getVlanGateway() != null;
final boolean ipv6 = vlanRange.getIp6Gateway() != null;
if (!ipv4) {
if (startIp != null || endIp != null || gateway != null || netmask != null) {
throw new InvalidParameterValueException("IPv4 is not support in this IP range.");
}
}
if (!ipv6) {
if (startIpv6 != null || endIpv6 != null || ip6Gateway != null || ip6Cidr != null) {
throw new InvalidParameterValueException("IPv6 is not support in this IP range.");
}
}
final Boolean isRangeForSystemVM = checkIfVlanRangeIsForSystemVM(id);
if (forSystemVms != null && isRangeForSystemVM != forSystemVms) {
if (VlanType.DirectAttached.equals(vlanRange.getVlanType())) {
throw new InvalidParameterValueException("forSystemVms is not available for this IP range with vlan type: " + VlanType.DirectAttached);
}
// Check if range has already been dedicated
final List<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByVlan(id);
if (maps != null && !maps.isEmpty()) {
throw new InvalidParameterValueException("Specified Public IP range has already been dedicated to an account");
}
List<DomainVlanMapVO> domainmaps = _domainVlanMapDao.listDomainVlanMapsByVlan(id);
if (domainmaps != null && !domainmaps.isEmpty()) {
throw new InvalidParameterValueException("Specified Public IP range has already been dedicated to a domain");
}
}
if (ipv4) {
updateVlanAndIpv4Range(id, vlanRange, startIp, endIp, gateway, netmask, isRangeForSystemVM, forSystemVms);
}
if (ipv6) {
updateVlanAndIpv6Range(id, vlanRange, startIpv6, endIpv6, ip6Gateway, ip6Cidr, isRangeForSystemVM, forSystemVms);
}
return _vlanDao.findById(id);
}
private void updateVlanAndIpv4Range(final long id, final VlanVO vlanRange,
String startIp,
String endIp,
String gateway,
String netmask,
final Boolean isRangeForSystemVM,
final Boolean forSystemVms) {
final List<IPAddressVO> listAllocatedIPs = _publicIpAddressDao.listByVlanIdAndState(id, IpAddress.State.Allocated);
if (gateway != null && !gateway.equals(vlanRange.getVlanGateway()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) {
throw new InvalidParameterValueException(String.format("Unable to change gateway to %s because some IPs are in use", gateway));
}
if (netmask != null && !netmask.equals(vlanRange.getVlanNetmask()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) {
throw new InvalidParameterValueException(String.format("Unable to change netmask to %s because some IPs are in use", netmask));
}
gateway = MoreObjects.firstNonNull(gateway, vlanRange.getVlanGateway());
netmask = MoreObjects.firstNonNull(netmask, vlanRange.getVlanNetmask());
final String[] existingVlanIPRangeArray = vlanRange.getIpRange().split("-");
final String currentStartIP = existingVlanIPRangeArray[0];
final String currentEndIP = existingVlanIPRangeArray[1];
startIp = MoreObjects.firstNonNull(startIp, currentStartIP);
endIp = MoreObjects.firstNonNull(endIp, currentEndIP);
final String cidr = NetUtils.ipAndNetMaskToCidr(gateway, netmask);
if (Strings.isNullOrEmpty(cidr)) {
throw new InvalidParameterValueException(String.format("Invalid gateway (%s) or netmask (%s)", gateway, netmask));
}
final String cidrAddress = getCidrAddress(cidr);
final long cidrSize = getCidrSize(cidr);
checkIpRange(startIp, endIp, cidrAddress, cidrSize);
checkGatewayOverlap(startIp, endIp, gateway);
checkAllocatedIpsAreWithinVlanRange(listAllocatedIPs, startIp, endIp, forSystemVms);
try {
final String newStartIP = startIp;
final String newEndIP = endIp;
VlanVO range = _vlanDao.acquireInLockTable(id, 30);
if (range == null) {
throw new CloudRuntimeException("Unable to acquire vlan configuration: " + id);
}
if (s_logger.isDebugEnabled()) {
s_logger.debug("lock vlan " + id + " is acquired");
}
commitUpdateVlanAndIpRange(id, newStartIP, newEndIP, currentStartIP, currentEndIP, gateway, netmask,true, isRangeForSystemVM, forSystemVms);
} catch (final Exception e) {
s_logger.error("Unable to edit VlanRange due to " + e.getMessage(), e);
throw new CloudRuntimeException("Failed to edit VlanRange. Please contact Cloud Support.");
} finally {
_vlanDao.releaseFromLockTable(id);
}
}
private void updateVlanAndIpv6Range(final long id, final VlanVO vlanRange,
String startIpv6,
String endIpv6,
String ip6Gateway,
String ip6Cidr,
final Boolean isRangeForSystemVM,
final Boolean forSystemVms) {
final List<UserIpv6AddressVO> listAllocatedIPs = _ipv6Dao.listByVlanIdAndState(id, IpAddress.State.Allocated);
if (ip6Gateway != null && !ip6Gateway.equals(vlanRange.getIp6Gateway()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) {
throw new InvalidParameterValueException(String.format("Unable to change ipv6 gateway to %s because some IPs are in use", ip6Gateway));
}
if (ip6Cidr != null && !ip6Cidr.equals(vlanRange.getIp6Cidr()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) {
throw new InvalidParameterValueException(String.format("Unable to change ipv6 cidr to %s because some IPs are in use", ip6Cidr));
}
ip6Gateway = MoreObjects.firstNonNull(ip6Gateway, vlanRange.getIp6Gateway());
ip6Cidr = MoreObjects.firstNonNull(ip6Cidr, vlanRange.getIp6Cidr());
final String[] existingVlanIPRangeArray = vlanRange.getIp6Range().split("-");
final String currentStartIPv6 = existingVlanIPRangeArray[0];
final String currentEndIPv6 = existingVlanIPRangeArray[1];
startIpv6 = MoreObjects.firstNonNull(startIpv6, currentStartIPv6);
endIpv6 = MoreObjects.firstNonNull(endIpv6, currentEndIPv6);
if (startIpv6 != currentStartIPv6 || endIpv6 != currentEndIPv6) {
_networkModel.checkIp6Parameters(startIpv6, endIpv6, ip6Gateway, ip6Cidr);
checkAllocatedIpv6sAreWithinVlanRange(listAllocatedIPs, startIpv6, endIpv6);
try {
VlanVO range = _vlanDao.acquireInLockTable(id, 30);
if (range == null) {
throw new CloudRuntimeException("Unable to acquire vlan configuration: " + id);
}
if (s_logger.isDebugEnabled()) {
s_logger.debug("lock vlan " + id + " is acquired");
}
commitUpdateVlanAndIpRange(id, startIpv6, endIpv6, currentStartIPv6, currentEndIPv6, ip6Gateway, ip6Cidr, false, isRangeForSystemVM,forSystemVms);
} catch (final Exception e) {
s_logger.error("Unable to edit VlanRange due to " + e.getMessage(), e);
throw new CloudRuntimeException("Failed to edit VlanRange. Please contact Cloud Support.");
} finally {
_vlanDao.releaseFromLockTable(id);
}
}
}
private VlanVO commitUpdateVlanAndIpRange(final Long id, final String newStartIP, final String newEndIP, final String currentStartIP, final String currentEndIP,
final String gateway, final String netmask,
final boolean ipv4, final Boolean isRangeForSystemVM, final Boolean forSystemvms) {
return Transaction.execute(new TransactionCallback<VlanVO>() {
@Override
public VlanVO doInTransaction(final TransactionStatus status) {
VlanVO vlanRange = _vlanDao.findById(id);
s_logger.debug("Updating vlan range " + vlanRange.getId());
if (ipv4) {
vlanRange.setIpRange(newStartIP + "-" + newEndIP);
vlanRange.setVlanGateway(gateway);
vlanRange.setVlanNetmask(netmask);
_vlanDao.update(vlanRange.getId(), vlanRange);
if (!updatePublicIPRange(newStartIP, currentStartIP, newEndIP, currentEndIP, vlanRange.getDataCenterId(), vlanRange.getId(), vlanRange.getNetworkId(), vlanRange.getPhysicalNetworkId(), isRangeForSystemVM, forSystemvms)) {
throw new CloudRuntimeException("Failed to update IPv4 range. Please contact Cloud Support.");
}
} else {
vlanRange.setIp6Range(newStartIP + "-" + newEndIP);
vlanRange.setIp6Gateway(gateway);
vlanRange.setIp6Cidr(netmask);
_vlanDao.update(vlanRange.getId(), vlanRange);
}
return vlanRange;
}
});
}
private boolean checkIfVlanRangeIsForSystemVM(final long vlanId) {
List<IPAddressVO> existingPublicIPs = _publicIpAddressDao.listByVlanId(vlanId);
boolean initialIsSystemVmValue = existingPublicIPs.get(0).isForSystemVms();
for (IPAddressVO existingIPs : existingPublicIPs) {
if (initialIsSystemVmValue != existingIPs.isForSystemVms()) {
throw new CloudRuntimeException("Your \"For System VM\" value seems to be inconsistent with the rest of the records. Please contact Cloud Support");
}
}
return initialIsSystemVmValue;
}
private void checkAllocatedIpsAreWithinVlanRange
(List<IPAddressVO> listAllocatedIPs, String startIp, String endIp, Boolean forSystemVms) {
Collections.sort(listAllocatedIPs, Comparator.comparing(IPAddressVO::getAddress));
for (IPAddressVO allocatedIP : listAllocatedIPs) {
if ((!Strings.isNullOrEmpty(startIp) && NetUtils.ip2Long(startIp) > NetUtils.ip2Long(allocatedIP.getAddress().addr()))
|| (!Strings.isNullOrEmpty(endIp) && NetUtils.ip2Long(endIp) < NetUtils.ip2Long(allocatedIP.getAddress().addr()))) {
throw new InvalidParameterValueException(String.format("The start IP address must be less than or equal to %s which is already in use. "
+ "The end IP address must be greater than or equal to %s which is already in use. "
+ "There are %d IPs already allocated in this range.",
listAllocatedIPs.get(0).getAddress(), listAllocatedIPs.get(listAllocatedIPs.size() - 1).getAddress(), listAllocatedIPs.size()));
}
if (forSystemVms != null && allocatedIP.isForSystemVms() != forSystemVms) {
throw new InvalidParameterValueException(String.format("IP %s is in use, cannot change forSystemVms of the IP range", allocatedIP.getAddress().addr()));
}
}
}
private void checkAllocatedIpv6sAreWithinVlanRange(List<UserIpv6AddressVO> listAllocatedIPs, String startIpv6, String endIpv6) {
Collections.sort(listAllocatedIPs, Comparator.comparing(UserIpv6AddressVO::getAddress));
for (UserIpv6AddressVO allocatedIP : listAllocatedIPs) {
if ((!Strings.isNullOrEmpty(startIpv6)
&& IPv6Address.fromString(startIpv6).toBigInteger().compareTo(IPv6Address.fromString(allocatedIP.getAddress()).toBigInteger()) > 0)
|| (!Strings.isNullOrEmpty(endIpv6)
&& IPv6Address.fromString(endIpv6).toBigInteger().compareTo(IPv6Address.fromString(allocatedIP.getAddress()).toBigInteger()) < 0)) {
throw new InvalidParameterValueException(String.format("The start IPv6 address must be less than or equal to %s which is already in use. "
+ "The end IPv6 address must be greater than or equal to %s which is already in use. "
+ "There are %d IPv6 addresses already allocated in this range.",
listAllocatedIPs.get(0).getAddress(), listAllocatedIPs.get(listAllocatedIPs.size() - 1).getAddress(), listAllocatedIPs.size()));
}
}
}
private void checkGatewayOverlap(String startIp, String endIp, String gateway) {
if (NetUtils.ipRangesOverlap(startIp, endIp, gateway, gateway)) {
throw new InvalidParameterValueException("The gateway shouldn't overlap the new start/end ip "
+ "addresses");
}
}
@Override
@DB
public boolean deleteVlanAndPublicIpRange(final long userId, final long vlanDbId, final Account caller) {
@ -4292,7 +4554,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
throw new InvalidParameterValueException("Can't delete account specific vlan " + vlanDbId + " as ip " + ip
+ " belonging to the range has firewall rules applied. Cleanup the rules first");
}
if (ip.getAllocatedTime() != null) {// This means IP is allocated
if (ip.getAllocatedTime() != null) {
// This means IP is allocated
// release public ip address here
success = _ipAddrMgr.disassociatePublicIpAddress(ip.getId(), userId, caller);
}
@ -4568,6 +4831,59 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
});
return CollectionUtils.isEmpty(problemIps);
}
@DB
protected boolean updatePublicIPRange(final String newStartIP, final String currentStartIP, final String newEndIP, final String currentEndIP, final long zoneId, final long vlanDbId, final long sourceNetworkid, final long physicalNetworkId, final boolean isRangeForSystemVM, final Boolean forSystemVms) {
long newStartIPLong = NetUtils.ip2Long(newStartIP);
long newEndIPLong = NetUtils.ip2Long(newEndIP);
long currentStartIPLong = NetUtils.ip2Long(currentStartIP);
long currentEndIPLong = NetUtils.ip2Long(currentEndIP);
List<Long> currentIPRange = new ArrayList<>();
List<Long> newIPRange = new ArrayList<>();
while (newStartIPLong <= newEndIPLong) {
newIPRange.add(newStartIPLong);
newStartIPLong++;
}
while (currentStartIPLong <= currentEndIPLong) {
currentIPRange.add(currentStartIPLong);
currentStartIPLong++;
}
final List<String> problemIps = Transaction.execute(new TransactionCallback<List<String>>() {
@Override
public List<String> doInTransaction(final TransactionStatus status) {
final IPRangeConfig config = new IPRangeConfig();
Vector<String> configResult = new Vector<>();
List<Long> ipAddressesToAdd = new ArrayList(newIPRange);
ipAddressesToAdd.removeAll(currentIPRange);
if (ipAddressesToAdd.size() > 0) {
for (Long startIP : ipAddressesToAdd) {
configResult.addAll(config.savePublicIPRange(TransactionLegacy.currentTxn(), startIP, startIP, zoneId, vlanDbId, sourceNetworkid, physicalNetworkId, forSystemVms != null ? forSystemVms : isRangeForSystemVM));
}
}
List<Long> ipAddressesToDelete = new ArrayList(currentIPRange);
ipAddressesToDelete.removeAll(newIPRange);
if (ipAddressesToDelete.size() > 0) {
for (Long startIP : ipAddressesToDelete) {
configResult.addAll(config.deletePublicIPRange(TransactionLegacy.currentTxn(), startIP, startIP, vlanDbId));
}
}
if (forSystemVms != null && isRangeForSystemVM != forSystemVms) {
List<Long> ipAddressesToUpdate = new ArrayList(currentIPRange);
ipAddressesToUpdate.removeAll(ipAddressesToDelete);
if (ipAddressesToUpdate.size() > 0) {
for (Long startIP : ipAddressesToUpdate) {
configResult.addAll(config.updatePublicIPRange(TransactionLegacy.currentTxn(), startIP, startIP, vlanDbId, forSystemVms));
}
}
}
return configResult;
}
});
return problemIps != null && problemIps.size() == 0;
}

View File

@ -262,6 +262,7 @@ import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ListVlanIpRangesCmd;
import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.UpdateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vm.AddNicToVMCmdByAdmin;
import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin;
@ -3105,6 +3106,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(RegisterCmd.class);
cmdList.add(UpdateUserCmd.class);
cmdList.add(CreateVlanIpRangeCmd.class);
cmdList.add(UpdateVlanIpRangeCmd.class);
cmdList.add(DeleteVlanIpRangeCmd.class);
cmdList.add(ListVlanIpRangesCmd.class);
cmdList.add(DedicatePublicIpRangeCmd.class);

View File

@ -313,9 +313,8 @@ public class IPRangeConfig {
return problemIPs;
}
private Vector<String> deletePublicIPRange(TransactionLegacy txn, long startIP, long endIP, long vlanDbId) {
String deleteSql = "DELETE FROM `cloud`.`user_ip_address` WHERE public_ip_address = ? AND vlan_id = ?";
String isPublicIPAllocatedSelectSql = "SELECT * FROM `cloud`.`user_ip_address` WHERE public_ip_address = ? AND vlan_id = ?";
public Vector<String> updatePublicIPRange(TransactionLegacy txn, long startIP, long endIP, long vlanDbId, boolean forSystemvms) {
String updateSql = "UPDATE `cloud`.`user_ip_address` SET forsystemvms = ? WHERE public_ip_address = ? AND vlan_db_id = ?";
Vector<String> problemIPs = new Vector<String>();
Connection conn = null;
@ -323,25 +322,55 @@ public class IPRangeConfig {
conn = txn.getConnection();
}
catch (SQLException e) {
System.out.println("deletePublicIPRange. Exception: " +e.getMessage());
System.out.println("updatePublicIPRange. Exception: " + e.getMessage());
return null;
}
try(PreparedStatement stmt = conn.prepareStatement(deleteSql);
try (PreparedStatement stmt = conn.prepareStatement(updateSql);) {
while (startIP <= endIP) {
stmt.clearParameters();
stmt.setBoolean(1, forSystemvms);
stmt.setString(2, NetUtils.long2Ip(startIP));
stmt.setLong(3, vlanDbId);
stmt.executeUpdate();
startIP += 1;
}
} catch (Exception ex) {
System.out.println("updatePublicIPRange. Exception: " + ex.getMessage());
return null;
}
return problemIPs;
}
public Vector<String> deletePublicIPRange(TransactionLegacy txn, long startIP, long endIP, long vlanDbId) {
String deleteSql = "DELETE FROM `cloud`.`user_ip_address` WHERE public_ip_address = ? AND vlan_db_id = ?";
String isPublicIPAllocatedSelectSql = "SELECT * FROM `cloud`.`user_ip_address` WHERE public_ip_address = ? AND vlan_db_id = ?";
Vector<String> problemIPs = new Vector<String>();
Connection conn = null;
try {
conn = txn.getConnection();
}
catch (SQLException e) {
System.out.println("deletePublicIPRange. Exception: " + e.getMessage());
return null;
}
try (PreparedStatement stmt = conn.prepareStatement(deleteSql);
PreparedStatement isAllocatedStmt = conn.prepareStatement(isPublicIPAllocatedSelectSql);) {
while (startIP <= endIP) {
if (!isPublicIPAllocated(startIP, vlanDbId, isAllocatedStmt)) {
if (!isPublicIPAllocated(NetUtils.long2Ip(startIP), vlanDbId, isAllocatedStmt)) {
stmt.clearParameters();
stmt.setLong(1, startIP);
stmt.setString(1, NetUtils.long2Ip(startIP));
stmt.setLong(2, vlanDbId);
stmt.executeUpdate();
}
}
else {
problemIPs.add(NetUtils.long2Ip(startIP));
}
startIP += 1;
}
}catch (Exception ex) {
System.out.println("deletePublicIPRange. Exception: " +ex.getMessage());
} catch (Exception ex) {
System.out.println("deletePublicIPRange. Exception: " + ex.getMessage());
return null;
}
@ -372,28 +401,33 @@ public class IPRangeConfig {
System.out.println("deletePrivateIPRange. Exception: " + e.getMessage());
printError("deletePrivateIPRange. Exception: " + e.getMessage());
}
}catch (SQLException e) {
} catch (SQLException e) {
System.out.println("deletePrivateIPRange. Exception: " + e.getMessage());
printError("deletePrivateIPRange. Exception: " + e.getMessage());
}
return problemIPs;
}
private boolean isPublicIPAllocated(long ip, long vlanDbId, PreparedStatement stmt) {
try(ResultSet rs = stmt.executeQuery();) {
stmt.clearParameters();
stmt.setLong(1, ip);
stmt.setLong(2, vlanDbId);
if (rs.next()) {
return (rs.getString("allocated") != null);
} else {
return false;
}
}
catch (SQLException ex) {
System.out.println(ex.getMessage());
return true;
}
private boolean isPublicIPAllocated(String ip, long vlanDbId, PreparedStatement stmt) {
try {
stmt.clearParameters();
stmt.setString(1, ip);
stmt.setLong(2, vlanDbId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return (rs.getString("allocated") != null);
} else {
return false;
}
}
catch (SQLException ex) {
System.out.println(ex.getMessage());
return true;
}
} catch (SQLException ex) {
System.out.println(ex.getMessage());
return true;
}
}
private boolean isPrivateIPAllocated(String ip, long podId, long zoneId, PreparedStatement stmt) {

View File

@ -44,6 +44,7 @@ import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.UpdateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
@ -258,6 +259,17 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
return null;
}
/* (non-Javadoc)
* @see com.cloud.configuration.ConfigurationService#updateVlanAndPublicIpRange(org.apache.cloudstack.api
* .commands.UpdateVlanIpRangeCmd)
*/
@Override
public Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOperationException,
ResourceUnavailableException, ResourceAllocationException{
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see com.cloud.configuration.ConfigurationService#markDefaultZone(java.lang.String, long, long)
*/

View File

@ -2240,6 +2240,7 @@
"label.unmanaged.instances": "Unmanaged Instances",
"label.untagged": "Untagged",
"label.update.instance.group": "Update Instance Group",
"label.update.ip.range": "Update IP range",
"label.update.physical.network": "Update Physical Network",
"label.update.project.resources": "Update project resources",
"label.update.project.role": "Update project role",
@ -3288,6 +3289,7 @@
"message.success.scale.kubernetes": "Successfully scaled Kubernetes cluster",
"message.success.unmanage.instance": "Successfully unmanaged instance",
"message.success.update.ipaddress": "Successfully updated IP Address",
"message.success.update.iprange": "Successfully updated IP range",
"message.success.update.kubeversion": "Successfully updated Kubernetes supported version",
"message.success.update.user": "Successfully updated user",
"message.success.upgrade.kubernetes": "Successfully upgraded Kubernetes cluster",

View File

@ -54,6 +54,13 @@
type="danger"
@click="() => handleRemoveAccount(record.id)"
:disabled="!('releasePublicIpRange' in $store.getters.apis)" />
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.update.ip.range')"
icon="edit"
type="danger"
@click="() => handleUpdateIpRangeModal(record)"
:disabled="!('updateVlanIpRange' in $store.getters.apis)" />
<tooltip-button
tooltipPlacement="bottom"
:tooltip="$t('label.remove.ip.range')"
@ -235,6 +242,52 @@
</a-form>
</a-modal>
<a-modal
v-model="updateIpRangeModal"
:title="$t('label.update.ip.range')"
v-if="selectedItem"
:maskClosable="false"
:footer="null"
v-ctrl-enter="handleUpdateIpRange"
@cancel="updateIpRangeModal = false">
<a-form
:form="form"
@submit="handleAddIpRange"
layout="vertical"
class="form"
>
<a-form-item :label="$t('label.startip')" class="form__item">
<a-input
autoFocus
v-decorator="['startip', { initialValue: selectedItem.startip || '', rules: [{ required: true, message: `${$t('label.required')}` }] }]">
</a-input>
</a-form-item>
<a-form-item :label="$t('label.endip')" class="form__item">
<a-input
v-decorator="['endip', { initialValue: selectedItem.endip || '', rules: [{ required: true, message: `${$t('label.required')}` }] }]">
</a-input>
</a-form-item>
<a-form-item :label="$t('label.gateway')" class="form__item">
<a-input
v-decorator="['gateway', { initialValue: selectedItem.gateway || '', rules: [{ required: true, message: `${$t('label.required')}` }] }]">
</a-input>
</a-form-item>
<a-form-item :label="$t('label.netmask')" class="form__item">
<a-input
v-decorator="['netmask', { initialValue: selectedItem.netmask || '', rules: [{ required: true, message: `${$t('label.required')}` }] }]">
</a-input>
</a-form-item>
<a-form-item :label="$t('label.system.vms')" class="form__item">
<a-switch v-decorator="['forsystemvms', { initialValue: selectedItem.forsystemvms }]"></a-switch>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="updateIpRangeModal = false">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleUpdateIpRange">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
</a-spin>
</template>
@ -280,6 +333,7 @@ export default {
domains: [],
domainsLoading: false,
addIpRangeModal: false,
updateIpRangeModal: false,
showAccountFields: false,
podsLoading: false,
pods: [],
@ -443,6 +497,10 @@ export default {
handleOpenAddIpRangeModal () {
this.addIpRangeModal = true
},
handleUpdateIpRangeModal (item) {
this.selectedItem = item
this.updateIpRangeModal = true
},
handleDeleteIpRange (id) {
this.componentLoading = true
api('deleteVlanIpRange', { id }).then(() => {
@ -498,6 +556,38 @@ export default {
})
})
},
handleUpdateIpRange (e) {
if (this.componentLoading) return
this.form.validateFields((error, values) => {
if (error) return
this.componentLoading = true
this.updateIpRangeModal = false
var params = {
id: this.selectedItem.id,
gateway: values.gateway,
netmask: values.netmask,
startip: values.startip,
endip: values.endip,
forsystemvms: values.forsystemvms
}
api('updateVlanIpRange', params).then(() => {
this.$notification.success({
message: this.$t('message.success.update.iprange')
})
}).catch(error => {
this.$notification.error({
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.updatevlaniprangeresponse
? error.response.data.updatevlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,
duration: 0
})
}).finally(() => {
this.componentLoading = false
this.fetchData()
})
})
},
changePage (page, pageSize) {
this.page = page
this.pageSize = pageSize