package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VmOperationParameterBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskCreationInfo;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskParameters;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
import org.ovirt.engine.core.common.businessentities.AsyncTaskResultEnum;
import org.ovirt.engine.core.common.businessentities.AsyncTaskStatusEnum;
import org.ovirt.engine.core.common.businessentities.DiskImage;
import org.ovirt.engine.core.common.businessentities.DiskImageBase;
import org.ovirt.engine.core.common.businessentities.DiskInterface;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmInterfaceType;
import org.ovirt.engine.core.common.businessentities.VmNetworkInterface;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.async_tasks;
import org.ovirt.engine.core.common.businessentities.tags;
import org.ovirt.engine.core.common.businessentities.tags_vm_map;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.vdscommands.DeleteImageGroupVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.RemoveVMVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.UpdateVMVDSCommandParameters;
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.compat.KeyValuePairCompat;
import org.ovirt.engine.core.compat.LogCompat;
import org.ovirt.engine.core.compat.LogFactoryCompat;
import org.ovirt.engine.core.compat.RefObject;
import org.ovirt.engine.core.compat.StringHelper;
import org.ovirt.engine.core.dal.VdcBllMessages;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.utils.linq.Function;
import org.ovirt.engine.core.utils.linq.LinqUtils;
import org.ovirt.engine.core.utils.linq.Predicate;
import org.ovirt.engine.core.utils.ovf.OvfManager;
import org.ovirt.engine.core.utils.vmproperties.VmPropertiesUtils;
import org.ovirt.engine.core.utils.vmproperties.VmPropertiesUtils.ValidationError;
import org.ovirt.engine.core.utils.vmproperties.VmPropertiesUtils.ValidationFailureReason;
public abstract class VmCommand<T extends VmOperationParameterBase> extends CommandBase<T> {
protected final static int MAX_NETWORK_INTERFACES_SUPPORTED = 8;
private static final Map<VmPropertiesUtils.ValidationFailureReason, String> failureReasonsToVdcBllMessagesMap =
new HashMap<VmPropertiesUtils.ValidationFailureReason, String>();
private static final Map<VmPropertiesUtils.ValidationFailureReason, String> failureReasonsToFormatMessages =
new HashMap<VmPropertiesUtils.ValidationFailureReason, String>();
static {
failureReasonsToVdcBllMessagesMap.put(ValidationFailureReason.DUPLICATE_KEY,
VdcBllMessages.ACTION_TYPE_FAILED_INVALID_CUSTOM_VM_PROPERTIES_DUPLICATE_KEYS.name());
failureReasonsToVdcBllMessagesMap.put(ValidationFailureReason.KEY_DOES_NOT_EXIST,
VdcBllMessages.ACTION_TYPE_FAILED_INVALID_CUSTOM_VM_PROPERTIES_INVALID_KEYS.name());
failureReasonsToVdcBllMessagesMap.put(ValidationFailureReason.INCORRECT_VALUE,
VdcBllMessages.ACTION_TYPE_FAILED_INVALID_CUSTOM_VM_PROPERTIES_INVALID_VALUES.name());
failureReasonsToFormatMessages.put(ValidationFailureReason.DUPLICATE_KEY, "$DuplicateKeys %1$s");
failureReasonsToFormatMessages.put(ValidationFailureReason.KEY_DOES_NOT_EXIST, "$MissingKeys %1$s");
failureReasonsToFormatMessages.put(ValidationFailureReason.INCORRECT_VALUE, "$WrongValueKeys %1$s");
}
public VmCommand(T parameters) {
super(parameters);
}
/**
* Constructor for command creation when compensation is applied on startup
*
* @param commandId
*/
protected VmCommand(Guid commandId) {
super(commandId);
}
public VmCommand() {
}
@Override
protected void executeCommand() {
ExecuteVmCommand();
}
protected void ExecuteVmCommand() {
}
@Override
protected String getDescription() {
return getVmName();
}
@Override
protected List<tags> GetTagsAttachedToObject() {
return DbFacade
.getInstance()
.getTagDAO()
.getAllForVm((getParameters()).getVmId().toString());
}
// 26 PCI slots: 31 total minus 5 saved for qemu (Host Bridge, ISA Bridge,
// IDE, Agent, ACPI)
private final static int MAX_PCI_SLOTS = 26;
// 3 IDE slots: 4 total minus 1 for CD
private final static int MAX_IDE_SLOTS = 3;
/**
* This method checks that with the given parameters, the max PCI and IDE limits defined are not passed.
*
* @param monitorsNumber
* @param interfaces
* @param disks
* @return
*/
public static boolean CheckPCIAndIDELimit(int monitorsNumber, List<VmNetworkInterface> interfaces,
List<DiskImageBase> disks, ArrayList<String> messages) {
boolean result = true;
// this adds: monitors + 2 * (interfaces with type rtl_pv) + (all other
// interfaces) + (all disks that are not IDE)
// LINQ 29456
// int pciInUse = monitorsNumber + interfaces.Select(a =>
// (a.type.HasValue &&
// (VmInterfaceType)a.type.Value == VmInterfaceType.rtl8139_pv) ? 2 :
// 1).Sum() +
// disks.Where(a => a.disk_interface != DiskInterface.IDE).Count();
int pciInUse = monitorsNumber;
for (VmNetworkInterface a : interfaces) {
if (a.getType() != null && VmInterfaceType.forValue(a.getType()) == VmInterfaceType.rtl8139_pv)
pciInUse += 2;
else
pciInUse += 1;
}
pciInUse += LinqUtils.filter(disks, new Predicate<DiskImageBase>() {
@Override
public boolean eval(DiskImageBase a) {
return a.getdisk_interface() != DiskInterface.IDE;
}
}).size();
// LINQ 29456
if (pciInUse > MAX_PCI_SLOTS) {
result = false;
messages.add(VdcBllMessages.ACTION_TYPE_FAILED_EXCEEDED_MAX_PCI_SLOTS.name());
}
// LINQ 29456
// else if (disks.Where(a => a.disk_interface ==
// DiskInterface.IDE).Count() > MAX_IDE_SLOTS)
else if (MAX_IDE_SLOTS < LinqUtils.filter(disks, new Predicate<DiskImageBase>() {
@Override
public boolean eval(DiskImageBase a) {
return a.getdisk_interface() == DiskInterface.IDE;
}
}).size()) {
result = false;
messages.add(VdcBllMessages.ACTION_TYPE_FAILED_EXCEEDED_MAX_IDE_SLOTS.name());
}
return result;
}
/**
* This method create OVF for each vm in list and call updateVm in SPM
*
* @param storagePoolId
* @param vmsList
* @return Returns true if updateVm succeeded.
*/
public static boolean UpdateVmInSpm(Guid storagePoolId, List<VM> vmsList) {
return UpdateVmInSpm(storagePoolId, vmsList, Guid.Empty);
}
public static boolean UpdateVmInSpm(Guid storagePoolId, List<VM> vmsList, Guid storageDomainId) {
java.util.HashMap<Guid, KeyValuePairCompat<String, List<Guid>>> vmsAndMetaDictionary =
new java.util.HashMap<Guid, KeyValuePairCompat<String, List<Guid>>>(
vmsList.size());
OvfManager ovfManager = new OvfManager();
for (VM vm : vmsList) {
java.util.ArrayList<DiskImage> AllVmImages = new java.util.ArrayList<DiskImage>();
VmHandler.updateDisksFromDb(vm);
if (vm.getInterfaces() == null || vm.getInterfaces().isEmpty()) {
vm.setInterfaces(DbFacade.getInstance().getVmNetworkInterfaceDAO().getAllForVm(vm.getvm_guid()));
}
for (DiskImage disk : vm.getDiskMap().values()) {
AllVmImages.addAll(ImagesHandler.getAllImageSnapshots(disk.getId(), disk.getit_guid()));
}
if (StringHelper.isNullOrEmpty(vm.getvmt_name())) {
VmTemplate t = DbFacade.getInstance().getVmTemplateDAO().get(vm.getvmt_guid());
vm.setvmt_name(t.getname());
}
String vmMeta = "";
// OVF Uncomment next line when OVF support is added
RefObject<String> tempRefObject = new RefObject<String>(vmMeta);
ovfManager.ExportVm(tempRefObject, vm, AllVmImages);
vmMeta = tempRefObject.argvalue;
// LINQ 29456
// vmsAndMetaDictionary.Add(vm.vm_guid, new KeyValuePair<string,
// List<Guid>>
// (vmMeta, vm.DiskMap.Values.Select(a =>
// a.image_group_id.Value).ToList()));
vmsAndMetaDictionary.put(
vm.getvm_guid(),
new KeyValuePairCompat<String, List<Guid>>(vmMeta, LinqUtils.foreach(vm.getDiskMap().values(),
new Function<DiskImage, Guid>() {
@Override
public Guid eval(DiskImage a) {
return a.getimage_group_id().getValue();
}
})));
}
UpdateVMVDSCommandParameters tempVar = new UpdateVMVDSCommandParameters(storagePoolId, vmsAndMetaDictionary);
tempVar.setStorageDomainId(storageDomainId);
return Backend.getInstance().getResourceManager().RunVdsCommand(VDSCommandType.UpdateVM, tempVar)
.getSucceeded();
}
protected boolean RemoveVmInSpm(Guid storagePoolId, Guid vmID) {
return RemoveVmInSpm(storagePoolId, vmID, Guid.Empty);
}
protected boolean RemoveVmInSpm(Guid storagePoolId, Guid vmID, Guid storageDomainId) {
return runVdsCommand(VDSCommandType.RemoveVM,
new RemoveVMVDSCommandParameters(storagePoolId, vmID, storageDomainId)).getSucceeded();
}
protected void RemoveVmStatic() {
DbFacade.getInstance().getVmStaticDAO().remove(getVmId());
}
protected void RemoveVmNetwork() {
List<VmNetworkInterface> interfaces = DbFacade.getInstance().getVmNetworkInterfaceDAO()
.getAllForVm(getVmId());
if (interfaces != null) {
for (VmNetworkInterface iface : interfaces) {
MacPoolManager.getInstance().freeMac(iface.getMacAddress());
// \\DbFacade.Instance.RemoveVmInterfaceById(iface.id);
// \\DbFacade.Instance.RemoveInterfaceStatistics(iface.id);
}
}
}
protected void RemoveVmDynamic() {
DbFacade.getInstance().getVmDynamicDAO().remove(getVmId());
}
protected void RemoveVmStatistics() {
DbFacade.getInstance().getVmStatisticsDAO().remove(getVmId());
}
protected void RemoveVmUsers() {
List<tags_vm_map> all = DbFacade.getInstance().getTagDAO().getTagVmMapByVmIdAndDefaultTag(getVmId());
for (tags_vm_map tagVm : all) {
DbFacade.getInstance().getTagDAO().detachVmFromTag(tagVm.gettag_id(), getVmId());
}
}
protected void EndVmCommand() {
EndActionOnDisks();
if (getVm() != null) {
VmHandler.unlockVm(getVm().getDynamicData(), getCompensationContext());
UpdateVmInSpm(getVm().getstorage_pool_id(),
new java.util.ArrayList<VM>(java.util.Arrays.asList(new VM[] { getVm() })));
}
else {
setCommandShouldBeLogged(false);
log.warn("VmCommand::EndVmCommand: Vm is null - not performing EndAction on Vm");
}
setSucceeded(true);
}
protected void EndActionOnDisks() {
for (VdcActionParametersBase p : getParameters().getImagesParameters()) {
Backend.getInstance().EndAction(getChildActionType(), p);
}
}
@Override
protected void EndSuccessfully() {
EndVmCommand();
}
@Override
protected void EndWithFailure() {
EndVmCommand();
}
protected VdcActionType getChildActionType() {
return VdcActionType.Unknown;
}
protected boolean HandleHibernatedVm(VdcActionType parentCommand, boolean startPollingTasks) {
// this is temp code until it will be implmented in SPM
// LINQ 29456
// Guid[] imagesList = Vm.hibernation_vol_handle.Split(',').Select(a =>
// new Guid(a)).ToArray();
String[] strings = getVm().gethibernation_vol_handle().split(",");
List<Guid> guids = new LinkedList<Guid>();
for (String string : strings) {
guids.add(new Guid(string));
}
Guid[] imagesList = guids.toArray(new Guid[0]);
if (imagesList.length == 6) {
// get all vm disks in order to check post zero - if one of the
// disks is marked with wipe_after_delete
// boolean postZero = false; //LINQ
// DbFacade.Instance.GetImagesByVmGuid(Vm.vm_guid).Exists(a =>
// a.wipe_after_delete);
boolean postZero =
LinqUtils.filter(DbFacade.getInstance().getDiskImageDAO().getAllForVm(getVm().getvm_guid()),
new Predicate<DiskImage>() {
@Override
public boolean eval(DiskImage diskImage) {
return diskImage.getwipe_after_delete();
}
}).size() > 0;
// delete first image
// the next 'DeleteImageGroup' command should also take care of the
// image removal:
VDSReturnValue vdsRetValue1 = Backend
.getInstance()
.getResourceManager()
.RunVdsCommand(
VDSCommandType.DeleteImageGroup,
new DeleteImageGroupVDSCommandParameters(imagesList[1], imagesList[0], imagesList[2],
postZero, false, getVm().getvds_group_compatibility_version().toString()));
if (!vdsRetValue1.getSucceeded()) {
return false;
}
Guid guid1 = CreateTask(vdsRetValue1.getCreationInfo(), parentCommand);
getTaskIdList().add(guid1);
// delete second image
// the next 'DeleteImageGroup' command should also take care of the
// image removal:
VDSReturnValue vdsRetValue2 = Backend
.getInstance()
.getResourceManager()
.RunVdsCommand(
VDSCommandType.DeleteImageGroup,
new DeleteImageGroupVDSCommandParameters(imagesList[1], imagesList[0], imagesList[4],
postZero, false, getVm().getvds_group_compatibility_version().toString()));
if (!vdsRetValue2.getSucceeded()) {
if (startPollingTasks) {
UpdateTasksWithActionParameters();
AsyncTaskManager.getInstance().StartPollingTask(guid1);
}
return false;
}
Guid guid2 = CreateTask(vdsRetValue2.getCreationInfo(), parentCommand);
getTaskIdList().add(guid2);
if (startPollingTasks) {
UpdateTasksWithActionParameters();
AsyncTaskManager.getInstance().StartPollingTask(guid1);
AsyncTaskManager.getInstance().StartPollingTask(guid2);
}
}
return true;
}
@Override
protected Guid ConcreteCreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) {
AsyncTaskParameters p = new AsyncTaskParameters(asyncTaskCreationInfo, new async_tasks(parentCommand,
AsyncTaskResultEnum.success, AsyncTaskStatusEnum.running, asyncTaskCreationInfo.getTaskID(),
getParameters()));
p.setEntityId(getParameters().getEntityId());
Guid taskID = AsyncTaskManager.getInstance().CreateTask(AsyncTaskType.deleteImage, p, false);
return taskID;
}
private static LogCompat log = LogFactoryCompat.getLog(VmCommand.class);
@Override
public Map<Guid, VdcObjectType> getPermissionCheckSubjects() {
return Collections.singletonMap(getParameters().getVmId(), VdcObjectType.VM);
}
protected int getNeededDiskSize() {
return getBlockSparseInitSizeInGB() * getVmTemplate().getDiskMap().size();
}
protected int getBlockSparseInitSizeInGB() {
return Config.<Integer> GetValue(ConfigValues.InitStorageSparseSizeInGB).intValue();
}
protected static void handleCustomPropertiesError(List<ValidationError> validationErrors, ArrayList<String> message) {
String invalidSyntaxMsg = VdcBllMessages.ACTION_TYPE_FAILED_INVALID_CUSTOM_VM_PROPERTIES_INVALID_SYNTAX.name();
List<String> errorMessages = VmPropertiesUtils.generateErrorMessages(validationErrors, invalidSyntaxMsg,
failureReasonsToVdcBllMessagesMap, failureReasonsToFormatMessages);
message.addAll(errorMessages);
}
/**
* Checks if VM name has valid length (check that it's too long). This is used for validation by descending
* commands.
*
* @param vm
* the VM to check
* @return true if the name is valid; false if it's too long
*/
protected boolean isVmNameValidLength(VM vm) {
// get VM name
String vmName = vm.getvm_name();
// get the max VM name (configuration parameter)
int maxVmNameLengthWindows = Config.<Integer> GetValue(ConfigValues.MaxVmNameLengthWindows);
int maxVmNameLengthNonWindows = Config.<Integer> GetValue(ConfigValues.MaxVmNameLengthNonWindows);
// names are allowed different lengths in Windows and non-Windows OSs,
// consider this when setting the max length.
int maxLength = vm.getvm_os().isWindows() ? maxVmNameLengthWindows : maxVmNameLengthNonWindows;
// check if name is longer than allowed name
boolean nameLengthValid = (vmName.length() <= maxLength);
// return result
return nameLengthValid;
}
/**
* Lock the VM.<br>
* If the command is run internally then compensation won't be used, since it might cause a deadlock if the calling
* command has already updated the VM's row in the DB but hasn't committed before calling the child command.<br>
* Otherwise, compensation will be used, and the VM will be locked in a new transaction, so that the lock gets
* reflected in the DB immediately.
*/
protected void lockVmWithCompensationIfNeeded() {
log.infoFormat("Locking VM(id = {0}) {1} compensation.", getVmId(), isInternalExecution() ? "without" : "with");
if (isInternalExecution()) {
VmHandler.checkStatusAndLockVm(getVmId());
} else {
VmHandler.checkStatusAndLockVm(getVmId(), getCompensationContext());
}
}
/**
* check that we number of Network-Interfaces does not exceed maximum (kvm limitation). This limitation is different
* for RHEL-5.5 and RHEL-6.0. This is expresses in configuraiton parameters.
*
* @param interfaces
* @return false if validation failed; i.e thera are more nics than allowed. true if validation succeeded.
*/
public static boolean validateNumberOfNics(List<VmNetworkInterface> interfaces, VmNetworkInterface networkInterface) {
int ifCount = 0;
if (networkInterface != null && networkInterface.getType() != null) {
ifCount += (VmInterfaceType.forValue(networkInterface.getType()) == VmInterfaceType.rtl8139_pv ? 2 : 1);
}
for (VmNetworkInterface i : interfaces) {
if (i.getType() != null) {
ifCount += (i.getType() == VmInterfaceType.rtl8139_pv.getValue()) ? 2 : 1;
}
}
return (ifCount <= MAX_NETWORK_INTERFACES_SUPPORTED);
}
}