Merge remote-tracking branch 'origin/4.15'

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2021-02-26 12:09:11 +05:30
commit 77290df0d5
23 changed files with 709 additions and 71 deletions

View File

@ -28,6 +28,12 @@ import com.cloud.vm.VirtualMachineProfile;
/**
*/
public interface StoragePoolAllocator extends Adapter {
/**
* Overloaded method calls allocateToPool with bypassStorageTypeCheck = false
* and returns a list of pools suitable.
**/
List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo);
/**
* Determines which storage pools are suitable for the guest virtual machine
* and returns a list of pools suitable.
@ -45,10 +51,12 @@ public interface StoragePoolAllocator extends Adapter {
* @param ExcludeList
* avoid
* @param int returnUpTo (use -1 to return all possible pools)
* @param boolean bypassStorageTypeCheck allows bypassing useLocalStorage check for provided DiskProfile when true
* @return List<StoragePool> List of storage pools that are suitable for the
* VM
**/
List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo);
List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck);
static int RETURN_UPTO_ALL = -1;

View File

@ -376,6 +376,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
tmpltTypeHyperSearch2.and("templateType", tmpltTypeHyperSearch2.entity().getTemplateType(), SearchCriteria.Op.EQ);
tmpltTypeHyperSearch2.and("hypervisorType", tmpltTypeHyperSearch2.entity().getHypervisorType(), SearchCriteria.Op.EQ);
tmpltTypeHyperSearch2.and("templateName", tmpltTypeHyperSearch2.entity().getName(), SearchCriteria.Op.EQ);
tmpltTypeHyperSearch2.and("state", tmpltTypeHyperSearch2.entity().getState(), SearchCriteria.Op.EQ);
tmpltTypeSearch = createSearchBuilder();
tmpltTypeSearch.and("state", tmpltTypeSearch.entity().getState(), SearchCriteria.Op.EQ);
@ -897,6 +898,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
SearchCriteria<VMTemplateVO> sc = tmpltTypeHyperSearch2.create();
sc.setParameters("templateType", TemplateType.ROUTING);
sc.setParameters("hypervisorType", hType);
sc.setParameters("state", VirtualMachineTemplate.State.Active.toString());
if (templateName != null) {
sc.setParameters("templateName", templateName);
}
@ -911,6 +913,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
sc = tmpltTypeHyperSearch2.create();
sc.setParameters("templateType", TemplateType.SYSTEM);
sc.setParameters("hypervisorType", hType);
sc.setParameters("state", VirtualMachineTemplate.State.Active.toString());
if (templateName != null) {
sc.setParameters("templateName", templateName);
}

View File

@ -122,6 +122,8 @@ public interface PrimaryDataStoreDao extends GenericDao<StoragePoolVO, Long> {
List<StoragePoolVO> listLocalStoragePoolByPath(long datacenterId, String path);
List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds);
void deletePoolTags(long poolId);
List<StoragePoolVO> listChildStoragePoolsInDatastoreCluster(long poolId);

View File

@ -56,6 +56,7 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
private final SearchBuilder<StoragePoolVO> DeleteLvmSearch;
private final SearchBuilder<StoragePoolVO> DcLocalStorageSearch;
private final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch;
private final SearchBuilder<StoragePoolVO> ClustersSearch;
@Inject
private StoragePoolDetailsDao _detailsDao;
@ -133,6 +134,10 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
DcLocalStorageSearch.and("scope", DcLocalStorageSearch.entity().getScope(), SearchCriteria.Op.EQ);
DcLocalStorageSearch.done();
ClustersSearch = createSearchBuilder();
ClustersSearch.and("clusterIds", ClustersSearch.entity().getClusterId(), Op.IN);
ClustersSearch.and("status", ClustersSearch.entity().getStatus(), Op.EQ);
}
@Override
@ -568,4 +573,12 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
sc.addAnd("removed", SearchCriteria.Op.NULL);
return getCount(sc);
}
@Override
public List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds) {
SearchCriteria<StoragePoolVO> sc = ClustersSearch.create();
sc.setParameters("clusterIds", clusterIds.toArray());
sc.setParameters("status", StoragePoolStatus.Up);
return listBy(sc);
}
}

