| /* |
| * 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; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; |
| import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; |
| import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; |
| import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; |
| import org.apache.cloudstack.engine.subsystem.api.storage.Scope; |
| import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; |
| import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; |
| import org.apache.cloudstack.framework.async.AsyncCallFuture; |
| import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher; |
| import org.apache.cloudstack.framework.async.AsyncCompletionCallback; |
| import org.apache.cloudstack.framework.async.AsyncRpcContext; |
| import org.apache.cloudstack.framework.config.dao.ConfigurationDao; |
| import org.apache.cloudstack.framework.messagebus.MessageBus; |
| import org.apache.cloudstack.framework.messagebus.PublishScope; |
| import org.apache.cloudstack.storage.command.CommandResult; |
| import org.apache.cloudstack.storage.command.DeleteCommand; |
| import org.apache.cloudstack.storage.datastore.DataObjectManager; |
| import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; |
| import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; |
| import org.apache.cloudstack.storage.image.store.TemplateObject; |
| import org.apache.cloudstack.storage.to.TemplateObjectTO; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| import org.springframework.stereotype.Component; |
| |
| import com.cloud.agent.api.Answer; |
| import com.cloud.agent.api.storage.ListTemplateAnswer; |
| import com.cloud.agent.api.storage.ListTemplateCommand; |
| import com.cloud.agent.api.to.DatadiskTO; |
| import com.cloud.alert.AlertManager; |
| import com.cloud.configuration.Config; |
| import com.cloud.configuration.Resource; |
| import com.cloud.configuration.Resource.ResourceType; |
| import com.cloud.dc.DataCenterVO; |
| import com.cloud.dc.dao.ClusterDao; |
| import com.cloud.dc.dao.DataCenterDao; |
| import com.cloud.event.EventTypes; |
| import com.cloud.event.UsageEventUtils; |
| import com.cloud.exception.ResourceAllocationException; |
| import com.cloud.hypervisor.Hypervisor.HypervisorType; |
| import com.cloud.storage.DataStoreRole; |
| import com.cloud.storage.ImageStoreDetailsUtil; |
| import com.cloud.storage.ScopeType; |
| import com.cloud.storage.Storage; |
| import com.cloud.storage.Storage.ImageFormat; |
| import com.cloud.storage.Storage.TemplateType; |
| import com.cloud.storage.StorageManager; |
| import com.cloud.storage.StoragePool; |
| import com.cloud.storage.VMTemplateStorageResourceAssoc; |
| import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; |
| import com.cloud.storage.VMTemplateVO; |
| import com.cloud.storage.VMTemplateZoneVO; |
| import com.cloud.storage.dao.VMTemplateDao; |
| import com.cloud.storage.dao.VMTemplateZoneDao; |
| import com.cloud.storage.template.TemplateConstants; |
| import com.cloud.storage.template.TemplateProp; |
| import com.cloud.template.TemplateManager; |
| import com.cloud.template.VirtualMachineTemplate; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountManager; |
| import com.cloud.user.ResourceLimitService; |
| import com.cloud.utils.UriUtils; |
| import com.cloud.utils.db.GlobalLock; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.utils.fsm.NoTransitionException; |
| import com.cloud.utils.fsm.StateMachine2; |
| import com.cloud.vm.VmDetailConstants; |
| |
| @Component |
| public class TemplateServiceImpl implements TemplateService { |
| protected Logger logger = LogManager.getLogger(getClass()); |
| @Inject |
| ObjectInDataStoreManager _objectInDataStoreMgr; |
| @Inject |
| DataObjectManager _dataObjectMgr; |
| @Inject |
| DataStoreManager _storeMgr; |
| @Inject |
| DataMotionService _motionSrv; |
| @Inject |
| ResourceLimitService _resourceLimitMgr; |
| @Inject |
| AccountManager _accountMgr; |
| @Inject |
| AlertManager _alertMgr; |
| @Inject |
| VMTemplateDao _templateDao; |
| @Inject |
| TemplateDataStoreDao _vmTemplateStoreDao; |
| @Inject |
| DataCenterDao _dcDao = null; |
| @Inject |
| VMTemplateZoneDao _vmTemplateZoneDao; |
| @Inject |
| ClusterDao _clusterDao; |
| @Inject |
| TemplateDataFactory _templateFactory; |
| @Inject |
| EndPointSelector _epSelector; |
| @Inject |
| TemplateManager _tmpltMgr; |
| @Inject |
| ConfigurationDao _configDao; |
| @Inject |
| StorageCacheManager _cacheMgr; |
| @Inject |
| MessageBus _messageBus; |
| @Inject |
| ImageStoreDetailsUtil imageStoreDetailsUtil; |
| @Inject |
| TemplateDataFactory imageFactory; |
| |
| class TemplateOpContext<T> extends AsyncRpcContext<T> { |
| final TemplateObject template; |
| final AsyncCallFuture<TemplateApiResult> future; |
| |
| public TemplateOpContext(AsyncCompletionCallback<T> callback, TemplateObject template, AsyncCallFuture<TemplateApiResult> future) { |
| super(callback); |
| this.template = template; |
| this.future = future; |
| } |
| |
| public TemplateObject getTemplate() { |
| return template; |
| } |
| |
| public AsyncCallFuture<TemplateApiResult> getFuture() { |
| return future; |
| } |
| |
| } |
| |
| @Override |
| public void createTemplateAsync(TemplateInfo template, DataStore store, AsyncCompletionCallback<TemplateApiResult> callback) { |
| // persist template_store_ref entry |
| TemplateObject templateOnStore = (TemplateObject)store.create(template); |
| // update template_store_ref and template state |
| try { |
| templateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.CreateOnlyRequested); |
| } catch (Exception e) { |
| TemplateApiResult result = new TemplateApiResult(templateOnStore); |
| result.setResult(e.toString()); |
| result.setSuccess(false); |
| if (callback != null) { |
| callback.complete(result); |
| } |
| return; |
| } |
| |
| try { |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(callback, templateOnStore, null); |
| |
| AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().createTemplateCallback(null, null)).setContext(context); |
| store.getDriver().createAsync(store, templateOnStore, caller); |
| } catch (CloudRuntimeException ex) { |
| // clean up already persisted template_store_ref entry in case of createTemplateCallback is never called |
| TemplateDataStoreVO templateStoreVO = _vmTemplateStoreDao.findByStoreTemplate(store.getId(), template.getId()); |
| if (templateStoreVO != null) { |
| TemplateInfo tmplObj = _templateFactory.getTemplate(template, store, null); |
| tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); |
| } |
| TemplateApiResult result = new TemplateApiResult(template); |
| result.setResult(ex.getMessage()); |
| if (callback != null) { |
| callback.complete(result); |
| } |
| } |
| } |
| |
| @Override |
| public void downloadBootstrapSysTemplate(DataStore store) { |
| Set<VMTemplateVO> toBeDownloaded = new HashSet(); |
| |
| List<VMTemplateVO> rtngTmplts = _templateDao.listAllSystemVMTemplates(); |
| |
| for (VMTemplateVO rtngTmplt : rtngTmplts) { |
| toBeDownloaded.add(rtngTmplt); |
| } |
| |
| List<HypervisorType> availHypers = _clusterDao.getAvailableHypervisorInZone(store.getScope().getScopeId()); |
| if (availHypers.isEmpty()) { |
| /* |
| * This is for cloudzone, local secondary storage resource started |
| * before cluster created |
| */ |
| availHypers.add(HypervisorType.KVM); |
| } |
| /* Baremetal need not to download any template */ |
| availHypers.remove(HypervisorType.BareMetal); |
| availHypers.add(HypervisorType.None); // bug 9809: resume ISO |
| // download. |
| |
| for (VMTemplateVO template : toBeDownloaded) { |
| if (availHypers.contains(template.getHypervisorType())) { |
| // only download sys template applicable for current hypervisor |
| TemplateDataStoreVO tmpltHost = _vmTemplateStoreDao.findByStoreTemplate(store.getId(), template.getId()); |
| if (tmpltHost == null || tmpltHost.getState() != ObjectInDataStoreStateMachine.State.Ready) { |
| TemplateInfo tmplt = _templateFactory.getTemplate(template.getId(), DataStoreRole.Image); |
| createTemplateAsync(tmplt, store, null); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void handleSysTemplateDownload(HypervisorType hostHyper, Long dcId) { |
| Set<VMTemplateVO> toBeDownloaded = new HashSet<VMTemplateVO>(); |
| List<DataStore> stores = _storeMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(dcId)); |
| if (stores == null || stores.isEmpty()) { |
| return; |
| } |
| |
| /* Download all the templates in zone with the same hypervisortype */ |
| for (DataStore store : stores) { |
| List<VMTemplateVO> rtngTmplts = _templateDao.listAllSystemVMTemplates(); |
| List<VMTemplateVO> defaultBuiltin = _templateDao.listDefaultBuiltinTemplates(); |
| |
| for (VMTemplateVO rtngTmplt : rtngTmplts) { |
| if (rtngTmplt.getHypervisorType() == hostHyper && !rtngTmplt.isDirectDownload()) { |
| toBeDownloaded.add(rtngTmplt); |
| } |
| } |
| |
| for (VMTemplateVO builtinTmplt : defaultBuiltin) { |
| if (builtinTmplt.getHypervisorType() == hostHyper && !builtinTmplt.isDirectDownload()) { |
| toBeDownloaded.add(builtinTmplt); |
| } |
| } |
| |
| for (VMTemplateVO template : toBeDownloaded) { |
| TemplateDataStoreVO tmpltHost = _vmTemplateStoreDao.findByStoreTemplate(store.getId(), template.getId()); |
| if (tmpltHost == null) { |
| associateTemplateToZone(template.getId(), dcId); |
| logger.info("Downloading builtin template " + template.getUniqueName() + " to data center: " + dcId); |
| TemplateInfo tmplt = _templateFactory.getTemplate(template.getId(), DataStoreRole.Image); |
| createTemplateAsync(tmplt, store, null); |
| } |
| } |
| } |
| } |
| |
| protected boolean isSkipTemplateStoreDownload(VMTemplateVO template, Long zoneId) { |
| if (template.isPublicTemplate()) { |
| return false; |
| } |
| if (template.isFeatured()) { |
| return false; |
| } |
| if (TemplateType.SYSTEM.equals(template.getTemplateType())) { |
| return false; |
| } |
| if (zoneId != null && _vmTemplateStoreDao.findByTemplateZone(template.getId(), zoneId, DataStoreRole.Image) == null) { |
| logger.debug(String.format("Template %s is not present on any image store for the zone ID: %d, its download cannot be skipped", template.getUniqueName(), zoneId)); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void handleTemplateSync(DataStore store) { |
| if (store == null) { |
| logger.warn("Huh? image store is null"); |
| return; |
| } |
| long storeId = store.getId(); |
| |
| // add lock to make template sync for a data store only be done once |
| String lockString = "templatesync.storeId:" + storeId; |
| GlobalLock syncLock = GlobalLock.getInternLock(lockString); |
| try { |
| if (syncLock.lock(3)) { |
| try { |
| Long zoneId = store.getScope().getScopeId(); |
| |
| Map<String, TemplateProp> templateInfos = listTemplate(store); |
| if (templateInfos == null) { |
| return; |
| } |
| |
| Set<VMTemplateVO> toBeDownloaded = new HashSet<VMTemplateVO>(); |
| List<VMTemplateVO> allTemplates = null; |
| if (zoneId == null) { |
| // region wide store |
| allTemplates = _templateDao.listByState(VirtualMachineTemplate.State.Active, VirtualMachineTemplate.State.NotUploaded, VirtualMachineTemplate.State.UploadInProgress); |
| } else { |
| // zone wide store |
| allTemplates = _templateDao.listInZoneByState(zoneId, VirtualMachineTemplate.State.Active, VirtualMachineTemplate.State.NotUploaded, VirtualMachineTemplate.State.UploadInProgress); |
| } |
| List<VMTemplateVO> rtngTmplts = _templateDao.listAllSystemVMTemplates(); |
| List<VMTemplateVO> defaultBuiltin = _templateDao.listDefaultBuiltinTemplates(); |
| |
| if (rtngTmplts != null) { |
| for (VMTemplateVO rtngTmplt : rtngTmplts) { |
| if (!allTemplates.contains(rtngTmplt)) { |
| allTemplates.add(rtngTmplt); |
| } |
| } |
| } |
| |
| if (defaultBuiltin != null) { |
| for (VMTemplateVO builtinTmplt : defaultBuiltin) { |
| if (!allTemplates.contains(builtinTmplt)) { |
| allTemplates.add(builtinTmplt); |
| } |
| } |
| } |
| |
| for (Iterator<VMTemplateVO> iter = allTemplates.listIterator(); iter.hasNext();) { |
| VMTemplateVO child_template = iter.next(); |
| if (child_template.getParentTemplateId() != null) { |
| String uniqueName = child_template.getUniqueName(); |
| if (templateInfos.containsKey(uniqueName)) { |
| templateInfos.remove(uniqueName); |
| } |
| iter.remove(); |
| } |
| } |
| |
| toBeDownloaded.addAll(allTemplates); |
| |
| final StateMachine2<VirtualMachineTemplate.State, VirtualMachineTemplate.Event, VirtualMachineTemplate> stateMachine = VirtualMachineTemplate.State.getStateMachine(); |
| Boolean followRedirect = StorageManager.DataStoreDownloadFollowRedirects.value(); |
| for (VMTemplateVO tmplt : allTemplates) { |
| String uniqueName = tmplt.getUniqueName(); |
| TemplateDataStoreVO tmpltStore = _vmTemplateStoreDao.findByStoreTemplate(storeId, tmplt.getId()); |
| if (templateInfos.containsKey(uniqueName)) { |
| TemplateProp tmpltInfo = templateInfos.remove(uniqueName); |
| toBeDownloaded.remove(tmplt); |
| if (tmpltStore != null) { |
| logger.info("Template Sync found " + uniqueName + " already in the image store"); |
| if (tmpltStore.getDownloadState() != Status.DOWNLOADED) { |
| tmpltStore.setErrorString(""); |
| } |
| if (tmpltInfo.isCorrupted()) { |
| tmpltStore.setDownloadState(Status.DOWNLOAD_ERROR); |
| String msg = "Template " + tmplt.getName() + ":" + tmplt.getId() + " is corrupted on secondary storage " + tmpltStore.getId(); |
| tmpltStore.setErrorString(msg); |
| logger.info(msg); |
| _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED, zoneId, null, msg, msg); |
| if (tmplt.getState() == VirtualMachineTemplate.State.NotUploaded || tmplt.getState() == VirtualMachineTemplate.State.UploadInProgress) { |
| logger.info("Template Sync found " + uniqueName + " on image store " + storeId + " uploaded using SSVM as corrupted, marking it as failed"); |
| tmpltStore.setState(State.Failed); |
| try { |
| stateMachine.transitTo(tmplt, VirtualMachineTemplate.Event.OperationFailed, null, _templateDao); |
| } catch (NoTransitionException e) { |
| logger.error("Unexpected state transition exception for template " + tmplt.getName() + ". Details: " + e.getMessage()); |
| } |
| } else if (tmplt.getUrl() == null) { |
| msg = "Private template (" + tmplt + ") with install path " + tmpltInfo.getInstallPath() + " is corrupted, please check in image store: " + tmpltStore.getDataStoreId(); |
| logger.warn(msg); |
| } else { |
| logger.info("Removing template_store_ref entry for corrupted template " + tmplt.getName()); |
| _vmTemplateStoreDao.remove(tmpltStore.getId()); |
| toBeDownloaded.add(tmplt); |
| } |
| } else { |
| if(tmpltStore.getDownloadState() != Status.DOWNLOADED) { |
| String etype = EventTypes.EVENT_TEMPLATE_CREATE; |
| if (tmplt.getFormat() == ImageFormat.ISO) { |
| etype = EventTypes.EVENT_ISO_CREATE; |
| } |
| |
| if (zoneId != null) { |
| UsageEventUtils.publishUsageEvent(etype, tmplt.getAccountId(), zoneId, tmplt.getId(), tmplt.getName(), null, null, |
| tmpltInfo.getPhysicalSize(), tmpltInfo.getSize(), VirtualMachineTemplate.class.getName(), tmplt.getUuid()); |
| } |
| } |
| |
| tmpltStore.setDownloadPercent(100); |
| tmpltStore.setDownloadState(Status.DOWNLOADED); |
| tmpltStore.setState(ObjectInDataStoreStateMachine.State.Ready); |
| tmpltStore.setInstallPath(tmpltInfo.getInstallPath()); |
| tmpltStore.setSize(tmpltInfo.getSize()); |
| tmpltStore.setPhysicalSize(tmpltInfo.getPhysicalSize()); |
| tmpltStore.setLastUpdated(new Date()); |
| // update size in vm_template table |
| VMTemplateVO tmlpt = _templateDao.findById(tmplt.getId()); |
| tmlpt.setSize(tmpltInfo.getSize()); |
| _templateDao.update(tmplt.getId(), tmlpt); |
| |
| if (tmplt.getState() == VirtualMachineTemplate.State.NotUploaded || tmplt.getState() == VirtualMachineTemplate.State.UploadInProgress) { |
| VirtualMachineTemplate.Event event = VirtualMachineTemplate.Event.OperationSucceeded; |
| // For multi-disk OVA, check and create data disk templates |
| if (tmplt.getFormat().equals(ImageFormat.OVA)) { |
| if (!createOvaDataDiskTemplates(_templateFactory.getTemplate(tmlpt.getId(), store), tmplt.isDeployAsIs())) { |
| event = VirtualMachineTemplate.Event.OperationFailed; |
| } |
| } |
| try { |
| stateMachine.transitTo(tmplt, event, null, _templateDao); |
| } catch (NoTransitionException e) { |
| logger.error("Unexpected state transition exception for template " + tmplt.getName() + ". Details: " + e.getMessage()); |
| } |
| } |
| |
| // Skipping limit checks for SYSTEM Account and for the templates created from volumes or snapshots |
| // which already got checked and incremented during createTemplate API call. |
| if (tmpltInfo.getSize() > 0 && tmplt.getAccountId() != Account.ACCOUNT_ID_SYSTEM && tmplt.getUrl() != null) { |
| long accountId = tmplt.getAccountId(); |
| try { |
| _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(accountId), |
| com.cloud.configuration.Resource.ResourceType.secondary_storage, |
| tmpltInfo.getSize() - UriUtils.getRemoteSize(tmplt.getUrl(), |
| followRedirect)); |
| } catch (ResourceAllocationException e) { |
| logger.warn(e.getMessage()); |
| _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED, zoneId, null, e.getMessage(), e.getMessage()); |
| } finally { |
| _resourceLimitMgr.recalculateResourceCount(accountId, _accountMgr.getAccount(accountId).getDomainId(), |
| com.cloud.configuration.Resource.ResourceType.secondary_storage.getOrdinal()); |
| } |
| } |
| } |
| _vmTemplateStoreDao.update(tmpltStore.getId(), tmpltStore); |
| } else { |
| tmpltStore = new TemplateDataStoreVO(storeId, tmplt.getId(), new Date(), 100, Status.DOWNLOADED, null, null, null, tmpltInfo.getInstallPath(), tmplt.getUrl()); |
| tmpltStore.setSize(tmpltInfo.getSize()); |
| tmpltStore.setPhysicalSize(tmpltInfo.getPhysicalSize()); |
| tmpltStore.setDataStoreRole(store.getRole()); |
| _vmTemplateStoreDao.persist(tmpltStore); |
| |
| // update size in vm_template table |
| VMTemplateVO tmlpt = _templateDao.findById(tmplt.getId()); |
| tmlpt.setSize(tmpltInfo.getSize()); |
| _templateDao.update(tmplt.getId(), tmlpt); |
| associateTemplateToZone(tmplt.getId(), zoneId); |
| |
| String etype = EventTypes.EVENT_TEMPLATE_CREATE; |
| if (tmplt.getFormat() == ImageFormat.ISO) { |
| etype = EventTypes.EVENT_ISO_CREATE; |
| } |
| |
| UsageEventUtils.publishUsageEvent(etype, tmplt.getAccountId(), zoneId, tmplt.getId(), tmplt.getName(), null, null, |
| tmpltInfo.getPhysicalSize(), tmpltInfo.getSize(), VirtualMachineTemplate.class.getName(), tmplt.getUuid()); |
| } |
| } else if (tmplt.getState() == VirtualMachineTemplate.State.NotUploaded || tmplt.getState() == VirtualMachineTemplate.State.UploadInProgress) { |
| logger.info("Template Sync did not find " + uniqueName + " on image store " + storeId + " uploaded using SSVM, marking it as failed"); |
| toBeDownloaded.remove(tmplt); |
| tmpltStore.setDownloadState(Status.DOWNLOAD_ERROR); |
| String msg = "Template " + tmplt.getName() + ":" + tmplt.getId() + " is corrupted on secondary storage " + tmpltStore.getId(); |
| tmpltStore.setErrorString(msg); |
| tmpltStore.setState(State.Failed); |
| _vmTemplateStoreDao.update(tmpltStore.getId(), tmpltStore); |
| try { |
| stateMachine.transitTo(tmplt, VirtualMachineTemplate.Event.OperationFailed, null, _templateDao); |
| } catch (NoTransitionException e) { |
| logger.error("Unexpected state transition exception for template " + tmplt.getName() + ". Details: " + e.getMessage()); |
| } |
| } else if (tmplt.isDirectDownload()) { |
| logger.info("Template " + tmplt.getName() + ":" + tmplt.getId() + " is marked for direct download, discarding it for download on image stores"); |
| toBeDownloaded.remove(tmplt); |
| } else { |
| logger.info("Template Sync did not find " + uniqueName + " on image store " + storeId + ", may request download based on available hypervisor types"); |
| if (tmpltStore != null) { |
| if (_storeMgr.isRegionStore(store) && tmpltStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED |
| && tmpltStore.getState() == State.Ready |
| && tmpltStore.getInstallPath() == null) { |
| logger.info("Keep fake entry in template store table for migration of previous NFS to object store"); |
| } else { |
| logger.info("Removing leftover template " + uniqueName + " entry from template store table"); |
| // remove those leftover entries |
| _vmTemplateStoreDao.remove(tmpltStore.getId()); |
| } |
| } |
| } |
| } |
| |
| if (toBeDownloaded.size() > 0) { |
| /* Only download templates whose hypervirsor type is in the zone */ |
| List<HypervisorType> availHypers = _clusterDao.getAvailableHypervisorInZone(zoneId); |
| if (availHypers.isEmpty()) { |
| /* |
| * This is for cloudzone, local secondary storage resource |
| * started before cluster created |
| */ |
| availHypers.add(HypervisorType.KVM); |
| } |
| /* Baremetal need not to download any template */ |
| availHypers.remove(HypervisorType.BareMetal); |
| availHypers.add(HypervisorType.None); // bug 9809: resume ISO |
| // download. |
| for (VMTemplateVO tmplt : toBeDownloaded) { |
| if (tmplt.getUrl() == null) { // If url is null, skip downloading |
| logger.info("Skip downloading template " + tmplt.getUniqueName() + " since no url is specified."); |
| continue; |
| } |
| // if this is private template, skip sync to a new image store |
| if (isSkipTemplateStoreDownload(tmplt, zoneId)) { |
| logger.info("Skip sync downloading private template " + tmplt.getUniqueName() + " to a new image store"); |
| continue; |
| } |
| |
| // if this is a region store, and there is already an DOWNLOADED entry there without install_path information, which |
| // means that this is a duplicate entry from migration of previous NFS to staging. |
| if (_storeMgr.isRegionStore(store)) { |
| TemplateDataStoreVO tmpltStore = _vmTemplateStoreDao.findByStoreTemplate(storeId, tmplt.getId()); |
| if (tmpltStore != null && tmpltStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED && tmpltStore.getState() == State.Ready |
| && tmpltStore.getInstallPath() == null) { |
| logger.info("Skip sync template for migration of previous NFS to object store"); |
| continue; |
| } |
| } |
| |
| if (availHypers.contains(tmplt.getHypervisorType())) { |
| logger.info("Downloading template " + tmplt.getUniqueName() + " to image store " + store.getName()); |
| associateTemplateToZone(tmplt.getId(), zoneId); |
| TemplateInfo tmpl = _templateFactory.getTemplate(tmplt.getId(), store); |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<>(null,(TemplateObject)tmpl, null); |
| AsyncCallbackDispatcher<TemplateServiceImpl, TemplateApiResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().createTemplateAsyncCallBack(null, null)); |
| caller.setContext(context); |
| createTemplateAsync(tmpl, store, caller); |
| } else { |
| logger.info("Skip downloading template " + tmplt.getUniqueName() + " since current data center does not have hypervisor " + |
| tmplt.getHypervisorType().toString()); |
| } |
| } |
| } |
| |
| for (String uniqueName : templateInfos.keySet()) { |
| TemplateProp tInfo = templateInfos.get(uniqueName); |
| if (_tmpltMgr.templateIsDeleteable(tInfo.getId())) { |
| // we cannot directly call deleteTemplateSync here to reuse delete logic since in this case db does not have this template at all. |
| TemplateObjectTO tmplTO = new TemplateObjectTO(); |
| tmplTO.setDataStore(store.getTO()); |
| tmplTO.setPath(tInfo.getInstallPath()); |
| tmplTO.setId(tInfo.getId()); |
| DeleteCommand dtCommand = new DeleteCommand(tmplTO); |
| EndPoint ep = _epSelector.select(store); |
| Answer answer = null; |
| if (ep == null) { |
| String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| logger.error(errMsg); |
| answer = new Answer(dtCommand, false, errMsg); |
| } else { |
| answer = ep.sendMessage(dtCommand); |
| } |
| if (answer == null || !answer.getResult()) { |
| logger.info("Failed to deleted template at store: " + store.getName()); |
| |
| } else { |
| String description = "Deleted template " + tInfo.getTemplateName() + " on secondary storage " + storeId; |
| logger.info(description); |
| } |
| |
| } |
| } |
| } finally { |
| syncLock.unlock(); |
| } |
| } else { |
| logger.info("Couldn't get global lock on " + lockString + ", another thread may be doing template sync on data store " + storeId + " now."); |
| } |
| } finally { |
| syncLock.releaseRef(); |
| } |
| |
| } |
| |
| // persist entry in template_zone_ref table. zoneId can be empty for |
| // region-wide image store, in that case, |
| // we will associate the template to all the zones. |
| @Override |
| public void associateTemplateToZone(long templateId, Long zoneId) { |
| List<Long> dcs = new ArrayList<Long>(); |
| if (zoneId != null) { |
| dcs.add(zoneId); |
| } else { |
| List<DataCenterVO> zones = _dcDao.listAll(); |
| for (DataCenterVO zone : zones) { |
| dcs.add(zone.getId()); |
| } |
| } |
| for (Long id : dcs) { |
| VMTemplateZoneVO tmpltZoneVO = _vmTemplateZoneDao.findByZoneTemplate(id, templateId); |
| if (tmpltZoneVO == null) { |
| tmpltZoneVO = new VMTemplateZoneVO(id, templateId, new Date()); |
| _vmTemplateZoneDao.persist(tmpltZoneVO); |
| } else { |
| tmpltZoneVO.setLastUpdated(new Date()); |
| _vmTemplateZoneDao.update(tmpltZoneVO.getId(), tmpltZoneVO); |
| } |
| } |
| } |
| |
| // update template_zone_ref for cross-zone template for newly added zone |
| @Override |
| public void associateCrosszoneTemplatesToZone(long dcId) { |
| VMTemplateZoneVO tmpltZone; |
| |
| List<VMTemplateVO> allTemplates = _templateDao.listAll(); |
| for (VMTemplateVO vt : allTemplates) { |
| if (vt.isCrossZones()) { |
| tmpltZone = _vmTemplateZoneDao.findByZoneTemplate(dcId, vt.getId()); |
| if (tmpltZone == null) { |
| VMTemplateZoneVO vmTemplateZone = new VMTemplateZoneVO(dcId, vt.getId(), new Date()); |
| _vmTemplateZoneDao.persist(vmTemplateZone); |
| } |
| } |
| } |
| } |
| |
| protected Void createTemplateAsyncCallBack(AsyncCallbackDispatcher<TemplateServiceImpl, TemplateApiResult> callback, |
| TemplateOpContext<TemplateApiResult> context) { |
| TemplateInfo template = context.template; |
| TemplateApiResult result = callback.getResult(); |
| if (result.isSuccess()) { |
| VMTemplateVO tmplt = _templateDao.findById(template.getId()); |
| // need to grant permission for public templates |
| if (tmplt.isPublicTemplate()) { |
| _messageBus.publish(null, TemplateManager.MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT, PublishScope.LOCAL, tmplt.getId()); |
| } |
| long accountId = tmplt.getAccountId(); |
| if (template.getSize() != null) { |
| // publish usage event |
| String etype = EventTypes.EVENT_TEMPLATE_CREATE; |
| if (tmplt.getFormat() == ImageFormat.ISO) { |
| etype = EventTypes.EVENT_ISO_CREATE; |
| } |
| // get physical size from template_store_ref table |
| long physicalSize = 0; |
| DataStore ds = template.getDataStore(); |
| TemplateDataStoreVO tmpltStore = _vmTemplateStoreDao.findByStoreTemplate(ds.getId(), template.getId()); |
| if (tmpltStore != null) { |
| physicalSize = tmpltStore.getPhysicalSize(); |
| } else { |
| logger.warn("No entry found in template_store_ref for template id: " + template.getId() + " and image store id: " + ds.getId() + |
| " at the end of registering template!"); |
| } |
| Scope dsScope = ds.getScope(); |
| if (dsScope.getScopeId() != null) { |
| UsageEventUtils.publishUsageEvent(etype, template.getAccountId(), dsScope.getScopeId(), template.getId(), template.getName(), null, null, |
| physicalSize, template.getSize(), VirtualMachineTemplate.class.getName(), template.getUuid()); |
| } else { |
| logger.warn("Zone scope image store " + ds.getId() + " has a null scope id"); |
| } |
| _resourceLimitMgr.incrementResourceCount(accountId, Resource.ResourceType.secondary_storage, template.getSize()); |
| } |
| } |
| |
| return null; |
| } |
| |
| private Map<String, TemplateProp> listTemplate(DataStore ssStore) { |
| String nfsVersion = imageStoreDetailsUtil.getNfsVersion(ssStore.getId()); |
| ListTemplateCommand cmd = new ListTemplateCommand(ssStore.getTO(), nfsVersion); |
| EndPoint ep = _epSelector.select(ssStore); |
| Answer answer = null; |
| if (ep == null) { |
| String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| logger.error(errMsg); |
| answer = new Answer(cmd, false, errMsg); |
| } else { |
| answer = ep.sendMessage(cmd); |
| } |
| if (answer != null && answer.getResult()) { |
| ListTemplateAnswer tanswer = (ListTemplateAnswer)answer; |
| return tanswer.getTemplateInfo(); |
| } else { |
| if (logger.isDebugEnabled()) { |
| logger.debug("can not list template for secondary storage host " + ssStore.getId()); |
| } |
| } |
| |
| return null; |
| } |
| |
| protected Void createTemplateCallback(AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> callback, TemplateOpContext<TemplateApiResult> context) { |
| TemplateObject template = context.getTemplate(); |
| AsyncCompletionCallback<TemplateApiResult> parentCallback = context.getParentCallback(); |
| TemplateApiResult result = new TemplateApiResult(template); |
| CreateCmdResult callbackResult = callback.getResult(); |
| if (callbackResult.isFailed()) { |
| template.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); |
| result.setResult(callbackResult.getResult()); |
| if (parentCallback != null) { |
| parentCallback.complete(result); |
| } |
| return null; |
| } |
| |
| // For multi-disk OVA, check and create data disk templates |
| if (template.getFormat().equals(ImageFormat.OVA)) { |
| if (!createOvaDataDiskTemplates(template, template.isDeployAsIs())) { |
| template.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); |
| result.setResult(callbackResult.getResult()); |
| if (parentCallback != null) { |
| parentCallback.complete(result); |
| } |
| return null; |
| } |
| } |
| |
| try { |
| template.processEvent(ObjectInDataStoreStateMachine.Event.OperationSuccessed); |
| } catch (Exception e) { |
| result.setResult(e.toString()); |
| if (parentCallback != null) { |
| parentCallback.complete(result); |
| } |
| return null; |
| } |
| |
| if (parentCallback != null) { |
| parentCallback.complete(result); |
| } |
| return null; |
| } |
| |
| @Override |
| public List<DatadiskTO> getTemplateDatadisksOnImageStore(TemplateInfo templateInfo, String configurationId) { |
| ImageStoreEntity tmpltStore = (ImageStoreEntity)templateInfo.getDataStore(); |
| return tmpltStore.getDataDiskTemplates(templateInfo, configurationId); |
| } |
| |
| @Override |
| public boolean createOvaDataDiskTemplates(TemplateInfo parentTemplate, boolean deployAsIs) { |
| try { |
| // Get Datadisk template (if any) for OVA |
| List<DatadiskTO> dataDiskTemplates = new ArrayList<DatadiskTO>(); |
| ImageStoreEntity tmpltStore = (ImageStoreEntity)parentTemplate.getDataStore(); |
| dataDiskTemplates = tmpltStore.getDataDiskTemplates(parentTemplate, null); |
| int diskCount = 0; |
| VMTemplateVO templateVO = _templateDao.findById(parentTemplate.getId()); |
| _templateDao.loadDetails(templateVO); |
| DataStore imageStore = parentTemplate.getDataStore(); |
| Map<String, String> details = parentTemplate.getDetails(); |
| if (details == null) { |
| details = templateVO.getDetails(); |
| if (details == null) { |
| details = new HashMap<>(); |
| } |
| } |
| |
| for (DatadiskTO diskTemplate : dataDiskTemplates) { |
| if (!deployAsIs) { |
| if (!diskTemplate.isBootable()) { |
| createChildDataDiskTemplate(diskTemplate, templateVO, parentTemplate, imageStore, diskCount++); |
| if (!diskTemplate.isIso() && StringUtils.isEmpty(details.get(VmDetailConstants.DATA_DISK_CONTROLLER))){ |
| details.put(VmDetailConstants.DATA_DISK_CONTROLLER, getOvaDiskControllerDetails(diskTemplate, false)); |
| details.put(VmDetailConstants.DATA_DISK_CONTROLLER + diskTemplate.getDiskId(), getOvaDiskControllerDetails(diskTemplate, false)); |
| } |
| } else { |
| finalizeParentTemplate(diskTemplate, templateVO, parentTemplate, imageStore, diskCount++); |
| if (StringUtils.isEmpty(VmDetailConstants.ROOT_DISK_CONTROLLER)) { |
| final String rootDiskController = getOvaDiskControllerDetails(diskTemplate, true); |
| if (StringUtils.isNotEmpty(rootDiskController)) { |
| details.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDiskController); |
| } |
| } |
| } |
| } |
| } |
| |
| templateVO.setDetails(details); |
| _templateDao.saveDetails(templateVO); |
| return true; |
| } catch (CloudRuntimeException | InterruptedException | ExecutionException e) { |
| return false; |
| } |
| } |
| |
| private boolean createChildDataDiskTemplate(DatadiskTO dataDiskTemplate, VMTemplateVO template, TemplateInfo parentTemplate, DataStore imageStore, int diskCount) throws ExecutionException, InterruptedException { |
| // Make an entry in vm_template table |
| Storage.ImageFormat format = dataDiskTemplate.isIso() ? Storage.ImageFormat.ISO : template.getFormat(); |
| String suffix = dataDiskTemplate.isIso() ? "-IsoDiskTemplate-" : "-DataDiskTemplate-"; |
| TemplateType ttype = dataDiskTemplate.isIso() ? TemplateType.ISODISK : TemplateType.DATADISK; |
| final long templateId = _templateDao.getNextInSequence(Long.class, "id"); |
| long guestOsId = dataDiskTemplate.isIso() ? 1 : 0; |
| String templateName = dataDiskTemplate.isIso() ? dataDiskTemplate.getPath().substring(dataDiskTemplate.getPath().lastIndexOf(File.separator) + 1) : template.getName() + suffix + diskCount; |
| VMTemplateVO templateVO = new VMTemplateVO(templateId, templateName, format, false, false, false, ttype, template.getUrl(), |
| template.requiresHvm(), template.getBits(), template.getAccountId(), null, templateName, false, guestOsId, false, template.getHypervisorType(), null, |
| null, false, false, false, false); |
| if (dataDiskTemplate.isIso()){ |
| templateVO.setUniqueName(templateName); |
| } |
| templateVO.setParentTemplateId(template.getId()); |
| templateVO.setSize(dataDiskTemplate.getVirtualSize()); |
| templateVO = _templateDao.persist(templateVO); |
| // Make sync call to create Datadisk templates in image store |
| TemplateApiResult result = null; |
| TemplateInfo dataDiskTemplateInfo = imageFactory.getTemplate(templateVO.getId(), imageStore); |
| AsyncCallFuture<TemplateApiResult> future = createDatadiskTemplateAsync(parentTemplate, dataDiskTemplateInfo, dataDiskTemplate.getPath(), dataDiskTemplate.getDiskId(), |
| dataDiskTemplate.getFileSize(), dataDiskTemplate.isBootable()); |
| result = future.get(); |
| if (result.isSuccess()) { |
| // Make an entry in template_zone_ref table |
| if (imageStore.getScope().getScopeType() == ScopeType.REGION) { |
| associateTemplateToZone(templateId, null); |
| } else if (imageStore.getScope().getScopeType() == ScopeType.ZONE) { |
| Long zoneId = ((ImageStoreEntity)imageStore).getDataCenterId(); |
| VMTemplateZoneVO templateZone = new VMTemplateZoneVO(zoneId, templateId, new Date()); |
| _vmTemplateZoneDao.persist(templateZone); |
| } |
| _resourceLimitMgr.incrementResourceCount(template.getAccountId(), ResourceType.secondary_storage, templateVO.getSize()); |
| } else { |
| // Delete the Datadisk templates that were already created as they are now invalid |
| logger.debug("Since creation of Datadisk template: " + templateVO.getId() + " failed, delete other Datadisk templates that were created as part of parent" |
| + " template download"); |
| TemplateInfo parentTemplateInfo = imageFactory.getTemplate(templateVO.getParentTemplateId(), imageStore); |
| cleanupDatadiskTemplates(parentTemplateInfo); |
| } |
| return result.isSuccess(); |
| } |
| |
| private boolean finalizeParentTemplate(DatadiskTO dataDiskTemplate, VMTemplateVO templateVO, TemplateInfo parentTemplate, DataStore imageStore, int diskCount) throws ExecutionException, InterruptedException, CloudRuntimeException { |
| TemplateInfo templateInfo = imageFactory.getTemplate(templateVO.getId(), imageStore); |
| AsyncCallFuture<TemplateApiResult> templateFuture = createDatadiskTemplateAsync(parentTemplate, templateInfo, dataDiskTemplate.getPath(), dataDiskTemplate.getDiskId(), |
| dataDiskTemplate.getFileSize(), dataDiskTemplate.isBootable()); |
| TemplateApiResult result = null; |
| result = templateFuture.get(); |
| if (!result.isSuccess()) { |
| logger.debug("Since creation of parent template: " + templateInfo.getId() + " failed, delete Datadisk templates that were created as part of parent" |
| + " template download"); |
| cleanupDatadiskTemplates(templateInfo); |
| } |
| return result.isSuccess(); |
| } |
| |
| private String getOvaDiskControllerDetails(DatadiskTO diskTemplate, boolean isRootDisk) { |
| String controller = diskTemplate.getDiskController() ; |
| String controllerSubType = diskTemplate.getDiskControllerSubType(); |
| |
| if (controller != null) { |
| controller = controller.toLowerCase(); |
| } |
| |
| if (controllerSubType != null) { |
| controllerSubType = controllerSubType.toLowerCase(); |
| } |
| |
| if (StringUtils.isNotBlank(controller)) { |
| if (controller.contains("ide")) { |
| return "ide"; |
| } |
| if (controller.contains("scsi")) { |
| if (StringUtils.isNotBlank(controllerSubType)) { |
| if (controllerSubType.equals("lsilogicsas")) { |
| return "lsisas1068"; |
| } |
| return controllerSubType; |
| } |
| if (!isRootDisk) { |
| return "scsi"; |
| } |
| } |
| if (!isRootDisk) { |
| return "osdefault"; |
| } |
| } |
| |
| // Root disk to use global setting vmware.root.disk.controller |
| if (!isRootDisk) { |
| return "scsi"; |
| } |
| return controller; |
| } |
| |
| private void cleanupDatadiskTemplates(TemplateInfo parentTemplateInfo) { |
| DataStore imageStore = parentTemplateInfo.getDataStore(); |
| List<VMTemplateVO> datadiskTemplatesToDelete = _templateDao.listByParentTemplatetId(parentTemplateInfo.getId()); |
| for (VMTemplateVO datadiskTemplateToDelete: datadiskTemplatesToDelete) { |
| logger.info("Delete template: " + datadiskTemplateToDelete.getId() + " from image store: " + imageStore.getName()); |
| AsyncCallFuture<TemplateApiResult> future = deleteTemplateAsync(imageFactory.getTemplate(datadiskTemplateToDelete.getId(), imageStore)); |
| try { |
| TemplateApiResult result = future.get(); |
| if (!result.isSuccess()) { |
| logger.warn("Failed to delete datadisk template: " + datadiskTemplateToDelete + " from image store: " + imageStore.getName() + " due to: " + result.getResult()); |
| break; |
| } |
| _vmTemplateZoneDao.deletePrimaryRecordsForTemplate(datadiskTemplateToDelete.getId()); |
| _resourceLimitMgr.decrementResourceCount(datadiskTemplateToDelete.getAccountId(), ResourceType.secondary_storage, datadiskTemplateToDelete.getSize()); |
| } catch (Exception e) { |
| logger.debug("Delete datadisk template failed", e); |
| throw new CloudRuntimeException("Delete template Failed", e); |
| } |
| } |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> deleteTemplateAsync(TemplateInfo template) { |
| TemplateObject to = (TemplateObject)template; |
| // update template_store_ref status |
| to.processEvent(ObjectInDataStoreStateMachine.Event.DestroyRequested); |
| |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); |
| |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, to, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CommandResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().deleteTemplateCallback(null, null)).setContext(context); |
| |
| if (to.canBeDeletedFromDataStore()) { |
| to.getDataStore().getDriver().deleteAsync(to.getDataStore(), to, caller); |
| } else { |
| CommandResult result = new CommandResult(); |
| caller.complete(result); |
| } |
| |
| return future; |
| } |
| |
| public Void deleteTemplateCallback(AsyncCallbackDispatcher<TemplateServiceImpl, CommandResult> callback, TemplateOpContext<TemplateApiResult> context) { |
| CommandResult result = callback.getResult(); |
| TemplateObject vo = context.getTemplate(); |
| if (result.isSuccess()) { |
| vo.processEvent(Event.OperationSuccessed); |
| } else { |
| vo.processEvent(Event.OperationFailed); |
| } |
| TemplateApiResult apiResult = new TemplateApiResult(vo); |
| apiResult.setResult(result.getResult()); |
| apiResult.setSuccess(result.isSuccess()); |
| context.future.complete(apiResult); |
| return null; |
| } |
| |
| private AsyncCallFuture<TemplateApiResult> copyAsync(DataObject source, TemplateInfo template, DataStore store) { |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); |
| DataObject templateOnStore = store.create(template); |
| templateOnStore.processEvent(Event.CreateOnlyRequested); |
| |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, (TemplateObject)templateOnStore, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().copyTemplateCallBack(null, null)).setContext(context); |
| _motionSrv.copyAsync(source, templateOnStore, caller); |
| return future; |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> createTemplateFromSnapshotAsync(SnapshotInfo snapshot, TemplateInfo template, DataStore store) { |
| return copyAsync(snapshot, template, store); |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> createTemplateFromVolumeAsync(VolumeInfo volume, TemplateInfo template, DataStore store) { |
| return copyAsync(volume, template, store); |
| } |
| |
| private AsyncCallFuture<TemplateApiResult> syncToRegionStoreAsync(TemplateInfo template, DataStore store) { |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); |
| // no need to create entry on template_store_ref here, since entries are already created when prepareSecondaryStorageForMigration is invoked. |
| // But we need to set default install path so that sync can be done in the right s3 path |
| TemplateInfo templateOnStore = _templateFactory.getTemplate(template, store, null); |
| String installPath = |
| TemplateConstants.DEFAULT_TMPLT_ROOT_DIR + "/" + TemplateConstants.DEFAULT_TMPLT_FIRST_LEVEL_DIR + template.getAccountId() + "/" + template.getId() + "/" + |
| template.getUniqueName(); |
| ((TemplateObject)templateOnStore).setInstallPath(installPath); |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, (TemplateObject)templateOnStore, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().syncTemplateCallBack(null, null)).setContext(context); |
| _motionSrv.copyAsync(template, templateOnStore, caller); |
| return future; |
| } |
| |
| protected Void syncTemplateCallBack(AsyncCallbackDispatcher<TemplateServiceImpl, CopyCommandResult> callback, TemplateOpContext<TemplateApiResult> context) { |
| TemplateInfo destTemplate = context.getTemplate(); |
| CopyCommandResult result = callback.getResult(); |
| AsyncCallFuture<TemplateApiResult> future = context.getFuture(); |
| TemplateApiResult res = new TemplateApiResult(destTemplate); |
| try { |
| if (result.isFailed()) { |
| res.setResult(result.getResult()); |
| // no change to existing template_store_ref, will try to re-sync later if other call triggers this sync operation, like copy template |
| } else { |
| // this will update install path properly, next time it will not sync anymore. |
| destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer()); |
| } |
| future.complete(res); |
| } catch (Exception e) { |
| logger.debug("Failed to process sync template callback", e); |
| res.setResult(e.toString()); |
| future.complete(res); |
| } |
| |
| return null; |
| } |
| |
| // This routine is used to push templates currently on cache store, but not in region store to region store. |
| // used in migrating existing NFS secondary storage to S3. |
| @Override |
| public void syncTemplateToRegionStore(long templateId, DataStore store) { |
| if (_storeMgr.isRegionStore(store)) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("Sync template " + templateId + " from cache to object store..."); |
| } |
| // if template is on region wide object store, check if it is really downloaded there (by checking install_path). Sync template to region |
| // wide store if it is not there physically. |
| TemplateInfo tmplOnStore = _templateFactory.getTemplate(templateId, store); |
| if (tmplOnStore == null) { |
| throw new CloudRuntimeException("Cannot find an entry in template_store_ref for template " + templateId + " on region store: " + store.getName()); |
| } |
| if (tmplOnStore.getInstallPath() == null || tmplOnStore.getInstallPath().length() == 0) { |
| // template is not on region store yet, sync to region store |
| TemplateInfo srcTemplate = _templateFactory.getReadyTemplateOnCache(templateId); |
| if (srcTemplate == null) { |
| throw new CloudRuntimeException("Cannot find template " + templateId + " on cache store"); |
| } |
| AsyncCallFuture<TemplateApiResult> future = syncToRegionStoreAsync(srcTemplate, store); |
| try { |
| TemplateApiResult result = future.get(); |
| if (result.isFailed()) { |
| throw new CloudRuntimeException("sync template from cache to region wide store failed for image store " + store.getName() + ":" + |
| result.getResult()); |
| } |
| _cacheMgr.releaseCacheObject(srcTemplate); // reduce reference count for template on cache, so it can recycled by schedule |
| } catch (Exception ex) { |
| throw new CloudRuntimeException("sync template from cache to region wide store failed for image store " + store.getName()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> copyTemplate(TemplateInfo srcTemplate, DataStore destStore) { |
| // for vmware template, we need to check if ova packing is needed, since template created from snapshot does not have .ova file |
| // we invoke createEntityExtractURL to trigger ova packing. Ideally, we can directly use extractURL to pass to following createTemplate. |
| // Need to understand what is the background to use two different urls for copy and extract. |
| if (srcTemplate.getFormat() == ImageFormat.OVA){ |
| ImageStoreEntity tmpltStore = (ImageStoreEntity)srcTemplate.getDataStore(); |
| tmpltStore.createEntityExtractUrl(srcTemplate.getInstallPath(), srcTemplate.getFormat(), srcTemplate); |
| } |
| // generate a URL from source template ssvm to download to destination data store |
| String url = generateCopyUrl(srcTemplate); |
| if (url == null) { |
| logger.warn("Unable to start/resume copy of template " + srcTemplate.getUniqueName() + " to " + destStore.getName() + |
| ", no secondary storage vm in running state in source zone"); |
| throw new CloudRuntimeException("No secondary VM in running state in source template zone "); |
| } |
| |
| TemplateObject tmplForCopy = (TemplateObject)_templateFactory.getTemplate(srcTemplate, destStore, null); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Setting source template url to " + url); |
| } |
| tmplForCopy.setUrl(url); |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("Mark template_store_ref entry as Creating"); |
| } |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); |
| DataObject templateOnStore = destStore.create(tmplForCopy); |
| templateOnStore.processEvent(Event.CreateOnlyRequested); |
| |
| if (templateOnStore instanceof TemplateObject) { |
| ((TemplateObject)templateOnStore).getImage().setChecksum(null); |
| } // else we don't know what to do. |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("Invoke datastore driver createAsync to create template on destination store"); |
| } |
| try { |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, (TemplateObject)templateOnStore, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().copyTemplateCrossZoneCallBack(null, null)).setContext(context); |
| destStore.getDriver().createAsync(destStore, templateOnStore, caller); |
| } catch (CloudRuntimeException ex) { |
| // clean up already persisted template_store_ref entry in case of createTemplateCallback is never called |
| TemplateDataStoreVO templateStoreVO = _vmTemplateStoreDao.findByStoreTemplate(destStore.getId(), srcTemplate.getId()); |
| if (templateStoreVO != null) { |
| TemplateInfo tmplObj = _templateFactory.getTemplate(srcTemplate, destStore, null); |
| tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); |
| } |
| TemplateApiResult res = new TemplateApiResult((TemplateObject)templateOnStore); |
| res.setResult(ex.getMessage()); |
| future.complete(res); |
| } |
| return future; |
| } |
| |
| private String generateCopyUrl(String ipAddress, String dir, String path) { |
| String hostname = ipAddress; |
| String scheme = "http"; |
| boolean _sslCopy = false; |
| String sslCfg = _configDao.getValue(Config.SecStorageEncryptCopy.toString()); |
| String _ssvmUrlDomain = _configDao.getValue("secstorage.ssl.cert.domain"); |
| if (sslCfg != null) { |
| _sslCopy = Boolean.parseBoolean(sslCfg); |
| } |
| if(_sslCopy && (_ssvmUrlDomain == null || _ssvmUrlDomain.isEmpty())){ |
| logger.warn("Empty secondary storage url domain, ignoring SSL"); |
| _sslCopy = false; |
| } |
| if (_sslCopy) { |
| if(_ssvmUrlDomain.startsWith("*")) { |
| hostname = ipAddress.replace(".", "-"); |
| hostname = hostname + _ssvmUrlDomain.substring(1); |
| } else { |
| hostname = _ssvmUrlDomain; |
| } |
| scheme = "https"; |
| } |
| return scheme + "://" + hostname + "/copy/SecStorage/" + dir + "/" + path; |
| } |
| |
| private String generateCopyUrl(TemplateInfo srcTemplate) { |
| DataStore srcStore = srcTemplate.getDataStore(); |
| EndPoint ep = _epSelector.select(srcTemplate); |
| if (ep != null) { |
| if (ep.getPublicAddr() == null) { |
| logger.warn("A running secondary storage vm has a null public ip?"); |
| return null; |
| } |
| return generateCopyUrl(ep.getPublicAddr(), ((ImageStoreEntity)srcStore).getMountPoint(), srcTemplate.getInstallPath()); |
| } |
| |
| VMTemplateVO tmplt = _templateDao.findById(srcTemplate.getId()); |
| HypervisorType hyperType = tmplt.getHypervisorType(); |
| /*No secondary storage vm yet*/ |
| if (hyperType != null && hyperType == HypervisorType.KVM) { |
| return "file://" + ((ImageStoreEntity)srcStore).getMountPoint() + "/" + srcTemplate.getInstallPath(); |
| } |
| return null; |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> prepareTemplateOnPrimary(TemplateInfo srcTemplate, StoragePool pool) { |
| return copyAsync(srcTemplate, srcTemplate, (DataStore)pool); |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> deleteTemplateOnPrimary(TemplateInfo template, StoragePool pool) { |
| TemplateObject templateObject = (TemplateObject)_templateFactory.getTemplateOnPrimaryStorage(template.getId(), (DataStore)pool, template.getDeployAsIsConfiguration()); |
| |
| templateObject.processEvent(ObjectInDataStoreStateMachine.Event.DestroyRequested); |
| |
| DataStore dataStore = _storeMgr.getPrimaryDataStore(pool.getId()); |
| |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<>(); |
| TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<>(null, templateObject, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CommandResult> caller = AsyncCallbackDispatcher.create(this); |
| |
| caller.setCallback(caller.getTarget().deleteTemplateCallback(null, null)).setContext(context); |
| |
| dataStore.getDriver().deleteAsync(dataStore, templateObject, caller); |
| |
| return future; |
| } |
| |
| protected Void copyTemplateCallBack(AsyncCallbackDispatcher<TemplateServiceImpl, CopyCommandResult> callback, TemplateOpContext<TemplateApiResult> context) { |
| TemplateInfo destTemplate = context.getTemplate(); |
| CopyCommandResult result = callback.getResult(); |
| AsyncCallFuture<TemplateApiResult> future = context.getFuture(); |
| TemplateApiResult res = new TemplateApiResult(destTemplate); |
| try { |
| if (result.isFailed()) { |
| res.setResult(result.getResult()); |
| destTemplate.processEvent(Event.OperationFailed); |
| } else { |
| destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer()); |
| } |
| future.complete(res); |
| } catch (Exception e) { |
| logger.debug("Failed to process copy template callback", e); |
| res.setResult(e.toString()); |
| future.complete(res); |
| } |
| |
| return null; |
| } |
| |
| protected Void copyTemplateCrossZoneCallBack(AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> callback, TemplateOpContext<TemplateApiResult> context) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("Performing copy template cross zone callback after completion"); |
| } |
| TemplateInfo destTemplate = context.getTemplate(); |
| CreateCmdResult result = callback.getResult(); |
| AsyncCallFuture<TemplateApiResult> future = context.getFuture(); |
| TemplateApiResult res = new TemplateApiResult(destTemplate); |
| try { |
| if (result.isFailed()) { |
| res.setResult(result.getResult()); |
| destTemplate.processEvent(Event.OperationFailed); |
| } else { |
| destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer()); |
| } |
| future.complete(res); |
| } catch (Exception e) { |
| logger.debug("Failed to process copy template cross zones callback", e); |
| res.setResult(e.toString()); |
| future.complete(res); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void addSystemVMTemplatesToSecondary(DataStore store) { |
| long storeId = store.getId(); |
| List<VMTemplateVO> rtngTmplts = _templateDao.listAllSystemVMTemplates(); |
| for (VMTemplateVO tmplt : rtngTmplts) { |
| TemplateDataStoreVO tmpltStore = _vmTemplateStoreDao.findByStoreTemplate(storeId, tmplt.getId()); |
| if (tmpltStore == null) { |
| if (_vmTemplateStoreDao.isTemplateMarkedForDirectDownload(tmplt.getId())) { |
| continue; |
| } |
| tmpltStore = |
| new TemplateDataStoreVO(storeId, tmplt.getId(), new Date(), 100, Status.DOWNLOADED, null, null, null, |
| TemplateConstants.DEFAULT_SYSTEM_VM_TEMPLATE_PATH + tmplt.getId() + '/', tmplt.getUrl()); |
| tmpltStore.setSize(0L); |
| tmpltStore.setPhysicalSize(0); // no size information for |
| // pre-seeded system vm templates |
| tmpltStore.setDataStoreRole(store.getRole()); |
| _vmTemplateStoreDao.persist(tmpltStore); |
| } |
| } |
| } |
| |
| private class CreateDataDiskTemplateContext<T> extends AsyncRpcContext<T> { |
| private final DataObject dataDiskTemplate; |
| private final AsyncCallFuture<TemplateApiResult> future; |
| |
| public CreateDataDiskTemplateContext(AsyncCompletionCallback<T> callback, DataObject dataDiskTemplate, AsyncCallFuture<TemplateApiResult> future) { |
| super(callback); |
| this.dataDiskTemplate = dataDiskTemplate; |
| this.future = future; |
| } |
| |
| public AsyncCallFuture<TemplateApiResult> getFuture() { |
| return this.future; |
| } |
| } |
| |
| @Override |
| public AsyncCallFuture<TemplateApiResult> createDatadiskTemplateAsync(TemplateInfo parentTemplate, TemplateInfo dataDiskTemplate, String path, String diskId, long fileSize, boolean bootable) { |
| AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); |
| // Make an entry for disk template in template_store_ref table |
| DataStore store = parentTemplate.getDataStore(); |
| TemplateObject dataDiskTemplateOnStore; |
| if (!bootable) { |
| dataDiskTemplateOnStore = (TemplateObject)store.create(dataDiskTemplate); |
| dataDiskTemplateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.CreateOnlyRequested); |
| } else { |
| dataDiskTemplateOnStore = (TemplateObject) imageFactory.getTemplate(parentTemplate, store, null); |
| } |
| try { |
| CreateDataDiskTemplateContext<TemplateApiResult> context = new CreateDataDiskTemplateContext<TemplateApiResult>(null, dataDiskTemplateOnStore, future); |
| AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); |
| caller.setCallback(caller.getTarget().createDatadiskTemplateCallback(null, null)).setContext(context); |
| ImageStoreEntity tmpltStore = (ImageStoreEntity)parentTemplate.getDataStore(); |
| tmpltStore.createDataDiskTemplateAsync(dataDiskTemplate, path, diskId, fileSize, bootable, caller); |
| } catch (CloudRuntimeException ex) { |
| dataDiskTemplateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); |
| TemplateApiResult result = new TemplateApiResult(dataDiskTemplate); |
| result.setResult(ex.getMessage()); |
| if (future != null) { |
| future.complete(result); |
| } |
| } |
| return future; |
| } |
| |
| protected Void createDatadiskTemplateCallback(AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> callback, |
| CreateDataDiskTemplateContext<TemplateApiResult> context) { |
| DataObject dataDiskTemplate = context.dataDiskTemplate; |
| AsyncCallFuture<TemplateApiResult> future = context.getFuture(); |
| CreateCmdResult result = callback.getResult(); |
| TemplateApiResult dataDiskTemplateResult = new TemplateApiResult((TemplateObject)dataDiskTemplate); |
| try { |
| if (result.isSuccess()) { |
| dataDiskTemplate.processEvent(Event.OperationSuccessed, result.getAnswer()); |
| } else { |
| dataDiskTemplate.processEvent(Event.OperationFailed); |
| dataDiskTemplateResult.setResult(result.getResult()); |
| } |
| } catch (CloudRuntimeException e) { |
| logger.debug("Failed to process create template callback", e); |
| dataDiskTemplateResult.setResult(e.toString()); |
| } |
| future.complete(dataDiskTemplateResult); |
| return null; |
| } |
| } |