blob: 49f7abccda5bcfee5deff8f72c6fdfef97ea7c28 [file] [log] [blame]
/*
* 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.volume;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import com.cloud.storage.VolumeApiServiceImpl;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.secret.dao.PassphraseDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.MoveVolumeCommand;
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;
import org.apache.cloudstack.storage.image.store.TemplateObject;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.stereotype.Component;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.ModifyTargetsCommand;
import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
import com.cloud.agent.api.storage.ListVolumeAnswer;
import com.cloud.agent.api.storage.ListVolumeCommand;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.StorageAccessException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostDetailsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.DiskOffering;
import com.cloud.org.Cluster;
import com.cloud.org.Grouping.AllocationState;
import com.cloud.resource.ResourceState;
import com.cloud.server.ManagementService;
import com.cloud.storage.CheckAndRepairVolumePayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.RegisterVolumePayload;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.Volume.State;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.storage.resource.StorageProcessor;
import com.cloud.storage.snapshot.SnapshotApiService;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.storage.template.TemplateConstants;
import com.cloud.storage.template.TemplateProp;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.ResourceLimitService;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
@Component
public class VolumeServiceImpl implements VolumeService {
protected Logger logger = LogManager.getLogger(getClass());
@Inject
protected AgentManager agentMgr;
@Inject
VolumeDao volDao;
@Inject
PrimaryDataStoreProviderManager dataStoreMgr;
@Inject
DataMotionService motionSrv;
@Inject
VolumeDataFactory volFactory;
@Inject
SnapshotManager snapshotMgr;
@Inject
ResourceLimitService _resourceLimitMgr;
@Inject
AccountManager _accountMgr;
@Inject
AlertManager _alertMgr;
@Inject
ConfigurationDao configDao;
@Inject
VolumeDataStoreDao _volumeStoreDao;
@Inject
VMTemplatePoolDao _tmpltPoolDao;
@Inject
SnapshotDataStoreDao _snapshotStoreDao;
@Inject
VolumeDao _volumeDao;
@Inject
EndPointSelector _epSelector;
@Inject
HostDao _hostDao;
@Inject
private PrimaryDataStoreDao storagePoolDao;
@Inject
private StoragePoolDetailsDao _storagePoolDetailsDao;
@Inject
private HostDetailsDao hostDetailsDao;
@Inject
private ManagementService mgr;
@Inject
private ClusterDao clusterDao;
@Inject
private VolumeDetailsDao _volumeDetailsDao;
@Inject
private VMTemplateDao templateDao;
@Inject
private TemplateDataFactory tmplFactory;
@Inject
private VolumeOrchestrationService _volumeMgr;
@Inject
protected StorageManager _storageMgr;
@Inject
private AnnotationDao annotationDao;
@Inject
private SnapshotApiService snapshotApiService;
@Inject
private PassphraseDao passphraseDao;
@Inject
private DiskOfferingDao diskOfferingDao;
public VolumeServiceImpl() {
}
private class CreateVolumeContext<T> extends AsyncRpcContext<T> {
private final DataObject volume;
private final AsyncCallFuture<VolumeApiResult> future;
public CreateVolumeContext(AsyncCompletionCallback<T> callback, DataObject volume, AsyncCallFuture<VolumeApiResult> future) {
super(callback);
this.volume = volume;
this.future = future;
}
public DataObject getVolume() {
return this.volume;
}
public AsyncCallFuture<VolumeApiResult> getFuture() {
return this.future;
}
}
@Override
public ChapInfo getChapInfo(DataObject dataObject, DataStore dataStore) {
DataStoreDriver dataStoreDriver = dataStore.getDriver();
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
return ((PrimaryDataStoreDriver)dataStoreDriver).getChapInfo(dataObject);
}
return null;
}
@Override
public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
return ((PrimaryDataStoreDriver)dataStoreDriver).grantAccess(dataObject, host, dataStore);
}
return false;
}
@Override
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
if (dataStoreDriver == null) {
return;
}
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
((PrimaryDataStoreDriver)dataStoreDriver).revokeAccess(dataObject, host, dataStore);
}
}
@Override
public boolean requiresAccessForMigration(DataObject dataObject, DataStore dataStore) {
DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
if (dataStoreDriver == null) {
return false;
}
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
return ((PrimaryDataStoreDriver)dataStoreDriver).requiresAccessForMigration(dataObject);
}
return false;
}
@Override
public AsyncCallFuture<VolumeApiResult> createVolumeAsync(VolumeInfo volume, DataStore dataStore) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
DataObject volumeOnStore = dataStore.create(volume);
volumeOnStore.processEvent(Event.CreateOnlyRequested);
try {
CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volumeOnStore, future);
AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().createVolumeCallback(null, null)).setContext(context);
dataStore.getDriver().createAsync(dataStore, volumeOnStore, caller);
} catch (CloudRuntimeException ex) {
// clean up already persisted volume_store_ref entry in case of createVolumeCallback is never called
VolumeDataStoreVO volStoreVO = _volumeStoreDao.findByStoreVolume(dataStore.getId(), volume.getId());
if (volStoreVO != null) {
VolumeInfo volObj = volFactory.getVolume(volume, dataStore);
volObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
}
VolumeApiResult volResult = new VolumeApiResult((VolumeObject)volumeOnStore);
volResult.setResult(ex.getMessage());
future.complete(volResult);
}
return future;
}
protected Void createVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
CreateCmdResult result = callback.getResult();
DataObject vo = context.getVolume();
String errMsg = null;
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
} else {
vo.processEvent(Event.OperationFailed);
errMsg = result.getResult();
}
VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo);
if (errMsg != null) {
volResult.setResult(errMsg);
}
context.getFuture().complete(volResult);
return null;
}
private class DeleteVolumeContext<T> extends AsyncRpcContext<T> {
private final VolumeObject volume;
private final AsyncCallFuture<VolumeApiResult> future;
public DeleteVolumeContext(AsyncCompletionCallback<T> callback, VolumeObject volume, AsyncCallFuture<VolumeApiResult> future) {
super(callback);
this.volume = volume;
this.future = future;
}
public VolumeObject getVolume() {
return this.volume;
}
public AsyncCallFuture<VolumeApiResult> getFuture() {
return this.future;
}
}
// check if a volume is expunged on both primary and secondary
private boolean canVolumeBeRemoved(long volumeId) {
VolumeVO vol = volDao.findById(volumeId);
if (vol == null) {
// already removed from volumes table
return false;
}
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volumeId);
if ((vol.getState() == State.Expunged || (vol.getPodId() == null && vol.getState() == State.Destroy)) && volumeStore == null) {
// volume is expunged from primary, as well as on secondary
return true;
} else {
return false;
}
}
@DB
@Override
public AsyncCallFuture<VolumeApiResult> expungeVolumeAsync(VolumeInfo volume) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult result = new VolumeApiResult(volume);
if (volume.getDataStore() == null) {
logger.info("Expunge volume with no data store specified");
if (canVolumeBeRemoved(volume.getId())) {
logger.info("Volume " + volume.getId() + " is not referred anywhere, remove it from volumes table");
volDao.remove(volume.getId());
}
future.complete(result);
return future;
}
// Find out if the volume is at state of download_in_progress on secondary storage
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
if (volumeStore != null) {
if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
String msg = "Volume: " + volume.getName() + " is currently being uploaded; can't delete it.";
logger.debug(msg);
result.setSuccess(false);
result.setResult(msg);
future.complete(result);
return future;
}
}
VolumeVO vol = volDao.findById(volume.getId());
if (vol == null) {
logger.debug("Volume " + volume.getId() + " is not found");
future.complete(result);
return future;
}
if (!volumeExistsOnPrimary(vol)) {
// not created on primary store
if (volumeStore == null) {
// also not created on secondary store
if (logger.isDebugEnabled()) {
logger.debug("Marking volume that was never created as destroyed: " + vol);
}
VMTemplateVO template = templateDao.findById(vol.getTemplateId());
if (template != null && !template.isDeployAsIs()) {
volDao.remove(vol.getId());
future.complete(result);
return future;
}
}
}
VolumeObject vo = (VolumeObject)volume;
if (volume.getDataStore().getRole() == DataStoreRole.Image) {
// no need to change state in volumes table
volume.processEventOnly(Event.DestroyRequested);
} else if (volume.getDataStore().getRole() == DataStoreRole.Primary) {
volume.processEvent(Event.ExpungeRequested);
}
DeleteVolumeContext<VolumeApiResult> context = new DeleteVolumeContext<VolumeApiResult>(null, vo, future);
AsyncCallbackDispatcher<VolumeServiceImpl, CommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().deleteVolumeCallback(null, null)).setContext(context);
volume.getDataStore().getDriver().deleteAsync(volume.getDataStore(), volume, caller);
return future;
}
public void ensureVolumeIsExpungeReady(long volumeId) {
VolumeVO volume = volDao.findById(volumeId);
if (volume != null && volume.getPodId() != null) {
volume.setPodId(null);
volDao.update(volumeId, volume);
}
}
private boolean volumeExistsOnPrimary(VolumeVO vol) {
Long poolId = vol.getPoolId();
if (poolId == null) {
return false;
}
PrimaryDataStore primaryStore = dataStoreMgr.getPrimaryDataStore(poolId);
if (primaryStore == null) {
return false;
}
if (primaryStore.isManaged()) {
return true;
}
String volumePath = vol.getPath();
if (volumePath == null || volumePath.trim().isEmpty()) {
return false;
}
return true;
}
public Void deleteVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CommandResult> callback, DeleteVolumeContext<VolumeApiResult> context) {
CommandResult result = callback.getResult();
VolumeObject vo = context.getVolume();
VolumeApiResult apiResult = new VolumeApiResult(vo);
try {
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed);
if (vo.getPassphraseId() != null) {
vo.deletePassphrase();
}
if (canVolumeBeRemoved(vo.getId())) {
logger.info("Volume " + vo.getId() + " is not referred anywhere, remove it from volumes table");
volDao.remove(vo.getId());
}
List<SnapshotDataStoreVO> snapStoreVOs = _snapshotStoreDao.listAllByVolumeAndDataStore(vo.getId(), DataStoreRole.Primary);
for (SnapshotDataStoreVO snapStoreVo : snapStoreVOs) {
long storagePoolId = snapStoreVo.getDataStoreId();
StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolId);
if (storagePoolVO.isManaged()) {
DataStore primaryDataStore = dataStoreMgr.getPrimaryDataStore(storagePoolId);
Map<String, String> mapCapabilities = primaryDataStore.getDriver().getCapabilities();
String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
Boolean supportsStorageSystemSnapshots = new Boolean(value);
if (!supportsStorageSystemSnapshots) {
_snapshotStoreDao.remove(snapStoreVo.getId());
}
} else {
_snapshotStoreDao.remove(snapStoreVo.getId());
}
}
snapshotApiService.markVolumeSnapshotsAsDestroyed(vo);
} else {
vo.processEvent(Event.OperationFailed);
apiResult.setResult(result.getResult());
}
} catch (Exception e) {
logger.debug("ignore delete volume status update failure, it will be picked up by storage clean up thread later", e);
}
context.getFuture().complete(apiResult);
return null;
}
@Override
public boolean cloneVolume(long volumeId, long baseVolId) {
// TODO Auto-generated method stub
return false;
}
@Override
public VolumeEntity getVolumeEntity(long volumeId) {
return null;
}
private class ManagedCreateBaseImageContext<T> extends AsyncRpcContext<T> {
private final VolumeInfo _volumeInfo;
private final PrimaryDataStore _primaryDataStore;
private final TemplateInfo _templateInfo;
private final AsyncCallFuture<VolumeApiResult> _future;
public ManagedCreateBaseImageContext(AsyncCompletionCallback<T> callback, VolumeInfo volumeInfo, PrimaryDataStore primaryDatastore, TemplateInfo templateInfo,
AsyncCallFuture<VolumeApiResult> future) {
super(callback);
_volumeInfo = volumeInfo;
_primaryDataStore = primaryDatastore;
_templateInfo = templateInfo;
_future = future;
}
public VolumeInfo getVolumeInfo() {
return _volumeInfo;
}
public PrimaryDataStore getPrimaryDataStore() {
return _primaryDataStore;
}
public TemplateInfo getTemplateInfo() {
return _templateInfo;
}
public AsyncCallFuture<VolumeApiResult> getFuture() {
return _future;
}
}
class CreateBaseImageContext<T> extends AsyncRpcContext<T> {
private final VolumeInfo volume;
private final PrimaryDataStore dataStore;
private final TemplateInfo srcTemplate;
private final AsyncCallFuture<VolumeApiResult> future;
final DataObject destObj;
long templatePoolId;
public CreateBaseImageContext(AsyncCompletionCallback<T> callback, VolumeInfo volume, PrimaryDataStore datastore, TemplateInfo srcTemplate, AsyncCallFuture<VolumeApiResult> future,
DataObject destObj, long templatePoolId) {
super(callback);
this.volume = volume;
this.dataStore = datastore;
this.future = future;
this.srcTemplate = srcTemplate;
this.destObj = destObj;
this.templatePoolId = templatePoolId;
}
public VolumeInfo getVolume() {
return this.volume;
}
public PrimaryDataStore getDataStore() {
return this.dataStore;
}
public TemplateInfo getSrcTemplate() {
return this.srcTemplate;
}
public AsyncCallFuture<VolumeApiResult> getFuture() {
return this.future;
}
public long getTemplatePoolId() {
return templatePoolId;
}
}
private TemplateInfo waitForTemplateDownloaded(PrimaryDataStore store, TemplateInfo template) {
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
int sleepTime = 120;
int tries = storagePoolMaxWaitSeconds / sleepTime;
while (tries > 0) {
TemplateInfo tmpl = store.getTemplate(template.getId(), null);
if (tmpl != null) {
return tmpl;
}
try {
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
logger.debug("waiting for template download been interrupted: " + e.toString());
}
tries--;
}
return null;
}
@DB
protected void createBaseImageAsync(VolumeInfo volume, PrimaryDataStore dataStore, TemplateInfo template, AsyncCallFuture<VolumeApiResult> future) {
String deployAsIsConfiguration = volume.getDeployAsIsConfiguration();
DataObject templateOnPrimaryStoreObj = dataStore.create(template, deployAsIsConfiguration);
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(dataStore.getId(), template.getId(), deployAsIsConfiguration);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + template.getUniqueName() + " in storage pool " + dataStore.getId());
} else {
if (logger.isDebugEnabled()) {
logger.debug("Found template " + template.getUniqueName() + " in storage pool " + dataStore.getId() + " with VMTemplateStoragePool id: " + templatePoolRef.getId());
}
}
long templatePoolRefId = templatePoolRef.getId();
CreateBaseImageContext<CreateCmdResult> context = new CreateBaseImageContext<CreateCmdResult>(null, volume, dataStore, template, future, templateOnPrimaryStoreObj, templatePoolRefId);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copyBaseImageCallback(null, null)).setContext(context);
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
if (logger.isDebugEnabled()) {
logger.debug("Acquire lock on VMTemplateStoragePool " + templatePoolRefId + " with timeout " + storagePoolMaxWaitSeconds + " seconds");
}
templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
if (templatePoolRef == null) {
if (logger.isDebugEnabled()) {
logger.info("Unable to acquire lock on VMTemplateStoragePool " + templatePoolRefId);
}
templatePoolRef = _tmpltPoolDao.findByPoolTemplate(dataStore.getId(), template.getId(), deployAsIsConfiguration);
if (templatePoolRef != null && templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
logger.info(
"Unable to acquire lock on VMTemplateStoragePool " + templatePoolRefId + ", But Template " + template.getUniqueName() + " is already copied to primary storage, skip copying");
createVolumeFromBaseImageAsync(volume, templateOnPrimaryStoreObj, dataStore, future);
return;
}
throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
}
if (logger.isDebugEnabled()) {
logger.info("lock is acquired for VMTemplateStoragePool " + templatePoolRefId);
}
try {
if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
logger.info("Template " + template.getUniqueName() + " is already copied to primary storage, skip copying");
createVolumeFromBaseImageAsync(volume, templateOnPrimaryStoreObj, dataStore, future);
return;
}
templateOnPrimaryStoreObj.processEvent(Event.CreateOnlyRequested);
motionSrv.copyAsync(template, templateOnPrimaryStoreObj, caller);
} catch (Throwable e) {
logger.debug("failed to create template on storage", e);
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
dataStore.create(template, deployAsIsConfiguration); // make sure that template_spool_ref entry is still present so that the second thread can acquire the lock
VolumeApiResult result = new VolumeApiResult(volume);
result.setResult(e.toString());
future.complete(result);
} finally {
if (logger.isDebugEnabled()) {
logger.info("releasing lock for VMTemplateStoragePool " + templatePoolRefId);
}
_tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
}
return;
}
protected Void managedCopyBaseImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, ManagedCreateBaseImageContext<VolumeApiResult> context) {
CopyCommandResult result = callback.getResult();
VolumeInfo volumeInfo = context.getVolumeInfo();
VolumeApiResult res = new VolumeApiResult(volumeInfo);
if (result.isSuccess()) {
// volumeInfo.processEvent(Event.OperationSuccessed, result.getAnswer());
VolumeVO volume = volDao.findById(volumeInfo.getId());
CopyCmdAnswer answer = (CopyCmdAnswer)result.getAnswer();
TemplateObjectTO templateObjectTo = (TemplateObjectTO)answer.getNewData();
volume.setPath(templateObjectTo.getPath());
if (templateObjectTo.getFormat() != null) {
volume.setFormat(templateObjectTo.getFormat());
}
volDao.update(volume.getId(), volume);
} else {
volumeInfo.processEvent(Event.DestroyRequested);
res.setResult(result.getResult());
}
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
future.complete(res);
return null;
}
protected Void createManagedTemplateImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<CreateCmdResult> context) {
CreateCmdResult result = callback.getResult();
VolumeApiResult res = new VolumeApiResult(null);
res.setResult(result.getResult());
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
DataObject templateOnPrimaryStoreObj = context.getVolume();
if (result.isSuccess()) {
((TemplateObject)templateOnPrimaryStoreObj).setInstallPath(result.getPath());
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
} else {
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
}
future.complete(res);
return null;
}
protected Void copyManagedTemplateCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateBaseImageContext<VolumeApiResult> context) {
CopyCommandResult result = callback.getResult();
VolumeApiResult res = new VolumeApiResult(context.getVolume());
res.setResult(result.getResult());
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
DataObject templateOnPrimaryStoreObj = context.destObj;
if (result.isSuccess()) {
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
} else {
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
}
future.complete(res);
return null;
}
@DB
protected Void copyBaseImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateBaseImageContext<VolumeApiResult> context) {
CopyCommandResult result = callback.getResult();
VolumeApiResult res = new VolumeApiResult(context.getVolume());
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
DataObject templateOnPrimaryStoreObj = context.destObj;
if (!result.isSuccess()) {
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
res.setResult(result.getResult());
future.complete(res);
return null;
}
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
createVolumeFromBaseImageAsync(context.volume, templateOnPrimaryStoreObj, context.dataStore, future);
return null;
}
private class CreateVolumeFromBaseImageContext<T> extends AsyncRpcContext<T> {
private final DataObject vo;
private final AsyncCallFuture<VolumeApiResult> future;
private final DataObject templateOnStore;
private final SnapshotInfo snapshot;
private final String deployAsIsConfiguration;
public CreateVolumeFromBaseImageContext(AsyncCompletionCallback<T> callback, DataObject vo, DataStore primaryStore, DataObject templateOnStore, AsyncCallFuture<VolumeApiResult> future,
SnapshotInfo snapshot, String configuration) {
super(callback);
this.vo = vo;
this.future = future;
this.templateOnStore = templateOnStore;
this.snapshot = snapshot;
this.deployAsIsConfiguration = configuration;
}
public AsyncCallFuture<VolumeApiResult> getFuture() {
return this.future;
}
}
@DB
protected void createVolumeFromBaseImageAsync(VolumeInfo volume, DataObject templateOnPrimaryStore, PrimaryDataStore pd, AsyncCallFuture<VolumeApiResult> future) {
DataObject volumeOnPrimaryStorage = pd.create(volume, volume.getDeployAsIsConfiguration());
volumeOnPrimaryStorage.processEvent(Event.CreateOnlyRequested);
CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<VolumeApiResult>(null, volumeOnPrimaryStorage, pd, templateOnPrimaryStore, future, null, volume.getDeployAsIsConfiguration());
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().createVolumeFromBaseImageCallBack(null, null));
caller.setContext(context);
motionSrv.copyAsync(context.templateOnStore, volumeOnPrimaryStorage, caller);
return;
}
@DB
protected Void createVolumeFromBaseImageCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateVolumeFromBaseImageContext<VolumeApiResult> context) {
DataObject vo = context.vo;
DataObject tmplOnPrimary = context.templateOnStore;
CopyCommandResult result = callback.getResult();
VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo);
String deployAsIsConfiguration = context.deployAsIsConfiguration;
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
} else {
vo.processEvent(Event.OperationFailed);
volResult.setResult(result.getResult());
// hack for Vmware: host is down, previously download template to the host needs to be re-downloaded, so we need to reset
// template_spool_ref entry here to NOT_DOWNLOADED and Allocated state
Answer ans = result.getAnswer();
if (ans instanceof CopyCmdAnswer && ans.getDetails().contains(StorageProcessor.REQUEST_TEMPLATE_RELOAD)) {
if (tmplOnPrimary != null) {
logger.info("Reset template_spool_ref entry so that vmware template can be reloaded in next try");
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(tmplOnPrimary.getDataStore().getId(), tmplOnPrimary.getId(), deployAsIsConfiguration);
if (templatePoolRef != null) {
long templatePoolRefId = templatePoolRef.getId();
templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, 1200);
try {
if (templatePoolRef == null) {
logger.warn("Reset Template State On Pool failed - unable to lock TemplatePoolRef " + templatePoolRefId);
} else {
templatePoolRef.setTemplateSize(0);
templatePoolRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.NOT_DOWNLOADED);
templatePoolRef.setState(ObjectInDataStoreStateMachine.State.Allocated);
_tmpltPoolDao.update(templatePoolRefId, templatePoolRef);
}
} finally {
_tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
}
}
}
}
}
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
future.complete(volResult);
return null;
}
@DB
protected Void createVolumeFromBaseManagedImageCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateVolumeFromBaseImageContext<VolumeApiResult> context) {
CopyCommandResult result = callback.getResult();
DataObject vo = context.vo;
DataObject tmplOnPrimary = context.templateOnStore;
VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo);
if (result.isSuccess()) {
VolumeVO volume = volDao.findById(vo.getId());
CopyCmdAnswer answer = (CopyCmdAnswer)result.getAnswer();
VolumeObjectTO volumeObjectTo = (VolumeObjectTO)answer.getNewData();
volume.setPath(volumeObjectTo.getPath());
if (volumeObjectTo.getFormat() != null) {
volume.setFormat(volumeObjectTo.getFormat());
}
volDao.update(volume.getId(), volume);
vo.processEvent(Event.OperationSuccessed);
} else {
volResult.setResult(result.getResult());
try {
destroyAndReallocateManagedVolume((VolumeInfo) vo);
} catch (CloudRuntimeException ex) {
logger.warn("Couldn't destroy managed volume: " + vo.getId());
}
}
AsyncCallFuture<VolumeApiResult> future = context.getFuture();
future.complete(volResult);
return null;
}
/**
* Creates a template volume on managed storage, which will be used for creating ROOT volumes by cloning.
*
* @param srcTemplateInfo Source template on secondary storage
* @param destPrimaryDataStore Managed storage on which we need to create the volume
*/
private TemplateInfo createManagedTemplateVolume(TemplateInfo srcTemplateInfo, PrimaryDataStore destPrimaryDataStore) {
// create a template volume on primary storage
TemplateInfo templateOnPrimary = (TemplateInfo)destPrimaryDataStore.create(srcTemplateInfo, srcTemplateInfo.getDeployAsIsConfiguration());
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId(), srcTemplateInfo.getDeployAsIsConfiguration());
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
} else if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
// Template already exists
return templateOnPrimary;
}
// At this point, we have an entry in the DB that points to our cached template.
// We need to lock it as there may be other VMs that may get started using the same template.
// We want to avoid having to create multiple cache copies of the same template.
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
long templatePoolRefId = templatePoolRef.getId();
templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
}
try {
// create a cache volume on the back-end
templateOnPrimary.processEvent(Event.CreateOnlyRequested);
CreateAsyncCompleteCallback callback = new CreateAsyncCompleteCallback();
destPrimaryDataStore.getDriver().createAsync(destPrimaryDataStore, templateOnPrimary, callback);
// validate we got a good result back
if (callback.result == null || callback.result.isFailed()) {
String errMesg;
if (callback.result == null) {
errMesg = "Unknown/unable to determine result";
} else {
errMesg = callback.result.getResult();
}
templateOnPrimary.processEvent(Event.OperationFailed);
throw new CloudRuntimeException("Unable to create template " + templateOnPrimary.getId() + " on primary storage " + destPrimaryDataStore.getId() + ":" + errMesg);
}
templateOnPrimary.processEvent(Event.OperationSuccessed);
} catch (Throwable e) {
logger.debug("Failed to create template volume on storage", e);
templateOnPrimary.processEvent(Event.OperationFailed);
throw new CloudRuntimeException(e.getMessage());
} finally {
_tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
}
return templateOnPrimary;
}
private static class CreateAsyncCompleteCallback implements AsyncCompletionCallback<CreateCmdResult> {
public CreateCmdResult result;
@Override
public void complete(CreateCmdResult result) {
this.result = result;
}
}
/**
* This function copies a template from secondary storage to a template volume
* created on managed storage. This template volume will be used as a cache.
* Instead of copying the template to a ROOT volume every time, a clone is performed instead.
*
* @param srcTemplateInfo Source from which to copy the template
* @param templateOnPrimary Dest to copy to
* @param templatePoolRef Template reference on primary storage (entry in the template_spool_ref)
* @param destPrimaryDataStore The managed primary storage
* @param destHost The host that we will use for the copy
*/
private void copyTemplateToManagedTemplateVolume(TemplateInfo srcTemplateInfo, TemplateInfo templateOnPrimary, VMTemplateStoragePoolVO templatePoolRef, PrimaryDataStore destPrimaryDataStore,
Host destHost) throws StorageAccessException {
AsyncCallFuture<VolumeApiResult> copyTemplateFuture = new AsyncCallFuture<>();
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
long templatePoolRefId = templatePoolRef.getId();
try {
templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
}
if (templatePoolRef.getDownloadState() == Status.DOWNLOADED) {
// There can be cases where we acquired the lock, but the template
// was already copied by a previous thread. Just return in that case.
logger.debug("Template already downloaded, nothing to do");
return;
}
// copy the template from sec storage to the created volume
CreateBaseImageContext<CreateCmdResult> copyContext = new CreateBaseImageContext<>(null, null, destPrimaryDataStore, srcTemplateInfo, copyTemplateFuture, templateOnPrimary,
templatePoolRefId);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> copyCaller = AsyncCallbackDispatcher.create(this);
copyCaller.setCallback(copyCaller.getTarget().copyManagedTemplateCallback(null, null)).setContext(copyContext);
// Populate details which will be later read by the storage subsystem.
Map<String, String> details = new HashMap<>();
details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
details.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
details.put(PrimaryDataStore.MANAGED_STORE_TARGET, templateOnPrimary.getInstallPath());
details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, srcTemplateInfo.getUniqueName());
details.put(PrimaryDataStore.REMOVE_AFTER_COPY, Boolean.TRUE.toString());
details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(templateOnPrimary.getSize()));
details.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(destPrimaryDataStore.getId())));
ChapInfo chapInfo = getChapInfo(templateOnPrimary, destPrimaryDataStore);
if (chapInfo != null) {
details.put(PrimaryDataStore.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
details.put(PrimaryDataStore.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
details.put(PrimaryDataStore.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
details.put(PrimaryDataStore.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
destPrimaryDataStore.setDetails(details);
try {
grantAccess(templateOnPrimary, destHost, destPrimaryDataStore);
} catch (Exception e) {
throw new StorageAccessException("Unable to grant access to template: " + templateOnPrimary.getId() + " on host: " + destHost.getId());
}
templateOnPrimary.processEvent(Event.CopyingRequested);
VolumeApiResult result;
try {
motionSrv.copyAsync(srcTemplateInfo, templateOnPrimary, destHost, copyCaller);
result = copyTemplateFuture.get();
} finally {
revokeAccess(templateOnPrimary, destHost, destPrimaryDataStore);
if (HypervisorType.VMware.equals(destHost.getHypervisorType())) {
details.put(ModifyTargetsCommand.IQN, templateOnPrimary.getInstallPath());
List<Map<String, String>> targets = new ArrayList<>();
targets.add(details);
removeDynamicTargets(destHost.getId(), targets);
}
}
if (result.isFailed()) {
throw new CloudRuntimeException("Failed to copy template " + templateOnPrimary.getId() + " to primary storage " + destPrimaryDataStore.getId() + ": " + result.getResult());
// XXX: I find it is useful to destroy the volume on primary storage instead of another thread trying the copy again because I've seen
// something weird happens to the volume (XenServer creates an SR, but the VDI copy can fail).
// For now, I just retry the copy.
}
} catch (StorageAccessException e) {
throw e;
} catch (Throwable e) {
logger.debug("Failed to create a template on primary storage", e);
templateOnPrimary.processEvent(Event.OperationFailed);
throw new CloudRuntimeException(e.getMessage());
} finally {
_tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
}
}
private void removeDynamicTargets(long hostId, List<Map<String, String>> targets) {
ModifyTargetsCommand cmd = new ModifyTargetsCommand();
cmd.setTargets(targets);
cmd.setApplyToAllHostsInCluster(true);
cmd.setAdd(false);
cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
sendModifyTargetsCommand(cmd, hostId);
}
private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
Answer answer = agentMgr.easySend(hostId, cmd);
if (answer == null) {
String msg = "Unable to get an answer to the modify targets command";
logger.warn(msg);
} else if (!answer.getResult()) {
String msg = "Unable to modify target on the following host: " + hostId;
logger.warn(msg);
}
}
/**
* Clones the template volume on managed storage to the ROOT volume
*
* @param volumeInfo ROOT volume to create
* @param templateOnPrimary Template from which to clone the ROOT volume
* @param destPrimaryDataStore Primary storage of the volume
* @param future For async
*/
private void createManagedVolumeCloneTemplateAsync(VolumeInfo volumeInfo, TemplateInfo templateOnPrimary, PrimaryDataStore destPrimaryDataStore, AsyncCallFuture<VolumeApiResult> future) {
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId(), volumeInfo.getDeployAsIsConfiguration());
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + templateOnPrimary.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
}
//XXX: not sure if this the right thing to do here. We can always fallback to the "copy from sec storage"
if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
throw new CloudRuntimeException("Template " + templateOnPrimary.getUniqueName() + " has not been downloaded to primary storage.");
}
try {
volumeInfo.processEvent(Event.CreateOnlyRequested);
CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<>(null, volumeInfo, destPrimaryDataStore, templateOnPrimary, future, null, volumeInfo.getDeployAsIsConfiguration());
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().createVolumeFromBaseImageCallBack(null, null));
caller.setContext(context);
motionSrv.copyAsync(templateOnPrimary, volumeInfo, caller);
} catch (Throwable e) {
logger.debug("Failed to clone template on primary storage", e);
volumeInfo.processEvent(Event.OperationFailed);
throw new CloudRuntimeException(e.getMessage());
}
}
private void createManagedVolumeCopyManagedTemplateAsync(VolumeInfo volumeInfo, PrimaryDataStore destPrimaryDataStore, TemplateInfo srcTemplateOnPrimary, Host destHost, AsyncCallFuture<VolumeApiResult> future) throws StorageAccessException {
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), srcTemplateOnPrimary.getId(), null);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + srcTemplateOnPrimary.getUniqueName() + " in storage pool " + srcTemplateOnPrimary.getId());
}
if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
throw new CloudRuntimeException("Template " + srcTemplateOnPrimary.getUniqueName() + " has not been downloaded to primary storage.");
}
String volumeDetailKey = "POOL_TEMPLATE_ID_COPY_ON_HOST_" + destHost.getId();
try {
try {
grantAccess(srcTemplateOnPrimary, destHost, destPrimaryDataStore);
} catch (Exception e) {
throw new StorageAccessException("Unable to grant access to src template: " + srcTemplateOnPrimary.getId() + " on host: " + destHost.getId());
}
_volumeDetailsDao.addDetail(volumeInfo.getId(), volumeDetailKey, String.valueOf(templatePoolRef.getId()), false);
// Create a volume on managed storage.
AsyncCallFuture<VolumeApiResult> createVolumeFuture = createVolumeAsync(volumeInfo, destPrimaryDataStore);
VolumeApiResult createVolumeResult = createVolumeFuture.get();
if (createVolumeResult.isFailed()) {
throw new CloudRuntimeException("Creation of a volume failed: " + createVolumeResult.getResult());
}
// Refresh the volume info from the DB.
volumeInfo = volFactory.getVolume(volumeInfo.getId(), destPrimaryDataStore);
Map<String, String> details = new HashMap<String, String>();
details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
details.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
details.put(PrimaryDataStore.MANAGED_STORE_TARGET, volumeInfo.get_iScsiName());
details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, volumeInfo.getName());
details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(volumeInfo.getSize()));
details.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(destPrimaryDataStore.getId())));
destPrimaryDataStore.setDetails(details);
grantAccess(volumeInfo, destHost, destPrimaryDataStore);
volumeInfo.processEvent(Event.CreateRequested);
CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<>(null, volumeInfo, destPrimaryDataStore, srcTemplateOnPrimary, future, null, null);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().createVolumeFromBaseManagedImageCallBack(null, null));
caller.setContext(context);
try {
motionSrv.copyAsync(srcTemplateOnPrimary, volumeInfo, destHost, caller);
} finally {
revokeAccess(volumeInfo, destHost, destPrimaryDataStore);
}
} catch (StorageAccessException e) {
throw e;
} catch (Throwable e) {
logger.debug("Failed to copy managed template on primary storage", e);
String errMsg = "Failed due to " + e.toString();
try {
destroyAndReallocateManagedVolume(volumeInfo);
} catch (CloudRuntimeException ex) {
logger.warn("Failed to destroy managed volume: " + volumeInfo.getId());
errMsg += " : " + ex.getMessage();
}
VolumeApiResult result = new VolumeApiResult(volumeInfo);
result.setResult(errMsg);
future.complete(result);
} finally {
_volumeDetailsDao.removeDetail(volumeInfo.getId(), volumeDetailKey);
List<VolumeDetailVO> volumeDetails = _volumeDetailsDao.findDetails(volumeDetailKey, String.valueOf(templatePoolRef.getId()), false);
if (volumeDetails == null || volumeDetails.isEmpty()) {
revokeAccess(srcTemplateOnPrimary, destHost, destPrimaryDataStore);
}
}
}
private void destroyAndReallocateManagedVolume(VolumeInfo volumeInfo) {
if (volumeInfo == null) {
return;
}
VolumeVO volume = volDao.findById(volumeInfo.getId());
if (volume == null) {
return;
}
if (volume.getState() == State.Allocated) { // Possible states here: Allocated, Ready & Creating
return;
}
volumeInfo.processEvent(Event.DestroyRequested);
Volume newVol = _volumeMgr.allocateDuplicateVolume(volume, null);
VolumeVO newVolume = (VolumeVO) newVol;
newVolume.set_iScsiName(null);
volDao.update(newVolume.getId(), newVolume);
logger.debug("Allocated new volume: " + newVolume.getId() + " for the VM: " + volume.getInstanceId());
try {
AsyncCallFuture<VolumeApiResult> expungeVolumeFuture = expungeVolumeAsync(volumeInfo);
VolumeApiResult expungeVolumeResult = expungeVolumeFuture.get();
if (expungeVolumeResult.isFailed()) {
logger.warn("Failed to expunge volume: " + volumeInfo.getId() + " that was created");
throw new CloudRuntimeException("Failed to expunge volume: " + volumeInfo.getId() + " that was created");
}
} catch (Exception ex) {
if (canVolumeBeRemoved(volumeInfo.getId())) {
volDao.remove(volumeInfo.getId());
}
logger.warn("Unable to expunge volume: " + volumeInfo.getId() + " due to: " + ex.getMessage());
throw new CloudRuntimeException("Unable to expunge volume: " + volumeInfo.getId() + " due to: " + ex.getMessage());
}
}
private void createManagedVolumeCopyTemplateAsync(VolumeInfo volumeInfo, PrimaryDataStore primaryDataStore, TemplateInfo srcTemplateInfo, Host destHost, AsyncCallFuture<VolumeApiResult> future) {
try {
// Create a volume on managed storage.
TemplateInfo destTemplateInfo = (TemplateInfo)primaryDataStore.create(srcTemplateInfo, false, volumeInfo.getDeployAsIsConfiguration());
AsyncCallFuture<VolumeApiResult> createVolumeFuture = createVolumeAsync(volumeInfo, primaryDataStore);
VolumeApiResult createVolumeResult = createVolumeFuture.get();
if (createVolumeResult.isFailed()) {
throw new CloudRuntimeException("Creation of a volume failed: " + createVolumeResult.getResult());
}
// Refresh the volume info from the DB.
volumeInfo = volFactory.getVolume(volumeInfo.getId(), primaryDataStore);
ManagedCreateBaseImageContext<CreateCmdResult> context = new ManagedCreateBaseImageContext<CreateCmdResult>(null, volumeInfo, primaryDataStore, srcTemplateInfo, future);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().managedCopyBaseImageCallback(null, null)).setContext(context);
Map<String, String> details = new HashMap<String, String>();
details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
details.put(PrimaryDataStore.STORAGE_HOST, primaryDataStore.getHostAddress());
details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(primaryDataStore.getPort()));
// for managed storage, the storage repository (XenServer) or datastore (ESX) name is based off of the iScsiName property of a volume
details.put(PrimaryDataStore.MANAGED_STORE_TARGET, volumeInfo.get_iScsiName());
details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, volumeInfo.getName());
details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(volumeInfo.getSize()));
details.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(primaryDataStore.getId())));
ChapInfo chapInfo = getChapInfo(volumeInfo, primaryDataStore);
if (chapInfo != null) {
details.put(PrimaryDataStore.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
details.put(PrimaryDataStore.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
details.put(PrimaryDataStore.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
details.put(PrimaryDataStore.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
primaryDataStore.setDetails(details);
grantAccess(volumeInfo, destHost, primaryDataStore);
try {
motionSrv.copyAsync(srcTemplateInfo, destTemplateInfo, destHost, caller);
} finally {
revokeAccess(volumeInfo, destHost, primaryDataStore);
}
} catch (Throwable t) {
String errMsg = t.toString();
volumeInfo.processEvent(Event.DestroyRequested);
try {
AsyncCallFuture<VolumeApiResult> expungeVolumeFuture = expungeVolumeAsync(volumeInfo);
VolumeApiResult expungeVolumeResult = expungeVolumeFuture.get();
if (expungeVolumeResult.isFailed()) {
errMsg += " : Failed to expunge a volume that was created";
}
} catch (Exception ex) {
errMsg += " : " + ex.getMessage();
}
VolumeApiResult result = new VolumeApiResult(volumeInfo);
result.setResult(errMsg);
future.complete(result);
}
}
@Override
public TemplateInfo createManagedStorageTemplate(long srcTemplateId, long destDataStoreId, long destHostId) throws StorageAccessException {
Host destHost = _hostDao.findById(destHostId);
if (destHost == null) {
throw new CloudRuntimeException("Destination host should not be null.");
}
TemplateInfo srcTemplateInfo = tmplFactory.getTemplate(srcTemplateId);
if (srcTemplateInfo == null) {
throw new CloudRuntimeException("Failed to get info of template: " + srcTemplateId);
}
if (Storage.ImageFormat.ISO.equals(srcTemplateInfo.getFormat())) {
throw new CloudRuntimeException("Unsupported format: " + Storage.ImageFormat.ISO.toString() + " for managed storage template");
}
GlobalLock lock = null;
TemplateInfo templateOnPrimary = null;
try {
String templateIdManagedPoolIdLockString = "templateId:" + srcTemplateId + "managedPoolId:" + destDataStoreId;
lock = GlobalLock.getInternLock(templateIdManagedPoolIdLockString);
if (lock == null) {
throw new CloudRuntimeException("Unable to create managed storage template, couldn't get global lock on " + templateIdManagedPoolIdLockString);
}
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
if (!lock.lock(storagePoolMaxWaitSeconds)) {
logger.debug("Unable to create managed storage template, couldn't lock on " + templateIdManagedPoolIdLockString);
throw new CloudRuntimeException("Unable to create managed storage template, couldn't lock on " + templateIdManagedPoolIdLockString);
}
PrimaryDataStore destPrimaryDataStore = dataStoreMgr.getPrimaryDataStore(destDataStoreId);
// Check if template exists on the storage pool. If not, downland and copy to managed storage pool
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destDataStoreId, srcTemplateId, null);
if (templatePoolRef != null && templatePoolRef.getDownloadState() == Status.DOWNLOADED) {
return tmplFactory.getTemplate(srcTemplateId, destPrimaryDataStore);
}
templateOnPrimary = createManagedTemplateVolume(srcTemplateInfo, destPrimaryDataStore);
if (templateOnPrimary == null) {
throw new CloudRuntimeException("Failed to create template " + srcTemplateInfo.getUniqueName() + " on primary storage: " + destDataStoreId);
}
templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId(), null);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
}
if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
// Populate details which will be later read by the storage subsystem.
Map<String, String> details = new HashMap<>();
details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
details.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
details.put(PrimaryDataStore.MANAGED_STORE_TARGET, templateOnPrimary.getInstallPath());
details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, srcTemplateInfo.getUniqueName());
details.put(PrimaryDataStore.REMOVE_AFTER_COPY, Boolean.TRUE.toString());
details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(templateOnPrimary.getSize()));
details.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(destPrimaryDataStore.getId())));
destPrimaryDataStore.setDetails(details);
try {
grantAccess(templateOnPrimary, destHost, destPrimaryDataStore);
} catch (Exception e) {
throw new StorageAccessException("Unable to grant access to template: " + templateOnPrimary.getId() + " on host: " + destHost.getId());
}
templateOnPrimary.processEvent(Event.CopyingRequested);
try {
//Download and copy template to the managed volume
TemplateInfo templateOnPrimaryNow = tmplFactory.getReadyBypassedTemplateOnManagedStorage(srcTemplateId, templateOnPrimary, destDataStoreId, destHostId);
if (templateOnPrimaryNow == null) {
logger.debug("Failed to prepare ready bypassed template: " + srcTemplateId + " on primary storage: " + templateOnPrimary.getId());
throw new CloudRuntimeException("Failed to prepare ready bypassed template: " + srcTemplateId + " on primary storage: " + templateOnPrimary.getId());
}
templateOnPrimary.processEvent(Event.OperationSuccessed);
return templateOnPrimaryNow;
} finally {
revokeAccess(templateOnPrimary, destHost, destPrimaryDataStore);
}
}
return null;
} catch (StorageAccessException e) {
throw e;
} catch (Throwable e) {
logger.debug("Failed to create template on managed primary storage", e);
if (templateOnPrimary != null) {
templateOnPrimary.processEvent(Event.OperationFailed);
}
throw new CloudRuntimeException(e.getMessage());
} finally {
if (lock != null) {
lock.unlock();
lock.releaseRef();
}
}
}
@Override
public AsyncCallFuture<VolumeApiResult> createManagedStorageVolumeFromTemplateAsync(VolumeInfo volumeInfo, long destDataStoreId, TemplateInfo srcTemplateInfo, long destHostId) throws StorageAccessException {
PrimaryDataStore destPrimaryDataStore = dataStoreMgr.getPrimaryDataStore(destDataStoreId);
Host destHost = _hostDao.findById(destHostId);
if (destHost == null) {
throw new CloudRuntimeException("Destination host should not be null.");
}
Boolean storageCanCloneVolume = new Boolean(destPrimaryDataStore.getDriver().getCapabilities().get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString()));
boolean computeSupportsVolumeClone = computeSupportsVolumeClone(destHost.getDataCenterId(), destHost.getHypervisorType());
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<>();
if (storageCanCloneVolume && computeSupportsVolumeClone) {
logger.debug("Storage " + destDataStoreId + " can support cloning using a cached template and compute side is OK with volume cloning.");
GlobalLock lock = null;
TemplateInfo templateOnPrimary = null;
try {
String tmplIdManagedPoolIdLockString = "tmplId:" + srcTemplateInfo.getId() + "managedPoolId:" + destDataStoreId;
lock = GlobalLock.getInternLock(tmplIdManagedPoolIdLockString);
if (lock == null) {
throw new CloudRuntimeException("Unable to create managed storage template/volume, couldn't get global lock on " + tmplIdManagedPoolIdLockString);
}
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
if (!lock.lock(storagePoolMaxWaitSeconds)) {
logger.debug("Unable to create managed storage template/volume, couldn't lock on " + tmplIdManagedPoolIdLockString);
throw new CloudRuntimeException("Unable to create managed storage template/volume, couldn't lock on " + tmplIdManagedPoolIdLockString);
}
templateOnPrimary = destPrimaryDataStore.getTemplate(srcTemplateInfo.getId(), null);
if (templateOnPrimary == null) {
templateOnPrimary = createManagedTemplateVolume(srcTemplateInfo, destPrimaryDataStore);
if (templateOnPrimary == null) {
throw new CloudRuntimeException("Failed to create template " + srcTemplateInfo.getUniqueName() + " on primary storage: " + destDataStoreId);
}
}
// Copy the template to the template volume.
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId(), null);
if (templatePoolRef == null) {
throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
}
if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
copyTemplateToManagedTemplateVolume(srcTemplateInfo, templateOnPrimary, templatePoolRef, destPrimaryDataStore, destHost);
}
} catch (Exception e) {
if (templateOnPrimary != null) {
templateOnPrimary.processEvent(Event.OperationFailed);
}
VolumeApiResult result = new VolumeApiResult(volumeInfo);
result.setResult(e.getLocalizedMessage());
result.setSuccess(false);
future.complete(result);
logger.warn("Failed to create template on primary storage", e);
return future;
} finally {
if (lock != null) {
lock.unlock();
lock.releaseRef();
}
}
if (destPrimaryDataStore.getPoolType() != StoragePoolType.PowerFlex) {
// We have a template on primary storage. Clone it to new volume.
logger.debug("Creating a clone from template on primary storage " + destDataStoreId);
createManagedVolumeCloneTemplateAsync(volumeInfo, templateOnPrimary, destPrimaryDataStore, future);
} else {
// We have a template on PowerFlex primary storage. Create new volume and copy to it.
createManagedVolumeCopyManagedTemplateAsyncWithLock(volumeInfo, destPrimaryDataStore, templateOnPrimary,
destHost, future, destDataStoreId, srcTemplateInfo.getId());
}
} else {
logger.debug("Primary storage does not support cloning or no support for UUID resigning on the host side; copying the template normally");
createManagedVolumeCopyTemplateAsync(volumeInfo, destPrimaryDataStore, srcTemplateInfo, destHost, future);
}
return future;
}
private void createManagedVolumeCopyManagedTemplateAsyncWithLock(VolumeInfo volumeInfo, PrimaryDataStore destPrimaryDataStore, TemplateInfo templateOnPrimary,
Host destHost, AsyncCallFuture<VolumeApiResult> future, long destDataStoreId, long srcTemplateId) {
GlobalLock lock = null;
try {
String tmplIdManagedPoolIdDestinationHostLockString = "tmplId:" + srcTemplateId + "managedPoolId:" + destDataStoreId + "destinationHostId:" + destHost.getId();
lock = GlobalLock.getInternLock(tmplIdManagedPoolIdDestinationHostLockString);
if (lock == null) {
throw new CloudRuntimeException("Unable to create volume from template, couldn't get global lock on " + tmplIdManagedPoolIdDestinationHostLockString);
}
int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
if (!lock.lock(storagePoolMaxWaitSeconds)) {
logger.debug("Unable to create volume from template, couldn't lock on " + tmplIdManagedPoolIdDestinationHostLockString);
throw new CloudRuntimeException("Unable to create volume from template, couldn't lock on " + tmplIdManagedPoolIdDestinationHostLockString);
}
logger.debug("Copying the template to the volume on primary storage");
createManagedVolumeCopyManagedTemplateAsync(volumeInfo, destPrimaryDataStore, templateOnPrimary, destHost, future);
} finally {
if (lock != null) {
lock.unlock();
lock.releaseRef();
}
}
}
private boolean computeSupportsVolumeClone(long zoneId, HypervisorType hypervisorType) {
if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) {
return true;
}
return getHost(zoneId, hypervisorType, true) != null;
}
private HostVO getHost(Long zoneId, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) {
if (zoneId == null) {
throw new CloudRuntimeException("Zone ID cannot be null.");
}
List<? extends Cluster> clusters = mgr.searchForClusters(zoneId, new Long(0), Long.MAX_VALUE, hypervisorType.toString());
if (clusters == null) {
clusters = new ArrayList<>();
}
Collections.shuffle(clusters, new Random(System.nanoTime()));
clusters: for (Cluster cluster : clusters) {
if (cluster.getAllocationState() == AllocationState.Enabled) {
List<HostVO> hosts = _hostDao.findByClusterId(cluster.getId());
if (hosts != null) {
Collections.shuffle(hosts, new Random(System.nanoTime()));
for (HostVO host : hosts) {
if (host.getResourceState() == ResourceState.Enabled) {
if (computeClusterMustSupportResign) {
if (clusterDao.getSupportsResigning(cluster.getId())) {
return host;
} else {
// no other host in the cluster in question should be able to satisfy our requirements here, so move on to the next cluster
continue clusters;
}
} else {
return host;
}
}
}
}
}
}
return null;
}
@DB
@Override
public AsyncCallFuture<VolumeApiResult> createVolumeFromTemplateAsync(VolumeInfo volume, long dataStoreId, TemplateInfo template) {
PrimaryDataStore pd = dataStoreMgr.getPrimaryDataStore(dataStoreId);
TemplateInfo templateOnPrimaryStore = pd.getTemplate(template.getId(), volume.getDeployAsIsConfiguration());
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<>();
if (templateOnPrimaryStore == null) {
createBaseImageAsync(volume, pd, template, future);
return future;
}
createVolumeFromBaseImageAsync(volume, templateOnPrimaryStore, pd, future);
return future;
}
@DB
@Override
public void destroyVolume(long volumeId) {
// mark volume entry in volumes table as destroy state
VolumeInfo vol = volFactory.getVolume(volumeId);
vol.stateTransit(Volume.Event.DestroyRequested);
snapshotMgr.deletePoliciesForVolume(volumeId);
annotationDao.removeByEntityType(AnnotationService.EntityType.VOLUME.name(), vol.getUuid());
vol.stateTransit(Volume.Event.OperationSucceeded);
if (vol.getAttachedVM() == null || vol.getAttachedVM().getType() == VirtualMachine.Type.User) {
// Decrement the resource count for volumes and primary storage belonging user VM's only
_resourceLimitMgr.decrementVolumeResourceCount(vol.getAccountId(), vol.isDisplay(), vol.getSize(), diskOfferingDao.findById(vol.getDiskOfferingId()));
}
}
@Override
public AsyncCallFuture<VolumeApiResult> createVolumeFromSnapshot(VolumeInfo volume, DataStore store, SnapshotInfo snapshot) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
try {
DataObject volumeOnStore = store.create(volume);
volumeOnStore.processEvent(Event.CreateOnlyRequested);
_volumeDetailsDao.addDetail(volume.getId(), SNAPSHOT_ID, Long.toString(snapshot.getId()), false);
CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<VolumeApiResult>(null, volume, store, volumeOnStore, future, snapshot, null);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().createVolumeFromSnapshotCallback(null, null)).setContext(context);
motionSrv.copyAsync(snapshot, volumeOnStore, caller);
} catch (Exception e) {
logger.debug("create volume from snapshot failed", e);
VolumeApiResult result = new VolumeApiResult(volume);
result.setResult(e.toString());
future.complete(result);
}
return future;
}
protected Void createVolumeFromSnapshotCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateVolumeFromBaseImageContext<VolumeApiResult> context) {
CopyCommandResult result = callback.getResult();
VolumeInfo volume = (VolumeInfo)context.templateOnStore;
SnapshotInfo snapshot = context.snapshot;
VolumeApiResult apiResult = new VolumeApiResult(volume);
Event event = null;
if (result.isFailed()) {
apiResult.setResult(result.getResult());
event = Event.OperationFailed;
} else {
event = Event.OperationSuccessed;
}
try {
if (result.isSuccess()) {
volume.processEvent(event, result.getAnswer());
} else {
volume.processEvent(event);
}
_volumeDetailsDao.removeDetail(volume.getId(), SNAPSHOT_ID);
} catch (Exception e) {
logger.debug("create volume from snapshot failed", e);
apiResult.setResult(e.toString());
}
AsyncCallFuture<VolumeApiResult> future = context.future;
future.complete(apiResult);
return null;
}
protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool pool) {
Long lastPoolId = volume.getPoolId();
String folder = pool.getPath();
// For SMB, pool credentials are also stored in the uri query string. We trim the query string
// 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);
newVol.setInstanceId(null);
newVol.setChainInfo(null);
newVol.setPath(null);
newVol.setFolder(folder);
newVol.setPodId(pool.getPodId());
newVol.setPoolId(pool.getId());
newVol.setPoolType(pool.getPoolType());
newVol.setLastPoolId(lastPoolId);
newVol.setPodId(pool.getPodId());
if (volume.getPassphraseId() != null) {
newVol.setPassphraseId(volume.getPassphraseId());
newVol.setEncryptFormat(volume.getEncryptFormat());
}
return volDao.persist(newVol);
}
private class CopyVolumeContext<T> extends AsyncRpcContext<T> {
final VolumeInfo srcVolume;
final VolumeInfo destVolume;
final AsyncCallFuture<VolumeApiResult> future;
public CopyVolumeContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<VolumeApiResult> future, VolumeInfo srcVolume, VolumeInfo destVolume, DataStore destStore) {
super(callback);
this.srcVolume = srcVolume;
this.destVolume = destVolume;
this.future = future;
}
}
protected AsyncCallFuture<VolumeApiResult> copyVolumeFromImageToPrimary(VolumeInfo srcVolume, DataStore destStore) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult res = new VolumeApiResult(srcVolume);
VolumeInfo destVolume = null;
try {
destVolume = (VolumeInfo)destStore.create(srcVolume);
destVolume.processEvent(Event.CopyingRequested);
srcVolume.processEvent(Event.CopyingRequested);
CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copyVolumeFromImageToPrimaryCallback(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, caller);
return future;
} catch (Exception e) {
logger.error("failed to copy volume from image store", e);
if (destVolume != null) {
destVolume.processEvent(Event.OperationFailed);
}
srcVolume.processEvent(Event.OperationFailed);
res.setResult(e.toString());
future.complete(res);
return future;
}
}
protected Void copyVolumeFromImageToPrimaryCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
VolumeInfo srcVolume = context.srcVolume;
VolumeInfo destVolume = context.destVolume;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeApiResult res = new VolumeApiResult(destVolume);
try {
if (result.isFailed()) {
destVolume.processEvent(Event.OperationFailed);
srcVolume.processEvent(Event.OperationFailed);
res.setResult(result.getResult());
future.complete(res);
return null;
}
srcVolume.processEvent(Event.OperationSuccessed);
destVolume.processEvent(Event.OperationSuccessed, result.getAnswer());
srcVolume.getDataStore().delete(srcVolume);
future.complete(res);
} catch (Exception e) {
res.setResult(e.toString());
future.complete(res);
}
return null;
}
protected AsyncCallFuture<VolumeApiResult> copyVolumeFromPrimaryToImage(VolumeInfo srcVolume, DataStore destStore) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult res = new VolumeApiResult(srcVolume);
VolumeInfo destVolume = null;
try {
destVolume = (VolumeInfo)destStore.create(srcVolume);
srcVolume.processEvent(Event.MigrationRequested); // this is just used for locking that src volume record in DB to avoid using lock
destVolume.processEventOnly(Event.CreateOnlyRequested);
CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copyVolumeFromPrimaryToImageCallback(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, caller);
return future;
} catch (Exception e) {
logger.error("failed to copy volume to image store", e);
if (destVolume != null) {
destVolume.getDataStore().delete(destVolume);
}
srcVolume.processEvent(Event.OperationFailed); // unlock source volume record
res.setResult(e.toString());
future.complete(res);
return future;
}
}
protected Void copyVolumeFromPrimaryToImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
VolumeInfo srcVolume = context.srcVolume;
VolumeInfo destVolume = context.destVolume;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeApiResult res = new VolumeApiResult(destVolume);
try {
if (result.isFailed()) {
srcVolume.processEvent(Event.OperationFailed); // back to Ready state in Volume table
destVolume.processEventOnly(Event.OperationFailed);
res.setResult(result.getResult());
future.complete(res);
} else {
srcVolume.processEvent(Event.OperationSuccessed); // back to Ready state in Volume table
destVolume.processEventOnly(Event.OperationSuccessed, result.getAnswer());
future.complete(res);
}
} catch (Exception e) {
res.setResult(e.toString());
future.complete(res);
}
return null;
}
@Override
public AsyncCallFuture<VolumeApiResult> copyVolume(VolumeInfo srcVolume, DataStore destStore) {
DataStore srcStore = srcVolume.getDataStore();
if (logger.isDebugEnabled()) {
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)"
, srcVolume.getName()
, srcVolume.getId()
, srcRole
, destStore.getName()
, destStore.getId()
, destStore.getRole());
logger.debug(msg);
}
if (srcVolume.getState() == Volume.State.Uploaded) {
return copyVolumeFromImageToPrimary(srcVolume, destStore);
}
if (destStore.getRole() == DataStoreRole.Image) {
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>();
VolumeApiResult res = new VolumeApiResult(srcVolume);
try {
if (!snapshotMgr.canOperateOnVolume(srcVolume)) {
logger.debug("There are snapshots creating on this volume, can not move this volume");
res.setResult("There are snapshots creating on this volume, can not move this volume");
future.complete(res);
return future;
}
VolumeVO destVol = duplicateVolumeOnAnotherStorage(srcVolume, (StoragePool)destStore);
VolumeInfo destVolume = volFactory.getVolume(destVol.getId(), destStore);
destVolume.processEvent(Event.MigrationCopyRequested);
srcVolume.processEvent(Event.MigrationRequested);
CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copyVolumeCallBack(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, caller);
} catch (Exception e) {
logger.error("Failed to copy volume:" + e);
if(logger.isDebugEnabled()) {
logger.debug("Failed to copy volume.", e);
}
res.setResult(e.toString());
future.complete(res);
}
return future;
}
protected Void copyVolumeCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
VolumeInfo srcVolume = context.srcVolume;
VolumeInfo destVolume = context.destVolume;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeApiResult res = new VolumeApiResult(destVolume);
try {
if (result.isFailed()) {
res.setResult(result.getResult());
destVolume.processEvent(Event.MigrationCopyFailed);
srcVolume.processEvent(Event.OperationFailed);
destroyVolume(destVolume.getId());
if (destVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
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();
future.complete(res);
} else {
if (copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event.MigrationCopySucceeded, result.getAnswer(), srcVolume, destVolume, true)) {
future.complete(res);
}
}
} catch (Exception e) {
logger.debug("Failed to process copy volume callback", e);
res.setResult(e.toString());
future.complete(res);
}
return null;
}
@Override
public boolean copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event destinationEvent, Answer destinationEventAnswer, VolumeInfo sourceVolume,
VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync) {
VolumeVO sourceVolumeVo = ((VolumeObject) sourceVolume).getVolume();
snapshotMgr.copySnapshotPoliciesBetweenVolumes(sourceVolumeVo, ((VolumeObject) destinationVolume).getVolume());
return destroySourceVolumeAfterMigration(destinationEvent, destinationEventAnswer, sourceVolume, destinationVolume, retryExpungeVolumeAsync);
}
protected boolean destroySourceVolumeAfterMigration(Event destinationEvent, Answer destinationEventAnswer, VolumeInfo sourceVolume,
VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync) {
sourceVolume.processEvent(Event.OperationSuccessed);
destinationVolume.processEvent(destinationEvent, destinationEventAnswer);
VolumeVO sourceVolumeVo = ((VolumeObject) sourceVolume).getVolume();
long sourceVolumeId = sourceVolume.getId();
volDao.updateUuid(sourceVolumeId, destinationVolume.getId());
volDao.detachVolume(sourceVolumeId);
logger.info(String.format("Cleaning up %s on storage [%s].", sourceVolumeVo.getVolumeDescription(), sourceVolumeVo.getPoolId()));
destroyVolume(sourceVolumeId);
try {
if (sourceVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
logger.info(String.format("Source volume %s can be removed.", sourceVolumeVo.getVolumeDescription()));
sourceVolume.processEvent(Event.ExpungeRequested);
sourceVolume.processEvent(Event.OperationSuccessed);
volDao.remove(sourceVolume.getId());
return true;
}
expungeSourceVolumeAfterMigration(sourceVolumeVo, retryExpungeVolumeAsync);
return true;
} catch (InterruptedException | ExecutionException e) {
logger.error(String.format("Failed to clean up %s on storage [%s].", sourceVolumeVo.getVolumeDescription(), sourceVolumeVo.getPoolId()), e);
return false;
}
}
protected void expungeSourceVolumeAfterMigration(VolumeVO sourceVolumeVo, boolean retryExpungeVolumeAsync) throws
ExecutionException, InterruptedException {
VolumeInfo sourceVolume = volFactory.getVolume(sourceVolumeVo.getId());
AsyncCallFuture<VolumeApiResult> destroyFuture = expungeVolumeAsync(sourceVolume);
VolumeApiResult volumeApiResult = destroyFuture.get();
if (volumeApiResult.isSuccess()) {
logger.debug(String.format("%s on storage [%s] was cleaned up successfully.", sourceVolumeVo.getVolumeDescription(), sourceVolumeVo.getPoolId()));
return;
}
String message = String.format("Failed to clean up %s on storage [%s] due to [%s].", sourceVolumeVo.getVolumeDescription(), sourceVolumeVo.getPoolId(),
volumeApiResult.getResult());
if (!retryExpungeVolumeAsync) {
logger.warn(message);
} else {
int intervalBetweenExpungeVolumeAsyncTriesInSeconds = 5;
logger.info(String.format("%s Trying again in [%s] seconds.", message, intervalBetweenExpungeVolumeAsyncTriesInSeconds));
Thread.sleep(intervalBetweenExpungeVolumeAsyncTriesInSeconds * 1000);
destroyFuture = expungeVolumeAsync(sourceVolume);
destroyFuture.get();
}
}
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)) {
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)) {
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) {
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()) {
logger.debug("Failed to create dest volume " + destVolume.getId() + ", volume can be removed");
destroyVolume(destVolume.getId());
destVolume.processEvent(Event.ExpungeRequested);
destVolume.processEvent(Event.OperationSuccessed);
volDao.remove(destVolume.getId());
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) {
logger.error("Copy to managed volume failed due to: " + e);
if(logger.isDebugEnabled()) {
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) {
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());
volDao.detachVolume(srcVolume.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) {
logger.debug("failed to clean up volume on storage", e);
}
}
} catch (Exception e) {
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) {
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 (StringUtils.isAnyEmpty(srcPoolSystemId, destPoolSystemId)) {
logger.warn("PowerFlex src pool: " + srcDataStore.getId() + " or dest pool: " + destDataStore.getId() +
" storage instance details are not available");
return false;
}
if (!srcPoolSystemId.equals(destPoolSystemId)) {
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;
final AsyncCallFuture<VolumeApiResult> future;
/**
* @param callback
*/
public MigrateVolumeContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<VolumeApiResult> future, VolumeInfo srcVolume, VolumeInfo destVolume, DataStore destStore) {
super(callback);
this.srcVolume = srcVolume;
this.destVolume = destVolume;
this.future = future;
}
}
@Override
public AsyncCallFuture<VolumeApiResult> migrateVolume(VolumeInfo srcVolume, DataStore destStore) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult res = new VolumeApiResult(srcVolume);
try {
if (!snapshotMgr.canOperateOnVolume(srcVolume)) {
logger.debug("Snapshots are being created on this volume. This volume cannot be migrated now.");
res.setResult("Snapshots are being created on this volume. This volume cannot be migrated now.");
future.complete(res);
return future;
}
VolumeInfo destVolume = volFactory.getVolume(srcVolume.getId(), destStore);
srcVolume.processEvent(Event.MigrationRequested);
MigrateVolumeContext<VolumeApiResult> context = new MigrateVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().migrateVolumeCallBack(null, null)).setContext(context);
motionSrv.copyAsync(srcVolume, destVolume, caller);
} catch (Exception e) {
logger.debug("Failed to migrate volume", e);
res.setResult(e.toString());
future.complete(res);
}
return future;
}
protected Void migrateVolumeCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, MigrateVolumeContext<VolumeApiResult> context) {
VolumeInfo srcVolume = context.srcVolume;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeApiResult res = new VolumeApiResult(srcVolume);
try {
if (result.isFailed()) {
res.setResult(result.getResult());
srcVolume.processEvent(Event.OperationFailed);
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);
}
} catch (Exception e) {
logger.error("Failed to process migrate volume callback", e);
res.setResult(e.toString());
future.complete(res);
}
return null;
}
private class MigrateVmWithVolumesContext<T> extends AsyncRpcContext<T> {
final Map<VolumeInfo, DataStore> volumeToPool;
final AsyncCallFuture<CommandResult> future;
public MigrateVmWithVolumesContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<CommandResult> future, Map<VolumeInfo, DataStore> volumeToPool) {
super(callback);
this.volumeToPool = volumeToPool;
this.future = future;
}
}
@Override
public AsyncCallFuture<CommandResult> migrateVolumes(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost) {
AsyncCallFuture<CommandResult> future = new AsyncCallFuture<CommandResult>();
CommandResult res = new CommandResult();
try {
// Check to make sure there are no snapshot operations on a volume
// and
// put it in the migrating state.
List<VolumeInfo> volumesMigrating = new ArrayList<VolumeInfo>();
for (Map.Entry<VolumeInfo, DataStore> entry : volumeMap.entrySet()) {
VolumeInfo volume = entry.getKey();
if (!snapshotMgr.canOperateOnVolume(volume)) {
logger.debug("Snapshots are being created on a volume. Volumes cannot be migrated now.");
res.setResult("Snapshots are being created on a volume. Volumes cannot be migrated now.");
future.complete(res);
// All the volumes that are already in migrating state need
// to be put back in ready state.
for (VolumeInfo volumeMigrating : volumesMigrating) {
volumeMigrating.processEvent(Event.OperationFailed);
}
return future;
} else {
volume.processEvent(Event.MigrationRequested);
volumesMigrating.add(volume);
}
}
MigrateVmWithVolumesContext<CommandResult> context = new MigrateVmWithVolumesContext<CommandResult>(null, future, volumeMap);
AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().migrateVmWithVolumesCallBack(null, null)).setContext(context);
motionSrv.copyAsync(volumeMap, vmTo, srcHost, destHost, caller);
} catch (Exception e) {
logger.debug("Failed to copy volume", e);
res.setResult(e.toString());
future.complete(res);
}
return future;
}
protected Void migrateVmWithVolumesCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, MigrateVmWithVolumesContext<CommandResult> context) {
Map<VolumeInfo, DataStore> volumeToPool = context.volumeToPool;
CopyCommandResult result = callback.getResult();
AsyncCallFuture<CommandResult> future = context.future;
CommandResult res = new CommandResult();
try {
if (result.isFailed()) {
res.setResult(result.getResult());
for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
VolumeInfo volume = entry.getKey();
volume.processEvent(Event.OperationFailed);
}
future.complete(res);
} else {
for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
VolumeInfo volume = entry.getKey();
snapshotMgr.cleanupSnapshotsByVolume(volume.getId());
volume.processEvent(Event.OperationSuccessed);
}
future.complete(res);
}
} catch (Exception e) {
logger.error("Failed to process copy volume callback", e);
res.setResult(e.toString());
future.complete(res);
}
return null;
}
@Override
public AsyncCallFuture<VolumeApiResult> registerVolume(VolumeInfo volume, DataStore store) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
DataObject volumeOnStore = store.create(volume);
volumeOnStore.processEvent(Event.CreateOnlyRequested);
try {
CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volumeOnStore, future);
AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().registerVolumeCallback(null, null));
caller.setContext(context);
store.getDriver().createAsync(store, volumeOnStore, caller);
} catch (CloudRuntimeException ex) {
// clean up already persisted volume_store_ref entry in case of createVolumeCallback is never called
VolumeDataStoreVO volStoreVO = _volumeStoreDao.findByStoreVolume(store.getId(), volume.getId());
if (volStoreVO != null) {
VolumeInfo volObj = volFactory.getVolume(volume, store);
volObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
}
VolumeApiResult res = new VolumeApiResult((VolumeObject)volumeOnStore);
res.setResult(ex.getMessage());
future.complete(res);
}
return future;
}
@Override
public Pair<EndPoint, DataObject> registerVolumeForPostUpload(VolumeInfo volume, DataStore store) {
EndPoint ep = _epSelector.select(store);
if (ep == null) {
String errorMessage = "There is no secondary storage VM for image store " + store.getName();
logger.warn(errorMessage);
throw new CloudRuntimeException(errorMessage);
}
DataObject volumeOnStore = store.create(volume);
return new Pair<>(ep, volumeOnStore);
}
protected Void registerVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
CreateCmdResult result = callback.getResult();
VolumeObject vo = (VolumeObject)context.volume;
try {
if (result.isFailed()) {
vo.processEvent(Event.OperationFailed);
// delete the volume entry from volumes table in case of failure
VolumeVO vol = volDao.findById(vo.getId());
if (vol != null) {
volDao.remove(vo.getId());
}
} else {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
if (vo.getSize() != null) {
// publish usage events
// get physical size from volume_store_ref table
long physicalSize = 0;
DataStore ds = vo.getDataStore();
VolumeDataStoreVO volStore = _volumeStoreDao.findByStoreVolume(ds.getId(), vo.getId());
if (volStore != null) {
physicalSize = volStore.getPhysicalSize();
} else {
logger.warn("No entry found in volume_store_ref for volume id: " + vo.getId() + " and image store id: " + ds.getId() + " at the end of uploading volume!");
}
Scope dsScope = ds.getScope();
if (dsScope.getScopeType() == ScopeType.ZONE) {
if (dsScope.getScopeId() != null) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_UPLOAD, vo.getAccountId(), dsScope.getScopeId(), vo.getId(), vo.getName(), null, null, physicalSize, vo.getSize(),
Volume.class.getName(), vo.getUuid());
} else {
logger.warn("Zone scope image store " + ds.getId() + " has a null scope id");
}
} else if (dsScope.getScopeType() == ScopeType.REGION) {
// publish usage event for region-wide image store using a -1 zoneId for 4.2, need to revisit post-4.2
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_UPLOAD, vo.getAccountId(), -1, vo.getId(), vo.getName(), null, null, physicalSize, vo.getSize(),
Volume.class.getName(), vo.getUuid());
_resourceLimitMgr.incrementResourceCount(vo.getAccountId(), ResourceType.secondary_storage, vo.getSize());
}
}
}
VolumeApiResult res = new VolumeApiResult(vo);
context.future.complete(res);
return null;
} catch (Exception e) {
logger.error("register volume failed: ", e);
// delete the volume entry from volumes table in case of failure
VolumeVO vol = volDao.findById(vo.getId());
if (vol != null) {
volDao.remove(vo.getId());
}
VolumeApiResult res = new VolumeApiResult(null);
context.future.complete(res);
return null;
}
}
@Override
public AsyncCallFuture<VolumeApiResult> resize(VolumeInfo volume) {
AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
VolumeApiResult result = new VolumeApiResult(volume);
try {
volume.processEvent(Event.ResizeRequested);
} catch (Exception e) {
logger.debug("Failed to change state to resize", e);
result.setResult(e.toString());
future.complete(result);
return future;
}
CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volume, future);
AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().resizeVolumeCallback(caller, context)).setContext(context);
try {
volume.getDataStore().getDriver().resize(volume, caller);
} catch (Exception e) {
logger.debug("Failed to change state to resize", e);
result.setResult(e.toString());
future.complete(result);
}
return future;
}
@Override
public void resizeVolumeOnHypervisor(long volumeId, long newSize, long destHostId, String instanceName) {
final String errMsg = "Resize command failed";
try {
Answer answer = null;
Host destHost = _hostDao.findById(destHostId);
EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(destHost);
if (ep != null) {
VolumeVO volume = volDao.findById(volumeId);
PrimaryDataStore primaryDataStore = this.dataStoreMgr.getPrimaryDataStore(volume.getPoolId());
ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(primaryDataStore), volume.getSize(), newSize, true, instanceName,
primaryDataStore.isManaged(), volume.get_iScsiName());
answer = ep.sendMessage(resizeCmd);
} else {
throw new CloudRuntimeException("Could not find a remote endpoint to send command to. Check if host or SSVM is down.");
}
if (answer == null || !answer.getResult()) {
throw new CloudRuntimeException(answer != null ? answer.getDetails() : errMsg);
}
} catch (Exception e) {
throw new CloudRuntimeException(errMsg, e);
}
}
protected Void resizeVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
CreateCmdResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
VolumeInfo volume = (VolumeInfo)context.volume;
if (result.isFailed()) {
try {
volume.processEvent(Event.OperationFailed);
} catch (Exception e) {
logger.debug("Failed to change state", e);
}
VolumeApiResult res = new VolumeApiResult(volume);
res.setResult(result.getResult());
future.complete(res);
return null;
}
try {
volume.processEvent(Event.OperationSuccessed);
} catch (Exception e) {
logger.debug("Failed to change state", e);
VolumeApiResult res = new VolumeApiResult(volume);
res.setResult(result.getResult());
future.complete(res);
return null;
}
VolumeApiResult res = new VolumeApiResult(volume);
future.complete(res);
return null;
}
@Override
public void handleVolumeSync(DataStore store) {
if (store == null) {
logger.warn("Huh? image store is null");
return;
}
long storeId = store.getId();
// add lock to make template sync for a data store only be done once
String lockString = "volumesync.storeId:" + storeId;
GlobalLock syncLock = GlobalLock.getInternLock(lockString);
try {
if (syncLock.lock(3)) {
try {
Map<Long, TemplateProp> volumeInfos = listVolume(store);
if (volumeInfos == null) {
return;
}
// find all the db volumes including those with NULL url column to avoid accidentally deleting volumes on image store later.
List<VolumeDataStoreVO> dbVolumes = _volumeStoreDao.listByStoreId(storeId);
List<VolumeDataStoreVO> toBeDownloaded = new ArrayList<VolumeDataStoreVO>(dbVolumes);
for (VolumeDataStoreVO volumeStore : dbVolumes) {
VolumeVO volume = volDao.findById(volumeStore.getVolumeId());
if (volume == null) {
logger.warn("Volume_store_ref table shows that volume " + volumeStore.getVolumeId() + " is on image store " + storeId
+ ", but the volume is not found in volumes table, potentially some bugs in deleteVolume, so we just treat this volume to be deleted and mark it as destroyed");
volumeStore.setDestroyed(true);
_volumeStoreDao.update(volumeStore.getId(), volumeStore);
continue;
}
// Exists then don't download
if (volumeInfos.containsKey(volume.getId())) {
TemplateProp volInfo = volumeInfos.remove(volume.getId());
toBeDownloaded.remove(volumeStore);
logger.info("Volume Sync found " + volume.getUuid() + " already in the volume image store table");
if (volumeStore.getDownloadState() != Status.DOWNLOADED) {
volumeStore.setErrorString("");
}
if (volInfo.isCorrupted()) {
volumeStore.setDownloadState(Status.DOWNLOAD_ERROR);
String msg = "Volume " + volume.getUuid() + " is corrupted on image store";
volumeStore.setErrorString(msg);
logger.info(msg);
if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) {
logger.info("Volume Sync found " + volume.getUuid() + " uploaded using SSVM on image store " + storeId + " as corrupted, marking it as failed");
_volumeStoreDao.update(volumeStore.getId(), volumeStore);
// mark volume as failed, so that storage GC will clean it up
VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
volObj.processEvent(Event.OperationFailed);
} else if (volumeStore.getDownloadUrl() == null) {
msg = "Volume (" + volume.getUuid() + ") with install path " + volInfo.getInstallPath() + " is corrupted, please check in image store: "
+ volumeStore.getDataStoreId();
logger.warn(msg);
} else {
logger.info("Removing volume_store_ref entry for corrupted volume " + volume.getName());
_volumeStoreDao.remove(volumeStore.getId());
toBeDownloaded.add(volumeStore);
}
} else { // Put them in right status
volumeStore.setDownloadPercent(100);
volumeStore.setDownloadState(Status.DOWNLOADED);
volumeStore.setState(ObjectInDataStoreStateMachine.State.Ready);
volumeStore.setInstallPath(volInfo.getInstallPath());
volumeStore.setSize(volInfo.getSize());
volumeStore.setPhysicalSize(volInfo.getPhysicalSize());
volumeStore.setLastUpdated(new Date());
_volumeStoreDao.update(volumeStore.getId(), volumeStore);
if (volume.getSize() == 0) {
// Set volume size in volumes table
volume.setSize(volInfo.getSize());
volDao.update(volumeStore.getVolumeId(), volume);
}
if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) {
VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
volObj.processEvent(Event.OperationSuccessed);
}
if (volInfo.getSize() > 0) {
try {
_resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), com.cloud.configuration.Resource.ResourceType.secondary_storage,
volInfo.getSize() - volInfo.getPhysicalSize());
} catch (ResourceAllocationException e) {
logger.warn(e.getMessage());
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED, volume.getDataCenterId(), volume.getPodId(), e.getMessage(), e.getMessage());
} finally {
_resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(),
com.cloud.configuration.Resource.ResourceType.secondary_storage.getOrdinal());
}
}
}
continue;
} else if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { // failed uploads through SSVM
logger.info("Volume Sync did not find " + volume.getUuid() + " uploaded using SSVM on image store " + storeId + ", marking it as failed");
toBeDownloaded.remove(volumeStore);
volumeStore.setDownloadState(Status.DOWNLOAD_ERROR);
String msg = "Volume " + volume.getUuid() + " is corrupted on image store";
volumeStore.setErrorString(msg);
_volumeStoreDao.update(volumeStore.getId(), volumeStore);
// mark volume as failed, so that storage GC will clean it up
VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
volObj.processEvent(Event.OperationFailed);
continue;
}
// Volume is not on secondary but we should download.
if (volumeStore.getDownloadState() != Status.DOWNLOADED) {
logger.info("Volume Sync did not find " + volume.getName() + " ready on image store " + storeId + ", will request download to start/resume shortly");
}
}
// Download volumes which haven't been downloaded yet.
if (toBeDownloaded.size() > 0) {
for (VolumeDataStoreVO volumeHost : toBeDownloaded) {
if (volumeHost.getDownloadUrl() == null) { // If url is null, skip downloading
logger.info("Skip downloading volume " + volumeHost.getVolumeId() + " since no download url is specified.");
continue;
}
// if this is a region store, and there is already an DOWNLOADED entry there without install_path information, which
// means that this is a duplicate entry from migration of previous NFS to staging.
if (store.getScope().getScopeType() == ScopeType.REGION) {
if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED && volumeHost.getInstallPath() == null) {
logger.info("Skip sync volume for migration of previous NFS to object store");
continue;
}
}
logger.debug("Volume " + volumeHost.getVolumeId() + " needs to be downloaded to " + store.getName());
// reset volume status back to Allocated
VolumeObject vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId());
vol.processEvent(Event.OperationFailed); // reset back volume status
// remove leftover volume_store_ref entry since re-download will create it again
_volumeStoreDao.remove(volumeHost.getId());
// get an updated volumeVO
vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId());
RegisterVolumePayload payload = new RegisterVolumePayload(volumeHost.getDownloadUrl(), volumeHost.getChecksum(), vol.getFormat().toString());
vol.addPayload(payload);
createVolumeAsync(vol, store);
}
}
// Delete volumes which are not present on DB.
for (Map.Entry<Long, TemplateProp> entry : volumeInfos.entrySet()) {
Long uniqueName = entry.getKey();
TemplateProp tInfo = entry.getValue();
// we cannot directly call expungeVolumeAsync here to reuse delete logic since in this case db does not have this volume at all.
VolumeObjectTO tmplTO = new VolumeObjectTO();
tmplTO.setDataStore(store.getTO());
tmplTO.setPath(tInfo.getInstallPath());
tmplTO.setId(tInfo.getId());
DeleteCommand dtCommand = new DeleteCommand(tmplTO);
EndPoint ep = _epSelector.select(store);
Answer answer = null;
if (ep == null) {
String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
logger.error(errMsg);
answer = new Answer(dtCommand, false, errMsg);
} else {
answer = ep.sendMessage(dtCommand);
}
if (answer == null || !answer.getResult()) {
logger.info("Failed to deleted volume at store: " + store.getName());
} else {
String description = "Deleted volume " + tInfo.getTemplateName() + " on secondary storage " + storeId;
logger.info(description);
}
}
} finally {
syncLock.unlock();
}
} else {
logger.info("Couldn't get global lock on " + lockString + ", another thread may be doing volume sync on data store " + storeId + " now.");
}
} finally {
syncLock.releaseRef();
}
}
private Map<Long, TemplateProp> listVolume(DataStore store) {
ListVolumeCommand cmd = new ListVolumeCommand(store.getTO(), store.getUri());
EndPoint ep = _epSelector.select(store);
Answer answer = null;
if (ep == null) {
String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
logger.error(errMsg);
answer = new Answer(cmd, false, errMsg);
} else {
answer = ep.sendMessage(cmd);
}
if (answer != null && answer.getResult()) {
ListVolumeAnswer tanswer = (ListVolumeAnswer)answer;
return tanswer.getTemplateInfo();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Can not list volumes for image store " + store.getId());
}
}
return null;
}
@Override
public SnapshotInfo takeSnapshot(VolumeInfo volume) {
SnapshotInfo snapshot = null;
try {
snapshot = snapshotMgr.takeSnapshot(volume);
} catch (CloudRuntimeException cre) {
logger.error("Take snapshot: " + volume.getId() + " failed", cre);
throw cre;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("unknown exception while taking snapshot for volume " + volume.getId() + " was caught", e);
}
throw new CloudRuntimeException("Failed to take snapshot", e);
}
return snapshot;
}
@Override
public void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host) {
if (HypervisorType.KVM.equals(host.getHypervisorType()) && DataObjectType.VOLUME.equals(dataObject.getType())) {
VolumeInfo volumeInfo = volFactory.getVolume(dataObject.getId());
if (VolumeApiServiceImpl.AllowCheckAndRepairVolume.valueIn(volumeInfo.getPoolId())) {
logger.info(String.format("Trying to check and repair the volume %d", dataObject.getId()));
String repair = CheckAndRepairVolumeCmd.RepairValues.LEAKS.name().toLowerCase();
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
volumeInfo.addPayload(payload);
checkAndRepairVolumeThroughHost(volumeInfo, host);
}
}
}
@Override
public Pair<String, String> checkAndRepairVolume(VolumeInfo volume) {
Long poolId = volume.getPoolId();
List<Long> hostIds = _storageMgr.getUpHostsInPool(poolId);
if (CollectionUtils.isEmpty(hostIds)) {
throw new CloudRuntimeException("Unable to find Up hosts to run the check volume command");
}
Collections.shuffle(hostIds);
Host host = _hostDao.findById(hostIds.get(0));
return checkAndRepairVolumeThroughHost(volume, host);
}
private Pair<String, String> checkAndRepairVolumeThroughHost(VolumeInfo volume, Host host) {
Long poolId = volume.getPoolId();
StoragePool pool = _storageMgr.getStoragePool(poolId);
CheckAndRepairVolumePayload payload = (CheckAndRepairVolumePayload) volume.getpayload();
CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
volume.getPassphrase(), volume.getEncryptFormat());
try {
grantAccess(volume, host, volume.getDataStore());
CheckAndRepairVolumeAnswer answer = (CheckAndRepairVolumeAnswer) _storageMgr.sendToPool(pool, new long[]{host.getId()}, command);
if (answer != null && answer.getResult()) {
logger.debug(String.format("Check volume response result: %s", answer.getDetails()));
return new Pair<>(answer.getVolumeCheckExecutionResult(), answer.getVolumeRepairExecutionResult());
} else {
String errMsg = (answer == null) ? null : answer.getDetails();
logger.debug(String.format("Failed to check and repair the volume with error %s", errMsg));
}
} catch (Exception e) {
logger.debug("sending check and repair volume command failed", e);
} finally {
revokeAccess(volume, host, volume.getDataStore());
command.clearPassphrase();
}
return null;
}
// For managed storage on Xen and VMware, we need to potentially make space for hypervisor snapshots.
// The disk offering can collect this information and pass it on to the volume that's about to be created.
// Ex. if you want a 10 GB CloudStack volume to reside on managed storage on Xen, this leads to an SR
// that is a total size of (10 GB * (hypervisorSnapshotReserveSpace / 100) + 10 GB).
@Override
public VolumeInfo updateHypervisorSnapshotReserveForVolume(DiskOffering diskOffering, long volumeId, HypervisorType hyperType) {
if (diskOffering != null && hyperType != null) {
Integer hypervisorSnapshotReserve = diskOffering.getHypervisorSnapshotReserve();
if (hyperType == HypervisorType.KVM) {
hypervisorSnapshotReserve = null;
} else if (hypervisorSnapshotReserve == null || hypervisorSnapshotReserve < 0) {
hypervisorSnapshotReserve = 0;
}
VolumeVO volume = volDao.findById(volumeId);
volume.setHypervisorSnapshotReserve(hypervisorSnapshotReserve);
volDao.update(volume.getId(), volume);
}
return volFactory.getVolume(volumeId);
}
@DB
@Override
/**
* The volume must be marked as expunged on DB to exclude it from the storage cleanup task
*/
public void unmanageVolume(long volumeId) {
VolumeInfo vol = volFactory.getVolume(volumeId);
if (vol != null) {
vol.stateTransit(Volume.Event.DestroyRequested);
snapshotMgr.deletePoliciesForVolume(volumeId);
vol.stateTransit(Volume.Event.OperationSucceeded);
vol.stateTransit(Volume.Event.ExpungingRequested);
vol.stateTransit(Volume.Event.OperationSucceeded);
volDao.remove(vol.getId());
}
}
@Override
public void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount) {
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
if (volumeStore == null) {
logger.debug(String.format("Volume [%s] is not present in the secondary storage. Therefore we do not need to move it in the secondary storage.", volume));
return;
}
logger.debug(String.format("Volume [%s] is present in secondary storage. It will be necessary to move it from the source account's [%s] folder to the destination "
+ "account's [%s] folder.",
volume.getUuid(), sourceAccount, destAccount));
VolumeInfo volumeInfo = volFactory.getVolume(volume.getId(), DataStoreRole.Image);
String datastoreUri = volumeInfo.getDataStore().getUri();
Path srcPath = Paths.get(volumeInfo.getPath());
String destPath = buildVolumePath(destAccount.getAccountId(), volume.getId());
EndPoint ssvm = _epSelector.findSsvm(volume.getDataCenterId());
MoveVolumeCommand cmd = new MoveVolumeCommand(volume.getUuid(), volume.getName(), destPath, srcPath.getParent().toString(), datastoreUri);
Answer answer = ssvm.sendMessage(cmd);
if (!answer.getResult()) {
String msg = String.format("Unable to move volume [%s] from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage, due "
+ "to [%s].",
volume.getUuid(), srcPath.getParent(), sourceAccount, destPath, destAccount, answer.getDetails());
logger.error(msg);
throw new CloudRuntimeException(msg);
}
logger.debug(String.format("Volume [%s] was moved from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage.",
volume.getUuid(), srcPath.getParent(), sourceAccount, destPath, destAccount));
volumeStore.setInstallPath(String.format("%s/%s", destPath, srcPath.getFileName().toString()));
if (!_volumeStoreDao.update(volumeStore.getId(), volumeStore)) {
String msg = String.format("Unable to update volume [%s] install path in the DB.", volumeStore.getVolumeId());
logger.error(msg);
throw new CloudRuntimeException(msg);
}
}
protected String buildVolumePath(long accountId, long volumeId) {
return String.format("%s/%s/%s", TemplateConstants.DEFAULT_VOLUME_ROOT_DIR, accountId, volumeId);
}
}