mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Restrict the number of managed clustered file systems per compute cluster (#2500)
* Restrict the number of managed clustered file systems per compute cluster
This commit is contained in:
parent
b11d63f28b
commit
d12c106a47
@ -92,6 +92,14 @@ public interface StorageManager extends StorageService {
|
||||
true,
|
||||
ConfigKey.Scope.Global,
|
||||
null);
|
||||
ConfigKey<Integer> MaxNumberOfManagedClusteredFileSystems = new ConfigKey<>(Integer.class,
|
||||
"max.number.managed.clustered.file.systems",
|
||||
"Storage",
|
||||
"200",
|
||||
"XenServer and VMware only: Maximum number of managed SRs or datastores per compute cluster",
|
||||
true,
|
||||
ConfigKey.Scope.Cluster,
|
||||
null);
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of tags for the specified storage pool
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
// 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 com.cloud.storage;
|
||||
|
||||
import com.cloud.dc.ClusterVO;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class StorageUtil {
|
||||
@Inject private ClusterDao clusterDao;
|
||||
@Inject private HostDao hostDao;
|
||||
@Inject private PrimaryDataStoreDao storagePoolDao;
|
||||
@Inject private VMInstanceDao vmInstanceDao;
|
||||
@Inject private VolumeDao volumeDao;
|
||||
|
||||
private Long getClusterId(Long hostId) {
|
||||
if (hostId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HostVO hostVO = hostDao.findById(hostId);
|
||||
|
||||
if (hostVO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return hostVO.getClusterId();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method relates to managed storage only. CloudStack currently supports managed storage with XenServer, vSphere, and KVM.
|
||||
* With managed storage on XenServer and vSphere, CloudStack needs to use an iSCSI SR (XenServer) or datastore (vSphere) per CloudStack
|
||||
* volume. Since XenServer and vSphere are limited to the hundreds with regards to how many SRs or datastores can be leveraged per
|
||||
* compute cluster, this method is used to check a Global Setting (that specifies the maximum number of SRs or datastores per compute cluster)
|
||||
* against what is being requested. KVM does not apply here here because it does not suffer from the same scalability limits as XenServer and
|
||||
* vSphere do. With XenServer and vSphere, each host is configured to see all the SRs/datastores of the cluster. With KVM, each host typically
|
||||
* is only configured to see the managed volumes of the VMs that are currently running on that host.
|
||||
*
|
||||
* If the clusterId is passed in, we use it. Otherwise, we use the hostId. If neither leads to a cluster, we just return true.
|
||||
*/
|
||||
public boolean managedStoragePoolCanScale(StoragePool storagePool, Long clusterId, Long hostId) {
|
||||
if (clusterId == null) {
|
||||
clusterId = getClusterId(hostId);
|
||||
|
||||
if (clusterId == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ClusterVO clusterVO = clusterDao.findById(clusterId);
|
||||
|
||||
if (clusterVO == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Hypervisor.HypervisorType hypervisorType = clusterVO.getHypervisorType();
|
||||
|
||||
if (hypervisorType == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Hypervisor.HypervisorType.XenServer.equals(hypervisorType) || Hypervisor.HypervisorType.VMware.equals(hypervisorType)) {
|
||||
int maxValue = StorageManager.MaxNumberOfManagedClusteredFileSystems.valueIn(clusterId);
|
||||
|
||||
return getNumberOfManagedClusteredFileSystemsInComputeCluster(storagePool.getDataCenterId(), clusterId) < maxValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getNumberOfManagedClusteredFileSystemsInComputeCluster(long zoneId, long clusterId) {
|
||||
int numberOfManagedClusteredFileSystemsInComputeCluster = 0;
|
||||
|
||||
List<VolumeVO> volumes = volumeDao.findByDc(zoneId);
|
||||
|
||||
if (CollectionUtils.isEmpty(volumes)) {
|
||||
return numberOfManagedClusteredFileSystemsInComputeCluster;
|
||||
}
|
||||
|
||||
for (VolumeVO volume : volumes) {
|
||||
Long instanceId = volume.getInstanceId();
|
||||
|
||||
if (instanceId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VMInstanceVO vmInstanceVO = vmInstanceDao.findById(instanceId);
|
||||
|
||||
if (vmInstanceVO == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Long vmHostId = vmInstanceVO.getHostId();
|
||||
|
||||
if (vmHostId == null) {
|
||||
vmHostId = vmInstanceVO.getLastHostId();
|
||||
}
|
||||
|
||||
if (vmHostId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HostVO vmHostVO = hostDao.findById(vmHostId);
|
||||
|
||||
if (vmHostVO == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Long vmHostClusterId = vmHostVO.getClusterId();
|
||||
|
||||
if (vmHostClusterId != null && vmHostClusterId == clusterId) {
|
||||
StoragePoolVO storagePoolVO = storagePoolDao.findById(volume.getPoolId());
|
||||
|
||||
if (storagePoolVO != null && storagePoolVO.isManaged()) {
|
||||
numberOfManagedClusteredFileSystemsInComputeCluster++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return numberOfManagedClusteredFileSystemsInComputeCluster;
|
||||
}
|
||||
}
|
||||
@ -26,5 +26,5 @@
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd"
|
||||
>
|
||||
|
||||
<bean id="storageUtil" class="com.cloud.storage.StorageUtil" />
|
||||
</beans>
|
||||
|
||||
@ -22,12 +22,10 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.storage.Storage;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
@ -42,10 +40,11 @@ import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
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.StorageUtil;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
@ -55,41 +54,30 @@ import com.cloud.vm.VirtualMachineProfile;
|
||||
|
||||
public abstract class AbstractStoragePoolAllocator extends AdapterBase implements StoragePoolAllocator {
|
||||
private static final Logger s_logger = Logger.getLogger(AbstractStoragePoolAllocator.class);
|
||||
@Inject
|
||||
StorageManager storageMgr;
|
||||
protected @Inject
|
||||
PrimaryDataStoreDao _storagePoolDao;
|
||||
@Inject
|
||||
VolumeDao _volumeDao;
|
||||
@Inject
|
||||
ConfigurationDao _configDao;
|
||||
@Inject
|
||||
ClusterDao _clusterDao;
|
||||
protected @Inject
|
||||
DataStoreManager dataStoreMgr;
|
||||
protected BigDecimal _storageOverprovisioningFactor = new BigDecimal(1);
|
||||
long _extraBytesPerVolume = 0;
|
||||
Random _rand;
|
||||
boolean _dontMatter;
|
||||
protected String _allocationAlgorithm = "random";
|
||||
@Inject
|
||||
DiskOfferingDao _diskOfferingDao;
|
||||
@Inject
|
||||
CapacityDao _capacityDao;
|
||||
|
||||
protected BigDecimal storageOverprovisioningFactor = new BigDecimal(1);
|
||||
protected String allocationAlgorithm = "random";
|
||||
protected long extraBytesPerVolume = 0;
|
||||
@Inject protected DataStoreManager dataStoreMgr;
|
||||
@Inject protected PrimaryDataStoreDao storagePoolDao;
|
||||
@Inject protected VolumeDao volumeDao;
|
||||
@Inject protected ConfigurationDao configDao;
|
||||
@Inject private CapacityDao capacityDao;
|
||||
@Inject private ClusterDao clusterDao;
|
||||
@Inject private StorageManager storageMgr;
|
||||
@Inject private StorageUtil storageUtil;
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
super.configure(name, params);
|
||||
if(_configDao != null) {
|
||||
Map<String, String> configs = _configDao.getConfiguration(null, params);
|
||||
if(configDao != null) {
|
||||
Map<String, String> configs = configDao.getConfiguration(null, params);
|
||||
String globalStorageOverprovisioningFactor = configs.get("storage.overprovisioning.factor");
|
||||
_storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
|
||||
_extraBytesPerVolume = 0;
|
||||
_rand = new Random(System.currentTimeMillis());
|
||||
_dontMatter = Boolean.parseBoolean(configs.get("storage.overwrite.provisioning"));
|
||||
storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
|
||||
extraBytesPerVolume = 0;
|
||||
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
|
||||
if (allocationAlgorithm != null) {
|
||||
_allocationAlgorithm = allocationAlgorithm;
|
||||
this.allocationAlgorithm = allocationAlgorithm;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -109,27 +97,26 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
||||
Long clusterId = plan.getClusterId();
|
||||
short capacityType;
|
||||
if(pools != null && pools.size() != 0){
|
||||
capacityType = pools.get(0).getPoolType().isShared() == true ?
|
||||
Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED : Capacity.CAPACITY_TYPE_LOCAL_STORAGE;
|
||||
capacityType = pools.get(0).getPoolType().isShared() ? Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED : Capacity.CAPACITY_TYPE_LOCAL_STORAGE;
|
||||
} else{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Long> poolIdsByCapacity = _capacityDao.orderHostsByFreeCapacity(clusterId, capacityType);
|
||||
List<Long> poolIdsByCapacity = capacityDao.orderHostsByFreeCapacity(clusterId, capacityType);
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("List of pools in descending order of free capacity: "+ poolIdsByCapacity);
|
||||
}
|
||||
|
||||
//now filter the given list of Pools by this ordered list
|
||||
Map<Long, StoragePool> poolMap = new HashMap<Long, StoragePool>();
|
||||
Map<Long, StoragePool> poolMap = new HashMap<>();
|
||||
for (StoragePool pool : pools) {
|
||||
poolMap.put(pool.getId(), pool);
|
||||
}
|
||||
List<Long> matchingPoolIds = new ArrayList<Long>(poolMap.keySet());
|
||||
List<Long> matchingPoolIds = new ArrayList<>(poolMap.keySet());
|
||||
|
||||
poolIdsByCapacity.retainAll(matchingPoolIds);
|
||||
|
||||
List<StoragePool> reorderedPools = new ArrayList<StoragePool>();
|
||||
List<StoragePool> reorderedPools = new ArrayList<>();
|
||||
for(Long id: poolIdsByCapacity){
|
||||
reorderedPools.add(poolMap.get(id));
|
||||
}
|
||||
@ -145,21 +132,21 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
||||
Long podId = plan.getPodId();
|
||||
Long clusterId = plan.getClusterId();
|
||||
|
||||
List<Long> poolIdsByVolCount = _volumeDao.listPoolIdsByVolumeCount(dcId, podId, clusterId, account.getAccountId());
|
||||
List<Long> poolIdsByVolCount = volumeDao.listPoolIdsByVolumeCount(dcId, podId, clusterId, account.getAccountId());
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("List of pools in ascending order of number of volumes for account id: " + account.getAccountId() + " is: " + poolIdsByVolCount);
|
||||
}
|
||||
|
||||
// now filter the given list of Pools by this ordered list
|
||||
Map<Long, StoragePool> poolMap = new HashMap<Long, StoragePool>();
|
||||
Map<Long, StoragePool> poolMap = new HashMap<>();
|
||||
for (StoragePool pool : pools) {
|
||||
poolMap.put(pool.getId(), pool);
|
||||
}
|
||||
List<Long> matchingPoolIds = new ArrayList<Long>(poolMap.keySet());
|
||||
List<Long> matchingPoolIds = new ArrayList<>(poolMap.keySet());
|
||||
|
||||
poolIdsByVolCount.retainAll(matchingPoolIds);
|
||||
|
||||
List<StoragePool> reorderedPools = new ArrayList<StoragePool>();
|
||||
List<StoragePool> reorderedPools = new ArrayList<>();
|
||||
for (Long id : poolIdsByVolCount) {
|
||||
reorderedPools.add(poolMap.get(id));
|
||||
}
|
||||
@ -176,12 +163,12 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
||||
account = vmProfile.getOwner();
|
||||
}
|
||||
|
||||
if (_allocationAlgorithm.equals("random") || _allocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
|
||||
if (allocationAlgorithm.equals("random") || allocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
|
||||
// Shuffle this so that we don't check the pools in the same order.
|
||||
Collections.shuffle(pools);
|
||||
} else if (_allocationAlgorithm.equals("userdispersing")) {
|
||||
} else if (allocationAlgorithm.equals("userdispersing")) {
|
||||
pools = reorderPoolsByNumberOfVolumes(plan, pools, account);
|
||||
} else if(_allocationAlgorithm.equals("firstfitleastconsumed")){
|
||||
} else if(allocationAlgorithm.equals("firstfitleastconsumed")){
|
||||
pools = reorderPoolsByCapacity(plan, pools);
|
||||
}
|
||||
return pools;
|
||||
@ -201,7 +188,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
||||
|
||||
Long clusterId = pool.getClusterId();
|
||||
if (clusterId != null) {
|
||||
ClusterVO cluster = _clusterDao.findById(clusterId);
|
||||
ClusterVO cluster = clusterDao.findById(clusterId);
|
||||
if (!(cluster.getHypervisorType() == dskCh.getHypervisorType())) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("StoragePool's Cluster does not have required hypervisorType, skipping this pool");
|
||||
@ -219,9 +206,13 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pool.isManaged() && !storageUtil.managedStoragePoolCanScale(pool, plan.getClusterId(), plan.getHostId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check capacity
|
||||
Volume volume = _volumeDao.findById(dskCh.getVolumeId());
|
||||
List<Volume> requestVolumes = new ArrayList<Volume>();
|
||||
Volume volume = volumeDao.findById(dskCh.getVolumeId());
|
||||
List<Volume> requestVolumes = new ArrayList<>();
|
||||
requestVolumes.add(volume);
|
||||
return storageMgr.storagePoolHasEnoughIops(requestVolumes, pool) && storageMgr.storagePoolHasEnoughSpace(requestVolumes, pool, plan.getClusterId());
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
|
||||
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
// Log the pools details that are ignored because they are in disabled state
|
||||
List<StoragePoolVO> disabledPools = _storagePoolDao.findDisabledPoolsByScope(dcId, podId, clusterId, ScopeType.CLUSTER);
|
||||
List<StoragePoolVO> disabledPools = storagePoolDao.findDisabledPoolsByScope(dcId, podId, clusterId, ScopeType.CLUSTER);
|
||||
if (disabledPools != null && !disabledPools.isEmpty()) {
|
||||
for (StoragePoolVO pool : disabledPools) {
|
||||
s_logger.trace("Ignoring pool " + pool + " as it is in disabled state.");
|
||||
@ -78,11 +78,11 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
|
||||
}
|
||||
}
|
||||
|
||||
List<StoragePoolVO> pools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags());
|
||||
List<StoragePoolVO> pools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags());
|
||||
s_logger.debug("Found pools matching tags: " + pools);
|
||||
|
||||
// add remaining pools in cluster, that did not match tags, to avoid set
|
||||
List<StoragePoolVO> allPools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, null);
|
||||
List<StoragePoolVO> allPools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, null);
|
||||
allPools.removeAll(pools);
|
||||
for (StoragePoolVO pool : allPools) {
|
||||
s_logger.debug("Adding pool " + pool + " to avoid set since it did not match tags");
|
||||
@ -119,11 +119,11 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
super.configure(name, params);
|
||||
|
||||
if (_configDao != null) {
|
||||
Map<String, String> configs = _configDao.getConfiguration(params);
|
||||
if (configDao != null) {
|
||||
Map<String, String> configs = configDao.getConfiguration(params);
|
||||
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
|
||||
if (allocationAlgorithm != null) {
|
||||
_allocationAlgorithm = allocationAlgorithm;
|
||||
this.allocationAlgorithm = allocationAlgorithm;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -69,7 +69,7 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
// Log the pools details that are ignored because they are in disabled state
|
||||
List<StoragePoolVO> disabledPools = _storagePoolDao.findDisabledPoolsByScope(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), ScopeType.HOST);
|
||||
List<StoragePoolVO> disabledPools = storagePoolDao.findDisabledPoolsByScope(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), ScopeType.HOST);
|
||||
if (disabledPools != null && !disabledPools.isEmpty()) {
|
||||
for (StoragePoolVO pool : disabledPools) {
|
||||
s_logger.trace("Ignoring pool " + pool + " as it is in disabled state.");
|
||||
@ -81,7 +81,7 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
|
||||
// data disk and host identified from deploying vm (attach volume case)
|
||||
if (plan.getHostId() != null) {
|
||||
List<StoragePoolVO> hostTagsPools = _storagePoolDao.findLocalStoragePoolsByHostAndTags(plan.getHostId(), dskCh.getTags());
|
||||
List<StoragePoolVO> hostTagsPools = storagePoolDao.findLocalStoragePoolsByHostAndTags(plan.getHostId(), dskCh.getTags());
|
||||
for (StoragePoolVO pool : hostTagsPools) {
|
||||
if (pool != null && pool.isLocal()) {
|
||||
StoragePool storagePool = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(pool.getId());
|
||||
@ -103,7 +103,7 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
return null;
|
||||
}
|
||||
List<StoragePoolVO> availablePools =
|
||||
_storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), dskCh.getTags());
|
||||
storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), dskCh.getTags());
|
||||
for (StoragePoolVO pool : availablePools) {
|
||||
if (suitablePools.size() == returnUpTo) {
|
||||
break;
|
||||
@ -118,7 +118,7 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
|
||||
// add remaining pools in cluster, that did not match tags, to avoid
|
||||
// set
|
||||
List<StoragePoolVO> allPools = _storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), null);
|
||||
List<StoragePoolVO> allPools = storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), null);
|
||||
allPools.removeAll(availablePools);
|
||||
for (StoragePoolVO pool : allPools) {
|
||||
avoid.addPool(pool.getId());
|
||||
@ -136,8 +136,8 @@ public class LocalStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
super.configure(name, params);
|
||||
|
||||
_storageOverprovisioningFactor = new BigDecimal(1);
|
||||
_extraBytesPerVolume = NumbersUtil.parseLong((String)params.get("extra.bytes.per.volume"), 50 * 1024L * 1024L);
|
||||
storageOverprovisioningFactor = new BigDecimal(1);
|
||||
extraBytesPerVolume = NumbersUtil.parseLong((String)params.get("extra.bytes.per.volume"), 50 * 1024L * 1024L);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
@ -41,51 +40,49 @@ import com.cloud.vm.VirtualMachineProfile;
|
||||
|
||||
@Component
|
||||
public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
private static final Logger s_logger = Logger.getLogger(ZoneWideStoragePoolAllocator.class);
|
||||
private static final Logger LOGGER = Logger.getLogger(ZoneWideStoragePoolAllocator.class);
|
||||
@Inject
|
||||
PrimaryDataStoreDao _storagePoolDao;
|
||||
@Inject
|
||||
DataStoreManager dataStoreMgr;
|
||||
private DataStoreManager dataStoreMgr;
|
||||
|
||||
|
||||
@Override
|
||||
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
|
||||
s_logger.debug("ZoneWideStoragePoolAllocator to find storage pool");
|
||||
LOGGER.debug("ZoneWideStoragePoolAllocator to find storage pool");
|
||||
|
||||
if (dskCh.useLocalStorage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
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);
|
||||
List<StoragePoolVO> disabledPools = storagePoolDao.findDisabledPoolsByScope(plan.getDataCenterId(), null, null, ScopeType.ZONE);
|
||||
if (disabledPools != null && !disabledPools.isEmpty()) {
|
||||
for (StoragePoolVO pool : disabledPools) {
|
||||
s_logger.trace("Ignoring pool " + pool + " as it is in disabled state.");
|
||||
LOGGER.trace("Ignoring pool " + pool + " as it is in disabled state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<StoragePool> suitablePools = new ArrayList<StoragePool>();
|
||||
List<StoragePool> suitablePools = new ArrayList<>();
|
||||
|
||||
List<StoragePoolVO> storagePools = _storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags());
|
||||
List<StoragePoolVO> storagePools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags());
|
||||
if (storagePools == null) {
|
||||
storagePools = new ArrayList<StoragePoolVO>();
|
||||
storagePools = new ArrayList<>();
|
||||
}
|
||||
|
||||
List<StoragePoolVO> anyHypervisorStoragePools = new ArrayList<StoragePoolVO>();
|
||||
List<StoragePoolVO> anyHypervisorStoragePools = new ArrayList<>();
|
||||
for (StoragePoolVO storagePool : storagePools) {
|
||||
if (HypervisorType.Any.equals(storagePool.getHypervisor())) {
|
||||
anyHypervisorStoragePools.add(storagePool);
|
||||
}
|
||||
}
|
||||
|
||||
List<StoragePoolVO> storagePoolsByHypervisor = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(plan.getDataCenterId(), dskCh.getHypervisorType());
|
||||
List<StoragePoolVO> storagePoolsByHypervisor = storagePoolDao.findZoneWideStoragePoolsByHypervisor(plan.getDataCenterId(), dskCh.getHypervisorType());
|
||||
storagePools.retainAll(storagePoolsByHypervisor);
|
||||
storagePools.addAll(anyHypervisorStoragePools);
|
||||
|
||||
// add remaining pools in zone, that did not match tags, to avoid set
|
||||
List<StoragePoolVO> allPools = _storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), null);
|
||||
List<StoragePoolVO> allPools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), null);
|
||||
allPools.removeAll(storagePools);
|
||||
for (StoragePoolVO pool : allPools) {
|
||||
avoid.addPool(pool.getId());
|
||||
@ -100,12 +97,19 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
if (filter(avoid, storagePool, dskCh, plan)) {
|
||||
suitablePools.add(storagePool);
|
||||
} else {
|
||||
avoid.addPool(storagePool.getId());
|
||||
if (canAddStoragePoolToAvoidSet(storage)) {
|
||||
avoid.addPool(storagePool.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return suitablePools;
|
||||
}
|
||||
|
||||
// Don't add zone-wide, managed storage to the avoid list because it may be usable for another cluster.
|
||||
private boolean canAddStoragePoolToAvoidSet(StoragePoolVO storagePoolVO) {
|
||||
return !ScopeType.ZONE.equals(storagePoolVO.getScope()) || !storagePoolVO.isManaged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StoragePool> reorderPoolsByNumberOfVolumes(DeploymentPlan plan, List<StoragePool> pools, Account account) {
|
||||
if (account == null) {
|
||||
@ -113,21 +117,21 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
}
|
||||
long dcId = plan.getDataCenterId();
|
||||
|
||||
List<Long> poolIdsByVolCount = _volumeDao.listZoneWidePoolIdsByVolumeCount(dcId, account.getAccountId());
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("List of pools in ascending order of number of volumes for account id: " + account.getAccountId() + " is: " + poolIdsByVolCount);
|
||||
List<Long> poolIdsByVolCount = volumeDao.listZoneWidePoolIdsByVolumeCount(dcId, account.getAccountId());
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("List of pools in ascending order of number of volumes for account id: " + account.getAccountId() + " is: " + poolIdsByVolCount);
|
||||
}
|
||||
|
||||
// now filter the given list of Pools by this ordered list
|
||||
Map<Long, StoragePool> poolMap = new HashMap<Long, StoragePool>();
|
||||
Map<Long, StoragePool> poolMap = new HashMap<>();
|
||||
for (StoragePool pool : pools) {
|
||||
poolMap.put(pool.getId(), pool);
|
||||
}
|
||||
List<Long> matchingPoolIds = new ArrayList<Long>(poolMap.keySet());
|
||||
List<Long> matchingPoolIds = new ArrayList<>(poolMap.keySet());
|
||||
|
||||
poolIdsByVolCount.retainAll(matchingPoolIds);
|
||||
|
||||
List<StoragePool> reorderedPools = new ArrayList<StoragePool>();
|
||||
List<StoragePool> reorderedPools = new ArrayList<>();
|
||||
for (Long id : poolIdsByVolCount) {
|
||||
reorderedPools.add(poolMap.get(id));
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ public class RandomStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||
}
|
||||
|
||||
s_logger.debug("Looking for pools in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId);
|
||||
List<StoragePoolVO> pools = _storagePoolDao.listBy(dcId, podId, clusterId, ScopeType.CLUSTER);
|
||||
List<StoragePoolVO> pools = storagePoolDao.listBy(dcId, podId, clusterId, ScopeType.CLUSTER);
|
||||
if (pools.size() == 0) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("No storage pools available for allocation, returning");
|
||||
|
||||
@ -2457,7 +2457,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {StorageCleanupInterval, StorageCleanupDelay, StorageCleanupEnabled, TemplateCleanupEnabled};
|
||||
return new ConfigKey<?>[] { StorageCleanupInterval, StorageCleanupDelay, StorageCleanupEnabled, TemplateCleanupEnabled,
|
||||
KvmStorageOfflineMigrationWait, KvmStorageOnlineMigrationWait, MaxNumberOfManagedClusteredFileSystems };
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -253,6 +253,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
private StorageManager storageMgr;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
@Inject
|
||||
private StorageUtil storageUtil;
|
||||
|
||||
protected Gson _gson;
|
||||
|
||||
@ -2741,6 +2743,42 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyManagedStorage(Long storagePoolId, Long hostId) {
|
||||
if (storagePoolId == null || hostId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
|
||||
|
||||
if (storagePoolVO == null || !storagePoolVO.isManaged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HostVO hostVO = _hostDao.findById(hostId);
|
||||
|
||||
if (hostVO == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!storageUtil.managedStoragePoolCanScale(storagePoolVO, hostVO.getClusterId(), hostVO.getId())) {
|
||||
throw new CloudRuntimeException("Insufficient number of available " + getNameOfClusteredFileSystem(hostVO));
|
||||
}
|
||||
}
|
||||
|
||||
private String getNameOfClusteredFileSystem(HostVO hostVO) {
|
||||
HypervisorType hypervisorType = hostVO.getHypervisorType();
|
||||
|
||||
if (HypervisorType.XenServer.equals(hypervisorType)) {
|
||||
return "SRs";
|
||||
}
|
||||
|
||||
if (HypervisorType.VMware.equals(hypervisorType)) {
|
||||
return "datastores";
|
||||
}
|
||||
|
||||
return "clustered file systems";
|
||||
}
|
||||
|
||||
private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) {
|
||||
String errorMsg = "Failed to attach volume " + volumeToAttach.getName() + " to VM " + vm.getHostName();
|
||||
boolean sendCommand = vm.getState() == State.Running;
|
||||
@ -2768,6 +2806,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
}
|
||||
}
|
||||
|
||||
verifyManagedStorage(volumeToAttachStoragePool.getId(), hostId);
|
||||
|
||||
// volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before
|
||||
DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null;
|
||||
|
||||
|
||||
@ -0,0 +1,431 @@
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import random
|
||||
import SignedAPICall
|
||||
import XenAPI
|
||||
|
||||
from solidfire.factory import ElementFactory
|
||||
|
||||
from util import sf_util
|
||||
|
||||
# All tests inherit from cloudstackTestCase
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
|
||||
# Import Integration Libraries
|
||||
|
||||
# base - contains all resources as entities and defines create, delete, list operations on them
|
||||
from marvin.lib.base import Account, Cluster, ServiceOffering, Snapshot, StoragePool, User, VirtualMachine, Volume
|
||||
|
||||
# common - commonly used methods for all tests are listed here
|
||||
from marvin.lib.common import get_domain, get_template, get_zone, list_hosts, list_volumes
|
||||
|
||||
# utils - utility classes for common cleanup, external library wrappers, etc.
|
||||
from marvin.lib.utils import cleanup_resources
|
||||
|
||||
# Prerequisites:
|
||||
# Only one zone
|
||||
# Only one pod
|
||||
# Two clusters
|
||||
#
|
||||
# Running the tests:
|
||||
# If using XenServer, verify the "xen_server_hostname" variable is correct.
|
||||
# Set the Global Setting "max.number.managed.clustered.file.systems" equal to 2.
|
||||
#
|
||||
# Note:
|
||||
# Verify that TestData.clusterId and TestData.clusterId2 are set properly.
|
||||
|
||||
|
||||
class TestData():
|
||||
# constants
|
||||
account = "account"
|
||||
allocationstate = "allocationstate"
|
||||
capacityBytes = "capacitybytes"
|
||||
capacityIops = "capacityiops"
|
||||
clusterId = "clusterId"
|
||||
clusterId2 = "clusterId2"
|
||||
computeOffering = "computeoffering"
|
||||
domainId = "domainId"
|
||||
email = "email"
|
||||
firstname = "firstname"
|
||||
hypervisor = "hypervisor"
|
||||
lastname = "lastname"
|
||||
mvip = "mvip"
|
||||
name = "name"
|
||||
password = "password"
|
||||
port = "port"
|
||||
primaryStorage = "primarystorage"
|
||||
provider = "provider"
|
||||
scope = "scope"
|
||||
solidFire = "solidfire"
|
||||
storageTag = "SolidFire_SAN_1"
|
||||
tags = "tags"
|
||||
url = "url"
|
||||
user = "user"
|
||||
username = "username"
|
||||
xenServer = "xenserver"
|
||||
zoneId = "zoneId"
|
||||
|
||||
hypervisor_type = xenServer
|
||||
xen_server_hostname = "XenServer-6.5-1"
|
||||
|
||||
def __init__(self):
|
||||
self.testdata = {
|
||||
TestData.solidFire: {
|
||||
TestData.mvip: "10.117.78.225",
|
||||
TestData.username: "admin",
|
||||
TestData.password: "admin",
|
||||
TestData.port: 443,
|
||||
TestData.url: "https://10.117.78.225:443"
|
||||
},
|
||||
TestData.xenServer: {
|
||||
TestData.username: "root",
|
||||
TestData.password: "solidfire"
|
||||
},
|
||||
TestData.account: {
|
||||
TestData.email: "test@test.com",
|
||||
TestData.firstname: "John",
|
||||
TestData.lastname: "Doe",
|
||||
TestData.username: "test",
|
||||
TestData.password: "test"
|
||||
},
|
||||
TestData.user: {
|
||||
TestData.email: "user@test.com",
|
||||
TestData.firstname: "Jane",
|
||||
TestData.lastname: "Doe",
|
||||
TestData.username: "testuser",
|
||||
TestData.password: "password"
|
||||
},
|
||||
TestData.primaryStorage: {
|
||||
TestData.name: "SolidFire-%d" % random.randint(0, 100),
|
||||
TestData.scope: "ZONE",
|
||||
TestData.url: "MVIP=10.117.78.225;SVIP=10.117.94.225;" +
|
||||
"clusterAdminUsername=admin;clusterAdminPassword=admin;" +
|
||||
"clusterDefaultMinIops=10000;clusterDefaultMaxIops=15000;" +
|
||||
"clusterDefaultBurstIopsPercentOfMaxIops=1.5;",
|
||||
TestData.provider: "SolidFire",
|
||||
TestData.tags: TestData.storageTag,
|
||||
TestData.capacityIops: 4500000,
|
||||
TestData.capacityBytes: 2251799813685248,
|
||||
TestData.hypervisor: "Any"
|
||||
},
|
||||
TestData.computeOffering: {
|
||||
TestData.name: "SF_CO_1",
|
||||
"displaytext": "SF_CO_1 (Min IOPS = 300; Max IOPS = 600)",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 100,
|
||||
"memory": 128,
|
||||
"storagetype": "shared",
|
||||
"customizediops": False,
|
||||
"miniops": "300",
|
||||
"maxiops": "600",
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
TestData.tags: TestData.storageTag
|
||||
},
|
||||
TestData.zoneId: 1,
|
||||
TestData.clusterId: 1,
|
||||
TestData.clusterId2: 6,
|
||||
TestData.domainId: 1,
|
||||
TestData.url: "10.117.40.114"
|
||||
}
|
||||
|
||||
|
||||
class TestManagedClusteredFilesystems(cloudstackTestCase):
|
||||
_should_only_be_one_volume_in_list_err_msg = "There should only be one volume in this list."
|
||||
_volume_should_have_failed_to_attach_to_vm = "The volume should have failed to attach to the VM."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Set up API client
|
||||
testclient = super(TestManagedClusteredFilesystems, cls).getClsTestClient()
|
||||
|
||||
cls.apiClient = testclient.getApiClient()
|
||||
cls.configData = testclient.getParsedTestDataConfig()
|
||||
cls.dbConnection = testclient.getDbConnection()
|
||||
|
||||
cls.testdata = TestData().testdata
|
||||
|
||||
sf_util.set_supports_resign(True, cls.dbConnection)
|
||||
|
||||
cls._connect_to_hypervisor()
|
||||
|
||||
# Set up SolidFire connection
|
||||
solidfire = cls.testdata[TestData.solidFire]
|
||||
|
||||
cls.sfe = ElementFactory.create(solidfire[TestData.mvip], solidfire[TestData.username], solidfire[TestData.password])
|
||||
|
||||
# Get Resources from Cloud Infrastructure
|
||||
cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId])
|
||||
cls.template = get_template(cls.apiClient, cls.zone.id, hypervisor=TestData.hypervisor_type)
|
||||
cls.domain = get_domain(cls.apiClient, cls.testdata[TestData.domainId])
|
||||
|
||||
# Create test account
|
||||
cls.account = Account.create(
|
||||
cls.apiClient,
|
||||
cls.testdata["account"],
|
||||
admin=1
|
||||
)
|
||||
|
||||
# Set up connection to make customized API calls
|
||||
cls.user = User.create(
|
||||
cls.apiClient,
|
||||
cls.testdata["user"],
|
||||
account=cls.account.name,
|
||||
domainid=cls.domain.id
|
||||
)
|
||||
|
||||
url = cls.testdata[TestData.url]
|
||||
|
||||
api_url = "http://" + url + ":8080/client/api"
|
||||
userkeys = User.registerUserKeys(cls.apiClient, cls.user.id)
|
||||
|
||||
cls.cs_api = SignedAPICall.CloudStack(api_url, userkeys.apikey, userkeys.secretkey)
|
||||
|
||||
primarystorage = cls.testdata[TestData.primaryStorage]
|
||||
|
||||
cls.primary_storage = StoragePool.create(
|
||||
cls.apiClient,
|
||||
primarystorage,
|
||||
scope=primarystorage[TestData.scope],
|
||||
zoneid=cls.zone.id,
|
||||
provider=primarystorage[TestData.provider],
|
||||
tags=primarystorage[TestData.tags],
|
||||
capacityiops=primarystorage[TestData.capacityIops],
|
||||
capacitybytes=primarystorage[TestData.capacityBytes],
|
||||
hypervisor=primarystorage[TestData.hypervisor]
|
||||
)
|
||||
|
||||
cls.compute_offering = ServiceOffering.create(
|
||||
cls.apiClient,
|
||||
cls.testdata[TestData.computeOffering]
|
||||
)
|
||||
|
||||
# Resources that are to be destroyed
|
||||
cls._cleanup = [
|
||||
cls.compute_offering,
|
||||
cls.user,
|
||||
cls.account
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
cleanup_resources(cls.apiClient, cls._cleanup)
|
||||
|
||||
cls.primary_storage.delete(cls.apiClient)
|
||||
|
||||
sf_util.purge_solidfire_volumes(cls.sfe)
|
||||
except Exception as e:
|
||||
logging.debug("Exception in tearDownClass(cls): %s" % e)
|
||||
|
||||
def setUp(self):
|
||||
self.cleanup = []
|
||||
|
||||
def tearDown(self):
|
||||
cleanup_resources(self.apiClient, self.cleanup)
|
||||
|
||||
# Only two 'permanent' SRs per cluster
|
||||
#
|
||||
# Disable the second cluster
|
||||
#
|
||||
# Create VM
|
||||
# Create VM
|
||||
# Create VM (should fail)
|
||||
# Take snapshot of first root disk
|
||||
# Create a volume from this snapshot
|
||||
# Attach new volume to second VM (should fail)
|
||||
#
|
||||
# Enable the second cluster
|
||||
#
|
||||
# Attach new volume to second VM (should fail)
|
||||
# Create VM (should end up in new cluster)
|
||||
# Delete first VM (this should free up one SR in the first cluster)
|
||||
# Attach new volume to second VM
|
||||
# Detach new volume from second VM
|
||||
# Attach new volume to second VM
|
||||
# Create a volume from the snapshot
|
||||
# Attach this new volume to the second VM (should fail)
|
||||
# Attach this new volume to the first VM in the new cluster
|
||||
def test_managed_clustered_filesystems_limit(self):
|
||||
args = { "id": self.testdata[TestData.clusterId2], TestData.allocationstate: "Disabled" }
|
||||
|
||||
Cluster.update(self.apiClient, **args)
|
||||
|
||||
virtual_machine_names = {
|
||||
"name": "TestVM1",
|
||||
"displayname": "Test VM 1"
|
||||
}
|
||||
|
||||
virtual_machine_1 = self._create_vm(virtual_machine_names)
|
||||
|
||||
list_volumes_response = list_volumes(
|
||||
self.apiClient,
|
||||
virtualmachineid=virtual_machine_1.id,
|
||||
listall=True
|
||||
)
|
||||
|
||||
sf_util.check_list(list_volumes_response, 1, self, TestManagedClusteredFilesystems._should_only_be_one_volume_in_list_err_msg)
|
||||
|
||||
vm_1_root_volume = list_volumes_response[0]
|
||||
|
||||
virtual_machine_names = {
|
||||
"name": "TestVM2",
|
||||
"displayname": "Test VM 2"
|
||||
}
|
||||
|
||||
virtual_machine_2 = self._create_vm(virtual_machine_names)
|
||||
|
||||
virtual_machine_names = {
|
||||
"name": "TestVM3",
|
||||
"displayname": "Test VM 3"
|
||||
}
|
||||
|
||||
class VMStartedException(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Exception.__init__(self, *args, **kwargs)
|
||||
|
||||
try:
|
||||
# The VM should fail to be created as there should be an insufficient number of clustered filesystems
|
||||
# remaining in the compute cluster.
|
||||
self._create_vm(virtual_machine_names)
|
||||
|
||||
raise VMStartedException("The VM should have failed to start.")
|
||||
except VMStartedException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
vol_snap = Snapshot.create(
|
||||
self.apiClient,
|
||||
volume_id=vm_1_root_volume.id
|
||||
)
|
||||
|
||||
services = {"diskname": "Vol-1", "zoneid": self.testdata[TestData.zoneId], "ispublic": True}
|
||||
|
||||
volume_created_from_snapshot_1 = Volume.create_from_snapshot(self.apiClient, vol_snap.id, services, account=self.account.name, domainid=self.domain.id)
|
||||
|
||||
class VolumeAttachedException(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Exception.__init__(self, *args, **kwargs)
|
||||
|
||||
try:
|
||||
# The volume should fail to be attached as there should be an insufficient number of clustered filesystems
|
||||
# remaining in the compute cluster.
|
||||
virtual_machine_2.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_1
|
||||
)
|
||||
|
||||
raise VolumeAttachedException(TestManagedClusteredFilesystems._volume_should_have_failed_to_attach_to_vm)
|
||||
except VolumeAttachedException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
args = { "id": self.testdata[TestData.clusterId2], TestData.allocationstate: "Enabled" }
|
||||
|
||||
Cluster.update(self.apiClient, **args)
|
||||
|
||||
try:
|
||||
# The volume should fail to be attached as there should be an insufficient number of clustered filesystems
|
||||
# remaining in the compute cluster.
|
||||
virtual_machine_2.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_1
|
||||
)
|
||||
|
||||
raise VolumeAttachedException(TestManagedClusteredFilesystems._volume_should_have_failed_to_attach_to_vm)
|
||||
except VolumeAttachedException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
virtual_machine_names = {
|
||||
"name": "TestVMA",
|
||||
"displayname": "Test VM A"
|
||||
}
|
||||
|
||||
virtual_machine_a = self._create_vm(virtual_machine_names)
|
||||
|
||||
host_for_vm_1 = list_hosts(self.apiClient, id=virtual_machine_1.hostid)[0]
|
||||
host_for_vm_a = list_hosts(self.apiClient, id=virtual_machine_a.hostid)[0]
|
||||
|
||||
self.assertTrue(
|
||||
host_for_vm_1.clusterid != host_for_vm_a.clusterid,
|
||||
"VMs 1 and VM a should be in different clusters."
|
||||
)
|
||||
|
||||
virtual_machine_1.delete(self.apiClient, True)
|
||||
|
||||
volume_created_from_snapshot_1 = virtual_machine_2.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_1
|
||||
)
|
||||
|
||||
virtual_machine_2.detach_volume(self.apiClient, volume_created_from_snapshot_1)
|
||||
|
||||
volume_created_from_snapshot_1 = virtual_machine_2.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_1
|
||||
)
|
||||
|
||||
services = {"diskname": "Vol-2", "zoneid": self.testdata[TestData.zoneId], "ispublic": True}
|
||||
|
||||
volume_created_from_snapshot_2 = Volume.create_from_snapshot(self.apiClient, vol_snap.id, services, account=self.account.name, domainid=self.domain.id)
|
||||
|
||||
try:
|
||||
# The volume should fail to be attached as there should be an insufficient number of clustered filesystems
|
||||
# remaining in the compute cluster.
|
||||
virtual_machine_2.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_2
|
||||
)
|
||||
|
||||
raise VolumeAttachedException(TestManagedClusteredFilesystems._volume_should_have_failed_to_attach_to_vm)
|
||||
except VolumeAttachedException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
virtual_machine_a.attach_volume(
|
||||
self.apiClient,
|
||||
volume_created_from_snapshot_2
|
||||
)
|
||||
|
||||
def _create_vm(self, virtual_machine_names):
|
||||
return VirtualMachine.create(
|
||||
self.apiClient,
|
||||
virtual_machine_names,
|
||||
accountid=self.account.name,
|
||||
zoneid=self.zone.id,
|
||||
serviceofferingid=self.compute_offering.id,
|
||||
templateid=self.template.id,
|
||||
domainid=self.domain.id,
|
||||
startvm=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _connect_to_hypervisor(cls):
|
||||
host_ip = "https://" + \
|
||||
list_hosts(cls.apiClient, clusterid=cls.testdata[TestData.clusterId], name=TestData.xen_server_hostname)[0].ipaddress
|
||||
|
||||
cls.xen_session = XenAPI.Session(host_ip)
|
||||
|
||||
xen_server = cls.testdata[TestData.xenServer]
|
||||
|
||||
cls.xen_session.xenapi.login_with_password(xen_server[TestData.username], xen_server[TestData.password])
|
||||
Loading…
x
Reference in New Issue
Block a user