-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Allow copy of templates from secondary storages of other zone when adding a new secondary storage #12296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow copy of templates from secondary storages of other zone when adding a new secondary storage #12296
Changes from all commits
aa5f8f3
0c51346
08c75ce
3226454
2a8c16c
5ee6c47
e10a3f8
ce134c9
2c7c736
04fed08
8a5f937
6f2920a
a6a1795
b8f3d7d
1de719e
e1305b1
1e8626f
dc05112
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,9 @@ | |
| import javax.inject.Inject; | ||
| import javax.naming.ConfigurationException; | ||
|
|
||
| import com.cloud.dc.dao.DataCenterDao; | ||
| import com.cloud.storage.dao.VMTemplateDao; | ||
| import com.cloud.template.TemplateManager; | ||
| import org.apache.cloudstack.api.response.MigrationResponse; | ||
| import org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; | ||
|
|
@@ -45,6 +48,7 @@ | |
| import org.apache.cloudstack.engine.subsystem.api.storage.SecondaryStorageService.DataObjectResult; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; | ||
| 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.TemplateService.TemplateApiResult; | ||
|
|
@@ -103,6 +107,15 @@ public class StorageOrchestrator extends ManagerBase implements StorageOrchestra | |
| VolumeDataStoreDao volumeDataStoreDao; | ||
| @Inject | ||
| DataMigrationUtility migrationHelper; | ||
| @Inject | ||
| TemplateManager templateManager; | ||
| @Inject | ||
| VMTemplateDao templateDao; | ||
| @Inject | ||
| TemplateDataFactory templateDataFactory; | ||
| @Inject | ||
| DataCenterDao dcDao; | ||
|
|
||
|
|
||
| ConfigKey<Double> ImageStoreImbalanceThreshold = new ConfigKey<>("Advanced", Double.class, | ||
| "image.store.imbalance.threshold", | ||
|
|
@@ -304,8 +317,9 @@ public MigrationResponse migrateResources(Long srcImgStoreId, Long destImgStoreI | |
| } | ||
|
|
||
| @Override | ||
| public Future<TemplateApiResult> orchestrateTemplateCopyToImageStore(TemplateInfo source, DataStore destStore) { | ||
| return submit(destStore.getScope().getScopeId(), new CopyTemplateTask(source, destStore)); | ||
harikrishna-patnala marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public Future<TemplateApiResult> orchestrateTemplateCopyFromSecondaryStores(long srcTemplateId, DataStore destStore) { | ||
| Long dstZoneId = destStore.getScope().getScopeId(); | ||
| return submit(dstZoneId, new CopyTemplateFromSecondaryStorageTask(srcTemplateId, destStore)); | ||
| } | ||
|
|
||
| protected Pair<String, Boolean> migrateCompleted(Long destDatastoreId, DataStore srcDatastore, List<DataObject> files, MigrationPolicy migrationPolicy, int skipped) { | ||
|
|
@@ -624,13 +638,13 @@ public DataObjectResult call() { | |
| } | ||
| } | ||
|
|
||
| private class CopyTemplateTask implements Callable<TemplateApiResult> { | ||
| private TemplateInfo sourceTmpl; | ||
| private DataStore destStore; | ||
| private String logid; | ||
| private class CopyTemplateFromSecondaryStorageTask implements Callable<TemplateApiResult> { | ||
| private final long srcTemplateId; | ||
| private final DataStore destStore; | ||
| private final String logid; | ||
|
|
||
| public CopyTemplateTask(TemplateInfo sourceTmpl, DataStore destStore) { | ||
| this.sourceTmpl = sourceTmpl; | ||
| CopyTemplateFromSecondaryStorageTask(long srcTemplateId, DataStore destStore) { | ||
| this.srcTemplateId = srcTemplateId; | ||
| this.destStore = destStore; | ||
| this.logid = ThreadContext.get(LOGCONTEXTID); | ||
| } | ||
|
|
@@ -639,17 +653,16 @@ public CopyTemplateTask(TemplateInfo sourceTmpl, DataStore destStore) { | |
| public TemplateApiResult call() { | ||
| ThreadContext.put(LOGCONTEXTID, logid); | ||
| TemplateApiResult result; | ||
| AsyncCallFuture<TemplateApiResult> future = templateService.copyTemplateToImageStore(sourceTmpl, destStore); | ||
| long destZoneId = destStore.getScope().getScopeId(); | ||
| TemplateInfo sourceTmpl = templateDataFactory.getTemplate(srcTemplateId, DataStoreRole.Image); | ||
| try { | ||
| result = future.get(); | ||
| } catch (ExecutionException | InterruptedException e) { | ||
| logger.warn("Exception while copying template [{}] from image store [{}] to image store [{}]: {}", | ||
| sourceTmpl.getUniqueName(), sourceTmpl.getDataStore().getName(), destStore.getName(), e.toString()); | ||
| templateService.handleTemplateCopyFromSecondaryStores(srcTemplateId, destStore); | ||
| result = new TemplateApiResult(sourceTmpl); | ||
| result.setResult(e.getMessage()); | ||
| } finally { | ||
| tryCleaningUpExecutor(destZoneId); | ||
| ThreadContext.clearAll(); | ||
| } | ||
|
Comment on lines
661
to
664
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would place these 2 lines outside the finally so that there's less idented code
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kept it finally to handle any runtime exceptions as well.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's better in fact. Could you also add the finally to |
||
| tryCleaningUpExecutor(destStore.getScope().getScopeId()); | ||
| ThreadContext.clearAll(); | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,8 @@ | |
|
|
||
| import javax.inject.Inject; | ||
|
|
||
| import com.cloud.exception.StorageUnavailableException; | ||
| import org.apache.cloudstack.context.CallContext; | ||
| import org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; | ||
|
|
@@ -67,9 +69,11 @@ | |
| 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.collections.CollectionUtils; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.apache.logging.log4j.Logger; | ||
| import org.apache.logging.log4j.LogManager; | ||
| import org.apache.logging.log4j.ThreadContext; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import com.cloud.agent.api.Answer; | ||
|
|
@@ -567,10 +571,7 @@ public void handleTemplateSync(DataStore store) { | |
| } | ||
|
|
||
| if (availHypers.contains(tmplt.getHypervisorType())) { | ||
| boolean copied = isCopyFromOtherStoragesEnabled(zoneId) && tryCopyingTemplateToImageStore(tmplt, store); | ||
| if (!copied) { | ||
| tryDownloadingTemplateToImageStore(tmplt, store); | ||
| } | ||
| storageOrchestrator.orchestrateTemplateCopyFromSecondaryStores(tmplt.getId(), store); | ||
| } else { | ||
| logger.info("Skip downloading template {} since current data center does not have hypervisor {}", tmplt, tmplt.getHypervisorType()); | ||
| } | ||
|
|
@@ -617,6 +618,16 @@ public void handleTemplateSync(DataStore store) { | |
|
|
||
| } | ||
|
|
||
| @Override | ||
| public void handleTemplateCopyFromSecondaryStores(long templateId, DataStore destStore) { | ||
| VMTemplateVO template = _templateDao.findById(templateId); | ||
| long zoneId = destStore.getScope().getScopeId(); | ||
| boolean copied = imageStoreDetailsUtil.isCopyTemplatesFromOtherStoragesEnabled(destStore.getId(), zoneId) && tryCopyingTemplateToImageStore(template, destStore); | ||
| if (!copied) { | ||
| tryDownloadingTemplateToImageStore(template, destStore); | ||
| } | ||
| } | ||
|
|
||
| protected void tryDownloadingTemplateToImageStore(VMTemplateVO tmplt, DataStore destStore) { | ||
| if (tmplt.getUrl() == null) { | ||
| logger.info("Not downloading template [{}] to image store [{}], as it has no URL.", tmplt.getUniqueName(), | ||
|
|
@@ -634,28 +645,134 @@ protected void tryDownloadingTemplateToImageStore(VMTemplateVO tmplt, DataStore | |
| } | ||
|
|
||
| protected boolean tryCopyingTemplateToImageStore(VMTemplateVO tmplt, DataStore destStore) { | ||
| Long zoneId = destStore.getScope().getScopeId(); | ||
| List<DataStore> storesInZone = _storeMgr.getImageStoresByZoneIds(zoneId); | ||
| for (DataStore sourceStore : storesInZone) { | ||
| Map<String, TemplateProp> existingTemplatesInSourceStore = listTemplate(sourceStore); | ||
| if (existingTemplatesInSourceStore == null || !existingTemplatesInSourceStore.containsKey(tmplt.getUniqueName())) { | ||
| logger.debug("Template [{}] does not exist on image store [{}]; searching on another one.", | ||
| tmplt.getUniqueName(), sourceStore.getName()); | ||
| if (searchAndCopyWithinZone(tmplt, destStore)) { | ||
| return true; | ||
| } | ||
|
|
||
| Long destZoneId = destStore.getScope().getScopeId(); | ||
| logger.debug("Template [{}] not found in any image store of zone [{}]. Checking other zones.", | ||
| tmplt.getUniqueName(), destZoneId); | ||
|
|
||
| return searchAndCopyAcrossZones(tmplt, destStore, destZoneId); | ||
| } | ||
|
|
||
| private boolean searchAndCopyAcrossZones(VMTemplateVO tmplt, DataStore destStore, Long destZoneId) { | ||
| List<Long> allZoneIds = _dcDao.listAllIds(); | ||
| for (Long otherZoneId : allZoneIds) { | ||
| if (otherZoneId.equals(destZoneId)) { | ||
| continue; | ||
| } | ||
| TemplateObject sourceTmpl = (TemplateObject) _templateFactory.getTemplate(tmplt.getId(), sourceStore); | ||
| if (sourceTmpl.getInstallPath() == null) { | ||
| logger.warn("Can not copy template [{}] from image store [{}], as it returned a null install path.", tmplt.getUniqueName(), | ||
| sourceStore.getName()); | ||
|
|
||
| List<DataStore> storesInOtherZone = _storeMgr.getImageStoresByZoneIds(otherZoneId); | ||
| logger.debug("Checking zone [{}] for template [{}]...", otherZoneId, tmplt.getUniqueName()); | ||
|
|
||
| if (CollectionUtils.isEmpty(storesInOtherZone)) { | ||
| logger.debug("Zone [{}] has no image stores. Skipping.", otherZoneId); | ||
| continue; | ||
| } | ||
| storageOrchestrator.orchestrateTemplateCopyToImageStore(sourceTmpl, destStore); | ||
| return true; | ||
|
|
||
| TemplateObject sourceTmpl = findUsableTemplate(tmplt, storesInOtherZone); | ||
| if (sourceTmpl == null) { | ||
| logger.debug("Template [{}] not found with a valid install path in any image store of zone [{}].", | ||
| tmplt.getUniqueName(), otherZoneId); | ||
| continue; | ||
| } | ||
|
|
||
| logger.info("Template [{}] found in zone [{}]. Initiating cross-zone copy to zone [{}].", | ||
| tmplt.getUniqueName(), otherZoneId, destZoneId); | ||
|
|
||
| return copyTemplateAcrossZones(destStore, sourceTmpl); | ||
| } | ||
| logger.debug("Can't copy template [{}] from another image store.", tmplt.getUniqueName()); | ||
|
|
||
| logger.debug("Template [{}] was not found in any zone. Cannot perform zone-to-zone copy.", tmplt.getUniqueName()); | ||
| return false; | ||
| } | ||
|
|
||
| protected TemplateObject findUsableTemplate(VMTemplateVO tmplt, List<DataStore> imageStores) { | ||
| for (DataStore store : imageStores) { | ||
|
|
||
| Map<String, TemplateProp> templates = listTemplate(store); | ||
| if (templates == null || !templates.containsKey(tmplt.getUniqueName())) { | ||
| continue; | ||
| } | ||
|
|
||
| TemplateObject tmpl = (TemplateObject) _templateFactory.getTemplate(tmplt.getId(), store); | ||
| if (tmpl.getInstallPath() == null) { | ||
| logger.debug("Template [{}] found in image store [{}] but install path is null. Skipping.", | ||
| tmplt.getUniqueName(), store.getName()); | ||
| continue; | ||
| } | ||
| return tmpl; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to keep the check with |
||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private boolean searchAndCopyWithinZone(VMTemplateVO tmplt, DataStore destStore) { | ||
| Long destZoneId = destStore.getScope().getScopeId(); | ||
| List<DataStore> storesInSameZone = _storeMgr.getImageStoresByZoneIds(destZoneId); | ||
|
|
||
| TemplateObject sourceTmpl = findUsableTemplate(tmplt, storesInSameZone); | ||
| if (sourceTmpl == null) { | ||
| return false; | ||
| } | ||
|
|
||
| TemplateApiResult result; | ||
| AsyncCallFuture<TemplateApiResult> future = copyTemplateToImageStore(sourceTmpl, destStore); | ||
| try { | ||
| result = future.get(); | ||
| } catch (ExecutionException | InterruptedException e) { | ||
| logger.warn("Exception while copying template [{}] from image store [{}] to image store [{}]: {}", | ||
| sourceTmpl.getUniqueName(), sourceTmpl.getDataStore().getName(), destStore.getName(), e.toString()); | ||
| result = new TemplateApiResult(sourceTmpl); | ||
| result.setResult(e.getMessage()); | ||
| } | ||
| return result.isSuccess(); | ||
| } | ||
|
|
||
| private boolean copyTemplateAcrossZones(DataStore destStore, TemplateObject sourceTmpl) { | ||
| Long dstZoneId = destStore.getScope().getScopeId(); | ||
| DataCenterVO dstZone = _dcDao.findById(dstZoneId); | ||
|
|
||
| if (dstZone == null) { | ||
| logger.warn("Destination zone [{}] not found for template [{}].", dstZoneId, sourceTmpl.getUniqueName()); | ||
| return false; | ||
| } | ||
|
|
||
| TemplateApiResult result; | ||
| try { | ||
| VMTemplateVO template = _templateDao.findById(sourceTmpl.getId()); | ||
| try { | ||
| DataStore sourceStore = sourceTmpl.getDataStore(); | ||
| long userId = CallContext.current().getCallingUserId(); | ||
| boolean success = _tmpltMgr.copy(userId, template, sourceStore, dstZone); | ||
|
|
||
| result = new TemplateApiResult(sourceTmpl); | ||
| if (!success) { | ||
| result.setResult("Cross-zone template copy failed"); | ||
| } | ||
| } catch (StorageUnavailableException | ResourceAllocationException e) { | ||
| logger.error("Exception while copying template [{}] from zone [{}] to zone [{}]", | ||
| template, | ||
| sourceTmpl.getDataStore().getScope().getScopeId(), | ||
| dstZone.getId(), | ||
| e); | ||
| result = new TemplateApiResult(sourceTmpl); | ||
| result.setResult(e.getMessage()); | ||
| } finally { | ||
| ThreadContext.clearAll(); | ||
| } | ||
| } catch (Exception e) { | ||
| logger.error("Failed to copy template [{}] from zone [{}] to zone [{}].", | ||
| sourceTmpl.getUniqueName(), | ||
| sourceTmpl.getDataStore().getScope().getScopeId(), | ||
| dstZoneId, | ||
| e); | ||
| return false; | ||
| } | ||
|
|
||
| return result.isSuccess(); | ||
| } | ||
|
|
||
| @Override | ||
| public AsyncCallFuture<TemplateApiResult> copyTemplateToImageStore(DataObject source, DataStore destStore) { | ||
| TemplateObject sourceTmpl = (TemplateObject) source; | ||
|
|
@@ -699,10 +816,6 @@ protected Void copyTemplateToImageStoreCallback(AsyncCallbackDispatcher<Template | |
| return null; | ||
| } | ||
|
|
||
| protected boolean isCopyFromOtherStoragesEnabled(Long zoneId) { | ||
| return StorageManager.COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES.valueIn(zoneId); | ||
| } | ||
|
|
||
| protected void publishTemplateCreation(TemplateInfo tmplt) { | ||
| VMTemplateVO tmpltVo = _templateDao.findById(tmplt.getId()); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.