package org.ovirt.engine.core.bll;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.network.cluster.NetworkHelper;
import org.ovirt.engine.core.bll.network.macpool.ReadMacPool;
import org.ovirt.engine.core.bll.profiles.CpuProfileHelper;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.ChangeVMClusterParameters;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.VmNic;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.scheduling.AffinityGroup;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.network.NetworkDao;
import org.ovirt.engine.core.dao.network.VmNicDao;
import org.ovirt.engine.core.dao.scheduling.AffinityGroupDao;
import org.ovirt.engine.core.utils.ObjectIdentityChecker;
public class ChangeVMClusterCommand<T extends ChangeVMClusterParameters> extends VmCommand<T> {
@Inject
private CpuProfileHelper cpuProfileHelper;
@Inject
private MoveMacs moveMacs;
@Inject
private ClusterDao clusterDao;
@Inject
private NetworkDao networkDao;
@Inject
private VmNicDao vmNicDao;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private AffinityGroupDao affinityGroupDao;
private boolean dedicatedHostWasCleared;
private Guid originalClusterId;
private Guid newClusterId;
private Guid sourceMacPoolId;
private Guid targetMacPoolId;
//cached value.
private List<String> macsToMigrate;
public ChangeVMClusterCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
@Override
protected void init() {
super.init();
originalClusterId = getVm().getClusterId();
newClusterId = getParameters().getClusterId();
sourceMacPoolId = getMacPoolId(originalClusterId);
targetMacPoolId = getMacPoolId(newClusterId);
}
private Guid getMacPoolId(Guid clusterId) {
return Optional.ofNullable(clusterDao.get(clusterId)).map(Cluster::getMacPoolId).orElse(null);
}
@Override
protected boolean validate() {
if (!canRunActionOnNonManagedVm()) {
return false;
}
if (!isInternalExecution() && !ObjectIdentityChecker.canUpdateField(getVm(), "clusterId", getVm().getStatus())) {
addValidationMessage(EngineMessage.VM_STATUS_NOT_VALID_FOR_UPDATE);
return false;
}
ChangeVmClusterValidator validator = ChangeVmClusterValidator.create(this,
newClusterId,
getParameters().getVmCustomCompatibilityVersion());
if (macPoolChanged()) {
ReadMacPool macPoolForTargetCluster = macPoolPerCluster.getMacPoolForCluster(newClusterId);
ValidationResult validationResult =
validator.validateCanMoveMacs(macPoolForTargetCluster, getMacsToMigrate());
if (!validationResult.isValid()) {
return validate(validationResult);
}
}
return validator.validate();
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__UPDATE);
addValidationMessage(EngineMessage.VAR__TYPE__VM__CLUSTER);
}
@Override
protected void executeCommand() {
VM vm = getVm();
boolean clusterRemainedTheSame = originalClusterId.equals(newClusterId);
if (clusterRemainedTheSame) {
setSucceeded(true);
return;
}
// update vm interfaces
List<Network> networks = networkDao.getAllForCluster(newClusterId);
List<VmNic> interfaces = vmNicDao.getAllForVm(getParameters().getVmId());
for (final VmNic iface : interfaces) {
if (iface.getVnicProfileId() != null) {
final Network network = NetworkHelper.getNetworkByVnicProfileId(iface.getVnicProfileId());
boolean networkFoundInCluster =
networks.stream().anyMatch(n -> Objects.equals(n.getId(), network.getId()));
// if network not exists in cluster we remove the network to
// interface connection
if (!networkFoundInCluster) {
iface.setVnicProfileId(null);
vmNicDao.update(iface);
}
}
}
if (vm.getDedicatedVmForVdsList().size() > 0) {
vm.setDedicatedVmForVdsList(Collections.emptyList());
dedicatedHostWasCleared = true;
}
vm.setClusterId(newClusterId);
// Set cpu profile from the new cluster
cpuProfileHelper.assignFirstCpuProfile(vm.getStaticData(), getUserIdIfExternal().orElse(null));
vmStaticDao.update(vm.getStaticData());
moveMacsToAnotherMacPoolIfNeeded();
// change vm cluster should remove the vm from all associated affinity groups
List<AffinityGroup> allAffinityGroupsByVmId = affinityGroupDao.getAllAffinityGroupsByVmId(vm.getId());
if (!allAffinityGroupsByVmId.isEmpty()) {
String groups = allAffinityGroupsByVmId.stream().map(AffinityGroup::getName).collect(Collectors.joining(" "));
log.info("Due to cluster change, removing VM from associated affinity group(s): {}", groups);
affinityGroupDao.removeVmFromAffinityGroups(vm.getId());
}
setSucceeded(true);
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getSucceeded() ?
dedicatedHostWasCleared ?
AuditLogType.USER_UPDATE_VM_CLUSTER_DEFAULT_HOST_CLEARED
: AuditLogType.USER_UPDATE_VM
: AuditLogType.USER_FAILED_UPDATE_VM;
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
return VmHandler.getPermissionsNeededToChangeCluster(getParameters().getVmId(), newClusterId);
}
void moveMacsToAnotherMacPoolIfNeeded() {
if (macPoolChanged()) {
moveMacs.migrateMacsToAnotherMacPool(sourceMacPoolId,
targetMacPoolId,
getMacsToMigrate(),
true,
getContext());
}
}
private boolean macPoolChanged() {
return !Objects.equals(sourceMacPoolId, targetMacPoolId);
}
private List<String> getMacsToMigrate() {
if (this.macsToMigrate == null) {
this.macsToMigrate = calculateMacsToMigrate();
}
return this.macsToMigrate;
}
private List<String> calculateMacsToMigrate() {
List<VmNic> vmNicsForVm = vmNicDao.getAllForVm(getVm().getId());
return vmNicsForVm.stream().map(VmNic::getMacAddress).collect(Collectors.toList());
}
}