View File

@ -26,9 +26,6 @@ import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.storage.StoragePoolStatus;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -42,10 +39,12 @@ import com.cloud.dc.ClusterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.StorageUtil;
import com.cloud.storage.Volume;
import com.cloud.storage.dao.VolumeDao;
@ -87,11 +86,16 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
return false;
}
protected abstract List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo);
protected abstract List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck);
@Override
public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
List<StoragePool> pools = select(dskCh, vmProfile, plan, avoid, returnUpTo);
return allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, false);
}
@Override
public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
List<StoragePool> pools = select(dskCh, vmProfile, plan, avoid, returnUpTo, bypassStorageTypeCheck);
return reorderPools(pools, vmProfile, plan);
}

View File

@ -45,9 +45,13 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
DiskOfferingDao _diskOfferingDao;
@Override
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
s_logger.debug("ClusterScopeStoragePoolAllocator looking for storage pool");
if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) {
return null;
}
List<StoragePool> suitablePools = new ArrayList<StoragePool>();
long dcId = plan.getDataCenterId();

View File

@ -47,7 +47,7 @@ public class GarbageCollectingStoragePoolAllocator extends AbstractStoragePoolAl
boolean _storagePoolCleanupEnabled;
@Override
public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
s_logger.debug("GarbageCollectingStoragePoolAllocator looking for storage pool");
if (!_storagePoolCleanupEnabled) {
s_logger.debug("Storage pool cleanup is not enabled, so GarbageCollectingStoragePoolAllocator is being skipped.");
@ -68,7 +68,7 @@ public class GarbageCollectingStoragePoolAllocator extends AbstractStoragePoolAl
ExcludeList myAvoids =
new ExcludeList(avoid.getDataCentersToAvoid(), avoid.getPodsToAvoid(), avoid.getClustersToAvoid(), avoid.getHostsToAvoid(), avoid.getPoolsToAvoid());
return allocator.allocateToPool(dskCh, vmProfile, plan, myAvoids, returnUpTo);
return allocator.allocateToPool(dskCh, vmProfile, plan, myAvoids, returnUpTo, bypassStorageTypeCheck);
}
@Override

View File

@ -60,10 +60,10 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
ConfigurationDao _configDao;
@Override
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
s_logger.debug("LocalStoragePoolAllocator trying to find storage pool to fit the vm");
if (!dskCh.useLocalStorage()) {
if (!bypassStorageTypeCheck && !dskCh.useLocalStorage()) {
return null;
}

View File

@ -39,12 +39,17 @@ public class UseLocalForRootAllocator extends LocalStoragePoolAllocator implemen
@Override
public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
return allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, false);
}
@Override
public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
DataCenterVO dc = _dcDao.findById(plan.getDataCenterId());
if (!dc.isLocalStorageEnabled()) {
return null;
}
return super.allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo);
return super.allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, bypassStorageTypeCheck);
}
@Override

View File

