-
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
base: 4.20
Are you sure you want to change the base?
Allow copy of templates from secondary storages of other zone when adding a new secondary storage #12296
Changes from all commits
1c448d3
a6d8bf6
b3232a4
7a2a9d5
1797f46
3556d74
b3916fa
fbbbbd0
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,7 +36,13 @@ | |
| import javax.inject.Inject; | ||
| import javax.naming.ConfigurationException; | ||
|
|
||
| import com.cloud.dc.DataCenterVO; | ||
| import com.cloud.dc.dao.DataCenterDao; | ||
| import com.cloud.storage.VMTemplateVO; | ||
| import com.cloud.storage.dao.VMTemplateDao; | ||
| import com.cloud.template.TemplateManager; | ||
| import org.apache.cloudstack.api.response.MigrationResponse; | ||
| import org.apache.cloudstack.context.CallContext; | ||
| import org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; | ||
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; | ||
|
|
@@ -103,6 +109,13 @@ public class StorageOrchestrator extends ManagerBase implements StorageOrchestra | |
| VolumeDataStoreDao volumeDataStoreDao; | ||
| @Inject | ||
| DataMigrationUtility migrationHelper; | ||
| @Inject | ||
| TemplateManager templateManager; | ||
| @Inject | ||
| VMTemplateDao templateDao; | ||
| @Inject | ||
| DataCenterDao dcDao; | ||
|
|
||
|
|
||
| ConfigKey<Double> ImageStoreImbalanceThreshold = new ConfigKey<>("Advanced", Double.class, | ||
| "image.store.imbalance.threshold", | ||
|
|
@@ -308,6 +321,14 @@ public Future<TemplateApiResult> orchestrateTemplateCopyToImageStore(TemplateInf | |
| return submit(destStore.getScope().getScopeId(), new CopyTemplateTask(source, destStore)); | ||
| } | ||
|
|
||
| @Override | ||
| public Future<TemplateApiResult> orchestrateTemplateCopyAcrossZones(TemplateInfo templateInfo, DataStore sourceStore, DataStore destStore) { | ||
|
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. You can reduce the number of parameters in this method. There's no need to pass the |
||
| Long dstZoneId = destStore.getScope().getScopeId(); | ||
| DataCenterVO dstZone = dcDao.findById(dstZoneId); | ||
| long userId = CallContext.current().getCallingUserId(); | ||
| return submit(dstZoneId, new CrossZoneCopyTemplateTask(userId, templateInfo, sourceStore, dstZone)); | ||
| } | ||
|
|
||
| protected Pair<String, Boolean> migrateCompleted(Long destDatastoreId, DataStore srcDatastore, List<DataObject> files, MigrationPolicy migrationPolicy, int skipped) { | ||
| String message = ""; | ||
| boolean success = true; | ||
|
|
@@ -653,4 +674,51 @@ public TemplateApiResult call() { | |
| return result; | ||
| } | ||
| } | ||
|
|
||
| private class CrossZoneCopyTemplateTask implements Callable<TemplateApiResult> { | ||
| private final long userId; | ||
| private final TemplateInfo sourceTmpl; | ||
| private final DataStore sourceStore; | ||
| private final DataCenterVO dstZone; | ||
| private final String logid; | ||
|
|
||
| CrossZoneCopyTemplateTask(long userId, | ||
| TemplateInfo sourceTmpl, | ||
| DataStore sourceStore, | ||
| DataCenterVO dstZone) { | ||
| this.userId = userId; | ||
| this.sourceTmpl = sourceTmpl; | ||
| this.sourceStore = sourceStore; | ||
| this.dstZone = dstZone; | ||
| this.logid = ThreadContext.get(LOGCONTEXTID); | ||
| } | ||
|
|
||
| @Override | ||
| public TemplateApiResult call() { | ||
| ThreadContext.put(LOGCONTEXTID, logid); | ||
| TemplateApiResult result; | ||
| VMTemplateVO template = templateDao.findById(sourceTmpl.getId()); | ||
| try { | ||
| boolean success = templateManager.copy(userId, template, sourceStore, dstZone); | ||
|
|
||
| result = new TemplateApiResult(sourceTmpl); | ||
| if (!success) { | ||
| result.setResult("Cross-zone template copy failed"); | ||
| } | ||
| } catch (Exception e) { | ||
|
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. Catch only the relevant exceptions ( |
||
| logger.error("Exception while copying template [{}] from zone [{}] to zone [{}]", | ||
| template, | ||
| sourceStore.getScope().getScopeId(), | ||
| dstZone.getId(), | ||
| e); | ||
| result = new TemplateApiResult(sourceTmpl); | ||
| result.setResult(e.getMessage()); | ||
| } finally { | ||
| tryCleaningUpExecutor(dstZone.getId()); | ||
| ThreadContext.clearAll(); | ||
| } | ||
|
Comment on lines
+716
to
+719
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 |
||
|
|
||
| return result; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -548,7 +548,7 @@ public void handleTemplateSync(DataStore store) { | |
| } | ||
|
|
||
| if (availHypers.contains(tmplt.getHypervisorType())) { | ||
| boolean copied = isCopyFromOtherStoragesEnabled(zoneId) && tryCopyingTemplateToImageStore(tmplt, store); | ||
| boolean copied = imageStoreDetailsUtil.isCopyTemplatesFromOtherStoragesEnabled(storeId, zoneId) && tryCopyingTemplateToImageStore(tmplt, store); | ||
| if (!copied) { | ||
| tryDownloadingTemplateToImageStore(tmplt, store); | ||
| } | ||
|
|
@@ -615,28 +615,118 @@ 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) { | ||
| Long destZoneId = destStore.getScope().getScopeId(); | ||
|
|
||
| List<DataStore> storesInSameZone = _storeMgr.getImageStoresByZoneIds(destZoneId); | ||
|
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. Move this to inside |
||
| if (searchAndCopyWithinZone(tmplt, destStore, storesInSameZone)) { | ||
| return true; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| List<DataStore> storesInOtherZone = _storeMgr.getImageStoresByZoneIds(otherZoneId); | ||
| logger.debug("Checking zone [{}] for template [{}]...", otherZoneId, tmplt.getUniqueName()); | ||
|
|
||
| if (storesInOtherZone == null || storesInOtherZone.isEmpty()) { | ||
| logger.debug("Zone [{}] has no image stores. Skipping.", otherZoneId); | ||
| continue; | ||
| } | ||
|
|
||
| DataStore sourceStore = findImageStorageHavingTemplate(tmplt, storesInOtherZone); | ||
| if (sourceStore == null) { | ||
| logger.debug("Template [{}] not found in any image store of zone [{}].", | ||
| tmplt.getUniqueName(), otherZoneId); | ||
| continue; | ||
| } | ||
|
|
||
| TemplateObject sourceTmpl = (TemplateObject) _templateFactory.getTemplate(tmplt.getId(), sourceStore); | ||
| if (sourceTmpl.getInstallPath() == null) { | ||
| logger.warn("Cannot copy template [{}] from image store [{}]; install path is null.", | ||
| tmplt.getUniqueName(), sourceStore.getName()); | ||
| continue; | ||
| } | ||
|
Comment on lines
+646
to
+658
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. In the following situation:
If It is better to loop through the secondary storages of zone A, search for a template entry with a install path that is not null, and return it. This way, you can also deduplicate the code in |
||
|
|
||
| logger.info("Template [{}] found in zone [{}]. Initiating cross-zone copy to zone [{}].", | ||
| tmplt.getUniqueName(), otherZoneId, destZoneId); | ||
|
|
||
| return copyTemplateAcrossZones(sourceStore, destStore, tmplt); | ||
| } | ||
|
|
||
| logger.debug("Template [{}] was not found in any zone. Cannot perform zone-to-zone copy.", | ||
| tmplt.getUniqueName()); | ||
| return false; | ||
| } | ||
|
|
||
| private boolean searchAndCopyWithinZone(VMTemplateVO tmplt, DataStore destStore, List<DataStore> stores) { | ||
| for (DataStore sourceStore : stores) { | ||
| 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.", | ||
| if (existingTemplatesInSourceStore == null || | ||
| !existingTemplatesInSourceStore.containsKey(tmplt.getUniqueName())) { | ||
| logger.debug("Template [{}] does not exist on image store [{}]; searching another.", | ||
| tmplt.getUniqueName(), sourceStore.getName()); | ||
| 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()); | ||
| logger.warn("Cannot copy template [{}] from image store [{}]; install path is null.", | ||
| tmplt.getUniqueName(), sourceStore.getName()); | ||
| continue; | ||
| } | ||
|
|
||
| storageOrchestrator.orchestrateTemplateCopyToImageStore(sourceTmpl, destStore); | ||
| return true; | ||
| } | ||
| logger.debug("Can't copy template [{}] from another image store.", tmplt.getUniqueName()); | ||
| return false; | ||
| } | ||
|
|
||
| private DataStore findImageStorageHavingTemplate(VMTemplateVO tmplt, List<DataStore> stores) { | ||
| for (DataStore store : stores) { | ||
| Map<String, TemplateProp> templates = listTemplate(store); | ||
| if (templates != null && templates.containsKey(tmplt.getUniqueName())) { | ||
| return store; | ||
|
Collaborator
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. We can add the sourceTmpl.getInstallPath() != null check here as well |
||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private boolean copyTemplateAcrossZones(DataStore sourceStore, | ||
| DataStore destStore, | ||
| VMTemplateVO tmplt) { | ||
| Long dstZoneId = destStore.getScope().getScopeId(); | ||
| DataCenterVO dstZone = _dcDao.findById(dstZoneId); | ||
|
|
||
| if (dstZone == null) { | ||
| logger.warn("Destination zone [{}] not found for template [{}].", | ||
| dstZoneId, tmplt.getUniqueName()); | ||
| return false; | ||
| } | ||
|
|
||
| TemplateObject sourceTmpl = (TemplateObject) _templateFactory.getTemplate(tmplt.getId(), sourceStore); | ||
| try { | ||
| storageOrchestrator.orchestrateTemplateCopyAcrossZones(sourceTmpl, sourceStore, destStore); | ||
| return true; | ||
| } catch (Exception e) { | ||
| logger.error("Failed to copy template [{}] from zone [{}] to zone [{}].", | ||
| tmplt.getUniqueName(), | ||
| sourceStore.getScope().getScopeId(), | ||
| dstZoneId, | ||
| e); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public AsyncCallFuture<TemplateApiResult> copyTemplateToImageStore(DataObject source, DataStore destStore) { | ||
| TemplateObject sourceTmpl = (TemplateObject) source; | ||
|
|
@@ -680,10 +770,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()); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually prefer having parameters instead of passing things as a detail, so that it is automatically added to the API's documentation, but I'm ok if you keep it like this seeing that there's already documentation and a UI change for it