| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.cloudstack.storage.image.deployasis; |
| |
| import com.cloud.agent.api.to.NicTO; |
| import com.cloud.agent.api.to.OVFInformationTO; |
| import com.cloud.agent.api.to.deployasis.OVFConfigurationTO; |
| import com.cloud.agent.api.to.deployasis.OVFEulaSectionTO; |
| import com.cloud.agent.api.to.deployasis.OVFPropertyTO; |
| import com.cloud.agent.api.to.deployasis.OVFVirtualHardwareItemTO; |
| import com.cloud.agent.api.to.deployasis.OVFVirtualHardwareSectionTO; |
| import com.cloud.agent.api.to.deployasis.TemplateDeployAsIsInformationTO; |
| import com.cloud.deployasis.DeployAsIsConstants; |
| import com.cloud.deployasis.TemplateDeployAsIsDetailVO; |
| import com.cloud.deployasis.UserVmDeployAsIsDetailVO; |
| import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; |
| import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; |
| import com.cloud.hypervisor.Hypervisor; |
| import com.cloud.storage.GuestOSCategoryVO; |
| import com.cloud.storage.GuestOSHypervisorVO; |
| import com.cloud.storage.GuestOSVO; |
| import com.cloud.storage.VMTemplateStorageResourceAssoc; |
| import com.cloud.storage.VMTemplateVO; |
| import com.cloud.storage.dao.GuestOSCategoryDao; |
| import com.cloud.storage.dao.GuestOSDao; |
| import com.cloud.storage.dao.GuestOSHypervisorDao; |
| import com.cloud.storage.dao.VMTemplateDao; |
| import com.cloud.storage.dao.VMTemplatePoolDao; |
| import com.cloud.utils.Pair; |
| import com.cloud.utils.compression.CompressionUtil; |
| import com.cloud.utils.crypt.DBEncryptionUtil; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.vm.VirtualMachineProfile; |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| import com.cloud.agent.api.to.deployasis.OVFNetworkTO; |
| import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| import org.springframework.stereotype.Component; |
| |
| import javax.inject.Inject; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State.Failed; |
| |
| @Component |
| public class DeployAsIsHelperImpl implements DeployAsIsHelper { |
| |
| protected Logger logger = LogManager.getLogger(getClass()); |
| private static Gson gson; |
| |
| @Inject |
| private TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao; |
| @Inject |
| private UserVmDeployAsIsDetailsDao userVmDeployAsIsDetailsDao; |
| @Inject |
| private PrimaryDataStoreDao storagePoolDao; |
| @Inject |
| private VMTemplatePoolDao templateStoragePoolDao; |
| @Inject |
| private VMTemplateDao templateDao; |
| @Inject |
| private GuestOSDao guestOSDao; |
| @Inject |
| private GuestOSHypervisorDao guestOSHypervisorDao; |
| @Inject |
| private GuestOSCategoryDao guestOSCategoryDao; |
| @Inject |
| private TemplateDataStoreDao templateDataStoreDao; |
| |
| static { |
| GsonBuilder builder = new GsonBuilder(); |
| builder.disableHtmlEscaping(); |
| gson = builder.create(); |
| } |
| |
| private void persistTemplateOVFInformation(long templateId, OVFInformationTO ovfInformationTO) { |
| List<OVFPropertyTO> ovfProperties = ovfInformationTO.getProperties(); |
| List<OVFNetworkTO> networkRequirements = ovfInformationTO.getNetworks(); |
| OVFVirtualHardwareSectionTO ovfHardwareSection = ovfInformationTO.getHardwareSection(); |
| List<OVFEulaSectionTO> eulaSections = ovfInformationTO.getEulaSections(); |
| Pair<String, String> guestOsInfo = ovfInformationTO.getGuestOsInfo(); |
| |
| if (CollectionUtils.isNotEmpty(ovfProperties)) { |
| persistTemplateDeployAsIsInformationTOList(templateId, ovfProperties); |
| } |
| if (CollectionUtils.isNotEmpty(networkRequirements)) { |
| persistTemplateDeployAsIsInformationTOList(templateId, networkRequirements); |
| } |
| if (CollectionUtils.isNotEmpty(eulaSections)) { |
| persistTemplateDeployAsIsInformationTOList(templateId, eulaSections); |
| } |
| String minimumHardwareVersion = null; |
| if (ovfHardwareSection != null) { |
| if (CollectionUtils.isNotEmpty(ovfHardwareSection.getConfigurations())) { |
| persistTemplateDeployAsIsInformationTOList(templateId, ovfHardwareSection.getConfigurations()); |
| } |
| if (CollectionUtils.isNotEmpty(ovfHardwareSection.getCommonHardwareItems())) { |
| persistTemplateDeployAsIsInformationTOList(templateId, ovfHardwareSection.getCommonHardwareItems()); |
| } |
| minimumHardwareVersion = ovfHardwareSection.getMinimiumHardwareVersion(); |
| } |
| if (guestOsInfo != null) { |
| String osType = guestOsInfo.first(); |
| String osDescription = guestOsInfo.second(); |
| logger.info("Guest OS information retrieved from the template: " + osType + " - " + osDescription); |
| handleGuestOsFromOVFDescriptor(templateId, osType, osDescription, minimumHardwareVersion); |
| } |
| } |
| |
| public boolean persistTemplateOVFInformationAndUpdateGuestOS(long templateId, OVFInformationTO ovfInformationTO, TemplateDataStoreVO tmpltStoreVO) { |
| try { |
| if (ovfInformationTO != null) { |
| persistTemplateOVFInformation(templateId, ovfInformationTO); |
| } |
| } catch (Exception e) { |
| logger.error("Error persisting deploy-as-is details for template " + templateId, e); |
| tmpltStoreVO.setErrorString(e.getMessage()); |
| tmpltStoreVO.setState(Failed); |
| tmpltStoreVO.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR); |
| templateDataStoreDao.update(tmpltStoreVO.getId(), tmpltStoreVO); |
| return false; |
| } |
| logger.info("Successfully persisted deploy-as-is details for template " + templateId); |
| return true; |
| } |
| |
| /** |
| * Returns the mapped guest OS from the OVF file of the template to the CloudStack database OS ID |
| */ |
| private Long retrieveTemplateGuestOsIdFromGuestOsInfo(long templateId, String guestOsType, String guestOsDescription, |
| String minimumHardwareVersion) { |
| VMTemplateVO template = templateDao.findById(templateId); |
| Hypervisor.HypervisorType hypervisor = template.getHypervisorType(); |
| if (hypervisor != Hypervisor.HypervisorType.VMware) { |
| return null; |
| } |
| |
| String minimumHypervisorVersion = getMinimumSupportedHypervisorVersionForHardwareVersion(minimumHardwareVersion); |
| logger.info("Minimum hardware version " + minimumHardwareVersion + " matched to hypervisor version " + minimumHypervisorVersion + ". " + |
| "Checking guest OS supporting this version"); |
| |
| List<GuestOSHypervisorVO> guestOsMappings = guestOSHypervisorDao.listByOsNameAndHypervisorMinimumVersion(guestOsType, |
| hypervisor.toString(), minimumHypervisorVersion); |
| |
| if (CollectionUtils.isNotEmpty(guestOsMappings)) { |
| if (logger.isDebugEnabled()) { |
| String msg = String.format("number of hypervisor mappings for guest os \"%s\" is: %d", guestOsType, guestOsMappings.size()); |
| logger.debug(msg); |
| } |
| Long guestOsId = null; |
| if (guestOsMappings.size() == 1) { |
| GuestOSHypervisorVO mapping = guestOsMappings.get(0); |
| guestOsId = mapping.getGuestOsId(); |
| } else { |
| if (StringUtils.isNotEmpty(guestOsDescription)) { |
| for (GuestOSHypervisorVO guestOSHypervisorVO : guestOsMappings) { |
| GuestOSVO guestOSVO = guestOSDao.findByIdIncludingRemoved(guestOSHypervisorVO.getGuestOsId()); |
| if (guestOSVO != null && guestOsDescription.equalsIgnoreCase(guestOSVO.getDisplayName())) { |
| guestOsId = guestOSHypervisorVO.getGuestOsId(); |
| break; |
| } |
| } |
| } |
| if (null == guestOsId) { |
| GuestOSHypervisorVO mapping = guestOsMappings.get(guestOsMappings.size()-1); |
| guestOsId = mapping.getGuestOsId(); |
| } |
| } |
| return guestOsId; |
| } else { |
| throw new CloudRuntimeException(String.format("Did not find a guest OS (%s) with type \"%s\" and minimal hypervisor hardware version %s.", guestOsDescription, guestOsType, minimumHardwareVersion)); |
| } |
| } |
| |
| /** |
| * Handle the guest OS read from the OVF and try to match it to an existing guest OS in DB. |
| * If the guest OS cannot be mapped to an existing guest OS in DB, then create it and create support for hypervisor versions. |
| * Roll back actions in case of unexpected errors |
| */ |
| private void handleGuestOsFromOVFDescriptor(long templateId, String guestOsType, String guestOsDescription, |
| String minimumHardwareVersion) { |
| Long guestOsId = retrieveTemplateGuestOsIdFromGuestOsInfo(templateId, guestOsType, guestOsDescription, minimumHardwareVersion); |
| if (guestOsId != null) { |
| logger.info("Updating deploy-as-is template guest OS to " + guestOsType); |
| VMTemplateVO template = templateDao.findById(templateId); |
| updateTemplateGuestOsId(template, guestOsId); |
| } |
| } |
| |
| /** |
| * Updates the deploy-as-is template guest OS doing: |
| * - Create a new guest OS with the guest OS description parsed from the OVF |
| * - Create mappings for the new guest OS and supported hypervisor versions |
| * - Update the template guest OS ID to the new guest OS ID |
| */ |
| private void updateDeployAsIsTemplateToNewGuestOs(VMTemplateVO template, String guestOsType, String guestOsDescription, |
| Hypervisor.HypervisorType hypervisor, Collection<String> hypervisorVersions) { |
| GuestOSVO newGuestOs = createGuestOsEntry(guestOsDescription); |
| for (String hypervisorVersion : hypervisorVersions) { |
| logger.info(String.format("Adding a new guest OS mapping for hypervisor: %s version: %s and " + |
| "guest OS: %s", hypervisor.toString(), hypervisorVersion, guestOsType)); |
| createGuestOsHypervisorMapping(newGuestOs.getId(), guestOsType, hypervisor.toString(), hypervisorVersion); |
| } |
| updateTemplateGuestOsId(template, newGuestOs.getId()); |
| } |
| |
| private void updateTemplateGuestOsId(VMTemplateVO template, long guestOsId) { |
| template.setGuestOSId(guestOsId); |
| templateDao.update(template.getId(), template); |
| } |
| |
| /** |
| * Create a new entry on guest_os_hypervisor |
| */ |
| private void createGuestOsHypervisorMapping(long guestOsId, String guestOsType, String hypervisorType, String hypervisorVersion) { |
| GuestOSHypervisorVO mappingVO = new GuestOSHypervisorVO(); |
| mappingVO.setGuestOsId(guestOsId); |
| mappingVO.setHypervisorType(hypervisorType); |
| mappingVO.setHypervisorVersion(hypervisorVersion); |
| mappingVO.setGuestOsName(guestOsType); |
| guestOSHypervisorDao.persist(mappingVO); |
| } |
| |
| /** |
| * Create new guest OS entry with category 'Other' |
| */ |
| private GuestOSVO createGuestOsEntry(String guestOsDescription) { |
| GuestOSCategoryVO categoryVO = guestOSCategoryDao.findByCategoryName("Other"); |
| long categoryId = categoryVO != null ? categoryVO.getId() : 7L; |
| GuestOSVO guestOSVO = new GuestOSVO(); |
| guestOSVO.setDisplayName(guestOsDescription); |
| guestOSVO.setCategoryId(categoryId); |
| return guestOSDao.persist(guestOSVO); |
| } |
| |
| /** |
| * Minimum VMware hosts supported version is 6.0 |
| */ |
| protected String mapHardwareVersionToHypervisorVersion(String hardwareVersion) { |
| String hypervisorVersion = "default"; |
| if (StringUtils.isBlank(hardwareVersion)) { |
| return hypervisorVersion; |
| } |
| String hwVersion = hardwareVersion.replace("vmx-", ""); |
| try { |
| int hwVersionNumber = Integer.parseInt(hwVersion); |
| if (hwVersionNumber <= 11) { |
| hypervisorVersion = "6.0"; |
| } else if (hwVersionNumber == 13) { |
| hypervisorVersion = "6.5"; |
| } else if (hwVersionNumber >= 14) { |
| hypervisorVersion = "6.7"; |
| } |
| } catch (NumberFormatException e) { |
| logger.error("Cannot parse hardware version " + hwVersion + " to integer. Using default hypervisor version", e); |
| } |
| return hypervisorVersion; |
| } |
| |
| /** |
| * Retrieve the minimal hypervisor version for a single or multiple hardware version(s) in the parameter |
| */ |
| protected String getMinimumSupportedHypervisorVersionForHardwareVersion(String hardwareVersion) { |
| // From https://kb.vmware.com/s/article/1003746 and https://kb.vmware.com/s/article/2007240 |
| String hypervisorVersion = "default"; |
| if (StringUtils.isBlank(hardwareVersion)) { |
| return hypervisorVersion; |
| } |
| if (hardwareVersion.contains(" ")) { |
| String[] versions = hardwareVersion.split(" "); |
| String minVersion = null; |
| for (String version : versions) { |
| String hvVersion = mapHardwareVersionToHypervisorVersion(version); |
| if (minVersion == null) { |
| minVersion = hvVersion; |
| } else if (hvVersion.equalsIgnoreCase("default")) { |
| return minVersion; |
| } else if (hvVersion.compareTo(minVersion) < 0) { |
| minVersion = hvVersion; |
| } |
| } |
| return minVersion; |
| } else { |
| return mapHardwareVersionToHypervisorVersion(hardwareVersion); |
| } |
| } |
| |
| @Override |
| public Map<String, String> getVirtualMachineDeployAsIsProperties(VirtualMachineProfile vm) { |
| Map<String, String> map = new HashMap<>(); |
| List<UserVmDeployAsIsDetailVO> details = userVmDeployAsIsDetailsDao.listDetails(vm.getId()); |
| if (CollectionUtils.isNotEmpty(details)) { |
| for (UserVmDeployAsIsDetailVO detail : details) { |
| OVFPropertyTO property = templateDeployAsIsDetailsDao.findPropertyByTemplateAndKey(vm.getTemplateId(), detail.getName()); |
| String value = property.isPassword() ? DBEncryptionUtil.decrypt(detail.getValue()) : detail.getValue(); |
| map.put(detail.getName(), value); |
| } |
| } |
| return map; |
| } |
| |
| @Override |
| public Map<Integer, String> getAllocatedVirtualMachineNicsAdapterMapping(VirtualMachineProfile vm, NicTO[] nics) { |
| Map<Integer, String> map = new HashMap<>(); |
| List<OVFNetworkTO> networks = templateDeployAsIsDetailsDao.listNetworkRequirementsByTemplateId(vm.getTemplateId()); |
| if (ArrayUtils.isNotEmpty(nics)) { |
| if (nics.length != networks.size()) { |
| String msg = "Different number of networks provided vs networks defined in deploy-as-is template"; |
| logger.error(msg); |
| return map; |
| } |
| for (int i = 0; i < nics.length; i++) { |
| // The nic Adapter is defined on the resource sub type |
| map.put(nics[i].getDeviceId(), networks.get(i).getResourceSubType()); |
| } |
| } |
| return map; |
| } |
| |
| public void persistTemplateDeployAsIsInformationTOList(long templateId, |
| List<? extends TemplateDeployAsIsInformationTO> informationTOList) { |
| for (TemplateDeployAsIsInformationTO informationTO : informationTOList) { |
| String propKey = getKeyFromInformationTO(informationTO); |
| if (logger.isTraceEnabled()) { |
| logger.trace(String.format("Saving property %s for template %d as detail", propKey, templateId)); |
| } |
| String propValue = null; |
| try { |
| propValue = getValueFromInformationTO(informationTO); |
| } catch (RuntimeException re) { |
| logger.error("gson marshalling of property object fails: " + propKey,re); |
| } catch (IOException e) { |
| logger.error("Could not decompress the license for template " + templateId, e); |
| } |
| saveTemplateDeployAsIsPropertyAttribute(templateId, propKey, propValue); |
| } |
| } |
| |
| private String getValueFromInformationTO(TemplateDeployAsIsInformationTO informationTO) throws IOException { |
| if (informationTO instanceof OVFEulaSectionTO) { |
| CompressionUtil compressionUtil = new CompressionUtil(); |
| byte[] compressedLicense = ((OVFEulaSectionTO) informationTO).getCompressedLicense(); |
| return compressionUtil.decompressByteArary(compressedLicense); |
| } |
| return gson.toJson(informationTO); |
| } |
| |
| private String getKeyFromInformationTO(TemplateDeployAsIsInformationTO informationTO) { |
| if (informationTO instanceof OVFPropertyTO) { |
| return DeployAsIsConstants.PROPERTY_PREFIX + ((OVFPropertyTO) informationTO).getKey(); |
| } else if (informationTO instanceof OVFNetworkTO) { |
| return DeployAsIsConstants.NETWORK_PREFIX + ((OVFNetworkTO) informationTO).getName(); |
| } else if (informationTO instanceof OVFConfigurationTO) { |
| return DeployAsIsConstants.CONFIGURATION_PREFIX + |
| ((OVFConfigurationTO) informationTO).getIndex() + "-" + ((OVFConfigurationTO) informationTO).getId(); |
| } else if (informationTO instanceof OVFVirtualHardwareItemTO) { |
| String key = ((OVFVirtualHardwareItemTO) informationTO).getResourceType().getName().trim().replaceAll("\\s","") |
| + "-" + ((OVFVirtualHardwareItemTO) informationTO).getInstanceId(); |
| return DeployAsIsConstants.HARDWARE_ITEM_PREFIX + key; |
| } else if (informationTO instanceof OVFEulaSectionTO) { |
| return DeployAsIsConstants.EULA_PREFIX + ((OVFEulaSectionTO) informationTO).getIndex() + |
| "-" + ((OVFEulaSectionTO) informationTO).getInfo(); |
| } |
| return null; |
| } |
| |
| private void saveTemplateDeployAsIsPropertyAttribute(long templateId, String key, String value) { |
| if (logger.isTraceEnabled()) { |
| logger.trace(String.format("Saving property %s for template %d as detail", key, templateId)); |
| } |
| if (templateDeployAsIsDetailsDao.findDetail(templateId,key) != null) { |
| logger.debug(String.format("Detail '%s' existed for template %d, deleting.", key, templateId)); |
| templateDeployAsIsDetailsDao.removeDetail(templateId,key); |
| } |
| if (logger.isTraceEnabled()) { |
| logger.trace(String.format("Template detail for template %d to save is '%s': '%s'", templateId, key, value)); |
| } |
| TemplateDeployAsIsDetailVO detailVO = new TemplateDeployAsIsDetailVO(templateId, key, value); |
| logger.debug("Persisting template details " + detailVO.getName() + " from OVF properties for template " + templateId); |
| templateDeployAsIsDetailsDao.persist(detailVO); |
| } |
| |
| } |