Fix for "Reboot skips network plugin" and Added addl functionality (details below) support for PowerFlex/ScaleIO volume operations (#112)

* Updated libvirt's native reboot operation for VM on KVM using ACPI event, and Added 'forced' reboot option to stop and start the VMs

- Added 'forced' reboot option for User VM (New parameter 'forced' in rebootVirtualMachine API, to stop and start User VM)
- Added 'forced' reboot option for System VM (New parameter 'forced' in rebootSystemVm API, to stop and then start System VM)
- Added 'forced' reboot option for Router (New parameter 'forced' in rebootRouter API, to force stop and then start Router)
- Added force reboot tests for User VM, System VM and Router

* Updated the PowerFlex/ScaleIO volume operations support in CloudStack. Added support for the folllowing:

- PowerFlex volume migration (with snapshots) within the same PowerFlex storage clusters, using native V-Tree migration.
- PowerFlex volume migration (without snapshots) across different PowerFlex storage clusters.
    => findStoragePoolsForMigration API returns PowerFlex pool(s) of different instance as suitable pool(s), for volume(s) on PowerFlex storage pool.
    => Volume(s) with snapshots are not allowed to migrate to different PowerFlex instance.
    => Volume(s) of running VM are not allowed to migrate to other PowerFlex storage pools.
    => Volume migration from PowerFlex pool to Non-PowerFlex pool, and vice versa are not supported.

- Template creation (on secondary storage) from PowerFelx/ScaleIO volume or snapshot.
- Added the PowerFlex/ScaleIO volume/snapshot name to the paths of respective CloudStack resources (Templates, Volumes, Snapshots and VM Snapshots)

Other Changes:
- Fix to remove the duplicate zone wide pools listed while finding storage pools for migration
- Added new response parameter “supportsStorageSnapshot” (true/false) to volume response, and Updated UI to hide the async backup option while taking snapshot for volume(s) with storage snapshot support.

* Provision to add PowerFlex/ScaleIO storage pool as Primary Storage from UI

* Fixed the PowerFlex/ScaleIO volume name inconsistency issue in the volume path after migration, due to rename failure
This commit is contained in:
sureshanaparti 2021-02-19 12:54:13 +05:30 committed by GitHub
parent 56f2c2643a
commit 6b8feb2b09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 3116 additions and 232 deletions

View File

@ -44,7 +44,7 @@ public interface VirtualNetworkApplianceService {
* the command specifying router's id
* @return router if successful
*/
VirtualRouter rebootRouter(long routerId, boolean reprogramNetwork) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
VirtualRouter rebootRouter(long routerId, boolean reprogramNetwork, boolean forced) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
VirtualRouter upgradeRouter(UpgradeRouterCmd cmd);

View File

@ -334,6 +334,7 @@ public class ApiConstants {
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
public static final String SNAPSHOT_TYPE = "snapshottype";
public static final String SNAPSHOT_QUIESCEVM = "quiescevm";
public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot";
public static final String SOURCE_ZONE_ID = "sourcezoneid";
public static final String START_DATE = "startdate";
public static final String START_ID = "startid";

View File

@ -49,6 +49,9 @@ public class RebootRouterCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DomainRouterResponse.class, required = true, description = "the ID of the router")
private Long id;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the router (Router is force Stopped and then Started)")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -96,10 +99,14 @@ public class RebootRouterCmd extends BaseAsyncCmd {
return getId();
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
@Override
public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class,getId()));
VirtualRouter result = _routerService.rebootRouter(getId(), true);
VirtualRouter result = _routerService.rebootRouter(getId(), true, isForced());
if (result != null) {
DomainRouterResponse response = _responseGenerator.createDomainRouterResponse(result);
response.setResponseName("router");

View File

@ -52,6 +52,9 @@ public class RebootSystemVmCmd extends BaseAsyncCmd {
description = "The ID of the system virtual machine")
private Long id;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the system VM (System VM is Stopped and then Started)")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -104,6 +107,10 @@ public class RebootSystemVmCmd extends BaseAsyncCmd {
return getId();
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
@Override
public void execute() {
CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()));

View File

@ -53,6 +53,9 @@ public class RebootVMCmd extends BaseAsyncCmd implements UserCmd {
required=true, description="The ID of the virtual machine")
private Long id;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the VM (VM is Stopped and then Started)")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -61,6 +64,10 @@ public class RebootVMCmd extends BaseAsyncCmd implements UserCmd {
return id;
}
public boolean isForced() {
return (forced != null) ? forced : false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -248,8 +248,12 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "need quiesce vm or not when taking snapshot", since = "4.3")
private boolean needQuiescevm;
@SerializedName(ApiConstants.SUPPORTS_STORAGE_SNAPSHOT)
@Param(description = "true if storage snapshot is supported for the volume, false otherwise")
private boolean supportsStorageSnapshot;
@SerializedName(ApiConstants.PHYSICAL_SIZE)
@Param(description = "the bytes alloaated")
@Param(description = "the bytes allocated")
private Long physicalsize;
@SerializedName(ApiConstants.VIRTUAL_SIZE)
@ -538,6 +542,14 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
return this.needQuiescevm;
}
public void setSupportsStorageSnapshot(boolean supportsStorageSnapshot) {
this.supportsStorageSnapshot = supportsStorageSnapshot;
}
public boolean getSupportsStorageSnapshot() {
return this.supportsStorageSnapshot;
}
public String getIsoId() {
return isoId;
}

View File

@ -96,10 +96,13 @@ public class StorageSubsystemCommandHandlerBase implements StorageSubsystemComma
//copy volume from image cache to primary
return processor.copyVolumeFromImageCacheToPrimary(cmd);
} else if (srcData.getObjectType() == DataObjectType.VOLUME && srcData.getDataStore().getRole() == DataStoreRole.Primary) {
if (destData.getObjectType() == DataObjectType.VOLUME && srcData instanceof VolumeObjectTO && ((VolumeObjectTO)srcData).isDirectDownload()) {
return processor.copyVolumeFromPrimaryToPrimary(cmd);
} else if (destData.getObjectType() == DataObjectType.VOLUME) {
return processor.copyVolumeFromPrimaryToSecondary(cmd);
if (destData.getObjectType() == DataObjectType.VOLUME) {
if ((srcData instanceof VolumeObjectTO && ((VolumeObjectTO)srcData).isDirectDownload()) ||
destData.getDataStore().getRole() == DataStoreRole.Primary) {
return processor.copyVolumeFromPrimaryToPrimary(cmd);
} else {
return processor.copyVolumeFromPrimaryToSecondary(cmd);
}
} else if (destData.getObjectType() == DataObjectType.TEMPLATE) {
return processor.createTemplateFromVolume(cmd);
}

View File

@ -22,6 +22,7 @@ import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.DiskOffering.DiskCacheMode;
import com.cloud.storage.MigrationOptions;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
import com.cloud.vm.VirtualMachine;
@ -35,6 +36,8 @@ public interface VolumeInfo extends DataObject, Volume {
HypervisorType getHypervisorType();
Storage.StoragePoolType getStoragePoolType();
Long getLastPoolId();
String getAttachedVmName();

View File

@ -186,6 +186,8 @@ public interface StorageManager extends StorageService {
StoragePoolVO findLocalStorageOnHost(long hostId);
Host findUpAndEnabledHostWithAccessToStoragePools(List<Long> poolIds);
List<StoragePoolHostVO> findStoragePoolsConnectedToHost(long hostId);
boolean canHostAccessStoragePool(Host host, StoragePool pool);
@ -230,7 +232,9 @@ public interface StorageManager extends StorageService {
*/
boolean storagePoolHasEnoughSpace(List<Volume> volume, StoragePool pool, Long clusterId);
boolean storagePoolHasEnoughSpaceForResize(StoragePool pool, long currentSize, long newSiz);
boolean storagePoolHasEnoughSpaceForResize(StoragePool pool, long currentSize, long newSize);
boolean storagePoolCompatibleWithVolumePool(StoragePool pool, Volume volume);
boolean registerHostListener(String providerUuid, HypervisorHostListener listener);

View File

@ -3191,7 +3191,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
return;
}
s_logger.info("Unable to reboot VM " + vm + " on " + dest.getHost() + " due to " + (rebootAnswer == null ? " no reboot answer" : rebootAnswer.getDetails()));
String errorMsg = "Unable to reboot VM " + vm + " on " + dest.getHost() + " due to " + (rebootAnswer == null ? "no reboot response" : rebootAnswer.getDetails());
s_logger.info(errorMsg);
throw new CloudRuntimeException(errorMsg);
} catch (final OperationTimedoutException e) {
s_logger.warn("Unable to send the reboot command to host " + dest.getHost() + " for the vm " + vm + " due to operation timeout", e);
throw new CloudRuntimeException("Failed to reboot the vm on host " + dest.getHost());

View File

@ -1050,6 +1050,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
VolumeApiResult result = future.get();
if (result.isFailed()) {
s_logger.error("Migrate volume failed:" + result.getResult());
if (result.getResult() != null && result.getResult().contains("[UNSUPPORTED]")) {
throw new CloudRuntimeException("Migrate volume failed: " + result.getResult());
}
throw new StorageUnavailableException("Migrate volume failed: " + result.getResult(), destPool.getId());
} else {
// update the volumeId for snapshots on secondary

View File

@ -32,6 +32,8 @@ public interface StoragePoolHostDao extends GenericDao<StoragePoolHostVO, Long>
List<StoragePoolHostVO> listByHostStatus(long poolId, Status hostStatus);
List<Long> findHostsConnectedToPools(List<Long> poolIds);
List<Pair<Long, Integer>> getDatacenterStoragePoolHostInfo(long dcId, boolean sharedOnly);
public void deletePrimaryRecordsForHost(long hostId);

View File

@ -21,7 +21,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -44,6 +44,8 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase<StoragePoolHostVO, Lo
protected static final String HOST_FOR_POOL_SEARCH = "SELECT * FROM storage_pool_host_ref ph, host h where ph.host_id = h.id and ph.pool_id=? and h.status=? ";
protected static final String HOSTS_FOR_POOLS_SEARCH = "SELECT DISTINCT(ph.host_id) FROM storage_pool_host_ref ph, host h WHERE ph.host_id = h.id AND h.status = 'Up' AND resource_state = 'Enabled' AND ph.pool_id IN (?)";
protected static final String STORAGE_POOL_HOST_INFO = "SELECT p.data_center_id, count(ph.host_id) " + " FROM storage_pool p, storage_pool_host_ref ph "
+ " WHERE p.id = ph.pool_id AND p.data_center_id = ? " + " GROUP by p.data_center_id";
@ -121,6 +123,33 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase<StoragePoolHostVO, Lo
return result;
}
@Override
public List<Long> findHostsConnectedToPools(List<Long> poolIds) {
List<Long> hosts = new ArrayList<Long>();
if (poolIds == null || poolIds.isEmpty()) {
return hosts;
}
String poolIdsInStr = poolIds.stream().map(poolId -> String.valueOf(poolId)).collect(Collectors.joining(",", "(", ")"));
String sql = HOSTS_FOR_POOLS_SEARCH.replace("(?)", poolIdsInStr);
TransactionLegacy txn = TransactionLegacy.currentTxn();
try(PreparedStatement pstmt = txn.prepareStatement(sql);) {
try(ResultSet rs = pstmt.executeQuery();) {
while (rs.next()) {
long hostId = rs.getLong(1); // host_id column
hosts.add(hostId);
}
} catch (SQLException e) {
s_logger.warn("findHostsConnectedToPools:Exception: ", e);
}
} catch (Exception e) {
s_logger.warn("findHostsConnectedToPools:Exception: ", e);
}
return hosts;
}
@Override
public List<Pair<Long, Integer>> getDatacenterStoragePoolHostInfo(long dcId, boolean sharedOnly) {
ArrayList<Pair<Long, Integer>> l = new ArrayList<Pair<Long, Integer>>();

View File

@ -574,6 +574,14 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
}
}
private void verifyFormatWithPoolType(ImageFormat imageFormat, StoragePoolType poolType) {
if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2 &&
!(imageFormat == ImageFormat.RAW && StoragePoolType.PowerFlex == poolType)) {
throw new CloudRuntimeException("Only the following image types are currently supported: " +
ImageFormat.VHD.toString() + ", " + ImageFormat.OVA.toString() + ", " + ImageFormat.QCOW2.toString() + ", and " + ImageFormat.RAW.toString() + "(for PowerFlex)");
}
}
private void verifyFormat(ImageFormat imageFormat) {
if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2) {
throw new CloudRuntimeException("Only the following image types are currently supported: " +
@ -585,8 +593,9 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
long volumeId = snapshotInfo.getVolumeId();
VolumeVO volumeVO = _volumeDao.findByIdIncludingRemoved(volumeId);
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeVO.getPoolId());
verifyFormat(volumeVO.getFormat());
verifyFormatWithPoolType(volumeVO.getFormat(), storagePoolVO.getPoolType());
}
private boolean usingBackendSnapshotFor(SnapshotInfo snapshotInfo) {
@ -917,6 +926,11 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
boolean keepGrantedAccess = false;
DataStore srcDataStore = snapshotInfo.getDataStore();
StoragePoolVO storagePoolVO = _storagePoolDao.findById(srcDataStore.getId());
if (HypervisorType.KVM.equals(snapshotInfo.getHypervisorType()) && storagePoolVO.getPoolType() == StoragePoolType.PowerFlex) {
usingBackendSnapshot = false;
}
if (usingBackendSnapshot) {
createVolumeFromSnapshot(snapshotInfo);
@ -1310,7 +1324,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
Preconditions.checkArgument(volumeInfo != null, "Passing 'null' to volumeInfo of " +
"handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
verifyFormat(templateInfo.getFormat());
DataStore dataStore = volumeInfo.getDataStore();
if (dataStore.getRole() == DataStoreRole.Primary) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
verifyFormatWithPoolType(templateInfo.getFormat(), storagePoolVO.getPoolType());
} else {
verifyFormat(templateInfo.getFormat());
}
HostVO hostVO = null;
@ -2305,7 +2325,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
CopyCmdAnswer copyCmdAnswer = null;
try {
if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat())) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat()) && !(ImageFormat.RAW.equals(volumeInfo.getFormat()) && StoragePoolType.PowerFlex == storagePoolVO.getPoolType())) {
throw new CloudRuntimeException("When using managed storage, you can only create a template from a volume on KVM currently.");
}
@ -2321,7 +2342,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
try {
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
if (srcVolumeDetached) {
if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType()) {
_volumeService.grantAccess(volumeInfo, hostVO, srcDataStore);
}
@ -2353,7 +2374,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
}
finally {
if (srcVolumeDetached) {
if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType()) {
try {
_volumeService.revokeAccess(volumeInfo, hostVO, srcDataStore);
}
@ -2448,7 +2469,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
long snapshotId = snapshotInfo.getId();
snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, DiskTO.IQN));
if (storagePoolVO.getPoolType() == StoragePoolType.PowerFlex) {
snapshotDetails.put(DiskTO.IQN, snapshotInfo.getPath());
} else {
snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, DiskTO.IQN));
}
snapshotDetails.put(DiskTO.VOLUME_SIZE, String.valueOf(snapshotInfo.getSize()));
snapshotDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, getSnapshotProperty(snapshotId, DiskTO.SCSI_NAA_DEVICE_ID));

View File

@ -141,7 +141,7 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
for (VolumeObjectTO volume : volumeTOs) {
String volumeSnapshotName = String.format("%s-%s-%s-%s-%s", ScaleIOUtil.VMSNAPSHOT_PREFIX, vmSnapshotVO.getId(), volume.getId(),
storagePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
srcVolumeDestSnapshotMap.put(volume.getPath(), volumeSnapshotName);
srcVolumeDestSnapshotMap.put(ScaleIOUtil.getVolumePath(volume.getPath()), volumeSnapshotName);
virtual_size += volume.getSize();
VolumeVO volumeVO = volumeDao.findById(volume.getId());
@ -173,7 +173,9 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "SnapshotGroupId", snapshotGroupId, false));
for (int index = 0; index < volumeIds.size(); index++) {
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "Vol_" + volumeTOs.get(index).getId() + "_Snapshot", volumeIds.get(index), false));
String volumeSnapshotName = srcVolumeDestSnapshotMap.get(ScaleIOUtil.getVolumePath(volumeTOs.get(index).getPath()));
String pathWithScaleIOVolumeName = ScaleIOUtil.updatedPathWithVolumeName(volumeIds.get(index), volumeSnapshotName);
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "Vol_" + volumeTOs.get(index).getId() + "_Snapshot", pathWithScaleIOVolumeName, false));
}
vmSnapshotDetailsDao.saveDetails(vmSnapshotDetails);
@ -265,8 +267,8 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
Map<String, String> srcSnapshotDestVolumeMap = new HashMap<>();
for (VolumeObjectTO volume : volumeTOs) {
VMSnapshotDetailsVO vmSnapshotDetail = vmSnapshotDetailsDao.findDetail(vmSnapshotVO.getId(), "Vol_" + volume.getId() + "_Snapshot");
String srcSnapshotVolumeId = vmSnapshotDetail.getValue();
String destVolumeId = volume.getPath();
String srcSnapshotVolumeId = ScaleIOUtil.getVolumePath(vmSnapshotDetail.getValue());
String destVolumeId = ScaleIOUtil.getVolumePath(volume.getPath());
srcSnapshotDestVolumeMap.put(srcSnapshotVolumeId, destVolumeId);
}

