package org.ovirt.engine.core.bll; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.profiles.CpuProfileHelper; import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaSanityParameter; import org.ovirt.engine.core.bll.quota.QuotaVdsDependent; import org.ovirt.engine.core.bll.utils.IconUtils; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.utils.RngDeviceUtils; import org.ovirt.engine.core.bll.validator.IconValidator; import org.ovirt.engine.core.bll.validator.VmValidator; import org.ovirt.engine.core.bll.validator.VmWatchdogValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.GraphicsParameters; import org.ovirt.engine.core.common.action.UpdateVmTemplateParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VmManagementParametersBase; import org.ovirt.engine.core.common.businessentities.ActionGroup; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.GraphicsDevice; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VmEntityType; import org.ovirt.engine.core.common.businessentities.VmTemplate; import org.ovirt.engine.core.common.businessentities.VmTemplateStatus; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.queries.IdQueryParameters; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.common.utils.CompatibilityVersionUtils; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.utils.SimpleDependencyInjector; import org.ovirt.engine.core.common.utils.customprop.VmPropertiesUtils; import org.ovirt.engine.core.common.validation.group.UpdateEntity; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.dao.DiskVmElementDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.dao.VmTemplateDao; import org.ovirt.engine.core.dao.network.VmNicDao; public class UpdateVmTemplateCommand<T extends UpdateVmTemplateParameters> extends VmTemplateManagementCommand<T> implements QuotaVdsDependent, RenamedEntityInfoProvider{ @Inject private CpuProfileHelper cpuProfileHelper; @Inject private RngDeviceUtils rngDeviceUtils; @Inject private VmTemplateDao vmTemplateDao; @Inject private VmNicDao vmNicDao; @Inject private DiskVmElementDao diskVmElementDao; @Inject private VmStaticDao vmStaticDao; @Inject private VmDao vmDao; private VmTemplate oldTemplate; private List<GraphicsDevice> cachedGraphics; protected final OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class); public UpdateVmTemplateCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); } @Override protected void init() { super.init(); setVmTemplate(getParameters().getVmTemplateData()); setVmTemplateId(getVmTemplate().getId()); setClusterId(getVmTemplate().getClusterId()); oldTemplate = vmTemplateDao.get(getVmTemplate().getId()); if (getCluster() != null) { setStoragePoolId(getCluster().getStoragePoolId() != null ? getCluster().getStoragePoolId() : Guid.Empty); } Version compatibilityVersion = isBlankTemplate() || isInstanceType() ? Version.getLast() : CompatibilityVersionUtils.getEffective(getVmTemplate(), this::getCluster); if (getCluster() != null || isBlankTemplate()) { getVmPropertiesUtils().separateCustomPropertiesToUserAndPredefined(compatibilityVersion, getParameters().getVmTemplateData()); if (oldTemplate != null) { getVmPropertiesUtils().separateCustomPropertiesToUserAndPredefined(compatibilityVersion, oldTemplate); } } vmHandler.autoSelectUsbPolicy(getParameters().getVmTemplateData()); vmHandler.updateDefaultTimeZone(getParameters().getVmTemplateData()); vmHandler.autoSelectDefaultDisplayType(getVmTemplateId(), getParameters().getVmTemplateData(), getCluster(), getParameters().getGraphicsDevices()); } @Override protected boolean validate() { boolean isInstanceType = isInstanceType(); boolean isBlankTemplate = isBlankTemplate(); if (getCluster() == null && !(isInstanceType || isBlankTemplate)) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_CLUSTER_CAN_NOT_BE_EMPTY); return false; } boolean returnValue = false; if (oldTemplate == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_TEMPLATE_DOES_NOT_EXIST); } if (!StringUtils.equals(oldTemplate.getName(), getVmTemplate().getName())) { if (!getVmTemplate().isBaseTemplate()) { // template version should always have the name of the base template return failValidation(EngineMessage.VMT_CANNOT_UPDATE_VERSION_NAME); } else { // validate uniqueness of template name. If template is a regular template, uniqueness // is considered in context of the datacenter. If template is an 'Instance-Type', name // must be unique also across datacenters. if (isInstanceType) { if (isInstanceWithSameNameExists(getVmTemplateName())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_NAME_ALREADY_USED); } } else { if (isVmTemplateWithSameNameExist(getVmTemplateName(), isBlankTemplate ? null : getCluster().getStoragePoolId())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_NAME_ALREADY_USED); } } } } if (VmHandler.isVmPriorityValueLegal(getParameters().getVmTemplateData().getPriority(), getReturnValue().getValidationMessages()) && checkDomain()) { returnValue = vmTemplateHandler.isUpdateValid(oldTemplate, getVmTemplate()); if (!returnValue) { addValidationMessage(EngineMessage.VMT_CANNOT_UPDATE_ILLEGAL_FIELD); } } if(!setAndValidateCpuProfile()) { return false; } if (getParameters().getVmLargeIcon() != null && !validate(IconValidator.validate( IconValidator.DimensionsType.LARGE_CUSTOM_ICON, getParameters().getVmLargeIcon()))) { return false; } if (getParameters().getVmTemplateData() != null && getParameters().getVmTemplateData().getSmallIconId() != null && getParameters().getVmLargeIcon() == null // icon id is ignored if large icon is sent && !validate(IconValidator.validateIconId(getParameters().getVmTemplateData().getSmallIconId(), "Small"))) { return false; } if (getParameters().getVmTemplateData() != null && getParameters().getVmTemplateData().getLargeIconId() != null && getParameters().getVmLargeIcon() == null // icon id is ignored if large icon is sent && !validate(IconValidator.validateIconId(getParameters().getVmTemplateData().getLargeIconId(), "Large"))) { return false; } if (returnValue && getParameters().getWatchdog() != null) { returnValue = validate(new VmWatchdogValidator.VmWatchdogClusterIndependentValidator( getParameters().getWatchdog()).isValid() ); } if (!validate(VmHandler.validateMaxMemorySize( getParameters().getVmTemplateData(), CompatibilityVersionUtils.getEffective(getParameters().getVmTemplateData(), this::getCluster)))) { return false; } if (!isInstanceType && !isBlankTemplate && returnValue) { return doClusterRelatedChecks(); } else { return returnValue; } } private boolean doClusterRelatedChecks() { if (oldTemplate.getStatus() == VmTemplateStatus.Locked) { return failValidation(EngineMessage.VM_TEMPLATE_IS_LOCKED); } // Check if the OS type is supported boolean returnValue = vmHandler.isOsTypeSupported(getParameters().getVmTemplateData().getOsId(), getCluster().getArchitecture(), getReturnValue().getValidationMessages()); // Check if the watchdog model is supported if (returnValue && getParameters().getWatchdog() != null) { returnValue = validate(new VmWatchdogValidator.VmWatchdogClusterDependentValidator(getParameters().getVmTemplateData().getOsId(), getParameters().getWatchdog(), getVmTemplate().getCompatibilityVersion()).isValid()); } // Check if the display type is supported if (returnValue) { returnValue = vmHandler.isGraphicsAndDisplaySupported(getParameters().getVmTemplateData().getOsId(), vmHandler.getResultingVmGraphics(getVmDeviceUtils().getGraphicsTypesOfEntity(getVmTemplateId()), getParameters().getGraphicsDevices()), getParameters().getVmTemplateData().getDefaultDisplayType(), getReturnValue().getValidationMessages(), getVmTemplate().getCompatibilityVersion()); } if (returnValue) { returnValue = validate(VmValidator.validateCpuSockets(getParameters().getVmTemplateData(), getVmTemplate().getCompatibilityVersion())); } if (returnValue && getParameters().getVmTemplateData().getSingleQxlPci() && getParameters().getVmTemplateData().getDefaultDisplayType() != DisplayType.none && !vmHandler.isSingleQxlDeviceLegal(getParameters().getVmTemplateData().getDefaultDisplayType(), getParameters().getVmTemplateData().getOsId(), getReturnValue().getValidationMessages())) { returnValue = false; } // Check PCI and IDE limits are ok if (returnValue) { List<VmNic> interfaces = vmNicDao.getAllForTemplate(getParameters().getVmTemplateData().getId()); List<DiskVmElement> diskVmElements = diskVmElementDao.getAllForVm(getVmTemplateId()); if (!validate(VmValidator.checkPciAndIdeLimit(getParameters().getVmTemplateData().getOsId(), getVmTemplate().getCompatibilityVersion(), getParameters().getVmTemplateData().getNumOfMonitors(), interfaces, diskVmElements, getVmDeviceUtils().hasVirtioScsiController(getParameters().getVmTemplateData().getId()), hasWatchdog(getParameters().getVmTemplateData().getId()), getVmDeviceUtils().hasMemoryBalloon(getParameters().getVmTemplateData().getId()), isSoundDeviceEnabled()))) { returnValue = false; } } if (getParameters().getVmTemplateData().getMinAllocatedMem() > getParameters().getVmTemplateData().getMemSizeMb()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_MIN_MEMORY_CANNOT_EXCEED_MEMORY_SIZE); } if (!getVmPropertiesUtils().validateVmProperties( getVmTemplate().getCompatibilityVersion(), getParameters().getVmTemplateData().getCustomProperties(), getReturnValue().getValidationMessages())) { return false; } if (returnValue) { boolean balloonEnabled = Boolean.TRUE.equals(getParameters().isBalloonEnabled()); if (balloonEnabled && !osRepository.isBalloonEnabled(getParameters().getVmTemplateData().getOsId(), getVmTemplate().getCompatibilityVersion())) { addValidationMessageVariable("clusterArch", getCluster().getArchitecture()); return failValidation(EngineMessage.BALLOON_REQUESTED_ON_NOT_SUPPORTED_ARCH); } } boolean soundDeviceEnabled = Boolean.TRUE.equals(getParameters().isSoundDeviceEnabled()); if (soundDeviceEnabled && !osRepository.isSoundDeviceEnabled(getParameters().getVmTemplateData().getOsId(), getVmTemplate().getCompatibilityVersion())) { addValidationMessageVariable("clusterArch", getCluster().getArchitecture()); return failValidation(EngineMessage.SOUND_DEVICE_REQUESTED_ON_NOT_SUPPORTED_ARCH); } return returnValue; } private boolean checkDomain() { if (getParameters().getVmTemplateData().getVmInit() != null && getParameters().getVmTemplateData().getVmInit().getDomain() != null) { return isDomainLegal(getParameters().getVmTemplateData().getVmInit().getDomain(), getReturnValue().getValidationMessages()); } return true; } /** * Determines whether the specified domain name is legal. * * @param domainName * Name of the domain. * @param reasons * The reasons in case of failure (output parameter). * @return <code>true</code> if domain name is legal; otherwise, <code>false</code>. */ private static boolean isDomainLegal(String domainName, ArrayList<String> reasons) { boolean result = true; char[] illegalChars = new char[] { '&' }; if (StringUtils.isNotEmpty(domainName)) { for (char c : illegalChars) { if (domainName.contains(Character.toString(c))) { result = false; reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_DOMAIN_NAME.toString()); reasons.add(String.format("$Domain %1$s", domainName)); reasons.add(String.format("$Char %1$s", c)); break; } } } return result; } protected boolean hasWatchdog(Guid templateId) { return getParameters().getWatchdog() != null; } protected boolean isSoundDeviceEnabled() { Boolean soundDeviceEnabled = getParameters().isSoundDeviceEnabled(); return soundDeviceEnabled != null ? soundDeviceEnabled : getVmDeviceUtils().hasSoundDevice(getParameters().getVmTemplateData().getId()); } @Override protected void executeCommand() { if (!isInstanceType() && !isBlankTemplate()) { vmHandler.warnMemorySizeLegal(getParameters().getVmTemplateData(), getVmTemplate().getCompatibilityVersion()); } vmStaticDao.incrementDbGeneration(getVmTemplate().getId()); updateOriginalTemplateNameOnDerivedVms(); List<Guid> oldIconIds = Collections.emptyList(); if (isTemplate()) { oldIconIds = IconUtils.updateVmIcon(oldTemplate, getVmTemplate(), getParameters().getVmLargeIcon()); } updateVmTemplate(); IconUtils.removeUnusedIcons(oldIconIds); updateWatchdog(getParameters().getVmTemplateData().getId()); updateRngDevice(getParameters().getVmTemplateData().getId()); updateGraphicsDevice(); checkTrustedService(); updateVmsOfInstanceType(); setSucceeded(true); } /** * only in case of InstanceType update, update all vms that are bound to it */ private void updateVmsOfInstanceType() { if (!isInstanceType()) { return; } // get vms from db List<VM> vmsToUpdate = vmDao.getVmsListByInstanceType(getVmTemplateId()); for (VM vm : vmsToUpdate) { VmManagementParametersBase params = new VmManagementParametersBase(vm); params.setApplyChangesLater(true); runInternalAction(VdcActionType.UpdateVm, params); } } private void checkTrustedService() { if (getCluster() == null) { return; } if (getVmTemplate().isTrustedService() && !getCluster().supportsTrustedService()) { auditLogDirector.log(this, AuditLogType.USER_UPDATE_VM_TEMPLATE_FROM_TRUSTED_TO_UNTRUSTED); } else if (!getVmTemplate().isTrustedService() && getCluster().supportsTrustedService()) { auditLogDirector.log(this, AuditLogType.USER_UPDATE_VM_TEMPLATE_FROM_UNTRUSTED_TO_TRUSTED); } } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_UPDATE_VM_TEMPLATE : AuditLogType.USER_FAILED_UPDATE_VM_TEMPLATE; } private void updateOriginalTemplateNameOnDerivedVms() { boolean templateNameChanged = !Objects.equals(oldTemplate.getName(), getVmTemplate().getName()); if (templateNameChanged) { vmDao.updateOriginalTemplateName(getVmTemplate().getId(), getVmTemplate().getName()); } } private void updateVmTemplate() { vmHandler.updateVmInitToDB(getVmTemplate()); vmTemplateDao.update(getVmTemplate()); // also update the smartcard device getVmDeviceUtils().updateSmartcardDevice(getVmTemplateId(), getParameters().getVmTemplateData().isSmartcardEnabled()); // update audio device getVmDeviceUtils().updateSoundDevice(oldTemplate, getVmTemplate(), getVmTemplate().getCompatibilityVersion(), getParameters().isSoundDeviceEnabled()); getVmDeviceUtils().updateConsoleDevice(getVmTemplateId(), getParameters().isConsoleEnabled()); if (oldTemplate.getUsbPolicy() != getVmTemplate().getUsbPolicy()) { getVmDeviceUtils().updateUsbSlots(oldTemplate, getVmTemplate()); } getVmDeviceUtils().updateVirtioScsiController(getVmTemplate(), getParameters().isVirtioScsiEnabled()); if (getParameters().isBalloonEnabled() != null) { getVmDeviceUtils().updateMemoryBalloon(getVmTemplateId(), getParameters().isBalloonEnabled()); } getVmDeviceUtils().updateVideoDevices(oldTemplate, getParameters().getVmTemplateData()); } @Override protected List<Class<?>> getValidationGroups() { addValidationGroup(UpdateEntity.class); return super.getValidationGroups(); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); addValidationMessage(EngineMessage.VAR__TYPE__VM_TEMPLATE); } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getVmTemplateId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.TEMPLATE, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } @Override public List<QuotaConsumptionParameter> getQuotaVdsConsumptionParameters() { List<QuotaConsumptionParameter> list = new ArrayList<>(); list.add(new QuotaSanityParameter(getParameters().getVmTemplateData().getQuotaId(), null)); return list; } @Override public String getEntityType() { return VdcObjectType.VmTemplate.getVdcObjectTranslation(); } @Override public String getEntityOldName() { return oldTemplate.getName(); } @Override public String getEntityNewName() { return getParameters().getVmTemplateData().getName(); } @Override public void setEntityId(AuditLogableBase logable) { logable.setVmTemplateId(oldTemplate.getId()); } @Override public List<PermissionSubject> getPermissionCheckSubjects() { final List<PermissionSubject> permissionList = super.getPermissionCheckSubjects(); if (getVmTemplate() != null && !isInstanceType() && !isBlankTemplate()) { // host-specific parameters can be changed by administration role only List<Guid> tmpltDdctHostsLst = getVmTemplate().getDedicatedVmForVdsList(); List<Guid> prmTmpltDdctHostsLst = getParameters().getVmTemplateData().getDedicatedVmForVdsList(); // tmpltDdctHostsLst.equals(prmTmpltDdctHostsLs is not good enough, lists order may change if (!CollectionUtils.isEqualCollection(tmpltDdctHostsLst, prmTmpltDdctHostsLst)) { permissionList.add( new PermissionSubject(getParameters().getVmTemplateId(), VdcObjectType.VmTemplate, ActionGroup.EDIT_ADMIN_TEMPLATE_PROPERTIES)); } } return permissionList; } @Override protected boolean isQuotaDependant() { if (isInstanceType() || isBlankTemplate()) { return false; } return super.isQuotaDependant(); } protected boolean setAndValidateCpuProfile() { // cpu profile isn't supported for instance types nor for blank template. if (isInstanceType() || isBlankTemplate()) { return true; } return validate(cpuProfileHelper.setAndValidateCpuProfile( getVmTemplate(), getUserIdIfExternal().orElse(null))); } private boolean isInstanceType() { return getVmTemplate().getTemplateType() == VmEntityType.INSTANCE_TYPE; } private boolean isTemplate() { return VmEntityType.TEMPLATE.equals(getVmTemplate().getTemplateType()); } private VmPropertiesUtils getVmPropertiesUtils() { return VmPropertiesUtils.getInstance(); } private void updateGraphicsDevice() { for (GraphicsType type : getParameters().getGraphicsDevices().keySet()) { GraphicsDevice vmGraphicsDevice = getGraphicsDevOfType(type); if (vmGraphicsDevice == null) { if (getParameters().getGraphicsDevices().get(type) != null) { getParameters().getGraphicsDevices().get(type).setVmId(getVmTemplateId()); GraphicsParameters parameters = new GraphicsParameters(getParameters().getGraphicsDevices().get(type)); parameters.setVm(false); getBackend().runInternalAction(VdcActionType.AddGraphicsDevice, parameters); } } else { if (getParameters().getGraphicsDevices().get(type) == null) { GraphicsParameters parameters = new GraphicsParameters(vmGraphicsDevice); parameters.setVm(false); getBackend().runInternalAction(VdcActionType.RemoveGraphicsDevice, parameters); } else { getParameters().getGraphicsDevices().get(type).setVmId(getVmTemplateId()); GraphicsParameters parameters = new GraphicsParameters(getParameters().getGraphicsDevices().get(type)); parameters.setVm(false); getBackend().runInternalAction(VdcActionType.UpdateGraphicsDevice, parameters); } } } } // first dev or null private GraphicsDevice getGraphicsDevOfType(GraphicsType type) { List<GraphicsDevice> graphicsDevices = getGraphicsDevices(); for (GraphicsDevice dev : graphicsDevices) { if (dev.getGraphicsType() == type) { return dev; } } return null; } private List<GraphicsDevice> getGraphicsDevices() { if (cachedGraphics == null) { cachedGraphics = getBackend() .runInternalQuery(VdcQueryType.GetGraphicsDevices, new IdQueryParameters(getVmTemplateId())).getReturnValue(); } return cachedGraphics; } }