@ -49,9 +49,13 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
private CapacityDao capacityDao;
@Override
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
LOGGER.debug("ZoneWideStoragePoolAllocator to find storage pool");
if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) {
return null;
}
if (LOGGER.isTraceEnabled()) {
// Log the pools details that are ignored because they are in disabled state
List<StoragePoolVO> disabledPools = storagePoolDao.findDisabledPoolsByScope(plan.getDataCenterId(), null, null, ScopeType.ZONE);

View File

@ -6,9 +6,9 @@ These scripts are also used by the CloudStack team to build packages for the off
# Requirements
The RPM and DEB packages have dependencies on versions of specific libraries. Due to these dependencies the following distributions and their versions are supported by the packages.
* CentOS / RHEL: 7
* CentOS / RHEL: 7 and 8
* Debian 7 (Wheezy) and 8 (Jessy) (untested!)
* Ubuntu: 16.04 (Xenial) and 18.04 (Bionic)
* Ubuntu: 16.04 (Xenial), 18.04 (Bionic) and 20.04 (Focal)
# Building
Using the scripts in the *packaging* directory the RPM and DEB packages can be build.

View File

@ -41,7 +41,10 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.Mockito;
@RunWith(MockitoJUnitRunner.class)
public class RootCAProviderTest {
@ -57,12 +60,6 @@ public class RootCAProviderTest {
f.set(provider, o);
}
private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
Field f = ConfigKey.class.getDeclaredField(name);
f.setAccessible(true);
f.set(configKey, o);
}
@Before
public void setUp() throws Exception {
caKeyPair = CertUtils.generateRandomKeyPair(1024);
@ -133,14 +130,16 @@ public class RootCAProviderTest {
@Test
public void testCreateSSLEngineWithoutAuthStrictness() throws Exception {
overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "false");
provider.rootCAAuthStrictness = Mockito.mock(ConfigKey.class);
Mockito.when(provider.rootCAAuthStrictness.value()).thenReturn(Boolean.FALSE);
final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
Assert.assertFalse(e.getNeedClientAuth());
}
@Test
public void testCreateSSLEngineWithAuthStrictness() throws Exception {
overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "true");
provider.rootCAAuthStrictness = Mockito.mock(ConfigKey.class);
Mockito.when(provider.rootCAAuthStrictness.value()).thenReturn(Boolean.TRUE);
final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
Assert.assertTrue(e.getNeedClientAuth());
}

View File

@ -24,7 +24,7 @@ import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainInfo.DomainState;
import org.libvirt.DomainInfo;
import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
@ -52,18 +52,33 @@ public final class LibvirtDeleteVMSnapshotCommandWrapper extends CommandWrapper<
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
Domain dm = null;
DomainSnapshot snapshot = null;
DomainInfo.DomainState oldState = null;
boolean tryingResume = false;
Connect conn = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
snapshot = dm.snapshotLookupByName(cmd.getTarget().getSnapshotName());
s_logger.debug("Suspending domain " + vmName);
dm.suspend(); // suspend the vm to avoid image corruption
oldState = dm.getInfo().state;
if (oldState == DomainInfo.DomainState.VIR_DOMAIN_RUNNING) {
s_logger.debug("Suspending domain " + vmName);
dm.suspend(); // suspend the vm to avoid image corruption
}
snapshot.delete(0); // only remove this snapshot, not children
if (oldState == DomainInfo.DomainState.VIR_DOMAIN_RUNNING) {
// Resume the VM
tryingResume = true;
dm = libvirtComputingResource.getDomain(conn, vmName);
if (dm.getInfo().state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
dm.resume();
}
}
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
} catch (LibvirtException e) {
String msg = " Delete VM snapshot failed due to " + e.toString();
@ -97,21 +112,26 @@ public final class LibvirtDeleteVMSnapshotCommandWrapper extends CommandWrapper<
} else if (snapshot == null) {
s_logger.debug("Can not find vm snapshot " + cmd.getTarget().getSnapshotName() + " on vm: " + vmName + ", return true");
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
} else if (tryingResume) {
s_logger.error("Failed to resume vm after delete snapshot " + cmd.getTarget().getSnapshotName() + " on vm: " + vmName + " return true : " + e);
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
}
s_logger.warn(msg, e);
return new DeleteVMSnapshotAnswer(cmd, false, msg);
} finally {
if (dm != null) {
// Make sure if the VM is paused, then resume it, in case we got an exception during our delete() and didn't have the chance before
try {
if (dm.getInfo().state == DomainState.VIR_DOMAIN_PAUSED) {
dm = libvirtComputingResource.getDomain(conn, vmName);
if (oldState == DomainInfo.DomainState.VIR_DOMAIN_RUNNING && dm.getInfo().state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
s_logger.debug("Resuming domain " + vmName);
dm.resume();
}
dm.free();
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
};
} catch (LibvirtException e) {
s_logger.error("Failed to resume vm after delete snapshot " + cmd.getTarget().getSnapshotName() + " on vm: " + vmName + " return true : " + e);
}
}
}
}

View File

@ -1057,7 +1057,7 @@ public class KVMStorageProcessor implements StorageProcessor {
}
}
} catch (final Exception ex) {
s_logger.debug("Failed to delete snapshots on primary", ex);
s_logger.error("Failed to delete snapshots on primary", ex);
}
}

View File