View File

@ -207,12 +207,16 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
return false;
}
Volume volume = volumeDao.findById(dskCh.getVolumeId());
if(!storageMgr.storagePoolCompatibleWithVolumePool(pool, volume)) {
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<>();
requestVolumes.add(volume);
return storageMgr.storagePoolHasEnoughIops(requestVolumes, pool) && storageMgr.storagePoolHasEnoughSpace(requestVolumes, pool, plan.getClusterId());

View File

@ -48,15 +48,10 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
@Inject
private CapacityDao capacityDao;
@Override
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
LOGGER.debug("ZoneWideStoragePoolAllocator to find storage pool");
if (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);
@ -92,7 +87,6 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
avoid.addPool(pool.getId());
}
for (StoragePoolVO storage : storagePools) {
if (suitablePools.size() == returnUpTo) {
break;
@ -114,7 +108,6 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
return !ScopeType.ZONE.equals(storagePoolVO.getScope()) || !storagePoolVO.isManaged();
}
@Override
protected List<StoragePool> reorderPoolsByCapacity(DeploymentPlan plan,
List<StoragePool> pools) {

View File

@ -203,8 +203,7 @@ public class PrimaryDataStoreImpl implements PrimaryDataStore {
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
return pdsv.getName();
}
@Override

View File

@ -20,9 +20,6 @@ import java.util.Date;
import javax.inject.Inject;
import com.cloud.storage.MigrationOptions;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@ -33,6 +30,7 @@ import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.DownloadAnswer;
@ -42,6 +40,8 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.DiskOffering.DiskCacheMode;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.MigrationOptions;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.ProvisioningType;
import com.cloud.storage.Volume;
@ -588,6 +588,11 @@ public class VolumeObject implements VolumeInfo {
return volumeDao.getHypervisorType(volumeVO.getId());
}
@Override
public Storage.StoragePoolType getStoragePoolType() {
return volumeVO.getPoolType();
}
@Override
public Long getLastPoolId() {
return volumeVO.getLastPoolId();

View File

@ -64,6 +64,8 @@ import org.apache.cloudstack.storage.datastore.PrimaryDataStoreProviderManager;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
@ -126,6 +128,7 @@ import com.cloud.utils.db.DB;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
import com.google.common.base.Strings;
@Component
public class VolumeServiceImpl implements VolumeService {
@ -165,6 +168,8 @@ public class VolumeServiceImpl implements VolumeService {
@Inject
private PrimaryDataStoreDao storagePoolDao;
@Inject
private StoragePoolDetailsDao _storagePoolDetailsDao;
@Inject
private HostDetailsDao hostDetailsDao;
@Inject
private ManagementService mgr;
@ -176,6 +181,8 @@ public class VolumeServiceImpl implements VolumeService {
private TemplateDataFactory tmplFactory;
@Inject
private VolumeOrchestrationService _volumeMgr;
@Inject
private StorageManager _storageMgr;
private final static String SNAPSHOT_ID = "SNAPSHOT_ID";
@ -1589,6 +1596,8 @@ public class VolumeServiceImpl implements VolumeService {
// part here to make sure the credentials do not get stored in the db unencrypted.
if (pool.getPoolType() == StoragePoolType.SMB && folder != null && folder.contains("?")) {
folder = folder.substring(0, folder.indexOf("?"));
} else if (pool.getPoolType() == StoragePoolType.PowerFlex) {
folder = volume.getFolder();
}
VolumeVO newVol = new VolumeVO(volume);
@ -1598,6 +1607,7 @@ public class VolumeServiceImpl implements VolumeService {
newVol.setFolder(folder);
newVol.setPodId(pool.getPodId());
newVol.setPoolId(pool.getId());
newVol.setPoolType(pool.getPoolType());
newVol.setLastPoolId(lastPoolId);
newVol.setPodId(pool.getPodId());
return volDao.persist(newVol);
@ -1614,7 +1624,6 @@ public class VolumeServiceImpl implements VolumeService {
this.destVolume = destVolume;
this.future = future;
}
}
protected AsyncCallFuture<VolumeApiResult> copyVolumeFromImageToPrimary(VolumeInfo srcVolume, DataStore destStore) {
@ -1724,8 +1733,8 @@ public class VolumeServiceImpl implements VolumeService {
@Override
public AsyncCallFuture<VolumeApiResult> copyVolume(VolumeInfo srcVolume, DataStore destStore) {
DataStore srcStore = srcVolume.getDataStore();
if (s_logger.isDebugEnabled()) {
DataStore srcStore = srcVolume.getDataStore();
String srcRole = (srcStore != null && srcStore.getRole() != null ? srcVolume.getDataStore().getRole().toString() : "<unknown role>");
String msg = String.format("copying %s(id=%d, role=%s) to %s (id=%d, role=%s)"
@ -1746,6 +1755,11 @@ public class VolumeServiceImpl implements VolumeService {
return copyVolumeFromPrimaryToImage(srcVolume, destStore);
}
if (srcStore.getRole() == DataStoreRole.Primary && destStore.getRole() == DataStoreRole.Primary && ((PrimaryDataStore) destStore).isManaged() &&
requiresNewManagedVolumeInDestStore((PrimaryDataStore) srcStore, (PrimaryDataStore) destStore)) {
return copyManagedVolume(srcVolume, destStore);
}
// OfflineVmwareMigration: aren't we missing secondary to secondary in this logic?
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
@ -1791,6 +1805,14 @@ public class VolumeServiceImpl implements VolumeService {
destVolume.processEvent(Event.MigrationCopyFailed);
srcVolume.processEvent(Event.OperationFailed);
destroyVolume(destVolume.getId());
if (destVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
s_logger.info("Dest volume " + destVolume.getId() + " can be removed");
destVolume.processEvent(Event.ExpungeRequested);
destVolume.processEvent(Event.OperationSuccessed);
volDao.remove(destVolume.getId());
future.complete(res);
return null;
}
destVolume = volFactory.getVolume(destVolume.getId());
AsyncCallFuture<VolumeApiResult> destroyFuture = expungeVolumeAsync(destVolume);
destroyFuture.get();
@ -1801,6 +1823,14 @@ public class VolumeServiceImpl implements VolumeService {
volDao.updateUuid(srcVolume.getId(), destVolume.getId());
try {
destroyVolume(srcVolume.getId());
if (srcVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
s_logger.info("Src volume " + srcVolume.getId() + " can be removed");
srcVolume.processEvent(Event.ExpungeRequested);
srcVolume.processEvent(Event.OperationSuccessed);
volDao.remove(srcVolume.getId());
future.complete(res);
return null;
}
srcVolume = volFactory.getVolume(srcVolume.getId());
AsyncCallFuture<VolumeApiResult> destroyFuture = expungeVolumeAsync(srcVolume);
// If volume destroy fails, this could be because of vdi is still in use state, so wait and retry.
@ -1823,6 +1853,213 @@ public class VolumeServiceImpl implements VolumeService {
return null;
}
private class CopyManagedVolumeContext<T> extends AsyncRpcContext<T> {
final VolumeInfo srcVolume;
final VolumeInfo destVolume;
final Host host;
final AsyncCallFuture<VolumeApiResult> future;
public CopyManagedVolumeContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<VolumeApiResult> future, VolumeInfo srcVolume, VolumeInfo destVolume, Host host) {
super(callback);
this.srcVolume = srcVolume;
this.destVolume = destVolume;
this.host = host;
this.future = future;
}
}
private AsyncCallFuture<VolumeApiResult> copyManagedVolume(VolumeInfo srcVolume, DataStore destStore) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult res = new VolumeApiResult(srcVolume);
try {
if (!snapshotMgr.canOperateOnVolume(srcVolume)) {
s_logger.debug("There are snapshots creating for this volume, can not move this volume");
res.setResult("There are snapshots creating for this volume, can not move this volume");
future.complete(res);
return future;
}
if (snapshotMgr.backedUpSnapshotsExistsForVolume(srcVolume)) {
s_logger.debug("There are backed up snapshots for this volume, can not move.");
res.setResult("[UNSUPPORTED] There are backed up snapshots for this volume, can not move. Please try again after removing them.");
future.complete(res);
return future;
}
List<Long> poolIds = new ArrayList<Long>();
poolIds.add(srcVolume.getPoolId());
poolIds.add(destStore.getId());
Host hostWithPoolsAccess = _storageMgr.findUpAndEnabledHostWithAccessToStoragePools(poolIds);
if (hostWithPoolsAccess == null) {
s_logger.debug("No host(s) available with pool access, can not move this volume");
res.setResult("No host(s) available with pool access, can not move this volume");
future.complete(res);
return future;
}
VolumeVO destVol = duplicateVolumeOnAnotherStorage(srcVolume, (StoragePool)destStore);
VolumeInfo destVolume = volFactory.getVolume(destVol.getId(), destStore);
// Create a volume on managed storage.
AsyncCallFuture<VolumeApiResult> createVolumeFuture = createVolumeAsync(destVolume, destStore);
VolumeApiResult createVolumeResult = createVolumeFuture.get();
if (createVolumeResult.isFailed()) {
throw new CloudRuntimeException("Creation of a dest volume failed: " + createVolumeResult.getResult());
}
// Refresh the volume info from the DB.
destVolume = volFactory.getVolume(destVolume.getId(), destStore);
PrimaryDataStore srcPrimaryDataStore = (PrimaryDataStore) srcVolume.getDataStore();
if (srcPrimaryDataStore.isManaged()) {
Map<String, String> srcPrimaryDataStoreDetails = new HashMap<String, String>();
srcPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
srcPrimaryDataStoreDetails.put(PrimaryDataStore.STORAGE_HOST, srcPrimaryDataStore.getHostAddress());
srcPrimaryDataStoreDetails.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(srcPrimaryDataStore.getPort()));
srcPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED_STORE_TARGET, srcVolume.get_iScsiName());
srcPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, srcVolume.getName());
srcPrimaryDataStoreDetails.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(srcVolume.getSize()));
srcPrimaryDataStoreDetails.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(srcPrimaryDataStore.getId())));
srcPrimaryDataStore.setDetails(srcPrimaryDataStoreDetails);
grantAccess(srcVolume, hostWithPoolsAccess, srcVolume.getDataStore());
}
PrimaryDataStore destPrimaryDataStore = (PrimaryDataStore) destStore;
Map<String, String> destPrimaryDataStoreDetails = new HashMap<String, String>();
destPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
destPrimaryDataStoreDetails.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
destPrimaryDataStoreDetails.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
destPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED_STORE_TARGET, destVolume.get_iScsiName());
destPrimaryDataStoreDetails.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, destVolume.getName());
destPrimaryDataStoreDetails.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(destVolume.getSize()));
destPrimaryDataStoreDetails.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(destPrimaryDataStore.getId())));
destPrimaryDataStore.setDetails(destPrimaryDataStoreDetails);
grantAccess(destVolume, hostWithPoolsAccess, destStore);
destVolume.processEvent(Event.CreateRequested);
srcVolume.processEvent(Event.MigrationRequested);
CopyManagedVolumeContext<VolumeApiResult> context = new CopyManagedVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, hostWithPoolsAccess);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copyManagedVolumeCallBack(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, hostWithPoolsAccess, caller);
} catch (Exception e) {
s_logger.error("Copy to managed volume failed due to: " + e);
if(s_logger.isDebugEnabled()) {
s_logger.debug("Copy to managed volume failed.", e);
}
res.setResult(e.toString());
future.complete(res);
}
return future;
}
protected Void copyManagedVolumeCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyManagedVolumeContext<VolumeApiResult> context) {
VolumeInfo srcVolume = context.srcVolume;
VolumeInfo destVolume = context.destVolume;
Host host = context.host;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeApiResult res = new VolumeApiResult(destVolume);
try {
if (srcVolume.getDataStore() != null && ((PrimaryDataStore) srcVolume.getDataStore()).isManaged()) {
revokeAccess(srcVolume, host, srcVolume.getDataStore());
}
revokeAccess(destVolume, host, destVolume.getDataStore());
if (result.isFailed()) {
res.setResult(result.getResult());
destVolume.processEvent(Event.MigrationCopyFailed);
srcVolume.processEvent(Event.OperationFailed);
try {
destroyVolume(destVolume.getId());
destVolume = volFactory.getVolume(destVolume.getId());
AsyncCallFuture<VolumeApiResult> destVolumeDestroyFuture = expungeVolumeAsync(destVolume);
destVolumeDestroyFuture.get();
// If dest managed volume destroy fails, wait and retry.
if (destVolumeDestroyFuture.get().isFailed()) {
Thread.sleep(5 * 1000);
destVolumeDestroyFuture = expungeVolumeAsync(destVolume);
destVolumeDestroyFuture.get();
}
future.complete(res);
} catch (Exception e) {
s_logger.debug("failed to clean up managed volume on storage", e);
}
} else {
srcVolume.processEvent(Event.OperationSuccessed);
destVolume.processEvent(Event.MigrationCopySucceeded, result.getAnswer());
volDao.updateUuid(srcVolume.getId(), destVolume.getId());
try {
destroyVolume(srcVolume.getId());
srcVolume = volFactory.getVolume(srcVolume.getId());
AsyncCallFuture<VolumeApiResult> srcVolumeDestroyFuture = expungeVolumeAsync(srcVolume);
// If src volume destroy fails, wait and retry.
if (srcVolumeDestroyFuture.get().isFailed()) {
Thread.sleep(5 * 1000);
srcVolumeDestroyFuture = expungeVolumeAsync(srcVolume);
srcVolumeDestroyFuture.get();
}
future.complete(res);
} catch (Exception e) {
s_logger.debug("failed to clean up volume on storage", e);
}
}
} catch (Exception e) {
s_logger.debug("Failed to process copy managed volume callback", e);
res.setResult(e.toString());
future.complete(res);
}
return null;
}
private boolean requiresNewManagedVolumeInDestStore(PrimaryDataStore srcDataStore, PrimaryDataStore destDataStore) {
if (srcDataStore == null || destDataStore == null) {
s_logger.warn("Unable to check for new volume, either src or dest pool is null");
return false;
}
if (srcDataStore.getPoolType() == StoragePoolType.PowerFlex && destDataStore.getPoolType() == StoragePoolType.PowerFlex) {
if (srcDataStore.getId() == destDataStore.getId()) {
return false;
}
final String STORAGE_POOL_SYSTEM_ID = "powerflex.storagepool.system.id";
String srcPoolSystemId = null;
StoragePoolDetailVO srcPoolSystemIdDetail = _storagePoolDetailsDao.findDetail(srcDataStore.getId(), STORAGE_POOL_SYSTEM_ID);
if (srcPoolSystemIdDetail != null) {
srcPoolSystemId = srcPoolSystemIdDetail.getValue();
}
String destPoolSystemId = null;
StoragePoolDetailVO destPoolSystemIdDetail = _storagePoolDetailsDao.findDetail(destDataStore.getId(), STORAGE_POOL_SYSTEM_ID);
if (destPoolSystemIdDetail != null) {
destPoolSystemId = destPoolSystemIdDetail.getValue();
}
if (Strings.isNullOrEmpty(srcPoolSystemId) || Strings.isNullOrEmpty(destPoolSystemId)) {
s_logger.warn("PowerFlex src pool: " + srcDataStore.getId() + " or dest pool: " + destDataStore.getId() +
" storage instance details are not available");
return false;
}
if (!srcPoolSystemId.equals(destPoolSystemId)) {
s_logger.debug("PowerFlex src pool: " + srcDataStore.getId() + " and dest pool: " + destDataStore.getId() +
" belongs to different storage instances, create new managed volume");
return true;
}
}
// New volume not required for all other cases (address any cases required in future)
return false;
}
private class MigrateVolumeContext<T> extends AsyncRpcContext<T> {
final VolumeInfo srcVolume;
final VolumeInfo destVolume;
@ -1858,7 +2095,7 @@ public class VolumeServiceImpl implements VolumeService {
caller.setCallback(caller.getTarget().migrateVolumeCallBack(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, caller);
} catch (Exception e) {
s_logger.debug("Failed to copy volume", e);
s_logger.debug("Failed to migrate volume", e);
res.setResult(e.toString());
future.complete(res);
}
@ -1877,6 +2114,10 @@ public class VolumeServiceImpl implements VolumeService {
future.complete(res);
} else {
srcVolume.processEvent(Event.OperationSuccessed);
if (srcVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
future.complete(res);
return null;
}
snapshotMgr.cleanupSnapshotsByVolume(srcVolume.getId());
future.complete(res);
}

View File

@ -131,7 +131,6 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.GuestNetType;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
@ -3216,35 +3215,15 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
String msg = null;
try {
dm = conn.domainLookupByName(vmName);
// Get XML Dump including the secure information such as VNC password
// By passing 1, or VIR_DOMAIN_XML_SECURE flag
// https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainXMLFlags
String vmDef = dm.getXMLDesc(1);
final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
parser.parseDomainXML(vmDef);
for (final InterfaceDef nic : parser.getInterfaces()) {
if (nic.getNetType() == GuestNetType.BRIDGE && nic.getBrName().startsWith("cloudVirBr")) {
try {
final int vnetId = Integer.parseInt(nic.getBrName().replaceFirst("cloudVirBr", ""));
final String pifName = getPif(_guestBridgeName);
final String newBrName = "br" + pifName + "-" + vnetId;
vmDef = vmDef.replace("'" + nic.getBrName() + "'", "'" + newBrName + "'");
s_logger.debug("VM bridge name is changed from " + nic.getBrName() + " to " + newBrName);
} catch (final NumberFormatException e) {
continue;
}
}
}
s_logger.debug(vmDef);
msg = stopVM(conn, vmName, false);
msg = startVM(conn, vmName, vmDef);
// Perform ACPI based reboot
// https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainReboot
// https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainRebootFlagValues
// Send ACPI event to Reboot
dm.reboot(0x1);
return null;
} catch (final LibvirtException e) {
s_logger.warn("Failed to create vm", e);
msg = e.getMessage();
} catch (final InternalErrorException e) {
s_logger.warn("Failed to create vm", e);
msg = e.getMessage();
} finally {
try {
if (dm != null) {

View File

@ -1829,23 +1829,56 @@ public class KVMStorageProcessor implements StorageProcessor {
final ImageFormat destFormat = destVol.getFormat();
final DataStoreTO srcStore = srcData.getDataStore();
final DataStoreTO destStore = destData.getDataStore();
final PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)srcStore;
final PrimaryDataStoreTO primaryStoreDest = (PrimaryDataStoreTO)destStore;
final PrimaryDataStoreTO srcPrimaryStore = (PrimaryDataStoreTO)srcStore;
final PrimaryDataStoreTO destPrimaryStore = (PrimaryDataStoreTO)destStore;
final String srcVolumePath = srcData.getPath();
final String destVolumePath = destData.getPath();
KVMStoragePool destPool = null;
try {
final String volumeName = UUID.randomUUID().toString();
s_logger.debug("Copying src volume (id: " + srcVol.getId() + ", format: " + srcFormat + ", path: " + srcVolumePath + ", primary storage: [id: " + srcPrimaryStore.getId() + ", type: " + srcPrimaryStore.getPoolType() + "]) to dest volume (id: " +
destVol.getId() + ", format: " + destFormat + ", path: " + destVolumePath + ", primary storage: [id: " + destPrimaryStore.getId() + ", type: " + destPrimaryStore.getPoolType() + "]).");
if (srcPrimaryStore.isManaged()) {
if (!storagePoolMgr.connectPhysicalDisk(srcPrimaryStore.getPoolType(), srcPrimaryStore.getUuid(), srcVolumePath, srcPrimaryStore.getDetails())) {
s_logger.warn("Failed to connect src volume at path: " + srcVolumePath + ", in storage pool id: " + srcPrimaryStore.getUuid());
}
}
final KVMPhysicalDisk volume = storagePoolMgr.getPhysicalDisk(srcPrimaryStore.getPoolType(), srcPrimaryStore.getUuid(), srcVolumePath);
if (volume == null) {
s_logger.debug("Failed to get physical disk for volume: " + srcVolumePath);
throw new CloudRuntimeException("Failed to get physical disk for volume at path: " + srcVolumePath);
}
final String destVolumeName = volumeName + "." + destFormat.getFileExtension();
final KVMPhysicalDisk volume = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), srcVolumePath);
volume.setFormat(PhysicalDiskFormat.valueOf(srcFormat.toString()));
destPool = storagePoolMgr.getStoragePool(primaryStoreDest.getPoolType(), primaryStoreDest.getUuid());
String destVolumeName = null;
if (destPrimaryStore.isManaged()) {
if (!storagePoolMgr.connectPhysicalDisk(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid(), destVolumePath, destPrimaryStore.getDetails())) {
s_logger.warn("Failed to connect dest volume at path: " + destVolumePath + ", in storage pool id: " + destPrimaryStore.getUuid());
}
String managedStoreTarget = destPrimaryStore.getDetails() != null ? destPrimaryStore.getDetails().get("managedStoreTarget") : null;
destVolumeName = managedStoreTarget != null ? managedStoreTarget : destVolumePath;
} else {
final String volumeName = UUID.randomUUID().toString();
destVolumeName = volumeName + "." + destFormat.getFileExtension();
}
destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid());
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds());
if (srcPrimaryStore.isManaged()) {
storagePoolMgr.disconnectPhysicalDisk(srcPrimaryStore.getPoolType(), srcPrimaryStore.getUuid(), srcVolumePath);
}
if (destPrimaryStore.isManaged()) {
storagePoolMgr.disconnectPhysicalDisk(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid(), destVolumePath);
}
final VolumeObjectTO newVol = new VolumeObjectTO();
newVol.setPath(destVolumePath + File.separator + destVolumeName);
String path = destPrimaryStore.isManaged() ? destVolumeName : destVolumePath + File.separator + destVolumeName;
newVol.setPath(path);
newVol.setFormat(destFormat);
return new CopyCmdAnswer(newVol);
} catch (final CloudRuntimeException e) {

View File

@ -67,12 +67,14 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
}
@Override
public KVMPhysicalDisk getPhysicalDisk(String volumeId, KVMStoragePool pool) {
if (Strings.isNullOrEmpty(volumeId) || pool == null) {
LOGGER.error("Unable to get physical disk, unspecified volumeid or pool");
public KVMPhysicalDisk getPhysicalDisk(String volumePath, KVMStoragePool pool) {
if (Strings.isNullOrEmpty(volumePath) || pool == null) {
LOGGER.error("Unable to get physical disk, volume path or pool not specified");
return null;
}
String volumeId = ScaleIOUtil.getVolumePath(volumePath);
try {
String diskFilePath = null;
String systemId = ScaleIOUtil.getSystemIdForVolume(volumeId);
@ -98,7 +100,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
}
}
KVMPhysicalDisk disk = new KVMPhysicalDisk(diskFilePath, volumeId, pool);
KVMPhysicalDisk disk = new KVMPhysicalDisk(diskFilePath, volumePath, pool);
disk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
long diskSize = getPhysicalDiskSize(diskFilePath);
@ -107,8 +109,8 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
return disk;
} catch (Exception e) {
LOGGER.error("Failed to get the physical disk: " + volumeId + " on the storage pool: " + pool.getUuid() + " due to " + e.getMessage());
throw new CloudRuntimeException("Failed to get the physical disk: " + volumeId + " on the storage pool: " + pool.getUuid());
LOGGER.error("Failed to get the physical disk: " + volumePath + " on the storage pool: " + pool.getUuid() + " due to " + e.getMessage());
throw new CloudRuntimeException("Failed to get the physical disk: " + volumePath + " on the storage pool: " + pool.getUuid());
}
}
@ -136,6 +138,8 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
throw new CloudRuntimeException("Unable to connect physical disk due to insufficient data");
}
volumePath = ScaleIOUtil.getVolumePath(volumePath);
int waitTimeInSec = DEFAULT_DISK_WAIT_TIME_IN_SECS;
if (details != null && details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
String waitTime = details.get(StorageManager.STORAGE_POOL_DISK_WAIT.toString());
@ -252,8 +256,8 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
}
destDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
destDisk.setSize(disk.getVirtualSize());
destDisk.setVirtualSize(disk.getSize());
destDisk.setVirtualSize(disk.getVirtualSize());
destDisk.setSize(disk.getSize());
QemuImg qemu = new QemuImg(timeout);
QemuImgFile srcFile = null;

