blob: cbbc8b39c22fdee294abf7a44ac7e9f3aceabbce [file]
// 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 com.cloud.api.query.dao;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.ChildTemplateResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.VnfNicResponse;
import org.apache.cloudstack.api.response.VnfTemplateResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.utils.security.DigestHelper;
import org.springframework.stereotype.Component;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.ResourceTagJoinVO;
import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.deployasis.DeployAsIsConstants;
import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VnfTemplateDetailVO;
import com.cloud.storage.VnfTemplateNicVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateDetailsDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VnfTemplateDetailsDao;
import com.cloud.storage.dao.VnfTemplateNicDao;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.exception.CloudRuntimeException;
@Component
public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<TemplateJoinVO, TemplateResponse> implements TemplateJoinDao {
@Inject
private ConfigurationDao _configDao;
@Inject
private AccountService _accountService;
@Inject
private VMTemplateDao _vmTemplateDao;
@Inject
private TemplateDataStoreDao _templateStoreDao;
@Inject
private ImageStoreDao dataStoreDao;
@Inject
private PrimaryDataStoreDao primaryDataStoreDao;
@Inject
private VMTemplatePoolDao templatePoolDao;
@Inject
private VMTemplateDetailsDao _templateDetailsDao;
@Inject
private TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao;
@Inject
private AnnotationDao annotationDao;
@Inject
private UserDataDao userDataDao;
@Inject
VnfTemplateDetailsDao vnfTemplateDetailsDao;
@Inject
VnfTemplateNicDao vnfTemplateNicDao;
private final SearchBuilder<TemplateJoinVO> tmpltIdPairSearch;
private final SearchBuilder<TemplateJoinVO> tmpltIdPairCrossZoneSearch;
private final SearchBuilder<TemplateJoinVO> tmpltIdSearch;
private final SearchBuilder<TemplateJoinVO> tmpltIdsSearch;
private final SearchBuilder<TemplateJoinVO> tmpltZoneSearch;
private final SearchBuilder<TemplateJoinVO> activeTmpltSearch;
private final SearchBuilder<TemplateJoinVO> publicTmpltSearch;
protected TemplateJoinDaoImpl() {
tmpltIdPairSearch = createSearchBuilder();
tmpltIdPairSearch.and("template_dc_pair_templateid", tmpltIdPairSearch.entity().getId(), SearchCriteria.Op.EQ);
tmpltIdPairSearch.and("template_dc_pair_dcid", tmpltIdPairSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
tmpltIdPairSearch.done();
tmpltIdPairCrossZoneSearch = createSearchBuilder();
tmpltIdPairCrossZoneSearch.and("template_dc_pair_templateid", tmpltIdPairCrossZoneSearch.entity().getId(), SearchCriteria.Op.EQ);
tmpltIdPairCrossZoneSearch.and("template_dc_pair_dcid", tmpltIdPairCrossZoneSearch.entity().getDataCenterId(), SearchCriteria.Op.NULL);
tmpltIdPairCrossZoneSearch.done();
tmpltIdSearch = createSearchBuilder();
tmpltIdSearch.and("id", tmpltIdSearch.entity().getId(), SearchCriteria.Op.EQ);
tmpltIdSearch.done();
tmpltIdsSearch = createSearchBuilder();
tmpltIdsSearch.and("idsIN", tmpltIdsSearch.entity().getId(), SearchCriteria.Op.IN);
tmpltIdsSearch.groupBy(tmpltIdsSearch.entity().getId());
tmpltIdsSearch.done();
tmpltZoneSearch = createSearchBuilder();
tmpltZoneSearch.and("id", tmpltZoneSearch.entity().getId(), SearchCriteria.Op.EQ);
tmpltZoneSearch.and("dataCenterId", tmpltZoneSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
tmpltZoneSearch.and("state", tmpltZoneSearch.entity().getState(), SearchCriteria.Op.EQ);
tmpltZoneSearch.done();
activeTmpltSearch = createSearchBuilder();
activeTmpltSearch.and("store_id", activeTmpltSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
activeTmpltSearch.and("type", activeTmpltSearch.entity().getTemplateType(), SearchCriteria.Op.EQ);
activeTmpltSearch.and("templateState", activeTmpltSearch.entity().getTemplateState(), SearchCriteria.Op.EQ);
activeTmpltSearch.done();
publicTmpltSearch = createSearchBuilder();
publicTmpltSearch.and("public", publicTmpltSearch.entity().isPublicTemplate(), SearchCriteria.Op.EQ);
publicTmpltSearch.done();
// select distinct pair (template_id, zone_id)
_count = "select count(distinct temp_zone_pair) from template_view WHERE ";
}
private enum TemplateStatus {
SUCCESSFULLY_INSTALLED("Successfully Installed"),
INSTALLING_TEMPLATE("Installing Template"),
INSTALLING_ISO("Installing ISO"),
BYPASSED_SECONDARY_STORAGE("Bypassed Secondary Storage"),
PROCESSING("Processing"),
DOWNLOADING("%d%% Downloaded"),
DOWNLOAD_COMPLETE("Download Complete");
private final String status;
TemplateStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
// For statuses that have dynamic details (e.g. "75% Downloaded").
public String format(int percent) {
return String.format(status, percent);
}
}
private String getTemplateStatus(TemplateJoinVO template) {
if (template == null) {
return null;
}
boolean isIso = Storage.ImageFormat.ISO == template.getFormat();
TemplateStatus templateStatus;
if (template.getDownloadState() == Status.DOWNLOADED) {
templateStatus = isIso ? TemplateStatus.SUCCESSFULLY_INSTALLED : TemplateStatus.DOWNLOAD_COMPLETE;
} else if (template.getDownloadState() == Status.DOWNLOAD_IN_PROGRESS) {
if (template.getDownloadPercent() == 100) {
templateStatus = isIso ? TemplateStatus.INSTALLING_ISO : TemplateStatus.INSTALLING_TEMPLATE;
} else {
return TemplateStatus.DOWNLOADING.format(template.getDownloadPercent());
}
} else if (template.getDownloadState() == Status.BYPASSED) {
templateStatus = TemplateStatus.BYPASSED_SECONDARY_STORAGE;
} else if (StringUtils.isNotBlank(template.getErrorString())) {
return template.getErrorString().trim();
} else {
templateStatus = TemplateStatus.PROCESSING;
}
return templateStatus.getStatus();
}
@Override
public TemplateResponse newTemplateResponse(EnumSet<ApiConstants.DomainDetails> detailsView, ResponseView view, TemplateJoinVO template) {
List<ImageStoreVO> storesInZone = dataStoreDao.listStoresByZoneId(template.getDataCenterId());
Long[] storeIds = storesInZone.stream().map(ImageStoreVO::getId).toArray(Long[]::new);
List<TemplateDataStoreVO> templatesInStore = _templateStoreDao.listByTemplateNotBypassed(template.getId(), storeIds);
List<Long> dataStoreIdList = templatesInStore.stream().map(TemplateDataStoreVO::getDataStoreId).collect(Collectors.toList());
Map<Long, ImageStoreVO> imageStoreMap = dataStoreDao.listByIds(dataStoreIdList).stream().collect(Collectors.toMap(ImageStoreVO::getId, imageStore -> imageStore));
List<Map<String, String>> downloadProgressDetails = new ArrayList<>();
HashMap<String, String> downloadDetailInImageStores = null;
for (TemplateDataStoreVO templateInStore : templatesInStore) {
downloadDetailInImageStores = new HashMap<>();
ImageStoreVO imageStore = imageStoreMap.get(templateInStore.getDataStoreId());
if (imageStore != null) {
downloadDetailInImageStores.put("datastore", imageStore.getName());
if (view.equals(ResponseView.Full)) {
downloadDetailInImageStores.put("datastoreId", imageStore.getUuid());
downloadDetailInImageStores.put("datastoreRole", imageStore.getRole().name());
}
downloadDetailInImageStores.put("downloadPercent", Integer.toString(templateInStore.getDownloadPercent()));
downloadDetailInImageStores.put("downloadState", (templateInStore.getDownloadState() != null ? templateInStore.getDownloadState().toString() : ""));
downloadProgressDetails.add(downloadDetailInImageStores);
}
}
List<StoragePoolVO> poolsInZone = primaryDataStoreDao.listByDataCenterId(template.getDataCenterId());
List<Long> poolIds = poolsInZone.stream().map(StoragePoolVO::getId).collect(Collectors.toList());
List<VMTemplateStoragePoolVO> templatesInPool = templatePoolDao.listByTemplateId(template.getId(), poolIds);
dataStoreIdList = templatesInStore.stream().map(TemplateDataStoreVO::getDataStoreId).collect(Collectors.toList());
Map<Long, StoragePoolVO> storagePoolMap = primaryDataStoreDao.listByIds(dataStoreIdList).stream().collect(Collectors.toMap(StoragePoolVO::getId, store -> store));
for (VMTemplateStoragePoolVO templateInPool : templatesInPool) {
downloadDetailInImageStores = new HashMap<>();
StoragePoolVO storagePool = storagePoolMap.get(templateInPool.getDataStoreId());
if (storagePool != null) {
downloadDetailInImageStores.put("datastore", storagePool.getName());
if (view.equals(ResponseView.Full)) {
downloadDetailInImageStores.put("datastoreId", storagePool.getUuid());
downloadDetailInImageStores.put("datastoreRole", DataStoreRole.Primary.name());
}
downloadDetailInImageStores.put("downloadPercent", Integer.toString(templateInPool.getDownloadPercent()));
downloadDetailInImageStores.put("downloadState", (templateInPool.getDownloadState() != null ? templateInPool.getDownloadState().toString() : ""));
downloadProgressDetails.add(downloadDetailInImageStores);
}
}
TemplateResponse templateResponse = initTemplateResponse(template);
templateResponse.setDownloadProgress(downloadProgressDetails);
templateResponse.setId(template.getUuid());
templateResponse.setName(template.getName());
templateResponse.setDisplayText(template.getDisplayText());
templateResponse.setPublic(template.isPublicTemplate());
templateResponse.setCreated(template.getCreatedOnStore());
if (template.getFormat() == Storage.ImageFormat.BAREMETAL || template.getFormat() == Storage.ImageFormat.EXTERNAL) {
// for baremetal template, we didn't download, but is ready to use.
templateResponse.setReady(true);
} else {
templateResponse.setReady(template.getState() == ObjectInDataStoreStateMachine.State.Ready);
}
templateResponse.setFeatured(template.isFeatured());
templateResponse.setExtractable(template.isExtractable() && !(template.getTemplateType() == TemplateType.SYSTEM));
templateResponse.setPasswordEnabled(template.isEnablePassword());
templateResponse.setDynamicallyScalable(template.isDynamicallyScalable());
templateResponse.setSshKeyEnabled(template.isEnableSshKey());
templateResponse.setCrossZones(template.isCrossZones());
if (template.getTemplateType() != null) {
templateResponse.setTemplateType(template.getTemplateType().toString());
}
templateResponse.setHypervisor(template.getHypervisorType().getHypervisorDisplayName());
templateResponse.setFormat(template.getFormat());
templateResponse.setOsTypeId(template.getGuestOSUuid());
templateResponse.setOsTypeName(template.getGuestOSName());
templateResponse.setOsTypeCategoryId(template.getGuestOSCategoryId());
// populate owner.
ApiResponseHelper.populateOwner(templateResponse, template);
// If the user is an 'Admin' or 'the owner of template' or template belongs to a project, add the template download status
if (view == ResponseView.Full ||
template.getAccountId() == CallContext.current().getCallingAccount().getId() ||
template.getAccountType() == Account.Type.PROJECT) {
String templateStatus = getTemplateStatus(template);
if (templateStatus != null) {
templateResponse.setStatus(templateStatus);
}
templateResponse.setUrl(template.getUrl());
}
if (template.getDataCenterId() > 0) {
templateResponse.setZoneId(template.getDataCenterUuid());
templateResponse.setZoneName(template.getDataCenterName());
}
Long templateSize = template.getSize();
if (templateSize > 0) {
templateResponse.setSize(templateSize);
}
Long templatePhysicalSize = template.getPhysicalSize();
if (templatePhysicalSize > 0) {
templateResponse.setPhysicalSize(templatePhysicalSize);
}
templateResponse.setChecksum(DigestHelper.getHashValueFromChecksumValue(template.getChecksum()));
if (template.getSourceTemplateId() != null) {
templateResponse.setSourceTemplateId(template.getSourceTemplateUuid());
}
templateResponse.setTemplateTag(template.getTemplateTag());
if (template.getParentTemplateId() != null) {
templateResponse.setParentTemplateId(template.getParentTemplateUuid());
}
// set details map
if (detailsView.contains(ApiConstants.DomainDetails.all)) {
Map<String, String> details = _templateDetailsDao.listDetailsKeyPairs(template.getId());
templateResponse.setDetails(details);
setDeployAsIsDetails(template, templateResponse);
templateResponse.setForCks(template.isForCks());
}
// update tag information
long tag_id = template.getTagId();
if (tag_id > 0) {
addTagInformation(template, templateResponse);
}
templateResponse.setHasAnnotation(annotationDao.hasAnnotations(template.getUuid(), AnnotationService.EntityType.TEMPLATE.name(),
_accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
templateResponse.setDirectDownload(template.isDirectDownload());
templateResponse.setDeployAsIs(template.isDeployAsIs());
templateResponse.setRequiresHvm(template.isRequiresHvm());
if (template.getArch() != null) {
templateResponse.setArch(template.getArch().getType());
}
if (template.getExtensionId() != null) {
templateResponse.setExtensionId(template.getExtensionUuid());
templateResponse.setExtensionName(template.getExtensionName());
}
//set template children disks
Set<ChildTemplateResponse> childTemplatesSet = new HashSet<ChildTemplateResponse>();
if (template.getHypervisorType() == HypervisorType.VMware) {
List<VMTemplateVO> childTemplates = _vmTemplateDao.listByParentTemplatetId(template.getId());
for (VMTemplateVO tmpl : childTemplates) {
if (tmpl.getTemplateType() != TemplateType.ISODISK) {
ChildTemplateResponse childTempl = new ChildTemplateResponse();
childTempl.setId(tmpl.getUuid());
childTempl.setName(tmpl.getName());
childTempl.setSize(Math.round(tmpl.getSize() / (1024 * 1024 * 1024)));
childTemplatesSet.add(childTempl);
}
}
templateResponse.setChildTemplates(childTemplatesSet);
}
if (template.getUserDataId() != null) {
templateResponse.setUserDataId(template.getUserDataUUid());
templateResponse.setUserDataName(template.getUserDataName());
templateResponse.setUserDataParams(template.getUserDataParams());
templateResponse.setUserDataPolicy(template.getUserDataPolicy());
}
templateResponse.setObjectName("template");
return templateResponse;
}
private TemplateResponse initTemplateResponse(TemplateJoinVO template) {
TemplateResponse templateResponse = new TemplateResponse();
if (Storage.TemplateType.VNF.equals(template.getTemplateType())) {
VnfTemplateResponse vnfTemplateResponse = new VnfTemplateResponse();
List<VnfTemplateNicVO> nics = vnfTemplateNicDao.listByTemplateId(template.getId());
for (VnfTemplateNicVO nic : nics) {
vnfTemplateResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.isRequired(), nic.isManagement(), nic.getDescription()));
}
List<VnfTemplateDetailVO> details = vnfTemplateDetailsDao.listDetails(template.getId());
Collections.sort(details, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName()));
for (VnfTemplateDetailVO detail : details) {
vnfTemplateResponse.addVnfDetail(detail.getName(), detail.getValue());
}
templateResponse = vnfTemplateResponse;
}
return templateResponse;
}
private void setDeployAsIsDetails(TemplateJoinVO template, TemplateResponse templateResponse) {
if (template.isDeployAsIs()) {
List<TemplateDeployAsIsDetailVO> deployAsIsDetails = templateDeployAsIsDetailsDao.listDetails(template.getId());
for (TemplateDeployAsIsDetailVO deployAsIsDetailVO : deployAsIsDetails) {
if (deployAsIsDetailVO.getName().startsWith(DeployAsIsConstants.HARDWARE_ITEM_PREFIX)) {
//Do not list hardware items
continue;
}
templateResponse.addDeployAsIsDetail(deployAsIsDetailVO.getName(), deployAsIsDetailVO.getValue());
}
}
}
//TODO: This is to keep compatibility with 4.1 API, where updateTemplateCmd and updateIsoCmd will return a simpler TemplateResponse
// compared to listTemplates and listIsos.
@Override
public TemplateResponse newUpdateResponse(TemplateJoinVO result) {
TemplateResponse response = initTemplateResponse(result);
response.setId(result.getUuid());
response.setName(result.getName());
response.setDisplayText(result.getDisplayText());
response.setPublic(result.isPublicTemplate());
response.setCreated(result.getCreated());
response.setFormat(result.getFormat());
response.setOsTypeId(result.getGuestOSUuid());
response.setOsTypeName(result.getGuestOSName());
response.setOsTypeCategoryId(result.getGuestOSCategoryId());
response.setBootable(result.isBootable());
response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName());
response.setDynamicallyScalable(result.isDynamicallyScalable());
// populate owner.
ApiResponseHelper.populateOwner(response, result);
// set details map
if (result.getDetailName() != null) {
Map<String, String> details = new HashMap<>();
details.put(result.getDetailName(), result.getDetailValue());
response.setDetails(details);
}
if (result.getUserDataId() != null) {
response.setUserDataId(result.getUserDataUUid());
response.setUserDataName(result.getUserDataName());
response.setUserDataParams(result.getUserDataParams());
response.setUserDataPolicy(result.getUserDataPolicy());
}
// update tag information
long tag_id = result.getTagId();
if (tag_id > 0) {
ResourceTagJoinVO vtag = ApiDBUtils.findResourceTagViewById(tag_id);
if (vtag != null) {
response.addTag(ApiDBUtils.newResourceTagResponse(vtag, false));
}
}
response.setObjectName("iso");
return response;
}
@Override
public TemplateResponse setTemplateResponse(EnumSet<ApiConstants.DomainDetails> detailsView, ResponseView view, TemplateResponse templateResponse, TemplateJoinVO template) {
if (detailsView.contains(ApiConstants.DomainDetails.all)) {
// update details map
String key = template.getDetailName();
if (key != null) {
templateResponse.addDetail(key, template.getDetailValue());
}
}
// update tag information
long tag_id = template.getTagId();
if (tag_id > 0) {
addTagInformation(template, templateResponse);
}
if (templateResponse.hasAnnotation() == null) {
templateResponse.setHasAnnotation(annotationDao.hasAnnotations(template.getUuid(), AnnotationService.EntityType.TEMPLATE.name(),
_accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
}
return templateResponse;
}
@Override
public TemplateResponse newIsoResponse(TemplateJoinVO iso, ResponseView view) {
TemplateResponse isoResponse = new TemplateResponse();
isoResponse.setId(iso.getUuid());
isoResponse.setName(iso.getName());
isoResponse.setDisplayText(iso.getDisplayText());
isoResponse.setPublic(iso.isPublicTemplate());
isoResponse.setExtractable(iso.isExtractable() && !(iso.getTemplateType() == TemplateType.PERHOST));
isoResponse.setCreated(iso.getCreatedOnStore());
isoResponse.setDynamicallyScalable(iso.isDynamicallyScalable());
isoResponse.setFormat(iso.getFormat());
if (iso.getTemplateType() == TemplateType.PERHOST) {
// for TemplateManager.XS_TOOLS_ISO and TemplateManager.VMWARE_TOOLS_ISO, we didn't download, but is ready to use.
isoResponse.setReady(true);
} else {
isoResponse.setReady(iso.getState() == ObjectInDataStoreStateMachine.State.Ready);
}
isoResponse.setBootable(iso.isBootable());
isoResponse.setFeatured(iso.isFeatured());
isoResponse.setCrossZones(iso.isCrossZones());
isoResponse.setPublic(iso.isPublicTemplate());
isoResponse.setChecksum(DigestHelper.getHashValueFromChecksumValue(iso.getChecksum()));
isoResponse.setOsTypeId(iso.getGuestOSUuid());
isoResponse.setOsTypeName(iso.getGuestOSName());
isoResponse.setOsTypeCategoryId(iso.getGuestOSCategoryId());
isoResponse.setBits(iso.getBits());
isoResponse.setPasswordEnabled(iso.isEnablePassword());
// populate owner.
ApiResponseHelper.populateOwner(isoResponse, iso);
Account caller = CallContext.current().getCallingAccount();
boolean isAdmin = false;
if ((caller == null) || _accountService.isAdmin(caller.getId())) {
isAdmin = true;
}
// If the user is an admin, add the template download status
if (isAdmin || caller.getId() == iso.getAccountId()) {
// add download status
String templateStatus = getTemplateStatus(iso);
if (templateStatus != null) {
isoResponse.setStatus(templateStatus);
}
isoResponse.setUrl(iso.getUrl());
List<TemplateDataStoreVO> isosInStore = _templateStoreDao.listByTemplateNotBypassed(iso.getId());
List<Map<String, String>> downloadProgressDetails = new ArrayList<>();
HashMap<String, String> downloadDetailInImageStores = null;
for (TemplateDataStoreVO isoInStore : isosInStore) {
downloadDetailInImageStores = new HashMap<>();
ImageStoreVO imageStore = dataStoreDao.findById(isoInStore.getDataStoreId());
if (imageStore != null) {
downloadDetailInImageStores.put("datastore", imageStore.getName());
if (view.equals(ResponseView.Full)) {
downloadDetailInImageStores.put("datastoreId", imageStore.getUuid());
downloadDetailInImageStores.put("datastoreRole", imageStore.getRole().name());
}
downloadDetailInImageStores.put("downloadPercent", Integer.toString(isoInStore.getDownloadPercent()));
downloadDetailInImageStores.put("downloadState", (isoInStore.getDownloadState() != null ? isoInStore.getDownloadState().toString() : ""));
downloadProgressDetails.add(downloadDetailInImageStores);
}
}
List<StoragePoolVO> poolsInZone = primaryDataStoreDao.listByDataCenterId(iso.getDataCenterId());
List<Long> poolIds = poolsInZone.stream().map(StoragePoolVO::getId).collect(Collectors.toList());
List<VMTemplateStoragePoolVO> isosInPool = templatePoolDao.listByTemplateId(iso.getId(), poolIds);
for (VMTemplateStoragePoolVO isoInPool : isosInPool) {
downloadDetailInImageStores = new HashMap<>();
StoragePoolVO storagePool = primaryDataStoreDao.findById(isoInPool.getDataStoreId());
if (storagePool != null) {
downloadDetailInImageStores.put("datastore", storagePool.getName());
if (view.equals(ResponseView.Full)) {
downloadDetailInImageStores.put("datastoreId", storagePool.getUuid());
downloadDetailInImageStores.put("datastoreRole", DataStoreRole.Primary.name());
}
downloadDetailInImageStores.put("downloadPercent", Integer.toString(isoInPool.getDownloadPercent()));
downloadDetailInImageStores.put("downloadState", (isoInPool.getDownloadState() != null ? isoInPool.getDownloadState().toString() : ""));
downloadProgressDetails.add(downloadDetailInImageStores);
}
}
isoResponse.setDownloadProgress(downloadProgressDetails);
}
if (iso.getDataCenterId() > 0) {
isoResponse.setZoneId(iso.getDataCenterUuid());
isoResponse.setZoneName(iso.getDataCenterName());
}
long isoSize = iso.getSize();
if (isoSize > 0) {
isoResponse.setSize(isoSize);
}
long isoPhysicalSize = iso.getPhysicalSize();
if (isoPhysicalSize > 0) {
isoResponse.setPhysicalSize(isoPhysicalSize);
}
if (iso.getUserDataId() != null) {
isoResponse.setUserDataId(iso.getUserDataUUid());
isoResponse.setUserDataName(iso.getUserDataName());
isoResponse.setUserDataParams(iso.getUserDataParams());
isoResponse.setUserDataPolicy(iso.getUserDataPolicy());
}
// update tag information
long tag_id = iso.getTagId();
if (tag_id > 0) {
ResourceTagJoinVO vtag = ApiDBUtils.findResourceTagViewById(tag_id);
if (vtag != null) {
isoResponse.addTag(ApiDBUtils.newResourceTagResponse(vtag, false));
}
}
isoResponse.setHasAnnotation(annotationDao.hasAnnotations(iso.getUuid(), AnnotationService.EntityType.ISO.name(),
_accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
isoResponse.setDirectDownload(iso.isDirectDownload());
if (iso.getArch() != null) {
isoResponse.setArch(iso.getArch().getType());
}
isoResponse.setObjectName("iso");
return isoResponse;
}
@Override
public List<TemplateJoinVO> newTemplateView(VirtualMachineTemplate template) {
SearchCriteria<TemplateJoinVO> sc = tmpltIdSearch.create();
sc.setParameters("id", template.getId());
return searchIncludingRemoved(sc, null, null, false);
}
@Override
public List<TemplateJoinVO> newTemplateView(VirtualMachineTemplate template, long zoneId, boolean readyOnly) {
SearchCriteria<TemplateJoinVO> sc = tmpltZoneSearch.create();
sc.setParameters("id", template.getId());
sc.setParameters("dataCenterId", zoneId);
if (readyOnly) {
sc.setParameters("state", TemplateState.Ready);
}
return searchIncludingRemoved(sc, null, null, false);
}
@Override
public List<TemplateJoinVO> searchByTemplateZonePair(Boolean showRemoved, String... idPairs) {
Filter searchFilter = new Filter(TemplateJoinVO.class, "sortKey", QueryService.SortKeyAscending.value(), null, null);
searchFilter.addOrderBy(TemplateJoinVO.class, "tempZonePair", QueryService.SortKeyAscending.value());
List<TemplateJoinVO> uvList = new ArrayList<TemplateJoinVO>();
if (idPairs == null || idPairs.length == 0) {
return uvList;
}
for (String idPair : idPairs) {
SearchCriteria<TemplateJoinVO> sc = buildTemplateZonePairSearchCriteria(idPair);
List<TemplateJoinVO> rows = searchIncludingRemoved(sc, searchFilter, null, false);
if (rows != null) {
uvList.addAll(rows);
}
}
return uvList;
}
private SearchCriteria<TemplateJoinVO> buildTemplateZonePairSearchCriteria(String idPair) {
if (idPair == null || idPair.isEmpty()) {
throw new IllegalArgumentException("template zone pair id is null or empty");
}
// eg "3124_3" → templateId=3124, dcId=3
String[] parts = idPair.split("_");
if (parts.length != 2) {
throw new IllegalArgumentException("unexpected template zone pair id format: " + idPair);
}
long templateId = Long.parseLong(parts[0]);
long dcId = Long.parseLong(parts[1]);
SearchCriteria<TemplateJoinVO> sc;
if (dcId == 0) {
sc = tmpltIdPairCrossZoneSearch.create();
sc.setParameters("template_dc_pair_templateid", templateId);
} else {
sc = tmpltIdPairSearch.create();
sc.setParameters("template_dc_pair_templateid", templateId);
sc.setParameters("template_dc_pair_dcid", dcId);
}
return sc;
}
@Override
public List<TemplateJoinVO> listActiveTemplates(long storeId) {
SearchCriteria<TemplateJoinVO> sc = activeTmpltSearch.create();
sc.setParameters("store_id", storeId);
sc.setParameters("type", TemplateType.USER);
sc.setParameters("templateState", VirtualMachineTemplate.State.Active);
return searchIncludingRemoved(sc, null, null, false);
}
@Override
public List<TemplateJoinVO> listPublicTemplates() {
SearchCriteria<TemplateJoinVO> sc = publicTmpltSearch.create();
sc.setParameters("public", Boolean.TRUE);
return listBy(sc);
}
@Override
public Pair<List<TemplateJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<TemplateJoinVO> sc, final Filter filter) {
List<TemplateJoinVO> objects = searchIncludingRemoved(sc, filter, null, false);
Integer count = getCountIncludingRemoved(sc);
return new Pair<List<TemplateJoinVO>, Integer>(objects, count);
}
// ============================================================================
// The standard Phase 1 path runs `SELECT DISTINCT temp_zone_pair FROM
// template_view WHERE ...` against a 13-table view.
//
// This bypass path issues hand-tuned SQL against only the 6 tables needed
// to compute the (template_id, data_center_id) pair: vm_template, account,
// template_store_ref, image_store, template_zone_ref, data_center. The OR
// join is replaced with COALESCE.
//
// Hard filters (tags, sharedAccountIds, domainPath, featured/community
// domain hierarchy) are not implemented here — TemplateListFilter#canBypass()
// returns false in those cases and the dispatcher falls back to the
// SearchBuilder path.
private static final Field TEMPLATE_JOIN_ID_FIELD;
private static final Field TEMPLATE_JOIN_PAIR_FIELD;
static {
try {
TEMPLATE_JOIN_ID_FIELD = findFieldUpHierarchy(TemplateJoinVO.class, "id");
TEMPLATE_JOIN_PAIR_FIELD = findFieldUpHierarchy(TemplateJoinVO.class, "tempZonePair");
TEMPLATE_JOIN_ID_FIELD.setAccessible(true);
TEMPLATE_JOIN_PAIR_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
private static Field findFieldUpHierarchy(Class<?> clazz, String name) throws NoSuchFieldException {
Class<?> c = clazz;
while (c != null) {
try {
return c.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
c = c.getSuperclass();
}
}
throw new NoSuchFieldException(name + " on " + clazz);
}
@Override
public Pair<List<TemplateJoinVO>, Integer> findDistinctTempZonePairs(TemplateListFilter filter) {
if (!filter.canBypass()) {
throw new IllegalArgumentException(
"findDistinctTempZonePairs called with unsupported filter (tags / sharedAccountIds / domainPath / domainIds populated). Caller should fall back to searchAndDistinctCount.");
}
List<Object> params = new ArrayList<>();
StringBuilder where = new StringBuilder(" WHERE 1=1");
appendCommonWhere(where, params, filter);
String fromClause = buildFromClause(filter);
String selectExpr = filter.showUnique
? "vt.id"
: "CONCAT(vt.id, '_', IFNULL(dc.id, 0))";
String dataSql = "SELECT DISTINCT " + selectExpr + " AS distinct_key, vt.sort_key"
+ " FROM " + fromClause
+ where
+ " ORDER BY vt.sort_key " + (filter.sortAscending ? "ASC" : "DESC")
+ ", distinct_key " + (filter.sortAscending ? "ASC" : "DESC")
+ buildLimitClause(filter);
String countSql = "SELECT COUNT(DISTINCT " + selectExpr + ")"
+ " FROM " + fromClause
+ where;
List<TemplateJoinVO> rows = executeDistinctQuery(dataSql, params, filter.showUnique);
int count = executeCountQuery(countSql, params);
return new Pair<>(rows, count);
}
/**
* Build the FROM clause. The 6-table base is always present; conditional
* joins are added when the filter actually needs them. Today only
* `domain` is conditional (used for {@code domainPathLike} predicates);
* `launch_permission` and `resource_tags` are added in later iterations.
*/
private String buildFromClause(TemplateListFilter filter) {
StringBuilder from = new StringBuilder()
.append("cloud.vm_template vt")
.append(" JOIN cloud.account a ON a.id = vt.account_id")
.append(" LEFT JOIN cloud.template_store_ref tsr")
.append(" ON tsr.template_id = vt.id AND tsr.store_role = 'Image' AND tsr.destroyed = 0")
.append(" LEFT JOIN cloud.image_store img")
.append(" ON img.id = tsr.store_id AND img.removed IS NULL")
.append(" LEFT JOIN cloud.template_zone_ref tzr")
.append(" ON tzr.template_id = vt.id AND tsr.store_id IS NULL AND tzr.removed IS NULL")
.append(" LEFT JOIN cloud.data_center dc")
.append(" ON dc.id = COALESCE(img.data_center_id, tzr.zone_id)");
if (filter.domainPathLike != null) {
from.append(" JOIN cloud.domain d ON d.id = a.domain_id");
}
return from.toString();
}
private void appendCommonWhere(StringBuilder where, List<Object> params, TemplateListFilter filter) {
if (filter.templateId != null) {
where.append(" AND vt.id = ?");
params.add(filter.templateId);
}
if (!filter.ids.isEmpty()) {
where.append(" AND vt.id IN ").append(inClausePlaceholders(filter.ids.size()));
params.addAll(filter.ids);
}
if (filter.keyword != null) {
where.append(" AND vt.name LIKE ?");
params.add("%" + filter.keyword + "%");
} else if (filter.name != null) {
where.append(" AND vt.name = ?");
params.add(filter.name);
}
if (filter.format != null) {
// searchForTemplatesInternal: format = 'ISO' for isos, format != 'ISO' otherwise.
String condition = filter.isIso ? "=" : "!=";
where.append(" AND vt.format " + condition + " 'ISO'");
}
if (filter.hypervisorType != null) {
where.append(" AND vt.hypervisor_type = ?");
params.add(filter.hypervisorType.toString());
}
if (!filter.availableHypervisors.isEmpty()) {
where.append(" AND vt.hypervisor_type IN ").append(inClausePlaceholders(filter.availableHypervisors.size()));
for (HypervisorType h : filter.availableHypervisors) {
params.add(h.toString());
}
}
if (filter.publicTemplate != null && !filter.publicOrAccountIdComposite) {
where.append(" AND vt.public = ?");
params.add(filter.publicTemplate ? 1 : 0);
}
if (filter.featured != null) {
where.append(" AND vt.featured = ?");
params.add(filter.featured ? 1 : 0);
}
if (filter.bootable != null) {
where.append(" AND vt.bootable = ?");
params.add(filter.bootable ? 1 : 0);
}
if (filter.parentTemplateId != null) {
where.append(" AND vt.parent_template_id = ?");
params.add(filter.parentTemplateId);
}
if (filter.excludeSystemTemplates) {
where.append(" AND vt.type != 'SYSTEM'");
}
if (filter.accountTypeNeq != null) {
where.append(" AND a.type != ?");
params.add(filter.accountTypeNeq.ordinal());
}
if (filter.accountTypeEq != null) {
where.append(" AND a.type = ?");
params.add(filter.accountTypeEq.ordinal());
}
// ACL — accountIds: either pure IN, or composite OR with public=true.
if (filter.publicOrAccountIdComposite) {
where.append(" AND (vt.public = 1");
if (!filter.accountIds.isEmpty()) {
where.append(" OR vt.account_id IN ").append(inClausePlaceholders(filter.accountIds.size()));
params.addAll(filter.accountIds);
}
where.append(")");
} else if (filter.publicOrDomainPathComposite) {
// all + non-admin + SkipProjectResources: (public OR domain.path LIKE)
where.append(" AND (vt.public = 1");
if (filter.domainPathLike != null) {
where.append(" OR d.path LIKE ?");
params.add(filter.domainPathLike);
}
where.append(")");
} else if (filter.accountIdRequiredForFilter && !filter.accountIds.isEmpty()) {
where.append(" AND vt.account_id IN ").append(inClausePlaceholders(filter.accountIds.size()));
params.addAll(filter.accountIds);
}
// Standalone domain_path predicate — used when domainPathLike is set
// outside any composite (self/selfexecutable + DOMAIN_ADMIN scoping).
if (filter.domainPathLike != null
&& !filter.publicOrDomainPathComposite) {
where.append(" AND d.path LIKE ?");
params.add(filter.domainPathLike);
}
if (filter.zoneId != null) {
// mirrors templateChecks(): zone match OR REGION-scoped store OR (ISO + PERHOST)
where.append(" AND (dc.id = ? OR img.scope = 'REGION' OR (vt.format = 'ISO' AND vt.type = 'PERHOST'))");
params.add(filter.zoneId);
}
if (filter.onlyReady) {
// mirrors templateChecks(): tsr Ready OR BAREMETAL format OR (ISO + PERHOST)
where.append(" AND (tsr.state = 'Ready' OR vt.format = 'BAREMETAL' OR (vt.format = 'ISO' AND vt.type = 'PERHOST'))");
}
if (!filter.showRemoved) {
where.append(" AND vt.removed IS NULL");
if (!filter.templateStates.isEmpty()) {
where.append(" AND vt.state IN ").append(inClausePlaceholders(filter.templateStates.size()));
for (VirtualMachineTemplate.State s : filter.templateStates) {
params.add(s.toString());
}
}
}
}
private String buildLimitClause(TemplateListFilter filter) {
if (filter.pageSize == null) {
return "";
}
long start = filter.startIndex == null ? 0L : filter.startIndex;
return " LIMIT " + start + ", " + filter.pageSize;
}
private static String inClausePlaceholders(int n) {
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < n; i++) {
if (i > 0) {
sb.append(',');
}
sb.append('?');
}
sb.append(')');
return sb.toString();
}
private List<TemplateJoinVO> executeDistinctQuery(String sql, List<Object> params, boolean showUnique) {
List<TemplateJoinVO> out = new ArrayList<>();
try (TransactionLegacy txn = TransactionLegacy.open("TemplateJoinDao");
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql)) {
bindParams(pstmt, params);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
TemplateJoinVO vo = new TemplateJoinVO();
if (showUnique) {
TEMPLATE_JOIN_ID_FIELD.setLong(vo, rs.getLong(1));
} else {
TEMPLATE_JOIN_PAIR_FIELD.set(vo, rs.getString(1));
}
out.add(vo);
}
}
} catch (SQLException | IllegalAccessException e) {
throw new CloudRuntimeException("findDistinctTempZonePairs data query failed: " + sql, e);
}
return out;
}
private int executeCountQuery(String sql, List<Object> params) {
try (TransactionLegacy txn = TransactionLegacy.open("TemplateJoinDao");
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql)) {
bindParams(pstmt, params);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
return 0;
}
} catch (SQLException e) {
throw new CloudRuntimeException("findDistinctTempZonePairs count query failed: " + sql, e);
}
}
private static void bindParams(PreparedStatement pstmt, List<Object> params) throws SQLException {
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(i + 1, params.get(i));
}
}
@Override
public List<TemplateJoinVO> findByDistinctIds(Long... ids) {
if (ids == null || ids.length == 0) {
return new ArrayList<TemplateJoinVO>();
}
Filter searchFilter = new Filter(TemplateJoinVO.class, "sortKey", QueryService.SortKeyAscending.value(), null, null);
searchFilter.addOrderBy(TemplateJoinVO.class, "tempZonePair", true);
SearchCriteria<TemplateJoinVO> sc = tmpltIdsSearch.create();
sc.setParameters("idsIN", ids);
return searchIncludingRemoved(sc, searchFilter, null, false);
}
}