|
40 | 40 | import javax.inject.Inject; |
41 | 41 | import javax.naming.ConfigurationException; |
42 | 42 |
|
| 43 | +import org.apache.cloudstack.api.ApiConstants; |
43 | 44 | import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; |
44 | 45 | import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; |
45 | 46 | import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; |
|
55 | 56 | import org.apache.cloudstack.framework.config.Configurable; |
56 | 57 | import org.apache.cloudstack.framework.extensions.ExtensionArchiveDataObject; |
57 | 58 | import org.apache.cloudstack.framework.extensions.command.DownloadAndSyncExtensionFilesCommand; |
| 59 | +import org.apache.cloudstack.framework.extensions.dao.ExtensionDao; |
| 60 | +import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao; |
| 61 | +import org.apache.cloudstack.framework.extensions.vo.ExtensionVO; |
58 | 62 | import org.apache.cloudstack.managed.context.ManagedContextRunnable; |
59 | 63 | import org.apache.cloudstack.management.ManagementServerHost; |
60 | 64 | import org.apache.cloudstack.storage.command.CommandResult; |
61 | 65 | import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; |
62 | 66 | import org.apache.cloudstack.utils.filesystem.ArchiveUtil; |
| 67 | +import org.apache.cloudstack.utils.identity.ManagementServerNode; |
63 | 68 | import org.apache.cloudstack.utils.security.DigestHelper; |
64 | 69 | import org.apache.cloudstack.utils.security.HMACSignUtil; |
65 | 70 | import org.apache.cloudstack.utils.server.ServerPropertiesUtil; |
|
70 | 75 | import com.cloud.agent.api.Answer; |
71 | 76 | import com.cloud.agent.api.Command; |
72 | 77 | import com.cloud.cluster.ClusterManager; |
| 78 | +import com.cloud.cluster.ManagementServerHostVO; |
| 79 | +import com.cloud.cluster.dao.ManagementServerHostDao; |
73 | 80 | import com.cloud.dc.dao.DataCenterDao; |
74 | 81 | import com.cloud.serializer.GsonHelper; |
75 | 82 | import com.cloud.server.ManagementService; |
| 83 | +import com.cloud.storage.DataStoreRole; |
76 | 84 | import com.cloud.storage.Storage; |
| 85 | +import com.cloud.storage.Upload; |
77 | 86 | import com.cloud.utils.FileUtil; |
78 | 87 | import com.cloud.utils.HttpUtils; |
79 | 88 | import com.cloud.utils.Pair; |
80 | 89 | import com.cloud.utils.StringUtils; |
81 | 90 | import com.cloud.utils.component.ManagerBase; |
82 | 91 | import com.cloud.utils.concurrency.NamedThreadFactory; |
83 | 92 | import com.cloud.utils.db.GlobalLock; |
| 93 | +import com.cloud.utils.db.Transaction; |
| 94 | +import com.cloud.utils.db.TransactionCallbackNoReturn; |
| 95 | +import com.cloud.utils.db.TransactionStatus; |
84 | 96 | import com.cloud.utils.exception.CloudRuntimeException; |
85 | 97 | import com.cloud.vm.SecondaryStorageVmVO; |
86 | 98 | import com.cloud.vm.VirtualMachine; |
@@ -118,12 +130,21 @@ public class ExtensionsShareManagerImpl extends ManagerBase implements Extension |
118 | 130 | @Inject |
119 | 131 | DataStoreManager dataStoreManager; |
120 | 132 |
|
| 133 | + @Inject |
| 134 | + ManagementServerHostDao managementServerHostDao; |
| 135 | + |
121 | 136 | @Inject |
122 | 137 | ManagementService managementService; |
123 | 138 |
|
124 | 139 | @Inject |
125 | 140 | EndPointSelector endPointSelector; |
126 | 141 |
|
| 142 | + @Inject |
| 143 | + ExtensionDao extensionDao; |
| 144 | + |
| 145 | + @Inject |
| 146 | + ExtensionDetailsDao extensionDetailsDao; |
| 147 | + |
127 | 148 | private ScheduledExecutorService extensionShareCleanupExecutor; |
128 | 149 | private int shareLinkValidityInterval; |
129 | 150 | private boolean serverShareEnabled = true; |
@@ -446,7 +467,7 @@ protected void applyExtensionSync(Extension extension, DownloadAndSyncExtensionF |
446 | 467 | FileUtil.deleteRecursively(applyRoot); |
447 | 468 | } |
448 | 469 |
|
449 | | - protected void cleanupExtensionsShareFiles(long cutoff) throws IOException { |
| 470 | + protected void cleanupExtensionsShareFilesOnMS(long cutoff) throws IOException { |
450 | 471 | Path sharePath = getExtensionsSharePath(); |
451 | 472 | if (!Files.exists(sharePath) || !Files.isDirectory(sharePath)) { |
452 | 473 | return; |
@@ -475,6 +496,18 @@ protected void cleanupExtensionsShareFiles(long cutoff) throws IOException { |
475 | 496 | } |
476 | 497 | } |
477 | 498 |
|
| 499 | + protected void cleanupExtensionsShareFilesOnSecondaryStorage(long cutoff) { |
| 500 | + ManagementServerHostVO msHost = managementServerHostDao.findOneByLongestRuntime(); |
| 501 | + if (msHost == null || (msHost.getMsid() != ManagementServerNode.getManagementServerId())) { |
| 502 | + logger.debug("Skipping the secondary storage extensions download files cleanup task on this management server"); |
| 503 | + return; |
| 504 | + } |
| 505 | + List<ExtensionVO> extensions = extensionDao.listAll(); |
| 506 | + for (ExtensionVO extension : extensions) { |
| 507 | + cleanupExistingExtensionDownloadArchiveAndDetails(extension, cutoff); |
| 508 | + } |
| 509 | + } |
| 510 | + |
478 | 511 | static protected class DownloadExtensionArchiveOnSecondaryStorageContext<T> extends AsyncRpcContext<T> { |
479 | 512 | final Extension extension; |
480 | 513 | final ArchiveInfo archiveInfo; |
@@ -532,6 +565,45 @@ protected AsyncCallFuture<DataObject> downloadExtensionArchiveOnSecondaryStorage |
532 | 565 | return future; |
533 | 566 | } |
534 | 567 |
|
| 568 | + protected void cleanupExistingExtensionDownloadArchiveAndDetails(Extension extension, Long cutoff) { |
| 569 | + Map<String, String> details = extensionDetailsDao.listDetailsKeyPairs(extension.getId(), false); |
| 570 | + if (!details.containsKey("imagestoredownloadurl")) { |
| 571 | + return; |
| 572 | + } |
| 573 | + final String url = details.get("imagestoredownloadurl"); |
| 574 | + final String storeIdStr = details.get(ApiConstants.IMAGE_STORE_ID); |
| 575 | + final String timestampStr = details.get("imagestoredownloadurltimestamp"); |
| 576 | + final long timestamp = StringUtils.isNotBlank(timestampStr) ? Long.parseLong(timestampStr) : -1L; |
| 577 | + if (cutoff != null && timestamp != -1L && timestamp >= cutoff) { |
| 578 | + return; |
| 579 | + } |
| 580 | + final String installPath = details.get("imagestorepath"); |
| 581 | + final long storeId = StringUtils.isNotBlank(storeIdStr) ? Long.parseLong(storeIdStr) : -1L; |
| 582 | + if (StringUtils.isNotBlank(url) && storeId != -1L && timestamp != -1L && StringUtils.isNotBlank(installPath)) { |
| 583 | + try { |
| 584 | + DataStore store = dataStoreManager.getDataStore(storeId, DataStoreRole.Image); |
| 585 | + if (store != null) { |
| 586 | + ((ImageStoreEntity) store).deleteExtractUrl(installPath, url, Upload.Type.ARCHIVE); |
| 587 | + } |
| 588 | + } catch (CloudRuntimeException e) { |
| 589 | + logger.warn("Failed to cleanup existing extension download archive for {}: {}", extension, |
| 590 | + e.getMessage()); |
| 591 | + if (cutoff == null) { |
| 592 | + return; |
| 593 | + } |
| 594 | + } |
| 595 | + } |
| 596 | + Transaction.execute(new TransactionCallbackNoReturn() { |
| 597 | + @Override |
| 598 | + public void doInTransactionWithoutResult(TransactionStatus status) { |
| 599 | + extensionDetailsDao.removeDetail(extension.getId(), "imagestoredownloadurl"); |
| 600 | + extensionDetailsDao.removeDetail(extension.getId(), "imagestoredownloadurltimestamp"); |
| 601 | + extensionDetailsDao.removeDetail(extension.getId(), ApiConstants.IMAGE_STORE_ID); |
| 602 | + extensionDetailsDao.removeDetail(extension.getId(), "imagestorepath"); |
| 603 | + } |
| 604 | + }); |
| 605 | + } |
| 606 | + |
535 | 607 | protected Pair<Boolean, String> downloadExtensionViaSecondaryStorage(Extension extension, ArchiveInfo archiveInfo, |
536 | 608 | String downloadUrl) { |
537 | 609 | // Find an active zone, if not available return error |
@@ -576,7 +648,19 @@ protected Pair<Boolean, String> downloadExtensionViaSecondaryStorage(Extension e |
576 | 648 | return new Pair<>(false, msg); |
577 | 649 | } |
578 | 650 | ImageStoreEntity imageStoreEntity = (ImageStoreEntity)imageStore; |
579 | | - String url = imageStoreEntity.createEntityExtractUrl(dataObject.getTO().getPath(), Storage.ImageFormat.ZIP, dataObject); |
| 651 | + String installPath = dataObject.getTO().getPath(); |
| 652 | + String url = imageStoreEntity.createEntityExtractUrl(installPath, Storage.ImageFormat.ZIP, dataObject); |
| 653 | + cleanupExistingExtensionDownloadArchiveAndDetails(extension, null); |
| 654 | + final long imageStoreId = imageStore.getId(); |
| 655 | + Transaction.execute(new TransactionCallbackNoReturn() { |
| 656 | + @Override |
| 657 | + public void doInTransactionWithoutResult(TransactionStatus status) { |
| 658 | + extensionDetailsDao.addDetail(extension.getId(), "imagestoredownloadurl", url, false); |
| 659 | + extensionDetailsDao.addDetail(extension.getId(), "imagestoredownloadurltimestamp", Long.toString(System.currentTimeMillis()), false); |
| 660 | + extensionDetailsDao.addDetail(extension.getId(), ApiConstants.IMAGE_STORE_ID, Long.toString(imageStoreId), false); |
| 661 | + extensionDetailsDao.addDetail(extension.getId(), "imagestorepath", installPath, false); |
| 662 | + } |
| 663 | + }); |
580 | 664 | return new Pair<>(true, url); |
581 | 665 | } |
582 | 666 |
|
@@ -751,7 +835,8 @@ protected void reallyRun() { |
751 | 835 | try { |
752 | 836 | long expiryMillis = shareLinkValidityInterval * 1100L; |
753 | 837 | long cutoff = System.currentTimeMillis() - expiryMillis; |
754 | | - cleanupExtensionsShareFiles(cutoff); |
| 838 | + cleanupExtensionsShareFilesOnMS(cutoff); |
| 839 | + cleanupExtensionsShareFilesOnSecondaryStorage(cutoff); |
755 | 840 | } catch (Exception e) { |
756 | 841 | logger.warn("Extensions share cleanup failed", e); |
757 | 842 | } |
|
0 commit comments