@ -35,7 +35,7 @@ public class RandomStoragePoolAllocator extends AbstractStoragePoolAllocator {
private static final Logger s_logger = Logger.getLogger(RandomStoragePoolAllocator.class);
@Override
public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
List<StoragePool> suitablePools = new ArrayList<StoragePool>();

View File

@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
@ -575,6 +576,8 @@ import com.cloud.alert.AlertManager;
import com.cloud.alert.AlertVO;
import com.cloud.alert.dao.AlertDao;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.query.dao.StoragePoolJoinDao;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.capacity.Capacity;
import com.cloud.capacity.CapacityVO;
import com.cloud.capacity.dao.CapacityDao;
@ -662,6 +665,7 @@ import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.VolumeVO;
@ -793,6 +797,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Inject
private PrimaryDataStoreDao _poolDao;
@Inject
private StoragePoolJoinDao _poolJoinDao;
@Inject
private NetworkDao _networkDao;
@Inject
private StorageManager _storageMgr;
@ -1149,7 +1155,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
return new Pair<List<? extends Cluster>, Integer>(result.first(), result.second());
}
private HypervisorType getHypervisorType(VMInstanceVO vm, StoragePool srcVolumePool, VirtualMachineProfile profile) {
private HypervisorType getHypervisorType(VMInstanceVO vm, StoragePool srcVolumePool) {
HypervisorType type = null;
if (vm == null) {
StoragePoolVO poolVo = _poolDao.findById(srcVolumePool.getId());
@ -1159,18 +1165,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
ClusterVO cluster = _clusterDao.findById(clusterId);
type = cluster.getHypervisorType();
}
} else if (ScopeType.ZONE.equals(poolVo.getScope())) {
Long zoneId = poolVo.getDataCenterId();
if (zoneId != null) {
DataCenterVO dc = _dcDao.findById(zoneId);
}
}
if (null == type) {
type = srcVolumePool.getHypervisor();
}
} else {
type = profile.getHypervisorType();
type = vm.getHypervisorType();
}
return type;
}
@ -1508,11 +1509,17 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
StoragePool srcVolumePool = _poolDao.findById(volume.getPoolId());
allPools = getAllStoragePoolsCompatibleWithVolumeSourceStoragePool(srcVolumePool);
HypervisorType hypervisorType = getHypervisorType(vm, srcVolumePool);
Pair<Host, List<Cluster>> hostClusterPair = getVolumeVmHostClusters(srcVolumePool, vm, hypervisorType);
Host vmHost = hostClusterPair.first();
List<Cluster> clusters = hostClusterPair.second();
allPools = getAllStoragePoolCompatibleWithVolumeSourceStoragePool(srcVolumePool, hypervisorType, clusters);
allPools.remove(srcVolumePool);
if (vm != null) {
suitablePools = findAllSuitableStoragePoolsForVm(volume, vm, srcVolumePool);
suitablePools = findAllSuitableStoragePoolsForVm(volume, vm, vmHost, srcVolumePool,
CollectionUtils.isNotEmpty(clusters) ? clusters.get(0) : null, hypervisorType);
} else {
suitablePools = allPools;
suitablePools = findAllSuitableStoragePoolsForDetachedVolume(volume, allPools);
}
List<StoragePool> avoidPools = new ArrayList<>();
if (srcVolumePool.getParent() != 0L) {
@ -1539,6 +1546,30 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
}
private Pair<Host, List<Cluster>> getVolumeVmHostClusters(StoragePool srcVolumePool, VirtualMachine vm, HypervisorType hypervisorType) {
Host host = null;
List<Cluster> clusters = new ArrayList<>();
Long clusterId = srcVolumePool.getClusterId();
if (vm != null) {
Long hostId = vm.getHostId();
if (hostId == null) {
hostId = vm.getLastHostId();
}
if (hostId != null) {
host = _hostDao.findById(hostId);
}
}
if (clusterId == null && host != null) {
clusterId = host.getClusterId();
}
if (clusterId != null && vm != null) {
clusters.add(_clusterDao.findById(clusterId));
} else {
clusters.addAll(_clusterDao.listByDcHyType(srcVolumePool.getDataCenterId(), hypervisorType.toString()));
}
return new Pair<>(host, clusters);
}
/**
* This method looks for all storage pools that are compatible with the given volume.
* <ul>
@ -1546,15 +1577,18 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
* <li>We also all storage available filtering by data center, pod and cluster as the current storage pool used by the given volume.</li>
* </ul>
*/
private List<? extends StoragePool> getAllStoragePoolsCompatibleWithVolumeSourceStoragePool(StoragePool srcVolumePool) {
private List<? extends StoragePool> getAllStoragePoolCompatibleWithVolumeSourceStoragePool(StoragePool srcVolumePool, HypervisorType hypervisorType, List<Cluster> clusters) {
List<StoragePoolVO> storagePools = new ArrayList<>();
// Storage pool with Zone Scope holds valid DataCenter Id only, Pod Id and Cluster Id are null
// Storage pool with Cluster/Host Scope holds valid DataCenter Id, Pod Id and Cluster Id
// Below methods call returns all the compatible pools with scope : ZONE, CLUSTER, HOST (as they are listed with Scope: null here)
List<StoragePoolVO> compatibleStoragePools = _poolDao.listBy(srcVolumePool.getDataCenterId(), srcVolumePool.getPodId(), srcVolumePool.getClusterId(), null);
if (CollectionUtils.isNotEmpty(compatibleStoragePools)) {
compatibleStoragePools.remove(srcVolumePool);
storagePools.addAll(compatibleStoragePools);
List<StoragePoolVO> zoneWideStoragePools = _poolDao.findZoneWideStoragePoolsByHypervisor(srcVolumePool.getDataCenterId(), hypervisorType);
if (CollectionUtils.isNotEmpty(zoneWideStoragePools)) {
storagePools.addAll(zoneWideStoragePools);
}
if (CollectionUtils.isNotEmpty(clusters)) {
List<Long> clusterIds = clusters.stream().map(Cluster::getId).collect(Collectors.toList());
List<StoragePoolVO> clusterAndLocalStoragePools = _poolDao.findPoolsInClusters(clusterIds);
if (CollectionUtils.isNotEmpty(clusterAndLocalStoragePools)) {
storagePools.addAll(clusterAndLocalStoragePools);
}
}
return storagePools;
@ -1567,35 +1601,33 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
*
* Side note: the idea behind this method is to provide power for administrators of manually overriding deployments defined by CloudStack.
*/
private List<StoragePool> findAllSuitableStoragePoolsForVm(final VolumeVO volume, VMInstanceVO vm, StoragePool srcVolumePool) {
private List<StoragePool> findAllSuitableStoragePoolsForVm(final VolumeVO volume, VMInstanceVO vm, Host vmHost, StoragePool srcVolumePool, Cluster srcCluster, HypervisorType hypervisorType) {
List<StoragePool> suitablePools = new ArrayList<>();
HostVO host = _hostDao.findById(vm.getHostId());
if (host == null) {
host = _hostDao.findById(vm.getLastHostId());
}
ExcludeList avoid = new ExcludeList();
avoid.addPool(srcVolumePool.getId());
DataCenterDeployment plan = new DataCenterDeployment(volume.getDataCenterId(), srcVolumePool.getPodId(), srcVolumePool.getClusterId(), null, null, null);
Long clusterId = null;
Long podId = null;
if (srcCluster != null) {
clusterId = srcCluster.getId();
podId = srcCluster.getPodId();
}
DataCenterDeployment plan = new DataCenterDeployment(volume.getDataCenterId(), podId, clusterId,
null, null, null, null);
VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm);
// OfflineVmwareMigration: vm might be null here; deal!
HypervisorType type = getHypervisorType(vm, srcVolumePool, profile);
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
//This is an override mechanism so we can list the possible local storage pools that a volume in a shared pool might be able to be migrated to
DiskProfile diskProfile = new DiskProfile(volume, diskOffering, type);
diskProfile.setUseLocalStorage(true);
DiskProfile diskProfile = new DiskProfile(volume, diskOffering, hypervisorType);
for (StoragePoolAllocator allocator : _storagePoolAllocators) {
List<StoragePool> pools = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL);
List<StoragePool> pools = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL, true);
if (CollectionUtils.isEmpty(pools)) {
continue;
}
for (StoragePool pool : pools) {
boolean isLocalPoolSameHostAsSourcePool = pool.isLocal() && StringUtils.equals(host.getPrivateIpAddress(), pool.getHostAddress());
if (isLocalPoolSameHostAsSourcePool || pool.isShared()) {
boolean isLocalPoolSameHostAsVmHost = pool.isLocal() &&
(vmHost == null || StringUtils.equals(vmHost.getPrivateIpAddress(), pool.getHostAddress()));
if (isLocalPoolSameHostAsVmHost || pool.isShared()) {
suitablePools.add(pool);
}
}
@ -1603,6 +1635,29 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
return suitablePools;
}
private List<StoragePool> findAllSuitableStoragePoolsForDetachedVolume(Volume volume, List<? extends StoragePool> allPools) {
List<StoragePool> suitablePools = new ArrayList<>();
if (CollectionUtils.isEmpty(allPools)) {
return suitablePools;
}
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
List<String> tags = new ArrayList<>();
String[] tagsArray = diskOffering.getTagsArray();
if (tagsArray != null && tagsArray.length > 0) {
tags = Arrays.asList(tagsArray);
}
Long[] poolIds = allPools.stream().map(StoragePool::getId).toArray(Long[]::new);
List<StoragePoolJoinVO> pools = _poolJoinDao.searchByIds(poolIds);
for (StoragePoolJoinVO storagePool : pools) {
if (StoragePoolStatus.Up.equals(storagePool.getStatus()) &&
(CollectionUtils.isEmpty(tags) || tags.contains(storagePool.getTag()))) {
Optional<? extends StoragePool> match = allPools.stream().filter(x -> x.getId() == storagePool.getId()).findFirst();
match.ifPresent(suitablePools::add);
}
}
return suitablePools;
}
private Pair<List<HostVO>, Integer> searchForServers(final Long startIndex, final Long pageSize, final Object name, final Object type,
final Object state, final Object zone, final Object pod, final Object cluster, final Object id, final Object keyword,
final Object resourceState, final Object haHosts, final Object hypervisorType, final Object hypervisorVersion, final Object... excludes) {

View File

@ -53,6 +53,10 @@ export default {
name: 'virtual.routers',
component: () => import('@/views/network/RoutersTab.vue'),
show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && 'listRouters' in store.getters.apis }
}, {
name: 'guest.ip.range',
component: () => import('@/views/network/GuestIpRanges.vue'),
show: (record) => { return 'listVlanIpRanges' in store.getters.apis && (record.type === 'Shared' || (record.service && record.service.filter(x => x.name === 'SourceNat').count === 0)) }
}],
actions: [
{

View File

@ -63,7 +63,9 @@ import {
Pagination,
Comment,
Tree,
Calendar
Calendar,
Slider,
AutoComplete
} from 'ant-design-vue'
Vue.use(ConfigProvider)
@ -110,6 +112,8 @@ Vue.use(Pagination)
Vue.use(Comment)
Vue.use(Tree)
Vue.use(Calendar)
Vue.use(Slider)
Vue.use(AutoComplete)
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message

View File

@ -216,11 +216,35 @@ export default {
if (this.hypervisor === 'VMware') {
this.clustertype = 'ExternalManaged'
if ((this.host === null || this.host.length === 0) &&
(this.dataCenter === null || this.dataCenter.length === 0)) {
api('listVmwareDcs', {
zoneid: this.zoneId
}).then(response => {
var vmwaredcs = response.listvmwaredcsresponse.VMwareDC
if (vmwaredcs !== null) {
this.host = vmwaredcs[0].vcenter
this.dataCenter = vmwaredcs[0].name
}
this.addCluster()
}).catch(error => {
this.$notification.error({
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.listvmwaredcsresponse.errortext,
duration: 0
})
})
return
}
}
this.addCluster()
},
addCluster () {
if (this.hypervisor === 'VMware') {
const clusternameVal = this.clustername
this.url = `http://${this.host}/${this.dataCenter}/${clusternameVal}`
this.clustername = `${this.host}/${this.dataCenter}/${clusternameVal}`
}
this.loading = true
this.parentToggleLoading()
api('addCluster', {}, 'POST', {

View File

@ -34,6 +34,11 @@
:rowKey="record => record.id"
:pagination="false"
>
<template slot="name" slot-scope="text, item">
<router-link :to="{ path: '/guestnetwork/' + item.id }">
{{ text }}
</router-link>
</template>
</a-table>
<a-pagination
class="row-element pagination"
@ -98,7 +103,8 @@ export default {
columns: [
{
title: this.$t('label.name'),
dataIndex: 'name'
dataIndex: 'name',
scopedSlots: { customRender: 'name' }
},
{
title: this.$t('label.type'),

View File

@ -90,7 +90,9 @@ export default {
}
},
mounted () {
this.fetchData()
if (this.resource.id) {
this.fetchData()
}
},
watch: {
loading (newData, oldData) {

View File

@ -0,0 +1,285 @@
// 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.
<template>
<a-spin :spinning="loading">
<div class="form-layout">
<div class="form">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item>
<span slot="label">
{{ $t('label.gateway') }}
<a-tooltip :title="apiParams.gateway.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['gateway', {
rules: [{ required: true, message: $t('message.error.gateway') }]
}]"
:placeholder="apiParams.gateway.description"/>
</a-form-item>
<a-form-item>
<span slot="label">
{{ $t('label.netmask') }}
<a-tooltip :title="apiParams.netmask.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['netmask', {
rules: [{ required: true, message: $t('message.error.netmask') }]
}]"
:placeholder="apiParams.netmask.description"/>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" lg="12">
<a-form-item>
<span slot="label">
{{ $t('label.startipv4') }}
<a-tooltip :title="apiParams.startip.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['startip', {
rules: [
{ required: true, message: $t('message.error.startip') },
{
validator: checkIpFormat,
ipV4: true,
message: $t('message.error.ipv4.address')
}
]
}]"
:placeholder="apiParams.startip.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item>
<span slot="label">
{{ $t('label.endipv4') }}
<a-tooltip :title="apiParams.endip.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['endip', {
rules: [
{ required: true, message: $t('message.error.endip') },
{
validator: checkIpFormat,
ipV4: true,
message: $t('message.error.ipv4.address')
}
]
}]"
:placeholder="apiParams.endip.description"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item>
<span slot="label">
{{ $t('label.ip6cidr') }}
<a-tooltip :title="apiParams.ip6cidr.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['ip6cidr']"
:placeholder="apiParams.ip6cidr.description"/>
</a-form-item>
<a-form-item>
<span slot="label">
{{ $t('label.ip6gateway') }}
<a-tooltip :title="apiParams.ip6gateway.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['ip6gateway']"
:placeholder="apiParams.ip6gateway.description"/>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item>
<span slot="label">
{{ $t('label.startipv6') }}
<a-tooltip :title="apiParams.startipv6.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['startipv6', {
rules: [
{
validator: checkIpFormat,
ipV6: true,
message: $t('message.error.ipv6.address')
}
]
}]"
:placeholder="apiParams.startipv6.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item>
<span slot="label">
{{ $t('label.endipv6') }}
<a-tooltip :title="apiParams.endipv6.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['endipv6', {
rules: [
{
validator: checkIpFormat,
ipV6: true,
message: $t('message.error.ipv6.address')
}
]
}]"
:placeholder="apiParams.endip.description"/>
</a-form-item>
</a-col>
</a-row>
<div :span="24" class="action-button">
<a-button
:loading="loading"
@click="closeAction">
{{ this.$t('label.cancel') }}
</a-button>
<a-button
:loading="loading"
type="primary"
@click="handleSubmit">
{{ this.$t('label.ok') }}
</a-button>
</div>
</a-form>
</div>
</div>
</a-spin>
</template>
<script>
import { api } from '@/api'
export default {
name: 'CreateVlanIpRange',
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
loading: false,
ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i,
ipV6Regex: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i
}
},
created () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.createVlanIpRange || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
methods: {
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
const params = {}
params.forVirtualNetwork = false
params.networkid = this.resource.id
params.gateway = values.gateway
params.netmask = values.netmask
params.startip = values.startip
params.endip = values.endip
params.ip6cidr = values.ip6cidr
params.ip6gateway = values.ip6gateway
params.startipv6 = values.startipv6
params.endipv6 = values.endipv6
this.loading = true
api('createVlanIpRange', params)
.then(() => {
this.$notification.success({
message: this.$t('message.success.add.iprange')
})
this.closeAction()
this.$emit('refresh-data')
}).catch(error => {
this.$notification.error({
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.createvlaniprangeresponse
? error.response.data.createvlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,
duration: 0
})
}).finally(() => {
this.loading = false
})
})
},
closeAction () {
this.$emit('close-action')
},
checkIpFormat (rule, value, callback) {
if (!value || value === '') {
callback()
} else if (rule.ipV4 && !this.ipV4Regex.test(value)) {
callback(rule.message)
} else if (rule.ipV6 && !this.ipV6Regex.test(value)) {
callback(rule.message)
} else {
callback()
}
}
}
}
</script>
<style lang="less" scoped>
.form-layout {
width: 60vw;
@media (min-width: 500px) {
width: 450px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,196 @@
// 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.
<template>
<div>
<a-spin :spinning="fetchLoading">
<a-button
icon="plus"
shape="round"
style="float: right;margin-bottom: 10px; z-index: 8"
@click="() => { showCreateForm = true }">
{{ $t('label.add.ip.range') }}
</a-button>
<br />
<br />
<a-table
size="small"
style="overflow-y: auto; width: 100%;"
:columns="columns"
:dataSource="ipranges"
:rowKey="item => item.id"
:pagination="false" >
<template slot="action" slot-scope="text, record">
<a-tooltip placement="bottom">
<template slot="title">
{{ $t('label.action.delete.ip.range') }}
</template>
<a-popconfirm
:title="$t('message.confirm.remove.ip.range')"
@confirm="removeIpRange(record.id)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')" >
<a-button
type="danger"
icon="delete"
shape="circle" />
</a-popconfirm>
</a-tooltip>
</template>
</a-table>
<a-divider/>
<a-pagination
class="row-element pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="total"
:showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger>
<template slot="buildOptionText" slot-scope="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
</a-spin>
<a-modal
v-if="showCreateForm"
:visible="showCreateForm"
:title="$t('label.add.ip.range')"
:maskClosable="false"
:footer="null"
:cancelText="$t('label.cancel')"
@cancel="() => { showCreateForm = false }"
centered
width="auto">
<CreateVlanIpRange
:resource="resource"
@refresh-data="fetchData"
@close-action="showCreateForm = false" />
</a-modal>
</div>
</template>
<script>
import { api } from '@/api'
import CreateVlanIpRange from '@/views/network/CreateVlanIpRange'
export default {
name: 'GuestIpRanges',
components: {
CreateVlanIpRange
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
fetchLoading: false,
showCreateForm: false,
total: 0,
ipranges: [],
page: 1,
pageSize: 10,
columns: [
{
title: this.$t('label.startipv4'),
dataIndex: 'startip'
},
{
title: this.$t('label.endipv4'),
dataIndex: 'endip'
},
{
title: this.$t('label.startipv6'),
dataIndex: 'startipv6'
},
{
title: this.$t('label.endipv6'),
dataIndex: 'endipv6'
},
{
title: this.$t('label.gateway'),
dataIndex: 'gateway'
},
{
title: this.$t('label.netmask'),
dataIndex: 'netmask'
},
{
title: '',
scopedSlots: { customRender: 'action' }
}
]
}
},
mounted () {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.fetchData()
}
},
methods: {
fetchData () {
const params = {
zoneid: this.resource.zoneid,
networkid: this.resource.id,
listall: true,
page: this.page,
pagesize: this.pageSize
}
this.fetchLoading = true
api('listVlanIpRanges', params).then(json => {
this.total = json.listvlaniprangesresponse.count || 0
this.ipranges = json.listvlaniprangesresponse.vlaniprange || []
}).finally(() => {
this.fetchLoading = false
})
},
removeIpRange (id) {
api('deleteVlanIpRange', { id: id }).then(json => {
}).finally(() => {
this.fetchData()
})
},
changePage (page, pageSize) {
this.page = page
this.pageSize = pageSize
this.fetchData()
},
changePageSize (currentPage, pageSize) {
this.page = currentPage
this.pageSize = pageSize
this.fetchData()
}
}
}
</script>