package org.ovirt.engine.core.bll.storage.domain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.CommandBase;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.hostedengine.HostedEngineHelper;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.AttachStorageDomainToPoolParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.RemoveDiskParameters;
import org.ovirt.engine.core.common.action.StorageDomainManagementParameter;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainType;
import org.ovirt.engine.core.common.businessentities.StoragePoolStatus;
import org.ovirt.engine.core.common.businessentities.StorageServerConnections;
import org.ovirt.engine.core.common.businessentities.storage.BaseDisk;
import org.ovirt.engine.core.common.businessentities.storage.LUNs;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
import org.ovirt.engine.core.common.constants.StorageConstants;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.queries.GetExistingStorageDomainListParameters;
import org.ovirt.engine.core.common.queries.VdcQueryReturnValue;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.GetDeviceListVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.BaseDiskDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.StorageServerConnectionDao;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
/**
* <pre>
* Try to import the hosted engine storage domain which is already connected to the host by the hosted engine broker.
* We use 1) the storage domain id that is fetched from the hosted engine vm and then passed as a parameter to the command
* 2) The connection details are fetched from the deviceList
* {@link org.ovirt.engine.core.common.vdscommands.VDSCommandType#GetDeviceList} connected in vdsm
* (as the domain already connected) and crossed the storage domain info.
* With that in hand we are able to get the connection user/pass (in case of block device)
* </pre>
*/
public class ImportHostedEngineStorageDomainCommand<T extends StorageDomainManagementParameter> extends CommandBase<T> {
@Inject
private HostedEngineHelper hostedEngineHelper;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private StorageServerConnectionDao storageServerConnectionDao;
@Inject
private BaseDiskDao baseDiskDao;
private StorageDomain heStorageDomain;
static final StorageType[] SUPPORTED_DOMAIN_TYPES =
{ StorageType.NFS, StorageType.FCP, StorageType.GLUSTERFS, StorageType.ISCSI };
public ImportHostedEngineStorageDomainCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
// This is needed for command resume infrastructure
public ImportHostedEngineStorageDomainCommand(Guid commandId) {
super(commandId);
}
@Override
protected void init() {
setVdsId(getParameters().getVdsId());
fetchStorageDomainInfo();
}
@Override
protected boolean validate() {
// no point in importing this domain without an active DC. The hosted
// engine domain should never be the first, or master domain of a DC
if (storagePoolDao.get(getVds().getStoragePoolId()).getStatus() != StoragePoolStatus.Up) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_MASTER_STORAGE_DOMAIN_NOT_ACTIVE);
}
// if sd imported already, fail
if (hostedEngineHelper.getStorageDomain() != null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_ALREADY_EXIST);
}
// fetch info on storage domain from VDSM, sets #heStorageDomain
if (heStorageDomain == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_NOT_EXIST);
}
if (!Arrays.asList(SUPPORTED_DOMAIN_TYPES).contains(heStorageDomain.getStorageType())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_TYPE_UNSUPPORTED);
}
return true;
}
@Override
protected void executeCommand() {
StorageDomainManagementParameter addSdParams =
new StorageDomainManagementParameter(heStorageDomain.getStorageStaticData());
addSdParams.setVdsId(getParameters().getVdsId());
addSdParams.setStoragePoolId(getVds().getStoragePoolId());
VdcActionType actionType = null;
switch (heStorageDomain.getStorageType()) {
case NFS:
case GLUSTERFS:
actionType = VdcActionType.AddExistingFileStorageDomain;
addStorageServerConnection();
break;
case ISCSI:
discoverBlockConnectionDetails();
case FCP:
actionType = VdcActionType.AddExistingBlockStorageDomain;
removeHostedEngineLunDisk();
break;
}
if (getSucceeded()) {
setSucceeded(getBackend().runInternalAction(
actionType,
addSdParams,
getContext()).getSucceeded());
}
if (getSucceeded()) {
AttachStorageDomainToPoolParameters attachSdParams =
new AttachStorageDomainToPoolParameters(
addSdParams.getStorageDomainId(),
addSdParams.getStoragePoolId());
setSucceeded(getBackend().runInternalAction(
VdcActionType.AttachStorageDomainToPool,
attachSdParams,
getContext()).getSucceeded());
}
setActionReturnValue(heStorageDomain);
}
private void discoverBlockConnectionDetails() {
// get device list
VDSReturnValue getDeviceList = runVdsCommand(
VDSCommandType.GetDeviceList,
new GetDeviceListVDSCommandParameters(
getParameters().getVdsId(),
heStorageDomain.getStorageType()));
if (getDeviceList.getSucceeded() && getDeviceList.getReturnValue() != null
&& heStorageDomain.getStorageStaticData().getStorage() != null) {
for (LUNs lun : (ArrayList<LUNs>) getDeviceList.getReturnValue()) {
// match a lun vgid to the domain vgid.
if (heStorageDomain.getStorage().equals(lun.getVolumeGroupId())) {
// found a lun. Use its connection details
heStorageDomain.getStorageStaticData()
.setConnection(lun.getLunConnections().get(0));
setSucceeded(true);
break;
}
}
if (!getSucceeded()) {
log.error("There are no luns with VG that match the SD VG '{}'."
+ " Connections details are missing. completing this automatic import");
}
}
}
/**
* For File based storage only, we need to save the connection in DB. It is implicitly called for SAN domains.
*/
private void addStorageServerConnection() {
TransactionSupport.executeInNewTransaction(() -> {
StorageServerConnections connection = heStorageDomain.getStorageStaticData().getConnection();
connection.setId(Guid.newGuid().toString());
if (heStorageDomain.getStorageType() == StorageType.GLUSTERFS) {
// The use of the vfs type is mainly used for posix Storage Domains, usually,
// adding a posix SD will have a defined vfs type configured by the user,
// for this specific Gluster Storage Domain the user does not indicate the vfs type,
// and that is the reason why it is done only for Gluster
connection.setVfsType(StorageType.GLUSTERFS.name().toLowerCase());
}
storageServerConnectionDao.save(connection);
// make sure the storage domain object is full for the rest of the flow
heStorageDomain.setStorage(connection.getId());
setSucceeded(true);
getCompensationContext().snapshotEntity(connection);
getCompensationContext().stateChanged();
return null;
});
}
private void removeHostedEngineLunDisk() {
List<BaseDisk> disks = baseDiskDao.getDisksByAlias(StorageConstants.HOSTED_ENGINE_LUN_DISK_ALIAS);
if (disks != null && !disks.isEmpty()) {
BaseDisk heDirectLun = disks.get(0);
VdcReturnValueBase removeDisk = getBackend().runInternalAction(
VdcActionType.RemoveDisk,
new RemoveDiskParameters(heDirectLun.getId()));
if (!removeDisk.getSucceeded()) {
setSucceeded(false);
log.error("Failed to remove the hosted engine direct lun disk");
return;
}
}
setSucceeded(true);
}
/**
* This command should run internal only. No permission is needed.
* @return empty collection. The subjects shouldn't be checked for permission.
*/
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
return Collections.emptyList();
}
private boolean fetchStorageDomainInfo() {
VdcQueryReturnValue allDomainsQuery = getBackend().runInternalQuery(
VdcQueryType.GetExistingStorageDomainList,
new GetExistingStorageDomainListParameters(
getParameters().getVdsId(),
null,
StorageDomainType.Data,
null));
if (allDomainsQuery.getSucceeded()) {
for (StorageDomain sd : (List<StorageDomain>) allDomainsQuery.getReturnValue()) {
if(sd.getId().equals(getParameters().getStorageDomainId())){
heStorageDomain = sd;
return true;
}
}
} else {
log.error("Failed query for all Storage Domains."
+ " The import command can not proceed without this info");
}
return false;
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getSucceeded()
? AuditLogType.HOSTED_ENGINE_DOMAIN_IMPORT_SUCCEEDED
: AuditLogType.HOSTED_ENGINE_DOMAIN_IMPORT_FAILED;
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(LockProperties.Scope.Command);
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
if (heStorageDomain != null && heStorageDomain.getId() != null) {
return Collections.singletonMap(
heStorageDomain.getId().toString(),
LockMessagesMatchUtil.makeLockingPair(
LockingGroup.STORAGE,
EngineMessage.ACTION_TYPE_FAILED_STORAGE_DEVICE_LOCKED));
}
return Collections.emptyMap();
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__ADD);
addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__DOMAIN);
}
}