blob: a0aaab1d0aa16a0a460b7931bd7604fc9c4c42fa [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.datastore.driver;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.storage.VolumeVO;
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.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.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction;
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.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.CreateObjectCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.RevertSnapshotCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.host.Host;
import com.cloud.host.dao.HostDao;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ResizeVolumePayload;
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.Volume;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.template.TemplateManager;
import com.cloud.utils.Pair;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
@Override
public Map<String, String> getCapabilities() {
Map<String, String> caps = new HashMap<String, String>();
caps.put(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString(), Boolean.FALSE.toString());
return caps;
}
private static final Logger s_logger = Logger.getLogger(CloudStackPrimaryDataStoreDriverImpl.class);
private static final String NO_REMOTE_ENDPOINT_WITH_ENCRYPTION = "No remote endpoint to send command, unable to find a valid endpoint. Requires encryption support: %s";
@Inject
DiskOfferingDao diskOfferingDao;
@Inject
VMTemplateDao templateDao;
@Inject
VolumeDao volumeDao;
@Inject
HostDao hostDao;
@Inject
StorageManager storageMgr;
@Inject
VMInstanceDao vmDao;
@Inject
SnapshotDao snapshotDao;
@Inject
PrimaryDataStoreDao primaryStoreDao;
@Inject
SnapshotManager snapshotMgr;
@Inject
EndPointSelector epSelector;
@Inject
ConfigurationDao configDao;
@Inject
TemplateManager templateManager;
@Inject
TemplateDataFactory templateDataFactory;
@Inject
VolumeDataFactory volFactory;
@Override
public DataTO getTO(DataObject data) {
return null;
}
@Override
public DataStoreTO getStoreTO(DataStore store) {
return null;
}
public Answer createVolume(VolumeInfo volume) throws StorageUnavailableException {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Creating volume: " + volume);
}
CreateObjectCommand cmd = new CreateObjectCommand(volume.getTO());
boolean encryptionRequired = anyVolumeRequiresEncryption(volume);
EndPoint ep = epSelector.select(volume, encryptionRequired);
Answer answer = null;
if (ep == null) {
String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired);
s_logger.error(errMsg);
answer = new Answer(cmd, false, errMsg);
} else {
answer = ep.sendMessage(cmd);
}
return answer;
}
@Override
public ChapInfo getChapInfo(DataObject dataObject) {
return null;
}
@Override
public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
return false;
}
@Override
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
}
@Override
public long getUsedBytes(StoragePool storagePool) {
return 0;
}
@Override
public long getUsedIops(StoragePool storagePool) {
return 0;
}
@Override
public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
return dataObject.getSize();
}
@Override
public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) {
return 0;
}
@Override
public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
String errMsg = null;
Answer answer = null;
CreateCmdResult result = new CreateCmdResult(null, null);
if (data.getType() == DataObjectType.VOLUME) {
try {
answer = createVolume((VolumeInfo) data);
if ((answer == null) || (!answer.getResult())) {
result.setSuccess(false);
if (answer != null) {
result.setResult(answer.getDetails());
}
} else {
result.setAnswer(answer);
}
} catch (Exception e) {
s_logger.debug("failed to create volume", e);
errMsg = e.toString();
}
}
if (errMsg != null) {
result.setResult(errMsg);
}
if (callback != null) {
callback.complete(result);
}
}
private boolean commandCanBypassHostMaintenance(DataObject data) {
if (DataObjectType.VOLUME.equals(data.getType())) {
Volume volume = (Volume)data;
if (volume.getInstanceId() != null) {
VMInstanceVO vm = vmDao.findById(volume.getInstanceId());
return vm != null && (VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) ||
VirtualMachine.Type.ConsoleProxy.equals(vm.getType()));
}
}
return false;
}
@Override
public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
DeleteCommand cmd = new DeleteCommand(data.getTO());
cmd.setBypassHostMaintenance(commandCanBypassHostMaintenance(data));
CommandResult result = new CommandResult();
try {
EndPoint ep = null;
if (data.getType() == DataObjectType.VOLUME) {
ep = epSelector.select(data, StorageAction.DELETEVOLUME);
} else {
ep = epSelector.select(data);
}
if (ep == null) {
String errMsg = "No remote endpoint to send DeleteCommand, check if host or ssvm is down?";
s_logger.error(errMsg);
result.setResult(errMsg);
} else {
Answer answer = ep.sendMessage(cmd);
if (answer != null && !answer.getResult()) {
result.setResult(answer.getDetails());
}
}
} catch (Exception ex) {
s_logger.debug("Unable to destroy volume" + data.getId(), ex);
result.setResult(ex.toString());
}
callback.complete(result);
}
@Override
public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
s_logger.debug(String.format("Copying volume %s(%s) to %s(%s)", srcdata.getId(), srcdata.getType(), destData.getId(), destData.getType()));
boolean encryptionRequired = anyVolumeRequiresEncryption(srcdata, destData);
DataStore store = destData.getDataStore();
if (store.getRole() == DataStoreRole.Primary) {
if ((srcdata.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.TEMPLATE)) {
//For CLVM, we need to copy template to primary storage at all, just fake the copy result.
TemplateObjectTO templateObjectTO = new TemplateObjectTO();
templateObjectTO.setPath(UUID.randomUUID().toString());
templateObjectTO.setSize(srcdata.getSize());
templateObjectTO.setPhysicalSize(srcdata.getSize());
templateObjectTO.setFormat(Storage.ImageFormat.RAW);
CopyCmdAnswer answer = new CopyCmdAnswer(templateObjectTO);
CopyCommandResult result = new CopyCommandResult("", answer);
callback.complete(result);
} else if (srcdata.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.VOLUME) {
//For CLVM, we need to pass template on secondary storage to hypervisor
int primaryStorageDownloadWait = StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value();
StoragePoolVO storagePoolVO = primaryStoreDao.findById(store.getId());
DataStore imageStore = templateManager.getImageStore(storagePoolVO.getDataCenterId(), srcdata.getId());
DataObject srcData = templateDataFactory.getTemplate(srcdata.getId(), imageStore);
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), primaryStorageDownloadWait, true);
EndPoint ep = epSelector.select(srcData, destData, encryptionRequired);
Answer answer = null;
if (ep == null) {
String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired);
s_logger.error(errMsg);
answer = new Answer(cmd, false, errMsg);
} else {
s_logger.debug(String.format("Sending copy command to endpoint %s, where encryption support is %s", ep.getHostAddr(), encryptionRequired ? "required" : "not required"));
answer = ep.sendMessage(cmd);
}
CopyCommandResult result = new CopyCommandResult("", answer);
callback.complete(result);
} else if (srcdata.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.VOLUME) {
SnapshotObjectTO srcTO = (SnapshotObjectTO) srcdata.getTO();
CopyCommand cmd = new CopyCommand(srcTO, destData.getTO(), StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value(), true);
EndPoint ep = epSelector.select(srcdata, destData, encryptionRequired);
CopyCmdAnswer answer = null;
if (ep == null) {
String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired);
s_logger.error(errMsg);
answer = new CopyCmdAnswer(errMsg);
} else {
answer = (CopyCmdAnswer) ep.sendMessage(cmd);
}
CopyCommandResult result = new CopyCommandResult("", answer);
callback.complete(result);
}
}
}
@Override
public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
copyAsync(srcData, destData, callback);
}
@Override
public boolean canCopy(DataObject srcData, DataObject destData) {
//BUG fix for CLOUDSTACK-4618
DataStore destStore = destData.getDataStore();
if (destStore.getRole() == DataStoreRole.Primary && srcData.getType() == DataObjectType.TEMPLATE
&& (destData.getType() == DataObjectType.TEMPLATE || destData.getType() == DataObjectType.VOLUME)) {
StoragePoolVO storagePoolVO = primaryStoreDao.findById(destStore.getId());
if (storagePoolVO != null && storagePoolVO.getPoolType() == Storage.StoragePoolType.CLVM) {
return true;
}
} else if (DataObjectType.SNAPSHOT.equals(srcData.getType()) && DataObjectType.VOLUME.equals(destData.getType())) {
DataStore srcStore = srcData.getDataStore();
if (DataStoreRole.Primary.equals(srcStore.getRole()) && DataStoreRole.Primary.equals(destStore.getRole())) {
StoragePoolVO srcStoragePoolVO = primaryStoreDao.findById(srcStore.getId());
StoragePoolVO dstStoragePoolVO = primaryStoreDao.findById(destStore.getId());
if (srcStoragePoolVO != null && StoragePoolType.RBD.equals(srcStoragePoolVO.getPoolType())
&& dstStoragePoolVO != null && (StoragePoolType.RBD.equals(dstStoragePoolVO.getPoolType())
|| StoragePoolType.NetworkFilesystem.equals(dstStoragePoolVO.getPoolType()))) {
return true;
}
}
}
return false;
}
@Override
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
CreateCmdResult result = null;
s_logger.debug("Taking snapshot of "+ snapshot);
try {
SnapshotObjectTO snapshotTO = (SnapshotObjectTO) snapshot.getTO();
Object payload = snapshot.getPayload();
if (payload != null && payload instanceof CreateSnapshotPayload) {
CreateSnapshotPayload snapshotPayload = (CreateSnapshotPayload) payload;
snapshotTO.setQuiescevm(snapshotPayload.getQuiescevm());
}
boolean encryptionRequired = anyVolumeRequiresEncryption(snapshot);
CreateObjectCommand cmd = new CreateObjectCommand(snapshotTO);
EndPoint ep = epSelector.select(snapshot, StorageAction.TAKESNAPSHOT, encryptionRequired);
Answer answer = null;
s_logger.debug("Taking snapshot of "+ snapshot + " and encryption required is " + encryptionRequired);
if (ep == null) {
String errMsg = "No remote endpoint to send createObjectCommand, check if host or ssvm is down?";
s_logger.error(errMsg);
answer = new Answer(cmd, false, errMsg);
} else {
answer = ep.sendMessage(cmd);
}
result = new CreateCmdResult(null, answer);
if (answer != null && !answer.getResult()) {
result.setResult(answer.getDetails());
}
callback.complete(result);
return;
} catch (Exception e) {
s_logger.debug("Failed to take snapshot: " + snapshot.getId(), e);
result = new CreateCmdResult(null, null);
result.setResult(e.toString());
}
callback.complete(result);
}
@Override
public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback) {
SnapshotObjectTO dataOnPrimaryStorage = null;
if (snapshotOnPrimaryStore != null) {
dataOnPrimaryStorage = (SnapshotObjectTO)snapshotOnPrimaryStore.getTO();
}
RevertSnapshotCommand cmd = new RevertSnapshotCommand((SnapshotObjectTO)snapshot.getTO(), dataOnPrimaryStorage);
CommandResult result = new CommandResult();
try {
EndPoint ep = null;
if (snapshotOnPrimaryStore != null) {
ep = epSelector.select(snapshotOnPrimaryStore);
} else {
VolumeInfo volumeInfo = volFactory.getVolume(snapshot.getVolumeId(), DataStoreRole.Primary);
ep = epSelector.select(volumeInfo);
}
if ( ep == null ){
String errMsg = "No remote endpoint to send RevertSnapshotCommand, check if host or ssvm is down?";
s_logger.error(errMsg);
result.setResult(errMsg);
} else {
Answer answer = ep.sendMessage(cmd);
if (answer != null && !answer.getResult()) {
result.setResult(answer.getDetails());
}
}
} catch (Exception ex) {
s_logger.debug("Unable to revert snapshot " + snapshot.getId(), ex);
result.setResult(ex.toString());
}
callback.complete(result);
}
@Override
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
VolumeObject vol = (VolumeObject) data;
StoragePool pool = (StoragePool) data.getDataStore();
ResizeVolumePayload resizeParameter = (ResizeVolumePayload) vol.getpayload();
boolean encryptionRequired = anyVolumeRequiresEncryption(vol);
long [] endpointsToRunResize = resizeParameter.hosts;
// if hosts are provided, they are where the VM last ran. We can use that.
if (endpointsToRunResize == null || endpointsToRunResize.length == 0) {
EndPoint ep = epSelector.select(data, encryptionRequired);
endpointsToRunResize = new long[] {ep.getId()};
}
ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(),
resizeParameter.newSize, resizeParameter.shrinkOk, resizeParameter.instanceName, vol.getChainInfo(), vol.getPassphrase(), vol.getEncryptFormat());
if (pool.getParent() != 0) {
resizeCmd.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString());
}
CreateCmdResult result = new CreateCmdResult(null, null);
try {
ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, endpointsToRunResize, resizeCmd);
if (answer != null && answer.getResult()) {
long finalSize = answer.getNewSize();
s_logger.debug("Resize: volume started at size: " + toHumanReadableSize(vol.getSize()) + " and ended at size: " + toHumanReadableSize(finalSize));
vol.setSize(finalSize);
vol.update();
updateVolumePathDetails(vol, answer);
} else if (answer != null) {
result.setResult(answer.getDetails());
} else {
s_logger.debug("return a null answer, mark it as failed for unknown reason");
result.setResult("return a null answer, mark it as failed for unknown reason");
}
} catch (Exception e) {
s_logger.debug("sending resize command failed", e);
result.setResult(e.toString());
} finally {
resizeCmd.clearPassphrase();
}
callback.complete(result);
}
private void updateVolumePathDetails(VolumeObject vol, ResizeVolumeAnswer answer) {
VolumeVO volumeVO = volumeDao.findById(vol.getId());
String datastoreUUID = answer.getContextParam("datastoreUUID");
if (datastoreUUID != null) {
StoragePoolVO storagePoolVO = primaryStoreDao.findByUuid(datastoreUUID);
if (storagePoolVO != null) {
volumeVO.setPoolId(storagePoolVO.getId());
} else {
s_logger.warn(String.format("Unable to find datastore %s while updating the new datastore of the volume %d", datastoreUUID, vol.getId()));
}
}
String volumePath = answer.getContextParam("volumePath");
if (volumePath != null) {
volumeVO.setPath(volumePath);
}
String chainInfo = answer.getContextParam("chainInfo");
if (chainInfo != null) {
volumeVO.setChainInfo(chainInfo);
}
volumeDao.update(volumeVO.getId(), volumeVO);
}
@Override
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
@Override
public boolean canProvideStorageStats() {
return false;
}
@Override
public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
return null;
}
@Override
public boolean canProvideVolumeStats() {
return false;
}
@Override
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
return null;
}
@Override
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
return true;
}
@Override
public boolean isVmInfoNeeded() {
return false;
}
@Override
public void provideVmInfo(long vmId, long volumeId) {
}
@Override
public boolean isVmTagsNeeded(String tagKey) {
return false;
}
@Override
public void provideVmTags(long vmId, long volumeId, String tagValue) {
}
/**
* Does any object require encryption support?
*/
private boolean anyVolumeRequiresEncryption(DataObject ... objects) {
for (DataObject o : objects) {
// this fails code smell for returning true twice, but it is more readable than combining all tests into one statement
if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) {
return true;
} else if (o instanceof SnapshotInfo && ((SnapshotInfo) o).getBaseVolume().getPassphraseId() != null) {
return true;
}
}
return false;
}
@Override
public boolean isStorageSupportHA(StoragePoolType type) {
return StoragePoolType.NetworkFilesystem == type;
}
@Override
public void detachVolumeFromAllStorageNodes(Volume volume) {
}
}