package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.network.HostSetupNetworksParametersBuilder;
import org.ovirt.engine.core.bll.utils.ClusterUtils;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.ChangeVDSClusterParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.PersistentHostSetupNetworksParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.action.VdsActionParameters;
import org.ovirt.engine.core.common.businessentities.ArchitectureType;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.Entities;
import org.ovirt.engine.core.common.businessentities.StoragePool;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.VdsDynamic;
import org.ovirt.engine.core.common.businessentities.VdsStatic;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.NetworkAttachment;
import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.utils.NetworkCommonUtils;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.common.vdscommands.gluster.AddGlusterServerVDSParameters;
import org.ovirt.engine.core.common.vdscommands.gluster.RemoveGlusterServerVDSParameters;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.VdsStaticDao;
import org.ovirt.engine.core.dao.network.InterfaceDao;
import org.ovirt.engine.core.dao.network.NetworkAttachmentDao;
import org.ovirt.engine.core.dao.network.NetworkClusterDao;
import org.ovirt.engine.core.dao.network.NetworkDao;
import org.ovirt.engine.core.utils.NetworkUtils;
import org.ovirt.engine.core.utils.ObjectIdentityChecker;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.ovirt.engine.core.vdsbroker.vdsbroker.HostNetworkAttachmentsPersister;
@NonTransactiveCommandAttribute(forceCompensation = true)
public class ChangeVDSClusterCommand<T extends ChangeVDSClusterParameters> extends VdsCommand<T> {
@Inject
private NetworkAttachmentDao networkAttachmentDao;
@Inject
private ChangeClusterParametersBuilder changeClusterParametersBuilder;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private VdsStaticDao vdsStaticDao;
@Inject
private NetworkDao networkDao;
@Inject
private InterfaceDao interfaceDao;
@Inject
private ClusterDao clusterDao;
private StoragePool targetStoragePool;
private Cluster targetCluster;
private AuditLogType errorType = AuditLogType.USER_FAILED_UPDATE_VDS;
private List<VdsNetworkInterface> hostNics;
private List<Network> targetClusterNetworks;
/**
* Constructor for command creation when compensation is applied on startup
* @param commandId id of command
*/
public ChangeVDSClusterCommand(Guid commandId) {
super(commandId);
}
public ChangeVDSClusterCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
@Override
protected boolean validate() {
VDS vds = getVds();
if (vds == null) {
addValidationMessage(EngineMessage.VDS_INVALID_SERVER_ID);
return false;
}
if (!ObjectIdentityChecker.canUpdateField(vds, "clusterId", vds.getStatus())) {
addValidationMessage(EngineMessage.VDS_STATUS_NOT_VALID_FOR_UPDATE);
return false;
}
if (getTargetCluster() == null) {
addValidationMessage(EngineMessage.VDS_CLUSTER_IS_NOT_VALID);
return false;
}
targetStoragePool = storagePoolDao.getForCluster(getTargetCluster().getId());
if (targetStoragePool != null && targetStoragePool.isLocal()) {
if (!vdsStaticDao.getAllForCluster(getParameters().getClusterId()).isEmpty()) {
addValidationMessage(EngineMessage.VDS_CANNOT_ADD_MORE_THEN_ONE_HOST_TO_LOCAL_STORAGE);
return false;
}
}
if (getCluster().supportsGlusterService()) {
if (glusterDBUtils.hasBricks(getVdsId())) {
addValidationMessage(EngineMessage.VDS_CANNOT_REMOVE_HOST_HAVING_GLUSTER_VOLUME);
return false;
}
if (!hasUpServer(getSourceCluster())) {
return false;
}
}
if (getTargetCluster().supportsGlusterService() && !hasUpServerInTarget(getTargetCluster())) {
return false;
}
vds.setCpuName(getCpuFlagsManagerHandler().
findMaxServerCpuByFlags(vds.getCpuFlags(), getTargetCluster().getCompatibilityVersion()));
// CPU flags are null if oVirt node cluster is changed during approve process.
if (getTargetCluster().supportsVirtService() && !StringUtils.isEmpty(vds.getCpuFlags())) {
if (vds.getCpuName() == null) {
return failValidation(EngineMessage.CPU_TYPE_UNSUPPORTED_IN_THIS_CLUSTER_VERSION);
}
if (getTargetCluster().getArchitecture() != ArchitectureType.undefined &&
getTargetCluster().getArchitecture() != vds.getCpuName().getArchitecture()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VDS_CLUSTER_DIFFERENT_ARCHITECTURES);
}
}
if (!(VDSStatus.PendingApproval == vds.getStatus() || isDetachedSourceCluster() || isSameManagementNetwork())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_HOST_CLUSTER_DIFFERENT_MANAGEMENT_NETWORKS);
}
return true;
}
private boolean isDetachedSourceCluster() {
return getSourceCluster().getStoragePoolId() == null;
}
private boolean isSameManagementNetwork() {
final Network sourceManagementNetwork = networkDao.getManagementNetwork(getSourceCluster().getId());
final Network targetManagementNetwork = networkDao.getManagementNetwork(getTargetCluster().getId());
return targetManagementNetwork != null
&& sourceManagementNetwork.getName().equals(targetManagementNetwork.getName());
}
private List<VdsNetworkInterface> getHostNics() {
if (hostNics == null) {
hostNics = interfaceDao.getAllInterfacesForVds(getVdsId());
}
return hostNics;
}
private boolean hasUpServer(Cluster cluster) {
if (getClusterUtils().hasMultipleServers(cluster.getId())
&& glusterUtil.getUpServer(cluster.getId()) == null) {
addNoUpServerMessage(cluster);
return false;
}
return true;
}
private void addNoUpServerMessage(Cluster cluster) {
addValidationMessageVariable("clusterName", cluster.getName());
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_NO_UP_SERVER_FOUND);
}
private boolean hasUpServerInTarget(Cluster cluster) {
if (getClusterUtils().hasServers(cluster.getId())
&& glusterUtil.getUpServer(cluster.getId()) == null) {
addNoUpServerMessage(cluster);
return false;
}
return true;
}
@Override
protected void executeCommand() {
final Guid targetClusterId = getParameters().getClusterId();
if (getSourceCluster().getId().equals(targetClusterId)) {
setSucceeded(true);
return;
}
// save the new cluster id
TransactionSupport.executeInNewTransaction(() -> {
VdsStatic staticData = getVds().getStaticData();
getCompensationContext().snapshotEntity(staticData);
staticData.setClusterId(targetClusterId);
vdsStaticDao.update(staticData);
getCompensationContext().stateChanged();
// remove the server from resource manager and add it back
initializeVds();
return null;
});
if (targetStoragePool != null
&& (getSourceCluster().getStoragePoolId()== null || !targetStoragePool.getId().equals(getSourceCluster().getStoragePoolId()))) {
VdsActionParameters addVdsSpmIdParams = new VdsActionParameters(getVdsIdRef());
addVdsSpmIdParams.setSessionId(getParameters().getSessionId());
addVdsSpmIdParams.setCompensationEnabled(true);
VdcReturnValueBase addVdsSpmIdReturn =
runInternalAction(VdcActionType.AddVdsSpmId,
addVdsSpmIdParams, cloneContext().withoutLock().withoutExecutionContext());
if (!addVdsSpmIdReturn.getSucceeded()) {
setSucceeded(false);
getReturnValue().setFault(addVdsSpmIdReturn.getFault());
return;
}
}
if (getSourceCluster().supportsGlusterService() && getClusterUtils().hasServers(getSourceCluster().getId())) {
if (!glusterHostRemove(getSourceCluster().getId())) {
setSucceeded(false);
return;
}
}
if (getTargetCluster().supportsGlusterService()
&& getClusterUtils().hasMultipleServers(getTargetCluster().getId())) {
if (!glusterHostAdd(getTargetCluster().getId())) {
setSucceeded(false);
return;
}
}
if (getSourceCluster().getStoragePoolId() != null
&& (targetStoragePool== null || !getSourceCluster().getStoragePoolId().equals(targetStoragePool.getId()))) {
vdsSpmIdMapDao.removeByVdsAndStoragePool(getVds().getId(), getSourceCluster().getStoragePoolId());
}
HostNetworkAttachmentsPersister persister = new HostNetworkAttachmentsPersister(this.networkAttachmentDao,
getVdsId(),
interfaceDao.getAllInterfacesForVds(getVdsId()),
Collections.emptyList(),
getTargetClusterNetworks());
persister.persistNetworkAttachments();
if (VDSStatus.PendingApproval != getVds().getStatus()) {
configureNetworks();
}
setSucceeded(true);
}
private List<Network> getTargetClusterNetworks() {
if (targetClusterNetworks == null) {
targetClusterNetworks = networkDao.getAllForCluster(getTargetCluster().getId());
}
return targetClusterNetworks;
}
private void configureNetworks() {
final PersistentHostSetupNetworksParameters params;
try {
params = changeClusterParametersBuilder.buildParameters(getVdsId(), getSourceCluster().getId(), getTargetCluster().getId());
} catch (EngineException e) {
auditLogDirector.log(this,
AuditLogType.CONFIGURE_NETWORK_BY_LABELS_WHEN_CHANGING_CLUSTER_FAILED);
return;
}
ThreadPoolUtil.execute(() -> runInternalAction(
VdcActionType.PersistentHostSetupNetworks,
params,
cloneContextAndDetachFromParent()));
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getSucceeded() ? AuditLogType.USER_UPDATE_VDS : errorType;
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
List<PermissionSubject> permissionList = new ArrayList<>();
VdsDynamic vdsDynamic = getVds().getDynamicData();
// If the state of the host is PendingApproval then we just check if the user has a permission on the destination cluster
// Otherwise we require adding this permission both on the host and on the cluster, and it is not really needed
// in order to approve a host
if (vdsDynamic != null && !VDSStatus.PendingApproval.equals(vdsDynamic.getStatus())) {
permissionList.add(new PermissionSubject(getParameters().getVdsId(), VdcObjectType.VDS, getActionType().getActionGroup()));
}
permissionList.add(new PermissionSubject(getParameters().getClusterId(), VdcObjectType.Cluster, getActionType().getActionGroup()));
List<PermissionSubject> unmodifiableList = Collections.unmodifiableList(permissionList);
return unmodifiableList;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__UPDATE);
addValidationMessage(EngineMessage.VAR__TYPE__HOST);
}
private boolean glusterHostRemove(Guid sourceClusterId) {
// If "gluster peer detach" and "gluster peer status" are executed simultaneously, the results
// are unpredictable. Hence locking the cluster to ensure the sync job does not lead to race
// condition.
try (EngineLock lock = glusterUtil.acquireGlusterLockWait(sourceClusterId)) {
VDS runningHostInSourceCluster = glusterUtil.getUpServer(sourceClusterId);
if (runningHostInSourceCluster == null) {
log.error("Cannot remove host from source cluster, no host in Up status found in source cluster");
handleError(-1, "No host in Up status found in source cluster");
errorType = AuditLogType.GLUSTER_SERVER_REMOVE_FAILED;
return false;
}
VDSReturnValue returnValue =
runVdsCommand(
VDSCommandType.RemoveGlusterServer,
new RemoveGlusterServerVDSParameters(runningHostInSourceCluster.getId(),
getVds().getHostName(),
false));
if (!returnValue.getSucceeded()) {
handleVdsError(returnValue);
errorType = AuditLogType.GLUSTER_SERVER_REMOVE_FAILED;
return false;
}
return true;
}
}
private boolean glusterHostAdd(Guid targetClusterId) {
// If "gluster peer probe" and "gluster peer status" are executed simultaneously, the results
// are unpredictable. Hence locking the cluster to ensure the sync job does not lead to race
// condition.
try (EngineLock lock = glusterUtil.acquireGlusterLockWait(targetClusterId)) {
VDS runningHostInTargetCluster = glusterUtil.getUpServer(targetClusterId);
if (runningHostInTargetCluster == null) {
log.error("Cannot add host to target cluster, no host in Up status found in target cluster");
handleError(-1, "No host in Up status found in target cluster");
errorType = AuditLogType.GLUSTER_SERVER_ADD_FAILED;
return false;
}
VDSReturnValue returnValue =
runVdsCommand(
VDSCommandType.AddGlusterServer,
new AddGlusterServerVDSParameters(runningHostInTargetCluster.getId(),
getVds().getHostName()));
if (!returnValue.getSucceeded()) {
handleVdsError(returnValue);
errorType = AuditLogType.GLUSTER_SERVER_ADD_FAILED;
return false;
}
return true;
}
}
private void handleError(int errorCode, String errorMsg) {
getReturnValue().getFault().setError(errorCode);
getReturnValue().getFault().setMessage(errorMsg);
getReturnValue().getExecuteFailedMessages().add(errorMsg);
}
private ClusterUtils getClusterUtils() {
return ClusterUtils.getInstance();
}
private Cluster getSourceCluster() {
return getCluster();
}
private Cluster getTargetCluster() {
if (targetCluster == null) {
targetCluster = clusterDao.get(getParameters().getClusterId());
}
return targetCluster;
}
private static class ChangeClusterParametersBuilder extends HostSetupNetworksParametersBuilder {
@Inject
NetworkDao networkDao;
@Inject
public ChangeClusterParametersBuilder(InterfaceDao interfaceDao,
VdsStaticDao vdsStaticDao,
NetworkClusterDao networkClusterDao,
NetworkAttachmentDao networkAttachmentDao) {
super(interfaceDao, vdsStaticDao, networkClusterDao, networkAttachmentDao);
}
public PersistentHostSetupNetworksParameters buildParameters(Guid hostId,
Guid sourceClusterId,
Guid targetClusterId) {
List<Network> targetClusterNetworks = networkDao.getAllForCluster(targetClusterId);
Map<String, Network> targetClusterNetworksByName = Entities.entitiesByName(targetClusterNetworks);
PersistentHostSetupNetworksParameters params = createHostSetupNetworksParameters(hostId);
Map<String, VdsNetworkInterface> nicsByNetwork =
NetworkUtils.hostInterfacesByNetworkName(getNics(hostId));
Map<String, List<Network>> targetNetworksByLabel = getClusterNetworksByLabel(targetClusterNetworks);
Map<String, List<Network>> sourceNetworksByLabel =
getClusterNetworksByLabel(networkDao.getAllForCluster(sourceClusterId));
// Detect which networks should be added and which should be removed
for (VdsNetworkInterface nic : getNics(hostId)) {
adjustNetworksByLabel(sourceNetworksByLabel,
targetClusterNetworksByName,
targetNetworksByLabel,
params,
nicsByNetwork,
nic);
}
return params;
}
/**
* Add or remove labeled networks from a nic by the assignment of networks to the target cluster:
* <ul>
* <li>Assigned labeled network will be defined on the nic if isn't already</li>
* <li>Unassigned labeled network will be removed from a nic if isn't already</li>
* </ul>
*
* @param sourceNetworksByLabel
* a map of the labeled source cluster networks by their label
* @param targetNetworksByName
* a map of the destination cluster networks by their name
* @param targetNetworksByLabel
* a map of the labeled target cluster networks by their label
* @param params
* the setup networks parameters to be adjusted according to the findings
* @param nicsByNetwork
* a map of nics by the network attached to them
* @param nic
* the current examined network interface
*/
private void adjustNetworksByLabel(Map<String, List<Network>> sourceNetworksByLabel,
Map<String, Network> targetNetworksByName,
Map<String, List<Network>> targetNetworksByLabel,
PersistentHostSetupNetworksParameters params,
Map<String, VdsNetworkInterface> nicsByNetwork,
VdsNetworkInterface nic) {
if (!NetworkUtils.isLabeled(nic)) {
return;
}
for (String label : nic.getLabels()) {
removeNetworksNoLongerAttachedViaLabel(sourceNetworksByLabel,
targetNetworksByName,
params,
nicsByNetwork,
nic,
label);
addNetworksThatShouldBeAttachedViaLabel(targetNetworksByName,
targetNetworksByLabel,
params,
nicsByNetwork,
nic,
label);
}
}
private void addNetworksThatShouldBeAttachedViaLabel(Map<String, Network> targetNetworksByName,
Map<String, List<Network>> targetNetworksByLabel,
PersistentHostSetupNetworksParameters params,
Map<String, VdsNetworkInterface> nicsByNetwork,
VdsNetworkInterface nic,
String label) {
// configure networks by target cluster assignment
List<Network> targetLabeledNetworks = targetNetworksByLabel.get(label);
if (targetLabeledNetworks != null) {
for (Network net : targetLabeledNetworks) {
if (targetNetworksByName.containsKey(net.getName())
&& !isNetworkAssignedToNic(nic, net.getName(), nicsByNetwork)) {
addAttachmentToParameters(nic, net, params);
}
}
}
}
private void removeNetworksNoLongerAttachedViaLabel(Map<String, List<Network>> sourceNetworksByLabel,
Map<String, Network> targetNetworksByName,
PersistentHostSetupNetworksParameters params,
Map<String, VdsNetworkInterface> nicsByNetwork,
VdsNetworkInterface nic,
String label) {
List<Network> sourceLabeledNetworks = sourceNetworksByLabel.get(label);
if (sourceLabeledNetworks != null) {
for (Network sourceLabeledNetwork : sourceLabeledNetworks) {
String networkName = sourceLabeledNetwork.getName();
if (isNetworkAssignedToNic(nic, networkName, nicsByNetwork)) {
// The network was attached to the nic via label (in the source cluster)
if (!isNetworkAssignedToTargetCluster(targetNetworksByName, networkName)) {
// the network is not attached to the target cluster- should be removed from the host
params.getRemovedUnmanagedNetworks().add(networkName);
} else if (!isNetworkLabelExistInTargetHost(nic.getVdsId(), networkName, targetNetworksByName)) {
// the target network doesn't have label that exist on the host
Network targetNetwork = targetNetworksByName.get(networkName);
NetworkAttachment attachment = getNetworkIdToAttachmentMap(nic.getVdsId()).get(targetNetwork.getId());
params.getRemovedNetworkAttachments().add(attachment.getId());
}
}
}
}
}
private boolean isNetworkAssignedToTargetCluster(Map<String, Network> targetNetworksByName,
String networkName) {
return targetNetworksByName.containsKey(networkName);
}
private boolean isNetworkAssignedToNic(VdsNetworkInterface nic,
String networkName,
Map<String, VdsNetworkInterface> nicsByNetwork) {
VdsNetworkInterface nicToWhichNetworkIsAssigned = nicsByNetwork.get(networkName);
String baseNicNameToWhichNetworkIsAssigned =
nicToWhichNetworkIsAssigned == null ? null : NetworkCommonUtils.stripVlan(nicToWhichNetworkIsAssigned);
return nic.getName().equals(baseNicNameToWhichNetworkIsAssigned);
}
private boolean isNetworkLabelExistInTargetHost(Guid hostId, String networkName, Map<String, Network> targetNetworksByName) {
Network targetNetwork = targetNetworksByName.get(networkName);
return NetworkUtils.isLabeled(targetNetwork) && isHostContainLabel(hostId, targetNetwork.getLabel());
}
private boolean isHostContainLabel(Guid hostId, String label) {
for (VdsNetworkInterface nic : getNics(hostId)) {
if (NetworkUtils.isLabeled(nic) && nic.getLabels().contains(label)) {
return true;
}
}
return false;
}
/**
* Returns a map of labels to the cluster networks represented by that label.
*/
private Map<String, List<Network>> getClusterNetworksByLabel(List<Network> clusterNetworks) {
Map<String, List<Network>> networksByLabel = new HashMap<>();
for (Network network : clusterNetworks) {
if (NetworkUtils.isLabeled(network)) {
if (!networksByLabel.containsKey(network.getLabel())) {
networksByLabel.put(network.getLabel(), new ArrayList<>());
}
networksByLabel.get(network.getLabel()).add(network);
}
}
return networksByLabel;
}
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(LockProperties.Scope.Execution);
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
Map<String, Pair<String, String>> locks = new HashMap<>();
locks.put(getParameters().getVdsId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VDS,
EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
return locks;
}
}