View File

@ -100,7 +100,7 @@ public class ScaleIOStoragePoolTest {
}
public void testGetPhysicalDiskWithWildcardFileFilter() throws Exception {
final String volumePath = "6c3362b500000001";
final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
final String systemId = "218ce1797566a00f";
File dir = PowerMockito.mock(File.class);
@ -108,7 +108,8 @@ public class ScaleIOStoragePoolTest {
// TODO: Mock file in dir
File[] files = new File[1];
String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumePath;
String volumeId = ScaleIOUtil.getVolumePath(volumePath);
String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
files[0] = new File(diskFilePath);
PowerMockito.when(dir.listFiles(any(FileFilter.class))).thenReturn(files);
@ -118,10 +119,11 @@ public class ScaleIOStoragePoolTest {
@Test
public void testGetPhysicalDiskWithSystemId() throws Exception {
final String volumePath = "6c3362b500000001";
final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
final String volumeId = ScaleIOUtil.getVolumePath(volumePath);
final String systemId = "218ce1797566a00f";
PowerMockito.mockStatic(ScaleIOUtil.class);
when(ScaleIOUtil.getSystemIdForVolume(volumePath)).thenReturn(systemId);
when(ScaleIOUtil.getSystemIdForVolume(volumeId)).thenReturn(systemId);
// TODO: Mock file exists
File file = PowerMockito.mock(File.class);
@ -134,9 +136,10 @@ public class ScaleIOStoragePoolTest {
@Test
public void testConnectPhysicalDisk() {
final String volumePath = "6c3362b500000001";
final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
final String volumeId = ScaleIOUtil.getVolumePath(volumePath);
final String systemId = "218ce1797566a00f";
final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumePath;
final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
KVMPhysicalDisk disk = new KVMPhysicalDisk(diskFilePath, volumePath, pool);
disk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
disk.setSize(8192);
@ -144,7 +147,7 @@ public class ScaleIOStoragePoolTest {
assertEquals(disk.getPath(), "/dev/disk/by-id/emc-vol-218ce1797566a00f-6c3362b500000001");
when(adapter.getPhysicalDisk(volumePath, pool)).thenReturn(disk);
when(adapter.getPhysicalDisk(volumeId, pool)).thenReturn(disk);
final boolean result = adapter.connectPhysicalDisk(volumePath, pool, null);
assertTrue(result);

View File

@ -0,0 +1,39 @@
// 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.storage.datastore.api;
public class VTree {
String storagePoolId;
VTreeMigrationInfo vtreeMigrationInfo;
public String getStoragePoolId() {
return storagePoolId;
}
public void setStoragePoolId(String storagePoolId) {
this.storagePoolId = storagePoolId;
}
public VTreeMigrationInfo getVTreeMigrationInfo() {
return vtreeMigrationInfo;
}
public void setVTreeMigrationInfo(VTreeMigrationInfo vtreeMigrationInfo) {
this.vtreeMigrationInfo = vtreeMigrationInfo;
}
}

View File

@ -0,0 +1,76 @@
// 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.storage.datastore.api;
import com.cloud.utils.EnumUtils;
public class VTreeMigrationInfo {
public enum MigrationStatus {
NotInMigration,
MigrationNormal,
PendingRetry,
InternalPausing,
GracefullyPausing,
ForcefullyPausing,
Paused,
PendingMigration,
PendingRebalance,
None
}
String sourceStoragePoolId;
String destinationStoragePoolId;
MigrationStatus migrationStatus;
Long migrationQueuePosition;
public String getSourceStoragePoolId() {
return sourceStoragePoolId;
}
public void setSourceStoragePoolId(String sourceStoragePoolId) {
this.sourceStoragePoolId = sourceStoragePoolId;
}
public String getDestinationStoragePoolId() {
return destinationStoragePoolId;
}
public void setDestinationStoragePoolId(String destinationStoragePoolId) {
this.destinationStoragePoolId = destinationStoragePoolId;
}
public MigrationStatus getMigrationStatus() {
return migrationStatus;
}
public void setMigrationStatus(String migrationStatus) {
this.migrationStatus = EnumUtils.fromString(MigrationStatus.class, migrationStatus, MigrationStatus.None);
}
public void setMigrationStatus(MigrationStatus migrationStatus) {
this.migrationStatus = migrationStatus;
}
public Long getMigrationQueuePosition() {
return migrationQueuePosition;
}
public void setMigrationQueuePosition(Long migrationQueuePosition) {
this.migrationQueuePosition = migrationQueuePosition;
}
}

View File

@ -51,9 +51,11 @@ public interface ScaleIOGatewayClient {
List<Volume> listSnapshotVolumes();
Volume getVolume(String volumeId);
Volume getVolumeByName(String name);
boolean renameVolume(final String volumeId, final String newName);
Volume resizeVolume(final String volumeId, final Integer sizeInGb);
Volume cloneVolume(final String sourceVolumeId, final String destVolumeName);
boolean deleteVolume(final String volumeId);
boolean migrateVolume(final String srcVolumeId, final String destPoolId, final int timeoutInSecs);
boolean mapVolumeToSdc(final String volumeId, final String sdcId);
boolean mapVolumeToSdcWithLimits(final String volumeId, final String sdcId, final Long iopsLimit, final Long bandwidthLimitInKbps);

View File

@ -44,6 +44,8 @@ import org.apache.cloudstack.storage.datastore.api.SnapshotDefs;
import org.apache.cloudstack.storage.datastore.api.SnapshotGroup;
import org.apache.cloudstack.storage.datastore.api.StoragePool;
import org.apache.cloudstack.storage.datastore.api.StoragePoolStatistics;
import org.apache.cloudstack.storage.datastore.api.VTree;
import org.apache.cloudstack.storage.datastore.api.VTreeMigrationInfo;
import org.apache.cloudstack.storage.datastore.api.Volume;
import org.apache.cloudstack.storage.datastore.api.VolumeStatistics;
import org.apache.cloudstack.utils.security.SSLUtils;
@ -361,6 +363,29 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient {
return null;
}
@Override
public boolean renameVolume(final String volumeId, final String newName) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null");
Preconditions.checkArgument(!Strings.isNullOrEmpty(newName), "New name for volume cannot be null");
HttpResponse response = null;
try {
response = post(
"/instances/Volume::" + volumeId + "/action/setVolumeName",
String.format("{\"newName\":\"%s\"}", newName));
checkResponseOK(response);
return true;
} catch (final IOException e) {
LOG.error("Failed to rename PowerFlex volume due to: ", e);
checkResponseTimeOut(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
return false;
}
@Override
public Volume resizeVolume(final String volumeId, final Integer sizeInGB) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null");
@ -755,6 +780,226 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient {
return false;
}
@Override
public boolean migrateVolume(final String srcVolumeId, final String destPoolId, final int timeoutInSecs) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(srcVolumeId), "src volume id cannot be null");
Preconditions.checkArgument(!Strings.isNullOrEmpty(destPoolId), "dest pool id cannot be null");
Preconditions.checkArgument(timeoutInSecs > 0, "timeout must be greater than 0");
try {
Volume volume = getVolume(srcVolumeId);
if (volume == null || Strings.isNullOrEmpty(volume.getVtreeId())) {
LOG.warn("Couldn't find the volume(-tree), can not migrate the volume " + srcVolumeId);
return false;
}
String srcPoolId = volume.getStoragePoolId();
LOG.debug("Migrating the volume: " + srcVolumeId + " on the src pool: " + srcPoolId + " to the dest pool: " + destPoolId +
" in the same PowerFlex cluster");
HttpResponse response = null;
try {
response = post(
"/instances/Volume::" + srcVolumeId + "/action/migrateVTree",
String.format("{\"destSPId\":\"%s\"}", destPoolId));
checkResponseOK(response);
} catch (final IOException e) {
LOG.error("Unable to migrate PowerFlex volume due to: ", e);
checkResponseTimeOut(e);
throw e;
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
LOG.debug("Wait until the migration is complete for the volume: " + srcVolumeId);
long migrationStartTime = System.currentTimeMillis();
boolean status = waitForVolumeMigrationToComplete(volume.getVtreeId(), timeoutInSecs);
// Check volume storage pool and migration status
// volume, v-tree, snapshot ids remains same after the migration
volume = getVolume(srcVolumeId);
if (volume == null || volume.getStoragePoolId() == null) {
LOG.warn("Couldn't get the volume: " + srcVolumeId + " details after migration");
return status;
} else {
String volumeOnPoolId = volume.getStoragePoolId();
// confirm whether the volume is on the dest storage pool or not
if (status && destPoolId.equalsIgnoreCase(volumeOnPoolId)) {
LOG.debug("Migration success for the volume: " + srcVolumeId);
return true;
} else {
try {
// Check and pause any migration activity on the volume
status = false;
VTreeMigrationInfo.MigrationStatus migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId());
if (migrationStatus != null && migrationStatus != VTreeMigrationInfo.MigrationStatus.NotInMigration) {
long timeElapsedInSecs = (System.currentTimeMillis() - migrationStartTime) / 1000;
int timeRemainingInSecs = (int) (timeoutInSecs - timeElapsedInSecs);
if (timeRemainingInSecs > (timeoutInSecs / 2)) {
// Try to pause gracefully (continue the migration) if atleast half of the time is remaining
pauseVolumeMigration(srcVolumeId, false);
status = waitForVolumeMigrationToComplete(volume.getVtreeId(), timeRemainingInSecs);
}
}
if (!status) {
rollbackVolumeMigration(srcVolumeId);
}
return status;
} catch (Exception ex) {
LOG.warn("Exception on pause/rollback migration of the volume: " + srcVolumeId + " - " + ex.getLocalizedMessage());
}
}
}
} catch (final Exception e) {
LOG.error("Failed to migrate PowerFlex volume due to: " + e.getMessage(), e);
throw new CloudRuntimeException("Failed to migrate PowerFlex volume due to: " + e.getMessage());
}
LOG.debug("Migration failed for the volume: " + srcVolumeId);
return false;
}
private boolean waitForVolumeMigrationToComplete(final String volumeTreeId, int waitTimeoutInSecs) {
LOG.debug("Waiting for the migration to complete for the volume-tree " + volumeTreeId);
if (Strings.isNullOrEmpty(volumeTreeId)) {
LOG.warn("Invalid volume-tree id, unable to check the migration status of the volume-tree " + volumeTreeId);
return false;
}
int delayTimeInSecs = 3;
while (waitTimeoutInSecs > 0) {
try {
// Wait and try after few secs (reduce no. of client API calls to check the migration status) and return after migration is complete
Thread.sleep(delayTimeInSecs * 1000);
VTreeMigrationInfo.MigrationStatus migrationStatus = getVolumeTreeMigrationStatus(volumeTreeId);
if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.NotInMigration) {
LOG.debug("Migration completed for the volume-tree " + volumeTreeId);
return true;
}
} catch (Exception ex) {
LOG.warn("Exception while checking for migration status of the volume-tree: " + volumeTreeId + " - " + ex.getLocalizedMessage());
// don't do anything
} finally {
waitTimeoutInSecs = waitTimeoutInSecs - delayTimeInSecs;
}
}
LOG.debug("Unable to complete the migration for the volume-tree " + volumeTreeId);
return false;
}
private VTreeMigrationInfo.MigrationStatus getVolumeTreeMigrationStatus(final String volumeTreeId) {
if (Strings.isNullOrEmpty(volumeTreeId)) {
LOG.warn("Invalid volume-tree id, unable to get the migration status of the volume-tree " + volumeTreeId);
return null;
}
HttpResponse response = null;
try {
response = get("/instances/VTree::" + volumeTreeId);
checkResponseOK(response);
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
VTree volumeTree = mapper.readValue(response.getEntity().getContent(), VTree.class);
if (volumeTree != null && volumeTree.getVTreeMigrationInfo() != null) {
return volumeTree.getVTreeMigrationInfo().getMigrationStatus();
}
} catch (final IOException e) {
LOG.error("Failed to migrate PowerFlex volume due to:", e);
checkResponseTimeOut(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
return null;
}
private boolean rollbackVolumeMigration(final String srcVolumeId) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(srcVolumeId), "src volume id cannot be null");
HttpResponse response = null;
try {
Volume volume = getVolume(srcVolumeId);
VTreeMigrationInfo.MigrationStatus migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId());
if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.NotInMigration) {
LOG.debug("Volume: " + srcVolumeId + " is not migrating, no need to rollback");
return true;
}
pauseVolumeMigration(srcVolumeId, true); // Pause forcefully
// Wait few secs for volume migration to change to Paused state
boolean paused = false;
int retryCount = 3;
while (retryCount > 0) {
try {
Thread.sleep(3000); // Try after few secs
migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId()); // Get updated migration status
if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.Paused) {
LOG.debug("Migration for the volume: " + srcVolumeId + " paused");
paused = true;
break;
}
} catch (Exception ex) {
LOG.warn("Exception while checking for migration pause status of the volume: " + srcVolumeId + " - " + ex.getLocalizedMessage());
// don't do anything
} finally {
retryCount--;
}
}
if (paused) {
// Rollback migration to the src pool (should be quick)
response = post(
"/instances/Volume::" + srcVolumeId + "/action/migrateVTree",
String.format("{\"destSPId\":\"%s\"}", volume.getStoragePoolId()));
checkResponseOK(response);
return true;
} else {
LOG.warn("Migration for the volume: " + srcVolumeId + " didn't pause, couldn't rollback");
}
} catch (final IOException e) {
LOG.error("Failed to rollback volume migration due to: ", e);
checkResponseTimeOut(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
return false;
}
private boolean pauseVolumeMigration(final String volumeId, final boolean forced) {
if (Strings.isNullOrEmpty(volumeId)) {
LOG.warn("Invalid Volume Id, Unable to pause migration of the volume " + volumeId);
return false;
}
HttpResponse response = null;
try {
// When paused gracefully, all data currently being moved is allowed to complete the migration.
// When paused forcefully, migration of unfinished data is aborted and data is left at the source, if possible.
// Pausing forcefully carries a potential risk to data.
response = post(
"/instances/Volume::" + volumeId + "/action/pauseVTreeMigration",
String.format("{\"pauseType\":\"%s\"}", forced ? "Forcefully" : "Gracefully"));
checkResponseOK(response);
return true;
} catch (final IOException e) {
LOG.error("Failed to pause migration of the volume due to: ", e);
checkResponseTimeOut(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
return false;
}
///////////////////////////////////////////////////////
//////////////// StoragePool APIs /////////////////////
///////////////////////////////////////////////////////

View File

@ -36,6 +36,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCommand;
@ -48,10 +49,12 @@ import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnec
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
@ -59,10 +62,12 @@ import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
import com.cloud.host.Host;
import com.cloud.server.ManagementServerImpl;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
@ -70,9 +75,11 @@ import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachineManager;
@ -97,7 +104,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Inject
private SnapshotDataStoreDao snapshotDataStoreDao;
@Inject
protected SnapshotDao snapshotDao;
@Inject
private AlertManager alertMgr;
@Inject
private ConfigurationDao configDao;
public ScaleIOPrimaryDataStoreDriver() {
@ -140,7 +151,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
throw new CloudRuntimeException("Unable to grant access to volume: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
return client.mapVolumeToSdcWithLimits(volume.getPath(), sdc.getId(), iopsLimit, bandwidthLimitInKbps);
return client.mapVolumeToSdcWithLimits(ScaleIOUtil.getVolumePath(volume.getPath()), sdc.getId(), iopsLimit, bandwidthLimitInKbps);
} else if (DataObjectType.TEMPLATE.equals(dataObject.getType())) {
final VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(dataStore.getId(), dataObject.getId());
LOGGER.debug("Granting access for PowerFlex template volume: " + templatePoolRef.getInstallPath());
@ -152,7 +163,19 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
throw new CloudRuntimeException("Unable to grant access to template: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
return client.mapVolumeToSdc(templatePoolRef.getInstallPath(), sdc.getId());
return client.mapVolumeToSdc(ScaleIOUtil.getVolumePath(templatePoolRef.getInstallPath()), sdc.getId());
} else if (DataObjectType.SNAPSHOT.equals(dataObject.getType())) {
SnapshotInfo snapshot = (SnapshotInfo) dataObject;
LOGGER.debug("Granting access for PowerFlex volume snapshot: " + snapshot.getPath());
final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
final Sdc sdc = client.getConnectedSdcByIp(host.getPrivateIpAddress());
if (sdc == null) {
alertHostSdcDisconnection(host);
throw new CloudRuntimeException("Unable to grant access to snapshot: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
return client.mapVolumeToSdc(ScaleIOUtil.getVolumePath(snapshot.getPath()), sdc.getId());
}
return false;
@ -174,7 +197,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
throw new CloudRuntimeException("Unable to revoke access for volume: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
client.unmapVolumeFromSdc(volume.getPath(), sdc.getId());
client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volume.getPath()), sdc.getId());
} else if (DataObjectType.TEMPLATE.equals(dataObject.getType())) {
final VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(dataStore.getId(), dataObject.getId());
LOGGER.debug("Revoking access for PowerFlex template volume: " + templatePoolRef.getInstallPath());
@ -185,7 +208,18 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
throw new CloudRuntimeException("Unable to revoke access for template: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
client.unmapVolumeFromSdc(templatePoolRef.getInstallPath(), sdc.getId());
client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(templatePoolRef.getInstallPath()), sdc.getId());
} else if (DataObjectType.SNAPSHOT.equals(dataObject.getType())) {
SnapshotInfo snapshot = (SnapshotInfo) dataObject;
LOGGER.debug("Revoking access for PowerFlex volume snapshot: " + snapshot.getPath());
final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
final Sdc sdc = client.getConnectedSdcByIp(host.getPrivateIpAddress());
if (sdc == null) {
throw new CloudRuntimeException("Unable to revoke access for snapshot: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(snapshot.getPath()), sdc.getId());
}
} catch (Exception e) {
LOGGER.warn("Failed to revoke access due to: " + e.getMessage(), e);
@ -300,7 +334,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO)snapshotInfo.getTO();
final ScaleIOGatewayClient client = getScaleIOClient(storagePoolId);
final String scaleIOVolumeId = volumeVO.getPath();
final String scaleIOVolumeId = ScaleIOUtil.getVolumePath(volumeVO.getPath());
String snapshotName = String.format("%s-%s-%s-%s", ScaleIOUtil.SNAPSHOT_PREFIX, snapshotInfo.getId(),
storagePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
@ -311,7 +345,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
throw new CloudRuntimeException("Failed to take snapshot on PowerFlex cluster");
}
snapshotObjectTo.setPath(scaleIOVolume.getId());
snapshotObjectTo.setPath(ScaleIOUtil.updatedPathWithVolumeName(scaleIOVolume.getId(), snapshotName));
CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotObjectTo);
result = new CreateCmdResult(null, createObjectAnswer);
result.setResult(null);
@ -347,8 +381,8 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
long storagePoolId = volumeVO.getPoolId();
final ScaleIOGatewayClient client = getScaleIOClient(storagePoolId);
String snapshotVolumeId = snapshot.getPath();
final String destVolumeId = volumeVO.getPath();
String snapshotVolumeId = ScaleIOUtil.getVolumePath(snapshot.getPath());
final String destVolumeId = ScaleIOUtil.getVolumePath(volumeVO.getPath());
client.revertSnapshot(snapshotVolumeId, destVolumeId);
CommandResult commandResult = new CommandResult();
@ -384,8 +418,9 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
VolumeVO volume = volumeDao.findById(volumeInfo.getId());
volume.set_iScsiName(scaleIOVolume.getId());
volume.setPath(scaleIOVolume.getId());
String volumePath = ScaleIOUtil.updatedPathWithVolumeName(scaleIOVolume.getId(), scaleIOVolumeName);
volume.set_iScsiName(volumePath);
volume.setPath(volumePath);
volume.setFolder(scaleIOVolume.getVtreeId());
volume.setSize(scaleIOVolume.getSizeInKb() * 1024);
volume.setPoolType(Storage.StoragePoolType.PowerFlex);
@ -399,7 +434,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
storagePoolDao.update(storagePoolId, storagePool);
return volume.getPath();
return volumePath;
} catch (Exception e) {
String errMsg = "Unable to create PowerFlex Volume due to " + e.getMessage();
LOGGER.warn(errMsg);
@ -431,7 +466,8 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(storagePoolId, templateInfo.getId());
templatePoolRef.setInstallPath(scaleIOVolume.getId());
String templatePath = ScaleIOUtil.updatedPathWithVolumeName(scaleIOVolume.getId(), scaleIOVolumeName);
templatePoolRef.setInstallPath(templatePath);
templatePoolRef.setLocalDownloadPath(scaleIOVolume.getId());
templatePoolRef.setTemplateSize(scaleIOVolume.getSizeInKb() * 1024);
vmTemplatePoolDao.update(templatePoolRef.getId(), templatePoolRef);
@ -442,7 +478,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
storagePoolDao.update(storagePoolId, storagePool);
return scaleIOVolume.getId();
return templatePath;
} catch (Exception e) {
String errMsg = "Unable to create PowerFlex template volume due to " + e.getMessage();
LOGGER.warn(errMsg);
@ -452,15 +488,15 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Override
public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
String scaleIOVolId = null;
String scaleIOVolumePath = null;
String errMsg = null;
try {
if (dataObject.getType() == DataObjectType.VOLUME) {
LOGGER.debug("createAsync - creating volume");
scaleIOVolId = createVolume((VolumeInfo) dataObject, dataStore.getId());
scaleIOVolumePath = createVolume((VolumeInfo) dataObject, dataStore.getId());
} else if (dataObject.getType() == DataObjectType.TEMPLATE) {
LOGGER.debug("createAsync - creating template");
scaleIOVolId = createTemplateVolume((TemplateInfo)dataObject, dataStore.getId());
scaleIOVolumePath = createTemplateVolume((TemplateInfo)dataObject, dataStore.getId());
} else {
errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
LOGGER.error(errMsg);
@ -474,7 +510,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
if (callback != null) {
CreateCmdResult result = new CreateCmdResult(scaleIOVolId, new Answer(null, errMsg == null, errMsg));
CreateCmdResult result = new CreateCmdResult(scaleIOVolumePath, new Answer(null, errMsg == null, errMsg));
result.setResult(errMsg);
callback.complete(result);
}
@ -490,23 +526,26 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
Preconditions.checkArgument(storagePool != null && storagePool.getHostAddress() != null, "storagePool and host address should not be null");
String errMsg = null;
String scaleIOVolumeId = null;
String scaleIOVolumePath = null;
try {
boolean deleteResult = false;
if (dataObject.getType() == DataObjectType.VOLUME) {
LOGGER.debug("deleteAsync - deleting volume");
scaleIOVolumeId = ((VolumeInfo) dataObject).getPath();
scaleIOVolumePath = ((VolumeInfo) dataObject).getPath();
} else if (dataObject.getType() == DataObjectType.SNAPSHOT) {
LOGGER.debug("deleteAsync - deleting snapshot");
scaleIOVolumeId = ((SnapshotInfo) dataObject).getPath();
scaleIOVolumePath = ((SnapshotInfo) dataObject).getPath();
} else if (dataObject.getType() == DataObjectType.TEMPLATE) {
LOGGER.debug("deleteAsync - deleting template");
scaleIOVolumeId = ((TemplateInfo) dataObject).getInstallPath();
scaleIOVolumePath = ((TemplateInfo) dataObject).getInstallPath();
} else {
errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
LOGGER.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
try {
String scaleIOVolumeId = ScaleIOUtil.getVolumePath(scaleIOVolumePath);
final ScaleIOGatewayClient client = getScaleIOClient(storagePoolId);
deleteResult = client.deleteVolume(scaleIOVolumeId);
if (!deleteResult) {
@ -518,7 +557,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
storagePoolDao.update(storagePoolId, storagePool);
} catch (Exception e) {
errMsg = "Unable to delete PowerFlex volume: " + scaleIOVolumeId + " due to " + e.getMessage();
errMsg = "Unable to delete PowerFlex volume: " + scaleIOVolumePath + " due to " + e.getMessage();
LOGGER.warn(errMsg);
throw new CloudRuntimeException(errMsg, e);
}
@ -544,46 +583,207 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Override
public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
DataStore srcStore = destData.getDataStore();
DataStore destStore = destData.getDataStore();
if (srcStore.getRole() == DataStoreRole.Primary && srcData.getType() == DataObjectType.TEMPLATE
&& (destStore.getRole() == DataStoreRole.Primary && destData.getType() == DataObjectType.VOLUME)) {
int primaryStorageDownloadWait = StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value();
Answer answer = null;
String errMsg = null;
Answer answer = null;
String errMsg = null;
try {
LOGGER.debug("Initiating copy from PowerFlex template volume on host " + destHost != null ? destHost.getId() : "");
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
try {
DataStore srcStore = srcData.getDataStore();
DataStore destStore = destData.getDataStore();
if (srcStore.getRole() == DataStoreRole.Primary && (destStore.getRole() == DataStoreRole.Primary && destData.getType() == DataObjectType.VOLUME)) {
if (srcData.getType() == DataObjectType.TEMPLATE) {
answer = copyTemplateToVolume(srcData, destData, destHost);
if (answer == null) {
errMsg = "No answer for copying template to PowerFlex volume";
} else if (!answer.getResult()) {
errMsg = answer.getDetails();
}
} else if (srcData.getType() == DataObjectType.VOLUME) {
if (isSameScaleIOStorageInstance(srcStore, destStore)) {
answer = migrateVolume(srcData, destData);
} else {
answer = copyVolume(srcData, destData, destHost);
}
EndPoint ep = destHost != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(destHost) : selector.select(srcData.getDataStore());
if (ep == null) {
errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
LOGGER.error(errMsg);
answer = new Answer(cmd, false, errMsg);
if (answer == null) {
errMsg = "No answer for migrate PowerFlex volume";
} else if (!answer.getResult()) {
errMsg = answer.getDetails();
}
} else {
answer = ep.sendMessage(cmd);
errMsg = "Unsupported copy operation from src object: (" + srcData.getType() + ", " + srcData.getDataStore() + "), dest object: ("
+ destData.getType() + ", " + destData.getDataStore() + ")";
LOGGER.warn(errMsg);
}
if (answer != null && !answer.getResult()) {
errMsg = answer.getDetails();
}
} catch (Exception e) {
LOGGER.debug("Failed to copy due to ", e);
errMsg = e.toString();
} else {
errMsg = "Unsupported copy operation";
}
CopyCommandResult result = new CopyCommandResult(null, answer);
result.setResult(errMsg);
callback.complete(result);
} catch (Exception e) {
LOGGER.debug("Failed to copy due to " + e.getMessage(), e);
errMsg = e.toString();
}
CopyCommandResult result = new CopyCommandResult(null, answer);
result.setResult(errMsg);
callback.complete(result);
}
private Answer copyTemplateToVolume(DataObject srcData, DataObject destData, Host destHost) {
// Copy PowerFlex/ScaleIO template to volume
LOGGER.debug("Initiating copy from PowerFlex template volume on host " + destHost != null ? destHost.getId() : "");
int primaryStorageDownloadWait = StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value();
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
Answer answer = null;
EndPoint ep = destHost != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(destHost) : selector.select(srcData.getDataStore());
if (ep == null) {
String errorMsg = "No remote endpoint to send command, check if host or ssvm is down?";
LOGGER.error(errorMsg);
answer = new Answer(cmd, false, errorMsg);
} else {
answer = ep.sendMessage(cmd);
}
return answer;
}
private Answer copyVolume(DataObject srcData, DataObject destData, Host destHost) {
// Copy PowerFlex/ScaleIO volume
LOGGER.debug("Initiating copy from PowerFlex volume on host " + destHost != null ? destHost.getId() : "");
String value = configDao.getValue(Config.CopyVolumeWait.key());
int copyVolumeWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue()));
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), copyVolumeWait, VirtualMachineManager.ExecuteInSequence.value());
Answer answer = null;
EndPoint ep = destHost != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(destHost) : selector.select(srcData.getDataStore());
if (ep == null) {
String errorMsg = "No remote endpoint to send command, check if host or ssvm is down?";
LOGGER.error(errorMsg);
answer = new Answer(cmd, false, errorMsg);
} else {
answer = ep.sendMessage(cmd);
}
return answer;
}
private Answer migrateVolume(DataObject srcData, DataObject destData) {
// Volume migration within same PowerFlex/ScaleIO cluster (with same System ID)
DataStore srcStore = srcData.getDataStore();
DataStore destStore = destData.getDataStore();
Answer answer = null;
try {
long srcPoolId = srcStore.getId();
long destPoolId = destStore.getId();
final ScaleIOGatewayClient client = getScaleIOClient(srcPoolId);
final String srcVolumePath = ((VolumeInfo) srcData).getPath();
final String srcVolumeId = ScaleIOUtil.getVolumePath(srcVolumePath);
final StoragePoolVO destStoragePool = storagePoolDao.findById(destPoolId);
final String destStoragePoolId = destStoragePool.getPath();
int migrationTimeout = StorageManager.KvmStorageOfflineMigrationWait.value();
boolean migrateStatus = client.migrateVolume(srcVolumeId, destStoragePoolId, migrationTimeout);
if (migrateStatus) {
String newVolumeName = String.format("%s-%s-%s-%s", ScaleIOUtil.VOLUME_PREFIX, destData.getId(),
destStoragePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
boolean renamed = client.renameVolume(srcVolumeId, newVolumeName);
if (srcData.getId() != destData.getId()) {
VolumeVO destVolume = volumeDao.findById(destData.getId());
// Volume Id in the PowerFlex/ScaleIO pool remains the same after the migration
// Update PowerFlex volume name only after it is renamed, to maintain the consistency
if (renamed) {
String newVolumePath = ScaleIOUtil.updatedPathWithVolumeName(srcVolumeId, newVolumeName);
destVolume.set_iScsiName(newVolumePath);
destVolume.setPath(newVolumePath);
} else {
destVolume.set_iScsiName(srcVolumePath);
destVolume.setPath(srcVolumePath);
}
volumeDao.update(destData.getId(), destVolume);
VolumeVO srcVolume = volumeDao.findById(srcData.getId());
srcVolume.set_iScsiName(null);
srcVolume.setPath(null);
srcVolume.setFolder(null);
volumeDao.update(srcData.getId(), srcVolume);
} else {
// Live migrate volume
VolumeVO volume = volumeDao.findById(srcData.getId());
Long oldPoolId = volume.getPoolId();
volume.setPoolId(destPoolId);
volume.setLastPoolId(oldPoolId);
volumeDao.update(srcData.getId(), volume);
}
List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcData.getId());
if (CollectionUtils.isNotEmpty(snapshots)) {
for (SnapshotVO snapshot : snapshots) {
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
continue;
}
String snapshotVolumeId = ScaleIOUtil.getVolumePath(snapshotStore.getInstallPath());
String newSnapshotName = String.format("%s-%s-%s-%s", ScaleIOUtil.SNAPSHOT_PREFIX, snapshot.getId(),
destStoragePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
renamed = client.renameVolume(snapshotVolumeId, newSnapshotName);
snapshotStore.setDataStoreId(destPoolId);
// Snapshot Id in the PowerFlex/ScaleIO pool remains the same after the migration
// Update PowerFlex snapshot name only after it is renamed, to maintain the consistency
if (renamed) {
snapshotStore.setInstallPath(ScaleIOUtil.updatedPathWithVolumeName(snapshotVolumeId, newSnapshotName));
}
snapshotDataStoreDao.update(snapshotStore.getId(), snapshotStore);
}
}
answer = new Answer(null, true, null);
} else {
String errorMsg = "Failed to migrate PowerFlex volume: " + srcData.getId() + " to storage pool " + destPoolId;
LOGGER.debug(errorMsg);
answer = new Answer(null, false, errorMsg);
}
} catch (Exception e) {
LOGGER.error("Failed to migrate PowerFlex volume: " + srcData.getId() + " due to: " + e.getMessage());
answer = new Answer(null, false, e.getMessage());
}
return answer;
}
private boolean isSameScaleIOStorageInstance(DataStore srcStore, DataStore destStore) {
long srcPoolId = srcStore.getId();
String srcPoolSystemId = null;
StoragePoolDetailVO srcPoolSystemIdDetail = storagePoolDetailsDao.findDetail(srcPoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (srcPoolSystemIdDetail != null) {
srcPoolSystemId = srcPoolSystemIdDetail.getValue();
}
long destPoolId = destStore.getId();
String destPoolSystemId = null;
StoragePoolDetailVO destPoolSystemIdDetail = storagePoolDetailsDao.findDetail(destPoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (destPoolSystemIdDetail != null) {
destPoolSystemId = destPoolSystemIdDetail.getValue();
}
if (Strings.isNullOrEmpty(srcPoolSystemId) || Strings.isNullOrEmpty(destPoolSystemId)) {
throw new CloudRuntimeException("Failed to validate PowerFlex pools compatibility for migration as storage instance details are not available");
}
if (srcPoolSystemId.equals(destPoolSystemId)) {
return true;
}
return false;
}
@Override
public boolean canCopy(DataObject srcData, DataObject destData) {
DataStore srcStore = destData.getDataStore();
DataStore destStore = destData.getDataStore();
if (srcStore.getRole() == DataStoreRole.Primary && srcData.getType() == DataObjectType.TEMPLATE
if ((srcStore.getRole() == DataStoreRole.Primary && (srcData.getType() == DataObjectType.TEMPLATE || srcData.getType() == DataObjectType.VOLUME))
&& (destStore.getRole() == DataStoreRole.Primary && destData.getType() == DataObjectType.VOLUME)) {
StoragePoolVO srcPoolVO = storagePoolDao.findById(srcStore.getId());
StoragePoolVO destPoolVO = storagePoolDao.findById(destStore.getId());
@ -601,7 +801,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
Preconditions.checkArgument(volumeInfo != null, "volumeInfo cannot be null");
try {
String scaleIOVolumeId = volumeInfo.getPath();
String scaleIOVolumeId = ScaleIOUtil.getVolumePath(volumeInfo.getPath());
Long storagePoolId = volumeInfo.getPoolId();
ResizeVolumePayload payload = (ResizeVolumePayload)volumeInfo.getpayload();
@ -642,11 +842,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Override
public void resize(DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
String scaleIOVolumeId = null;
String scaleIOVolumePath = null;
String errMsg = null;
try {
if (dataObject.getType() == DataObjectType.VOLUME) {
scaleIOVolumeId = ((VolumeInfo) dataObject).getPath();
scaleIOVolumePath = ((VolumeInfo) dataObject).getPath();
resizeVolume((VolumeInfo) dataObject);
} else {
errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to resize";
@ -660,7 +860,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
if (callback != null) {
CreateCmdResult result = new CreateCmdResult(scaleIOVolumeId, new Answer(null, errMsg == null, errMsg));
CreateCmdResult result = new CreateCmdResult(scaleIOVolumePath, new Answer(null, errMsg == null, errMsg));
result.setResult(errMsg);
callback.complete(result);
}
@ -702,20 +902,20 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
@Override
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumePath) {
Preconditions.checkArgument(storagePool != null, "storagePool cannot be null");
Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "volumeId cannot be null");
Preconditions.checkArgument(!Strings.isNullOrEmpty(volumePath), "volumePath cannot be null");
try {
final ScaleIOGatewayClient client = getScaleIOClient(storagePool.getId());
VolumeStatistics volumeStatistics = client.getVolumeStatistics(volumeId);
VolumeStatistics volumeStatistics = client.getVolumeStatistics(ScaleIOUtil.getVolumePath(volumePath));
if (volumeStatistics != null) {
Long provisionedSizeInBytes = volumeStatistics.getNetProvisionedAddressesInBytes();
Long allocatedSizeInBytes = volumeStatistics.getAllocatedSizeInBytes();
return new Pair<Long, Long>(provisionedSizeInBytes, allocatedSizeInBytes);
}
} catch (Exception e) {
String errMsg = "Unable to get stats for the volume: " + volumeId + " in the pool: " + storagePool.getId() + " due to " + e.getMessage();
String errMsg = "Unable to get stats for the volume: " + volumePath + " in the pool: " + storagePool.getId() + " due to " + e.getMessage();
LOGGER.warn(errMsg);
throw new CloudRuntimeException(errMsg, e);
}

View File

@ -131,6 +131,8 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc
public DataStore initialize(Map<String, Object> dsInfos) {
String url = (String) dsInfos.get("url");
Long zoneId = (Long) dsInfos.get("zoneId");
Long podId = (Long)dsInfos.get("podId");
Long clusterId = (Long)dsInfos.get("clusterId");
String dataStoreName = (String) dsInfos.get("name");
String providerName = (String) dsInfos.get("providerName");
Long capacityBytes = (Long)dsInfos.get("capacityBytes");
@ -138,6 +140,28 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc
String tags = (String)dsInfos.get("tags");
Map<String, String> details = (Map<String, String>) dsInfos.get("details");
if (zoneId == null) {
throw new CloudRuntimeException("Zone Id must be specified.");
}
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
if (clusterId != null) {
// Primary datastore is cluster-wide, check and set the podId and clusterId parameters
if (podId == null) {
throw new CloudRuntimeException("Pod Id must also be specified when the Cluster Id is specified for Cluster-wide primary storage.");
}
Hypervisor.HypervisorType hypervisorType = getHypervisorTypeForCluster(clusterId);
if (!isSupportedHypervisorType(hypervisorType)) {
throw new CloudRuntimeException("Unsupported hypervisor type: " + hypervisorType.toString());
}
parameters.setPodId(podId);
parameters.setClusterId(clusterId);
} else if (podId != null) {
throw new CloudRuntimeException("Cluster Id must also be specified when the Pod Id is specified for Cluster-wide primary storage.");
}
URI uri = null;
try {
uri = new URI(UriUtils.encodeURIComponent(url));
@ -188,7 +212,6 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc
final org.apache.cloudstack.storage.datastore.api.StoragePool scaleIOPool = this.findStoragePool(gatewayApiURL,
gatewayUsername, gatewayPassword, storagePoolName);
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
parameters.setZoneId(zoneId);
parameters.setName(dataStoreName);
parameters.setProviderName(providerName);
@ -414,6 +437,15 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc
}
}
private Hypervisor.HypervisorType getHypervisorTypeForCluster(long clusterId) {
ClusterVO cluster = clusterDao.findById(clusterId);
if (cluster == null) {
throw new CloudRuntimeException("Unable to locate the specified cluster: " + clusterId);
}
return cluster.getHypervisorType();
}
private static boolean isSupportedHypervisorType(Hypervisor.HypervisorType hypervisorType) {
return Hypervisor.HypervisorType.KVM.equals(hypervisorType);
}

View File

@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.datastore.util;
import org.apache.log4j.Logger;
import com.cloud.utils.script.Script;
import com.google.common.base.Strings;
public class ScaleIOUtil {
private static final Logger LOGGER = Logger.getLogger(ScaleIOUtil.class);
@ -95,4 +96,24 @@ public class ScaleIOUtil {
return result;
}
public static final String getVolumePath(String volumePathWithName) {
if (Strings.isNullOrEmpty(volumePathWithName)) {
return volumePathWithName;
}
if (volumePathWithName.contains(":")) {
return volumePathWithName.substring(0, volumePathWithName.indexOf(':'));
}
return volumePathWithName;
}
public static final String updatedPathWithVolumeName(String volumePath, String volumeName) {
if (Strings.isNullOrEmpty(volumePath) || Strings.isNullOrEmpty(volumeName)) {
return volumePath;
}
return String.format("%s:%s", volumePath, volumeName);
}
}

View File

@ -1792,6 +1792,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
if (caps != null) {
boolean quiescevm = Boolean.parseBoolean(caps.get(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString()));
vr.setNeedQuiescevm(quiescevm);
boolean supportsStorageSnapshot = Boolean.parseBoolean(caps.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()));
vr.setSupportsStorageSnapshot(supportsStorageSnapshot);
}
}
response.setResponses(volumeResponses, result.second());

View File

@ -2833,7 +2833,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
return _serviceOfferingDetailsDao.findZoneIds(serviceOfferingId);
}
protected DiskOfferingVO createDiskOffering(final Long userId, final List<Long> domainIds, final List<Long> zoneIds, final String name, final String description, final String provisioningType,
protected DiskOfferingVO createDiskOffering(final Long userId, final List<Long> domainIds, final List<Long> zoneIds, final String name, final String description, final String provisioningType,
final Long numGibibytes, String tags, boolean isCustomized, final boolean localStorageRequired,
final boolean isDisplayOfferingEnabled, final Boolean isCustomizedIops, Long minIops, Long maxIops,
Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength,

View File

@ -507,7 +507,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROUTER_REBOOT, eventDescription = "rebooting router Vm", async = true)
public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork) throws ConcurrentOperationException, ResourceUnavailableException,
public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork, final boolean forced) throws ConcurrentOperationException, ResourceUnavailableException,
InsufficientCapacityException {
final Account caller = CallContext.current().getCallingAccount();
@ -528,7 +528,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
final UserVO user = _userDao.findById(CallContext.current().getCallingUserId());
s_logger.debug("Stopping and starting router " + router + " as a part of router reboot");
if (stop(router, false, user, caller) != null) {
if (stop(router, forced, user, caller) != null) {
return startRouter(routerId, reprogramNetwork);
} else {
throw new CloudRuntimeException("Failed to reboot router " + router);
@ -1045,7 +1045,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
}
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER, backupRouter.getDataCenterId(), backupRouter.getPodIdToDeployIn(), title, title);
try {
rebootRouter(backupRouter.getId(), true);
rebootRouter(backupRouter.getId(), true, false);
} catch (final ConcurrentOperationException e) {
s_logger.warn("Fail to reboot " + backupRouter.getInstanceName(), e);
} catch (final ResourceUnavailableException e) {

View File

@ -1471,7 +1471,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
StoragePool srcVolumePool = _poolDao.findById(volume.getPoolId());
allPools = getAllStoragePoolCompatileWithVolumeSourceStoragePool(srcVolumePool);
allPools = getAllStoragePoolsCompatibleWithVolumeSourceStoragePool(srcVolumePool);
allPools.remove(srcVolumePool);
if (vm != null) {
suitablePools = findAllSuitableStoragePoolsForVm(volume, vm, srcVolumePool);
@ -1489,16 +1489,17 @@ 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> getAllStoragePoolCompatileWithVolumeSourceStoragePool(StoragePool srcVolumePool) {
private List<? extends StoragePool> getAllStoragePoolsCompatibleWithVolumeSourceStoragePool(StoragePool srcVolumePool) {
List<StoragePoolVO> storagePools = new ArrayList<>();
List<StoragePoolVO> zoneWideStoragePools = _poolDao.findZoneWideStoragePoolsByTags(srcVolumePool.getDataCenterId(), null);
if (CollectionUtils.isNotEmpty(zoneWideStoragePools)) {
storagePools.addAll(zoneWideStoragePools);
}
List<StoragePoolVO> clusterAndLocalStoragePools = _poolDao.listBy(srcVolumePool.getDataCenterId(), srcVolumePool.getPodId(), srcVolumePool.getClusterId(), null);
if (CollectionUtils.isNotEmpty(clusterAndLocalStoragePools)) {
storagePools.addAll(clusterAndLocalStoragePools);
// 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);
}
return storagePools;
}
@ -1540,7 +1541,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (isLocalPoolSameHostAsSourcePool || pool.isShared()) {
suitablePools.add(pool);
}
}
}
return suitablePools;
@ -2391,6 +2391,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
return _consoleProxyDao.findById(instanceId);
}
private ConsoleProxyVO forceRebootConsoleProxy(final VMInstanceVO systemVm) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException {
_itMgr.advanceStop(systemVm.getUuid(), false);
return _consoleProxyMgr.startProxy(systemVm.getId(), true);
}
protected ConsoleProxyVO destroyConsoleProxy(final long instanceId) {
final ConsoleProxyVO proxy = _consoleProxyDao.findById(instanceId);
@ -3262,6 +3267,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
return _secStorageVmDao.findById(instanceId);
}
private SecondaryStorageVmVO forceRebootSecondaryStorageVm(final VMInstanceVO systemVm) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException {
_itMgr.advanceStop(systemVm.getUuid(), false);
return _secStorageVmMgr.startSecStorageVm(systemVm.getId());
}
protected SecondaryStorageVmVO destroySecondaryStorageVm(final long instanceId) {
final SecondaryStorageVmVO secStorageVm = _secStorageVmDao.findById(instanceId);
if (_secStorageVmMgr.destroySecStorageVm(instanceId)) {
@ -3416,12 +3426,24 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
throw ex;
}
if (systemVm.getType().equals(VirtualMachine.Type.ConsoleProxy)) {
ActionEventUtils.startNestedActionEvent(EventTypes.EVENT_PROXY_REBOOT, "rebooting console proxy Vm");
return rebootConsoleProxy(cmd.getId());
} else {
ActionEventUtils.startNestedActionEvent(EventTypes.EVENT_SSVM_REBOOT, "rebooting secondary storage Vm");
return rebootSecondaryStorageVm(cmd.getId());
try {
if (systemVm.getType().equals(VirtualMachine.Type.ConsoleProxy)) {
ActionEventUtils.startNestedActionEvent(EventTypes.EVENT_PROXY_REBOOT, "rebooting console proxy Vm");
if (cmd.isForced()) {
return forceRebootConsoleProxy(systemVm);
}
return rebootConsoleProxy(cmd.getId());
} else {
ActionEventUtils.startNestedActionEvent(EventTypes.EVENT_SSVM_REBOOT, "rebooting secondary storage Vm");
if (cmd.isForced()) {
return forceRebootSecondaryStorageVm(systemVm);
}
return rebootSecondaryStorageVm(cmd.getId());
}
} catch (final ResourceUnavailableException e) {
throw new CloudRuntimeException("Unable to reboot " + systemVm, e);
} catch (final OperationTimedoutException e) {
throw new CloudRuntimeException("Operation timed out - Unable to reboot " + systemVm, e);
}
}

View File

@ -1710,6 +1710,38 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
}
}
@Override
public Host findUpAndEnabledHostWithAccessToStoragePools(List<Long> poolIds) {
List<Long> hostIds = _storagePoolHostDao.findHostsConnectedToPools(poolIds);
if (hostIds.isEmpty()) {
return null;
}
for (Long hostId : hostIds) {
Host host = _hostDao.findById(hostId);
if (canHostAccessStoragePools(host, poolIds)) {
return host;
}
}
return null;
}
private boolean canHostAccessStoragePools(Host host, List<Long> poolIds) {
if (poolIds == null || poolIds.isEmpty()) {
return false;
}
for (Long poolId : poolIds) {
StoragePool pool = _storagePoolDao.findById(poolId);
if (!canHostAccessStoragePool(host, pool)) {
return false;
}
}
return true;
}
@Override
@DB
public List<StoragePoolHostVO> findStoragePoolsConnectedToHost(long hostId) {
@ -2095,6 +2127,36 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
return tmpl.getSize();
}
@Override
public boolean storagePoolCompatibleWithVolumePool(StoragePool pool, Volume volume) {
if (pool == null || volume == null) {
return false;
}
if (volume.getPoolId() == null) {
// Volume is not allocated to any pool. Not possible to check compatibility with other pool, let it try
return true;
}
StoragePool volumePool = _storagePoolDao.findById(volume.getPoolId());
if (volumePool == null) {
// Volume pool doesn't exist. Not possible to check compatibility with other pool, let it try
return true;
}
if (volume.getState() == Volume.State.Ready) {
if (volumePool.getPoolType() == Storage.StoragePoolType.PowerFlex && pool.getPoolType() != Storage.StoragePoolType.PowerFlex) {
return false;
} else if (volumePool.getPoolType() != Storage.StoragePoolType.PowerFlex && pool.getPoolType() == Storage.StoragePoolType.PowerFlex) {
return false;
}
} else {
return false;
}
return true;
}
@Override
public void createCapacityEntry(long poolId) {
StoragePoolVO storage = _storagePoolDao.findById(poolId);

View File

@ -74,6 +74,8 @@ import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
@ -213,6 +215,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject
private SnapshotDao _snapshotDao;
@Inject
private SnapshotDataStoreDao _snapshotDataStoreDao;
@Inject
private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@Inject
private UserVmDao _userVmDao;
@ -689,6 +693,15 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
if (snapshotCheck.getState() != Snapshot.State.BackedUp) {
throw new InvalidParameterValueException("Snapshot id=" + snapshotId + " is not in " + Snapshot.State.BackedUp + " state yet and can't be used for volume creation");
}
SnapshotDataStoreVO snapshotStore = _snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
if (snapshotStore != null) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(snapshotStore.getDataStoreId());
if (storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex) {
throw new InvalidParameterValueException("Create volume from snapshot is not supported for PowerFlex volume snapshots");
}
}
parentVolume = _volsDao.findByIdIncludingRemoved(snapshotCheck.getVolumeId());
if (zoneId == null) {
@ -2183,6 +2196,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported");
}
StoragePoolVO storagePoolVO = _storagePoolDao.findById(vol.getPoolId());
if (storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex) {
throw new InvalidParameterValueException("Migrate volume of a running VM is unsupported on storage pool type " + storagePoolVO.getPoolType());
}
// Check if the underlying hypervisor supports storage motion.
Long hostId = vm.getHostId();
if (hostId != null) {
@ -2215,6 +2234,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
throw new InvalidParameterValueException("Cannot migrate volume " + vol + "to the destination storage pool " + destPool.getName() + " as the storage pool is in maintenance mode.");
}
if (!storageMgr.storagePoolCompatibleWithVolumePool(destPool, (Volume) vol)) {
throw new CloudRuntimeException("Storage pool " + destPool.getName() + " is not suitable to migrate volume " + vol.getName());
}
if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(vol), destPool)) {
throw new CloudRuntimeException("Storage pool " + destPool.getName() + " does not have enough space to migrate volume " + vol.getName());
}

View File

@ -77,6 +77,8 @@ public interface SnapshotManager extends Configurable {
boolean canOperateOnVolume(Volume volume);
boolean backedUpSnapshotsExistsForVolume(Volume volume);
void cleanupSnapshotsByVolume(Long volumeId);
Answer sendToPool(Volume vol, Command cmd);

View File

@ -1388,6 +1388,15 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
return true;
}
@Override
public boolean backedUpSnapshotsExistsForVolume(Volume volume) {
List<SnapshotVO> snapshots = _snapshotDao.listByStatus(volume.getId(), Snapshot.State.BackedUp);
if (snapshots.size() > 0) {
return true;
}
return false;
}
@Override
public void cleanupSnapshotsByVolume(Long volumeId) {
List<SnapshotInfo> infos = snapshotFactory.getSnapshots(volumeId, DataStoreRole.Primary);

View File

@ -789,7 +789,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return true;
}
if (rebootVirtualMachine(userId, vmId) == null) {
if (rebootVirtualMachine(userId, vmId, false) == null) {
s_logger.warn("Failed to reboot the vm " + vmInstance);
return false;
} else {
@ -900,7 +900,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
s_logger.debug("Vm " + vmInstance + " is stopped, not rebooting it as a part of SSH Key reset");
return true;
}
if (rebootVirtualMachine(userId, vmId) == null) {
if (rebootVirtualMachine(userId, vmId, false) == null) {
s_logger.warn("Failed to reboot the vm " + vmInstance);
return false;
} else {
@ -937,7 +937,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return status;
}
private UserVm rebootVirtualMachine(long userId, long vmId) throws InsufficientCapacityException, ResourceUnavailableException {
private UserVm rebootVirtualMachine(long userId, long vmId, boolean forced) throws InsufficientCapacityException, ResourceUnavailableException {
UserVmVO vm = _vmDao.findById(vmId);
if (vm == null || vm.getState() == State.Destroyed || vm.getState() == State.Expunging || vm.getRemoved() != null) {
@ -948,6 +948,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (vm.getState() == State.Running && vm.getHostId() != null) {
collectVmDiskStatistics(vm);
collectVmNetworkStatistics(vm);
if (forced) {
Host vmOnHost = _hostDao.findById(vm.getHostId());
if (vmOnHost == null || vmOnHost.getResourceState() != ResourceState.Enabled || vmOnHost.getStatus() != Status.Up ) {
throw new CloudRuntimeException("Unable to force reboot the VM as the host: " + vm.getHostId() + " is not in the right state");
}
return forceRebootVirtualMachine(vmId, vm.getHostId());
}
DataCenterVO dc = _dcDao.findById(vm.getDataCenterId());
try {
if (dc.getNetworkType() == DataCenter.NetworkType.Advanced) {
@ -971,7 +980,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new CloudRuntimeException("Concurrent operations on starting router. " + e);
} catch (Exception ex){
throw new CloudRuntimeException("Router start failed due to" + ex);
}finally {
} finally {
s_logger.info("Rebooting vm " + vm.getInstanceName());
_itMgr.reboot(vm.getUuid(), null);
}
@ -982,6 +991,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
private UserVm forceRebootVirtualMachine(long vmId, long hostId) {
try {
if (stopVirtualMachine(vmId, false) != null) {
return startVirtualMachine(vmId, null, null, hostId, null, null).first();
}
} catch (ResourceUnavailableException e) {
throw new CloudRuntimeException("Unable to reboot the VM: " + vmId, e);
} catch (CloudException e) {
throw new CloudRuntimeException("Unable to reboot the VM: " + vmId, e);
}
return null;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_UPGRADE, eventDescription = "upgrading Vm")
/*
@ -2856,7 +2878,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
// Verify input parameters
UserVmVO vmInstance = _vmDao.findById(vmId);
if (vmInstance == null) {
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
throw new InvalidParameterValueException("Unable to find a virtual machine with id " + vmId);
}
_accountMgr.checkAccess(caller, null, true, vmInstance);
@ -2874,7 +2896,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId + " corresponding to the vm");
}
UserVm userVm = rebootVirtualMachine(CallContext.current().getCallingUserId(), vmId);
UserVm userVm = rebootVirtualMachine(CallContext.current().getCallingUserId(), vmId, cmd.isForced());
if (userVm != null ) {
// update the vmIdCountMap if the vm is in advanced shared network with out services
final List<NicVO> nics = _nicDao.listByVmId(vmId);

View File

@ -110,7 +110,7 @@ public class MockVpcVirtualNetworkApplianceManager extends ManagerBase implement
* @see com.cloud.network.VirtualNetworkApplianceService#rebootRouter(long, boolean)
*/
@Override
public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork) throws ConcurrentOperationException, ResourceUnavailableException {
public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork, final boolean forced) throws ConcurrentOperationException, ResourceUnavailableException {
// TODO Auto-generated method stub
return null;
}

View File

@ -0,0 +1,46 @@
# PowerFlex/ScaleIO storage plugin
==================================
This directory contains the basic VM, Volume life cycle tests for PowerFlex/ScaleIO storage pool (in KVM hypervisor).
# Running tests
===============
To run the basic volume tests, first update the below test data of the CloudStack environment
````
TestData.zoneId: <id of zone>
TestData.clusterId: <id of cluster>
TestData.domainId: <id of domain>
TestData.url: <management server IP>
TestData.primaryStorage "url": <PowerFlex/ScaleIO storage pool url (see the format below) to use as primary storage>
````
and to enable and run volume migration tests, update the below test data
````
TestData.migrationTests: True
TestData.primaryStorageSameInstance "url": <PowerFlex/ScaleIO storage pool url (see the format below) of the pool on same storage cluster as TestData.primaryStorage>
TestData.primaryStorageDistinctInstance "url": <PowerFlex/ScaleIO storage pool url (see the format below) of the pool not on the same storage cluster as TestData.primaryStorage>
````
PowerFlex/ScaleIO storage pool url format:
````
powerflex://<api_user>:<api_password>@<gateway>/<storagepool>
where,
- <api_user> : user name for API access
- <api_password> : url-encoded password for API access
- <gateway> : scaleio gateway host
- <storagepool> : storage pool name (case sensitive)
For example: "powerflex://admin:P%40ssword123@10.10.2.130/cspool"
````
Then run the tests using python unittest runner: nosetests
````
nosetests --with-marvin --marvin-config=<marvin-cfg-file> <cloudstack-dir>/test/integration/plugins/scaleio/test_scaleio_volumes.py --zone=<zone> --hypervisor=kvm
````
You can also run these tests out of the box with PyDev or PyCharm or whatever.

File diff suppressed because it is too large Load Diff

View File

@ -821,3 +821,46 @@ class TestRouterServices(cloudstackTestCase):
"Router response after reboot is either is invalid\
or in stopped state")
return
@attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
def test_10_reboot_router_forced(self):
"""Test force reboot router
"""
list_router_response = list_routers(
self.apiclient,
account=self.account.name,
domainid=self.account.domainid
)
self.assertEqual(
isinstance(list_router_response, list),
True,
"Check list response returns a valid list"
)
router = list_router_response[0]
public_ip = router.publicip
self.debug("Force rebooting the router with ID: %s" % router.id)
# Reboot the router
cmd = rebootRouter.rebootRouterCmd()
cmd.id = router.id
cmd.forced = True
self.apiclient.rebootRouter(cmd)
# List routers to check state of router
retries_cnt = 10
while retries_cnt >= 0:
router_response = list_routers(
self.apiclient,
id=router.id
)
if self.verifyRouterResponse(router_response, public_ip):
self.debug("Router is running successfully after force reboot")
return
time.sleep(10)
retries_cnt = retries_cnt - 1
self.fail(
"Router response after force reboot is either invalid\
or router in stopped state")
return

View File

@ -957,7 +957,122 @@ class TestSSVMs(cloudstackTestCase):
"basic",
"sg"],
required_hardware="true")
def test_09_destroy_ssvm(self):
def test_09_reboot_ssvm_forced(self):
"""Test force reboot SSVM
"""
list_ssvm_response = list_ssvms(
self.apiclient,
systemvmtype='secondarystoragevm',
state='Running',
zoneid=self.zone.id
)
self.assertEqual(
isinstance(list_ssvm_response, list),
True,
"Check list response returns a valid list"
)
ssvm_response = list_ssvm_response[0]
hosts = list_hosts(
self.apiclient,
id=ssvm_response.hostid
)
self.assertEqual(
isinstance(hosts, list),
True,
"Check list response returns a valid list"
)
self.debug("Force rebooting SSVM: %s" % ssvm_response.id)
cmd = rebootSystemVm.rebootSystemVmCmd()
cmd.id = ssvm_response.id
cmd.forced = True
self.apiclient.rebootSystemVm(cmd)
ssvm_response = self.checkForRunningSystemVM(ssvm_response)
self.debug("SSVM State: %s" % ssvm_response.state)
self.assertEqual(
'Running',
str(ssvm_response.state),
"Check whether SSVM is running or not"
)
# Wait for the agent to be up
self.waitForSystemVMAgent(ssvm_response.name)
# Wait until NFS stores mounted before running the script
time.sleep(90)
# Call to verify cloud process is running
self.test_03_ssvm_internals()
@attr(
tags=[
"advanced",
"advancedns",
"smoke",
"basic",
"sg"],
required_hardware="true")
def test_10_reboot_cpvm_forced(self):
"""Test force reboot CPVM
"""
list_cpvm_response = list_ssvms(
self.apiclient,
systemvmtype='consoleproxy',
state='Running',
zoneid=self.zone.id
)
self.assertEqual(
isinstance(list_cpvm_response, list),
True,
"Check list response returns a valid list"
)
cpvm_response = list_cpvm_response[0]
hosts = list_hosts(
self.apiclient,
id=cpvm_response.hostid
)
self.assertEqual(
isinstance(hosts, list),
True,
"Check list response returns a valid list"
)
self.debug("Force rebooting CPVM: %s" % cpvm_response.id)
cmd = rebootSystemVm.rebootSystemVmCmd()
cmd.id = cpvm_response.id
cmd.forced = True
self.apiclient.rebootSystemVm(cmd)
cpvm_response = self.checkForRunningSystemVM(cpvm_response)
self.debug("CPVM state: %s" % cpvm_response.state)
self.assertEqual(
'Running',
str(cpvm_response.state),
"Check whether CPVM is running or not"
)
# Wait for the agent to be up
self.waitForSystemVMAgent(cpvm_response.name)
# Call to verify cloud process is running
self.test_04_cpvm_internals()
@attr(
tags=[
"advanced",
"advancedns",
"smoke",
"basic",
"sg"],
required_hardware="true")
def test_11_destroy_ssvm(self):
"""Test destroy SSVM
"""
@ -1029,7 +1144,7 @@ class TestSSVMs(cloudstackTestCase):
"basic",
"sg"],
required_hardware="true")
def test_10_destroy_cpvm(self):
def test_12_destroy_cpvm(self):
"""Test destroy CPVM
"""
@ -1100,7 +1215,7 @@ class TestSSVMs(cloudstackTestCase):
"basic",
"sg"],
required_hardware="true")
def test_11_ss_nfs_version_on_ssvm(self):
def test_13_ss_nfs_version_on_ssvm(self):
"""Test NFS Version on Secondary Storage mounted properly on SSVM
"""

View File

@ -484,6 +484,40 @@ class TestVMLifeCycle(cloudstackTestCase):
)
return
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_04_reboot_vm_forced(self):
"""Test Force Reboot Virtual Machine
"""
try:
self.debug("Force rebooting VM - ID: %s" % self.virtual_machine.id)
self.small_virtual_machine.reboot(self.apiclient, forced=True)
except Exception as e:
self.fail("Failed to force reboot VM: %s" % e)
list_vm_response = VirtualMachine.list(
self.apiclient,
id=self.small_virtual_machine.id
)
self.assertEqual(
isinstance(list_vm_response, list),
True,
"Check list response returns a valid list"
)
self.assertNotEqual(
len(list_vm_response),
0,
"Check VM available in List Virtual Machines"
)
self.assertEqual(
list_vm_response[0].state,
"Running",
"Check virtual machine is in running state"
)
return
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_06_destroy_vm(self):
"""Test destroy Virtual Machine

View File

@ -679,10 +679,12 @@ class VirtualMachine:
raise Exception(response[1])
return
def reboot(self, apiclient):
def reboot(self, apiclient, forced=None):
"""Reboot the instance"""
cmd = rebootVirtualMachine.rebootVirtualMachineCmd()
cmd.id = self.id
if forced:
cmd.forced = forced
apiclient.rebootVirtualMachine(cmd)
response = self.getState(apiclient, VirtualMachine.RUNNING)
@ -4323,10 +4325,12 @@ class Router:
return apiclient.stopRouter(cmd)
@classmethod
def reboot(cls, apiclient, id):
def reboot(cls, apiclient, id, forced=None):
"""Reboots the router"""
cmd = rebootRouter.rebootRouterCmd()
cmd.id = id
if forced:
cmd.forced = forced
return apiclient.rebootRouter(cmd)
@classmethod

View File

@ -4006,7 +4006,7 @@ table tr.selected td.actions .action.disabled .icon {
.ui-dialog div.form-container div.name label {
display: block;
width: 119px;
width: 127px;
margin-top: 2px;
font-size: 13px;
text-align: right;

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Your internet name cannot be resolved.",
"force.delete": "Force Delete",
"force.delete.domain.warning": "Warning: Choosing this option will cause the deletion of all child domains and all associated accounts and their resources.",
"force.reboot":"Force Reboot",
"force.remove": "Force Remove",
"force.remove.host.warning": "Warning: Choosing this option will cause CloudStack to forcefully stop all running virtual machines before removing this host from the cluster.",
"force.stop": "Force Stop",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Power State",
"label.prev": "Prev",
"label.previous": "السابق",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Your internet name cannot be resolved.",
"force.delete": "Force Delete",
"force.delete.domain.warning": "Warning: Choosing this option will cause the deletion of all child domains and all associated accounts and their resources.",
"force.reboot":"Force Reboot",
"force.remove": "Force Remove",
"force.remove.host.warning": "Warning: Choosing this option will cause CloudStack to forcefully stop all running virtual machines before removing this host from the cluster.",
"force.stop": "Force Stop",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Power State",
"label.prev": "Prev",
"label.previous": "Anterior",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Ihr Internetname kann nicht aufgelöst werden.",
"force.delete": "Erzwinge Löschung",
"force.delete.domain.warning": "Achtung: Diese Auswahl führt zu einer Löschung aller untergeordneten Domains und aller angeschlossenen Konten sowie ihrer Quellen.",
"force.reboot":"Neustart erzwingen",
"force.remove": "Erzwinge Entfernung",
"force.remove.host.warning": "Achtung: Diese Auswahl wird CloudStack zum sofortigen Anhalten der virtuellen Maschine führen, bevor der Host vom Cluster entfernt wurde.",
"force.stop": "Erzwinge Stopp",
@ -1263,6 +1264,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP-Bereichsdetails",
"label.portable.ip.ranges": "Portable IP-Bereiche",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Betriebszustand",
"label.prev": "Vor",
"label.previous": "Vorherige",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name":"Your internet name cannot be resolved.",
"force.delete":"Force Delete",
"force.delete.domain.warning":"Warning: Choosing this option will cause the deletion of all child domains and all associated accounts and their resources.",
"force.reboot":"Force Reboot",
"force.remove":"Force Remove",
"force.remove.host.warning":"Warning: Choosing this option will cause CloudStack to forcefully stop all running virtual machines before removing this host from the cluster.",
"force.stop":"Force Stop",
@ -1361,6 +1362,10 @@ var dictionary = {
"label.portable.ip.range.details":"Portable IP Range details",
"label.portable.ip.ranges":"Portable IP Ranges",
"label.portable.ips":"Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate":"Power State",
"label.prev":"Prev",
"label.previous":"Previous",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "El nombre de Internet no se puede resolver.",
"force.delete": "Forzar Borrado",
"force.delete.domain.warning": "Advertencia: Elegir esta opción, provocará la eliminación de todos los dominios hijos y todas las cuentas asociadas y sus recursos.",
"force.reboot":"Forzar reinicio",
"force.remove": "Forzar el retiro",
"force.remove.host.warning": "Advertencia: Elegir esta opción provocará que CloudStack detenga a la fuerza todas las máquinas virtuales antes de eliminar este host del clúster.",
"force.stop": "Forzar Parar",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Detalles del Rango de IP portátil",
"label.portable.ip.ranges": "Rangos de IP portátiles",
"label.portable.ips": "IPs Portátiles",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Estado de la Alimentación",
"label.prev": "Anterior",
"label.previous": "Previo",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Votre nom Internet ne peut pas être résolu.",
"force.delete": "Forcer la suppression",
"force.delete.domain.warning": "Attention : Choisir cette option entraînera la suppression de tous les domaines issus et l'ensemble des comptes associés, ainsi que de leur ressources",
"force.reboot":"Forcer le redémarrage",
"force.remove": "Suppression forcée",
"force.remove.host.warning": "Attention : Choisir cette option entraînera CloudStack à forcer l'arrêt de l'ensemble des machines virtuelles avant d'enlever cet hôte du cluster",
"force.stop": "Forcer l'arrêt",
@ -1263,6 +1264,10 @@ var dictionary = {
"label.portable.ip.range.details": "Détails Plages IP portables",
"label.portable.ip.ranges": "Plages IP portables",
"label.portable.ips": "IPs portables",
"label.powerflex.gateway": "passerelle",
"label.powerflex.gateway.username": "Nom d'utilisateur de la passerelle",
"label.powerflex.gateway.password": "Mot de passe de la passerelle",
"label.powerflex.storage.pool": "Pool de stockage",
"label.powerstate": "Status Alimentation",
"label.prev": "Précédent",
"label.previous": "Retour",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Az internet neved nem oldható fel.",
"force.delete": "Törlés kikényszerítése",
"force.delete.domain.warning": "Figyelmeztetés: Ha ezt választod, törlődni fog minden alárendelt domén és minden kapcsolódó számla és a hozzájuk tartozó erőforrások.",
"force.reboot":"Kényszer újraindítás",
"force.remove": "Eltávolítás kikényszerítése",
"force.remove.host.warning": "Figyelmeztetés: Ha ezt az opciót választod, a CloudStack minden virtuális gépet leállít mielőtt eltávolítja a kiszolgálót a fürtből.",
"force.stop": "Leállás kikényszerítése",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Hordozható IP tartomány részletek",
"label.portable.ip.ranges": "Hordozható IP tartományok",
"label.portable.ips": "Hordozható IP címek",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway felhasználónév",
"label.powerflex.gateway.password": "Átjáró jelszava",
"label.powerflex.storage.pool": "Tároló medence",
"label.powerstate": "Power State",
"label.prev": "Előző",
"label.previous": "Előző",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Il tuo nome internet non può essere risolto.",
"force.delete": "Forza la Cancellazione",
"force.delete.domain.warning": "Attenzione: La scelta di questa opzione provocherà la rimozione di tutti i sotto domini e agli account associati e alle loro risorse.",
"force.reboot":"Forza riavvio",
"force.remove": "Forza la Rimozione",
"force.remove.host.warning": "Attenzione: La scelta di questa opzione provocherà l'arresto forzato di tutte le virtual machine da parte di CloudStack prima di rimuovere questo host dal cluster.",
"force.stop": "Forza l'Arresto",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Power State",
"label.prev": "Prev",
"label.previous": "Precedente",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "インターネット名を解決できません。",
"force.delete": "強制的に削除する",
"force.delete.domain.warning": "警告: このオプションを選択すると、すべての子ドメインおよび関連するすべてのアカウントとそのリソースが削除されます。",
"force.reboot":"強制再起動",
"force.remove": "強制的に解除する",
"force.remove.host.warning": "警告: このオプションを選択すると、実行中のすべての仮想マシンが強制的に停止され、クラスターからこのホストが強制的に解除されます。",
"force.stop": "強制的に停止する",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "ポータブル IP アドレスの範囲の詳細",
"label.portable.ip.ranges": "ポータブル IP アドレスの範囲",
"label.portable.ips": "ポータブル IP アドレス",
"label.powerflex.gateway": "ゲートウェイ",
"label.powerflex.gateway.username": "ゲートウェイユーザー名",
"label.powerflex.gateway.password": "ゲートウェイパスワード",
"label.powerflex.storage.pool": "ストレージプール",
"label.powerstate": "Power State",
"label.prev": "戻る",
"label.previous": "戻る",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "인터넷 주소를 알수 없습니다.",
"force.delete": "강제 삭제",
"force.delete.domain.warning": "경고:이 옵션을 선택하면, 모든 내부 도메인 및 관련하는 모든 계정 정보와 그 자원이 삭제됩니다.",
"force.reboot":"강제 재부팅",
"force.remove": "강제 해제",
"force.remove.host.warning": "경고:이 옵션을 선택하면, 실행중 모든 가상 머신이 강제적으로 정지되어 클러스터에서 호스트가 강제적으로 해제됩니다.",
"force.stop": "강제 정지",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "게이트웨이",
"label.powerflex.gateway.username": "게이트웨이 사용자 이름",
"label.powerflex.gateway.password": "게이트웨이 비밀번호",
"label.powerflex.storage.pool": "스토리지 풀",
"label.powerstate": "Power State",
"label.prev": "뒤로",
"label.previous": "뒤로",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Ditt internettnavn kan ikke løses.",
"force.delete": "Tving sletting",
"force.delete.domain.warning": "Advarsel: dette alternativet vil medføre at alle underdomener og alle assosierte kontoer og dere resurser blir slettet.",
"force.reboot":"Tving omstart",
"force.remove": "Tving fjerning",
"force.remove.host.warning": "Advarsel: ved valg av dette alternativet vil CloudStack stoppe alle kjørende virtuelle maskiner, før verten blir fjernet fra klyngen.",
"force.stop": "Tving stopp",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portabel IP-rekke detaljer",
"label.portable.ip.ranges": "Transportable IP-rekker",
"label.portable.ips": "Portabel IP-rekke",
"label.powerflex.gateway": "Inngangsport",
"label.powerflex.gateway.username": "Gateway brukernavn",
"label.powerflex.gateway.password": "Gateway-passord",
"label.powerflex.storage.pool": "Oppbevaringsbasseng",
"label.powerstate": "Power State",
"label.prev": "Forrige",
"label.previous": "Forrige",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Uw internet naam kan niet worden omgezet.",
"force.delete": "Geforceerd verwijderen",
"force.delete.domain.warning": "Waarschuwing: Wanneer u deze optie selecteert zullen alle onderliggende domeinen, hun gekoppelde accounts en hun verbruik worden verwijderd.",
"force.reboot":"Forceer opnieuw opstarten",
"force.remove": "Geforceerd loskoppelen",
"force.remove.host.warning": "Waarschuwing: Wanneer u deze optie selecteert zal CloudStack alle draaiende virtuele machines geforceerd stoppen voordat de host van het cluster wordt verwijderd.",
"force.stop": "Geforceerd stoppen",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Porteerbare IP Range details",
"label.portable.ip.ranges": "Porteerbare IP Ranges",
"label.portable.ips": "Porteerbare IPs",
"label.powerflex.gateway": "poort",
"label.powerflex.gateway.username": "Gateway gebruikersnaam",
"label.powerflex.gateway.password": "Gateway-wachtwoord",
"label.powerflex.storage.pool": "Opslagpool",
"label.powerstate": "Power State",
"label.prev": "Terug",
"label.previous": "Vorige",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Your internet name cannot be resolved.",
"force.delete": "Force Delete",
"force.delete.domain.warning": "Warning: Choosing this option will cause the deletion of all child domains and all associated accounts and their resources.",
"force.reboot":"Force Reboot",
"force.remove": "Force Remove",
"force.remove.host.warning": "Warning: Choosing this option will cause CloudStack to forcefully stop all running virtual machines before removing this host from the cluster.",
"force.stop": "Force Stop",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Gateway",
"label.powerflex.gateway.username": "Gateway Username",
"label.powerflex.gateway.password": "Gateway Password",
"label.powerflex.storage.pool": "Storage Pool",
"label.powerstate": "Power State",
"label.prev": "Prev",
"label.previous": "Wstecz",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Impossível resolver DNS",
"force.delete": "Forçar Exclusão",
"force.delete.domain.warning": "Atenção: Esta opção removerá todos os domínios, contas e recursos associados.",
"force.reboot":"Forçar reinicialização",
"force.remove": "Forçar Remoção",
"force.remove.host.warning": "Atenção: O CloudStack desligará de maneira forçada todas as VMs antes de remover o host do cluster.",
"force.stop": "Forçar Parada",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Detalhes de Range de IP Portáveis",
"label.portable.ip.ranges": "Faixa de endereços IPs Portável",
"label.portable.ips": "IPs Portáveis",
"label.powerflex.gateway": "Porta de entrada",
"label.powerflex.gateway.username": "Nome de usuário do gateway",
"label.powerflex.gateway.password": "Senha de gateway",
"label.powerflex.storage.pool": "Pool de armazenamento",
"label.powerstate": "Power State",
"label.prev": "Prev",
"label.previous": "Anterior",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "Ваше сетевое имя не удалось разрешить.",
"force.delete": "Принудительное удаление",
"force.delete.domain.warning": "Предупреждение: Выбор этой опции приведет к удалению всех дочерних доменов и связанных с ними учетных записей и их ресурсов",
"force.reboot":"Принудительная перезагрузка",
"force.remove": "Принудительное удаление",
"force.remove.host.warning": "Выбор этой опции приведет к принудительной остановке работающих виртуальных машин перед удалением сервера из кластера.",
"force.stop": "Принудительно остановить",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "Portable IP Range details",
"label.portable.ip.ranges": "Portable IP Ranges",
"label.portable.ips": "Portable IPs",
"label.powerflex.gateway": "Шлюз",
"label.powerflex.gateway.username": "Имя пользователя шлюза",
"label.powerflex.gateway.password": "Пароль шлюза",
"label.powerflex.storage.pool": "Пул хранения",
"label.powerstate": "Power State",
"label.prev": "Предыдуший",
"label.previous": "Предыдущий",

View File

@ -37,6 +37,7 @@ var dictionary = {
"error.unresolved.internet.name": "无法解析您的 Internet 名称。",
"force.delete": "强制删除",
"force.delete.domain.warning": "警告: 选择此选项将导致删除所有子域以及所有相关联的帐户及其资源。",
"force.reboot":"強制重啟",
"force.remove": "强制移除",
"force.remove.host.warning": "警告: 选择此选项将导致 CloudStack 在从群集中移除此主机之前,强制停止所有正在运行的虚拟机。",
"force.stop": "强制停止",
@ -1261,6 +1262,10 @@ var dictionary = {
"label.portable.ip.range.details": "可移植 IP 范围详细信息",
"label.portable.ip.ranges": "可移植 IP 范围",
"label.portable.ips": "可移植 IP",
"label.powerflex.gateway": "網關",
"label.powerflex.gateway.username": "網關用戶名",
"label.powerflex.gateway.password": "網關密碼",
"label.powerflex.storage.pool": "儲存池",
"label.powerstate": "Power State",
"label.prev": "上一页",
"label.previous": "上一步",

View File

@ -734,6 +734,22 @@ cloudStack.docs = {
desc: 'In iSCSI, this is the LUN number. For example, 3.',
externalLink: ''
},
helpPrimaryStoragePowerFlexGateway: {
desc: 'The address of PowerFlex Gateway host',
externalLink: ''
},
helpPrimaryStoragePowerFlexGatewayUsername: {
desc: 'Username of PowerFlex Gateway for API access',
externalLink: ''
},
helpPrimaryStoragePowerFlexGatewayPassword: {
desc: 'Password of PowerFlex Gateway for API access',
externalLink: ''
},
helpPrimaryStoragePowerFlexStoragePool: {
desc: 'Storage pool on PowerFlex',
externalLink: ''
},
helpPrimaryStorageRBDMonitor: {
desc: 'The address of a Ceph monitor. Can also be a Round Robin DNS record',
externalLink: ''

View File

@ -137,6 +137,91 @@
return action;
};
var vmRestartAction = function(args) {
var action = {
messages: {
confirm: function(args) {
return 'message.action.reboot.instance';
},
notification: function(args) {
return 'label.action.reboot.instance';
},
complete: function(args) {
if (args.password != null && args.password.length > 0)
return _l('message.password.has.been.reset.to') + ' ' + args.password;
else
return null;
}
},
label: 'label.action.reboot.instance',
compactLabel: 'label.reboot',
addRow: 'false',
createForm: {
title: 'notification.reboot.instance',
desc: 'message.action.reboot.instance',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
action: function(args) {
var instances = args.context.instances;
var skippedInstances = 0;
$(instances).each(function(index, instance) {
if (instance.state === 'Stopped' || instance.state === 'Stopping') {
skippedInstances++;
} else {
var data = {
id: instance.id,
forced: (args.data.forced == "on")
};
$.ajax({
url: createURL("rebootVirtualMachine"),
data: data,
dataType: "json",
async: true,
success: function(json) {
var jid = json.rebootvirtualmachineresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
return json.queryasyncjobresultresponse.jobresult.virtualmachine;
},
getActionFilter: function() {
return vmActionfilter;
}
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
}
});
if (skippedInstances === instances.length) {
args.response.error();
}
},
notification: {
poll: pollAsyncJobResult
}
};
if (args && args.listView) {
$.extend(action, {
isHeader: true,
isMultiSelectAction: true
});
}
return action;
};
var vmStopAction = function(args) {
var action = {
messages: {
@ -1112,48 +1197,7 @@
}
},
stop: vmStopAction(),
restart: {
label: 'label.action.reboot.instance',
compactLabel: 'label.reboot',
action: function(args) {
$.ajax({
url: createURL("rebootVirtualMachine&id=" + args.context.instances[0].id),
dataType: "json",
async: true,
success: function(json) {
var jid = json.rebootvirtualmachineresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
return json.queryasyncjobresultresponse.jobresult.virtualmachine;
},
getActionFilter: function() {
return vmActionfilter;
}
}
});
}
});
},
messages: {
confirm: function(args) {
return 'message.action.reboot.instance';
},
notification: function(args) {
return 'label.action.reboot.instance';
},
complete: function(args) {
if (args.password != null && args.password.length > 0)
return _l('message.password.has.been.reset.to') + ' ' + args.password;
else
return null;
}
},
notification: {
poll: pollAsyncJobResult
}
},
restart: vmRestartAction(),
snapshot: vmSnapshotAction(),
storageSnapshot: {
messages: {
@ -1219,7 +1263,18 @@
},
asyncBackup: {
label: 'label.async.backup',
isBoolean: true
isBoolean: true,
dependsOn: 'volume',
isHidden: function(args) {
var selectedVolumeId = $('div[role=dialog] form .form-item[rel=volume] select').val();
for (var i = 0; i < args.context.volumes.length; i++) {
var volume = args.context.volumes[i];
if (volume.id === selectedVolumeId) {
return volume.supportsstoragesnapshot === true;
}
}
return false;
}
}
}
},

View File

@ -1878,6 +1878,10 @@ var processPropertiesInImagestoreObject = function(jsonObj) {
return url;
}
function powerflexURL(gateway, username, password, storagepool) {
var url = "powerflex://" + username + ":" + password + "@" + gateway + "/" + storagepool;
return url;
}
//VM Instance

View File

@ -945,7 +945,13 @@
},
asyncBackup: {
label: 'label.async.backup',
isBoolean: true
isBoolean: true,
isHidden: function(args) {
if (args.context.volumes[0].supportsstoragesnapshot == true)
return true;
else
return false;
}
},
tags: {
label: 'label.tags',

View File

@ -3793,6 +3793,17 @@
restart: {
label: 'label.action.reboot.router',
createForm: {
title: 'label.action.reboot.router',
desc: 'message.action.reboot.router',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function (args) {
return 'message.action.reboot.router';
@ -3802,8 +3813,10 @@
}
},
action: function (args) {
var array1 =[];
array1.push("&forced=" + (args.data.forced == "on"));
$.ajax({
url: createURL('rebootRouter&id=' + args.context.routers[0].id),
url: createURL('rebootRouter&id=' + args.context.routers[0].id + array1.join("")),
dataType: 'json',
async: true,
success: function (json) {
@ -8809,6 +8822,17 @@
restart: {
label: 'label.action.reboot.systemvm',
createForm: {
title: 'label.action.reboot.systemvm',
desc: 'message.action.reboot.systemvm',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function (args) {
return 'message.action.reboot.systemvm';
@ -8818,8 +8842,10 @@
}
},
action: function (args) {
var array1 =[];
array1.push("&forced=" + (args.data.forced == "on"));
$.ajax({
url: createURL('rebootSystemVm&id=' + args.context.systemVMs[0].id),
url: createURL('rebootSystemVm&id=' + args.context.systemVMs[0].id + array1.join("")),
dataType: 'json',
async: true,
success: function (json) {
@ -10340,6 +10366,17 @@
restart: {
label: 'label.action.reboot.router',
createForm: {
title: 'label.action.reboot.router',
desc: 'message.action.reboot.router',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function (args) {
return 'message.action.reboot.router';
@ -10349,8 +10386,10 @@
}
},
action: function (args) {
var array1 =[];
array1.push("&forced=" + (args.data.forced == "on"));
$.ajax({
url: createURL('rebootRouter&id=' + args.context.routers[0].id),
url: createURL('rebootRouter&id=' + args.context.routers[0].id + array1.join("")),
dataType: 'json',
async: true,
success: function (json) {
@ -11826,6 +11865,17 @@
restart: {
label: 'label.action.reboot.systemvm',
createForm: {
title: 'label.action.reboot.systemvm',
desc: 'message.action.reboot.systemvm',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function (args) {
return 'message.action.reboot.systemvm';
@ -11835,8 +11885,10 @@
}
},
action: function (args) {
var array1 =[];
array1.push("&forced=" + (args.data.forced == "on"));
$.ajax({
url: createURL('rebootSystemVm&id=' + args.context.systemVMs[0].id),
url: createURL('rebootSystemVm&id=' + args.context.systemVMs[0].id + array1.join("")),
dataType: 'json',
async: true,
success: function (json) {
@ -19128,6 +19180,7 @@
},
provider: {
label: 'label.provider',
dependsOn: 'protocol',
validation: {
required: true
},
@ -19136,6 +19189,8 @@
{ id: args.context.providers[0].id } :
{};
var items =[];
$.ajax({
url: createURL('listStorageProviders'),
data: {
@ -19146,10 +19201,19 @@
args.response.success({
data: $.map(providers, function (provider) {
return {
id: provider.name,
description: provider.name
};
if (args.protocol != "custom") {
if (provider.name != "PowerFlex") {
return {
id: provider.name,
description: provider.name
};
}
} else {
return {
id: provider.name,
description: provider.name
};
}
})
});
}
@ -19163,11 +19227,31 @@
$form.find('.form-item[rel=capacityIops]').hide();
$form.find('.form-item[rel=capacityBytes]').hide();
$form.find('.form-item[rel=url]').hide();
$form.find('.form-item[rel=powerflexGateway]').hide();
$form.find('.form-item[rel=powerflexGatewayUsername]').hide();;
$form.find('.form-item[rel=powerflexGatewayPassword]').hide();;
$form.find('.form-item[rel=powerflexStoragePool]').hide();;
} else if (scope == 'PowerFlex') {
$form.find('.form-item[rel=isManaged]').hide();
$form.find('.form-item[rel=capacityIops]').hide();
$form.find('.form-item[rel=capacityBytes]').hide();
$form.find('.form-item[rel=url]').hide();
$form.find('.form-item[rel=powerflexGateway]').css('display', 'inline-block');
$form.find('.form-item[rel=powerflexGatewayUsername]').css('display', 'inline-block');
$form.find('.form-item[rel=powerflexGatewayPassword]').css('display', 'inline-block');
$form.find('.form-item[rel=powerflexStoragePool]').css('display', 'inline-block');
} else {
$form.find('.form-item[rel=isManaged]').css('display', 'inline-block');
$form.find('.form-item[rel=capacityIops]').css('display', 'inline-block');
$form.find('.form-item[rel=capacityBytes]').css('display', 'inline-block');
$form.find('.form-item[rel=url]').css('display', 'inline-block');
$form.find('.form-item[rel=powerflexGateway]').hide();
$form.find('.form-item[rel=powerflexGatewayUsername]').hide();;
$form.find('.form-item[rel=powerflexGatewayPassword]').hide();;
$form.find('.form-item[rel=powerflexStoragePool]').hide();;
}
}
)
@ -19312,6 +19396,41 @@
isHidden: true
},
// PowerFlex/ScaleIO
powerflexGateway: {
label: 'label.powerflex.gateway',
docID: 'helpPrimaryStoragePowerFlexGateway',
validation: {
required: true
},
isHidden: true
},
powerflexGatewayUsername: {
label: 'label.powerflex.gateway.username',
docID: 'helpPrimaryStoragePowerFlexGatewayUsername',
validation: {
required: true
},
isHidden: true
},
powerflexGatewayPassword: {
label: 'label.powerflex.gateway.password',
docID: 'helpPrimaryStoragePowerFlexGatewayPassword',
isPassword: true,
validation: {
required: true
},
isHidden: true
},
powerflexStoragePool: {
label: 'label.powerflex.storage.pool',
docID: 'helpPrimaryStoragePowerFlexStoragePool',
validation: {
required: true
},
isHidden: true
},
//always appear (begin)
storageTags: {
label: 'label.storage.tags',
@ -19450,9 +19569,16 @@
}
array1.push("&url=" + encodeURIComponent(url));
}
else
{
} else if (args.data.provider == "PowerFlex") {
var url = null;
var gateway = args.data.powerflexGateway;
var gwusername = encodeURIComponent(args.data.powerflexGatewayUsername);
var gwpassword = encodeURIComponent(args.data.powerflexGatewayPassword);
var storagepool = encodeURIComponent(args.data.powerflexStoragePool);
url = powerflexURL(gateway, gwusername, gwpassword, storagepool);
array1.push("&url=" + encodeURIComponent(url));
} else {
array1.push("&managed=" + (args.data.isManaged == "on").toString());
if (args.data.capacityBytes != null && args.data.capacityBytes.length > 0)

View File

@ -1726,9 +1726,23 @@
},
restart: {
label: 'instances.actions.reboot.label',
addRow: 'false',
createForm: {
title: 'label.action.reboot.instance',
desc: 'message.action.reboot.instance',
fields: {
forced: {
label: 'force.reboot',
isBoolean: true,
isChecked: false
}
}
},
action: function(args) {
var array1 = [];
array1.push("&forced=" + (args.data.forced == "on"));
$.ajax({
url: createURL("rebootVirtualMachine&id=" + args.context.vpcTierInstances[0].id),
url: createURL("rebootVirtualMachine&id=" + args.context.vpcTierInstances[0].id + array1.join("")),
dataType: "json",
async: true,
success: function(json) {