From 2350661ee34bd0c4ef87f89ac05160bac6bd4899 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:27:20 +0530 Subject: [PATCH] Added progress to upload Image Transfers --- .../api/response/ImageTransferResponse.java | 8 + .../backup/IncrementalBackupService.java | 9 +- .../GetImageTransferProgressAnswer.java | 47 ++++++ .../GetImageTransferProgressCommand.java | 67 ++++++++ .../cloudstack/backup/ImageTransferVO.java | 12 ++ .../backup/dao/ImageTransferDao.java | 3 +- .../backup/dao/ImageTransferDaoImpl.java | 15 ++ .../META-INF/db/schema-42100to42200.sql | 1 + .../META-INF/db/schema-42210to42300.sql | 1 + ...etImageTransferProgressCommandWrapper.java | 102 +++++++++++++ .../backup/IncrementalBackupServiceImpl.java | 144 +++++++++++++++++- .../resource/NfsSecondaryStorageResource.java | 2 +- 12 files changed, 400 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressAnswer.java create mode 100644 core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressCommand.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetImageTransferProgressCommandWrapper.java diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java index 15576e8f101..8a24ed3966f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java @@ -62,6 +62,10 @@ public class ImageTransferResponse extends BaseResponse { @Param(description = "the image transfer direction: upload / download") private String direction; + @SerializedName("progress") + @Param(description = "progress in percentage for the upload image transfer") + private Integer progress; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date created") private Date created; @@ -98,6 +102,10 @@ public class ImageTransferResponse extends BaseResponse { this.direction = direction; } + public void setProgress(Integer progress) { + this.progress = progress; + } + public void setCreated(Date created) { this.created = created; } diff --git a/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java b/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java index 28f69cc38ad..45f73a08dcf 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java +++ b/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java @@ -29,13 +29,20 @@ import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CheckpointResponse; import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import com.cloud.utils.component.PluggableService; /** * Service for managing oVirt-style incremental backups using libvirt checkpoints */ -public interface IncrementalBackupService extends PluggableService { +public interface IncrementalBackupService extends Configurable, PluggableService { + + ConfigKey ImageTransferPollingInterval = new ConfigKey<>("Advanced", Long.class, + "image.transfer.polling.interval", + "10", + "The image transfer progress polling interval in seconds.", true, ConfigKey.Scope.Global); /** * Start a backup session for a VM diff --git a/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressAnswer.java new file mode 100644 index 00000000000..cc031abd21a --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressAnswer.java @@ -0,0 +1,47 @@ +//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 +//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.backup; + +import java.util.Map; + +import com.cloud.agent.api.Answer; + +public class GetImageTransferProgressAnswer extends Answer { + private Map progressMap; // transferId -> progress percentage (0-100) + + public GetImageTransferProgressAnswer() { + } + + public GetImageTransferProgressAnswer(GetImageTransferProgressCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + public GetImageTransferProgressAnswer(GetImageTransferProgressCommand cmd, boolean success, String details, + Map progressMap) { + super(cmd, success, details); + this.progressMap = progressMap; + } + + public Map getProgressMap() { + return progressMap; + } + + public void setProgressMap(Map progressMap) { + this.progressMap = progressMap; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressCommand.java b/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressCommand.java new file mode 100644 index 00000000000..2391f957f51 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/GetImageTransferProgressCommand.java @@ -0,0 +1,67 @@ +//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 +//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.backup; + +import java.util.List; +import java.util.Map; + +import com.cloud.agent.api.Command; + +public class GetImageTransferProgressCommand extends Command { + private List transferIds; + private Map volumePaths; // transferId -> volume path + private Map volumeSizes; // transferId -> volume size + + public GetImageTransferProgressCommand() { + } + + public GetImageTransferProgressCommand(List transferIds, Map volumePaths, Map volumeSizes) { + this.transferIds = transferIds; + this.volumePaths = volumePaths; + this.volumeSizes = volumeSizes; + } + + public List getTransferIds() { + return transferIds; + } + + public void setTransferIds(List transferIds) { + this.transferIds = transferIds; + } + + public Map getVolumePaths() { + return volumePaths; + } + + public void setVolumePaths(Map volumePaths) { + this.volumePaths = volumePaths; + } + + public Map getVolumeSizes() { + return volumeSizes; + } + + public void setVolumeSizes(Map volumeSizes) { + this.volumeSizes = volumeSizes; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java index 25e5b213ca8..a6c5bce07d7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java @@ -68,6 +68,9 @@ public class ImageTransferVO implements ImageTransfer { @Column(name = "signed_ticket_id") private String signedTicketId; + @Column(name = "progress") + private Integer progress; + @Column(name = "account_id") Long accountId; @@ -189,6 +192,15 @@ public class ImageTransferVO implements ImageTransfer { this.signedTicketId = signedTicketId; } + public Integer getProgress() { + return progress; + } + + public void setProgress(Integer progress) { + this.progress = progress; + this.updated = new Date(); + } + @Override public Class getEntityType() { return ImageTransfer.class; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java index 805e23d3358..035e22958e5 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.backup.dao; import java.util.List; +import org.apache.cloudstack.backup.ImageTransfer; import org.apache.cloudstack.backup.ImageTransferVO; import com.cloud.utils.db.GenericDao; @@ -27,6 +28,6 @@ public interface ImageTransferDao extends GenericDao { List listByBackupId(Long backupId); ImageTransferVO findByUuid(String uuid); ImageTransferVO findByNbdPort(int port); - ImageTransferVO findByVolume(Long volumeId); + List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java index 2a34650f210..e7d87446326 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java @@ -21,6 +21,7 @@ import java.util.List; import javax.annotation.PostConstruct; +import org.apache.cloudstack.backup.ImageTransfer; import org.apache.cloudstack.backup.ImageTransferVO; import org.springframework.stereotype.Component; @@ -35,6 +36,7 @@ public class ImageTransferDaoImpl extends GenericDaoBase private SearchBuilder uuidSearch; private SearchBuilder nbdPortSearch; private SearchBuilder volumeSearch; + private SearchBuilder phaseDirectionSearch; public ImageTransferDaoImpl() { } @@ -56,6 +58,11 @@ public class ImageTransferDaoImpl extends GenericDaoBase volumeSearch = createSearchBuilder(); volumeSearch.and("volumeId", volumeSearch.entity().getDiskId(), SearchCriteria.Op.EQ); volumeSearch.done(); + + phaseDirectionSearch = createSearchBuilder(); + phaseDirectionSearch.and("phase", phaseDirectionSearch.entity().getPhase(), SearchCriteria.Op.EQ); + phaseDirectionSearch.and("direction", phaseDirectionSearch.entity().getDirection(), SearchCriteria.Op.EQ); + phaseDirectionSearch.done(); } @Override @@ -85,4 +92,12 @@ public class ImageTransferDaoImpl extends GenericDaoBase sc.setParameters("volumeId", volumeId); return findOneBy(sc); } + + @Override + public List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction) { + SearchCriteria sc = phaseDirectionSearch.create(); + sc.setParameters("phase", phase); + sc.setParameters("direction", direction); + return listBy(sc); + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql index 858c46a7c1e..d9f2ccd70ce 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql @@ -92,3 +92,4 @@ CALL `cloud`.`IDEMPOTENT_ADD_UNIQUE_KEY`('cloud.counter', 'uc_counter__provider_ UPDATE `cloud`.`configuration` SET `scope` = 2 WHERE `name` = 'use.https.to.upload'; -- Delete the configuration for 'use.https.to.upload' from StoragePool DELETE FROM `cloud`.`storage_pool_details` WHERE `name` = 'use.https.to.upload'; + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index d3ee808cbac..3a2bbf0bd5b 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -143,6 +143,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`( `transfer_url` varchar(255) COMMENT 'ImageIO transfer URL', `phase` varchar(20) NOT NULL COMMENT 'Transfer phase: initializing, transferring, finished, failed', `direction` varchar(20) NOT NULL COMMENT 'Direction: upload, download', + `progress` int COMMENT 'Transfer progress percentage (0-100)', `signed_ticket_id` varchar(255) COMMENT 'Signed ticket ID from ImageIO', `created` datetime NOT NULL COMMENT 'date created', `updated` datetime COMMENT 'date updated if not null', diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetImageTransferProgressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetImageTransferProgressCommandWrapper.java new file mode 100644 index 00000000000..293e87f9cef --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetImageTransferProgressCommandWrapper.java @@ -0,0 +1,102 @@ +//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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.backup.GetImageTransferProgressAnswer; +import org.apache.cloudstack.backup.GetImageTransferProgressCommand; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = GetImageTransferProgressCommand.class) +public class LibvirtGetImageTransferProgressCommandWrapper extends CommandWrapper { + protected Logger logger = LogManager.getLogger(getClass()); + + @Override + public Answer execute(GetImageTransferProgressCommand cmd, LibvirtComputingResource resource) { + try { + List transferIds = cmd.getTransferIds(); + Map volumePaths = cmd.getVolumePaths(); + Map volumeSizes = cmd.getVolumeSizes(); + Map progressMap = new HashMap<>(); + + if (transferIds == null || transferIds.isEmpty()) { + return new GetImageTransferProgressAnswer(cmd, true, "No transfers to check", progressMap); + } + + for (String transferId : transferIds) { + String volumePath = volumePaths.get(transferId); + Long volumeSize = volumeSizes.get(transferId); + + if (volumePath == null || volumeSize == null || volumeSize == 0) { + logger.warn("Missing volume path or size for transferId: " + transferId); + progressMap.put(transferId, 0); + continue; + } + + try { + File file = new File(volumePath); + if (!file.exists()) { + logger.warn("Volume file does not exist: " + volumePath); + progressMap.put(transferId, 0); + continue; + } + + long currentSize = file.length(); + + if (volumePath.endsWith(".qcow2") || volumePath.endsWith(".qcow")) { + try { + long virtualSize = KVMPhysicalDisk.getVirtualSizeFromFile(volumePath); + currentSize = virtualSize; + } catch (Exception e) { + logger.warn("Failed to get virtual size for qcow2 file: " + volumePath + ", using physical size", e); + } + } + + int progress = 0; + if (volumeSize > 0) { + progress = (int) Math.min(100, Math.max(0, (currentSize * 100) / volumeSize)); + } + + progressMap.put(transferId, progress); + logger.debug("Transfer {} progress: {}% (current: {}, total: {})", transferId, progress, currentSize, volumeSize); + + } catch (Exception e) { + logger.error("Error getting progress for transferId: " + transferId + ", path: " + volumePath, e); + progressMap.put(transferId, 0); + } + } + + return new GetImageTransferProgressAnswer(cmd, true, "Progress retrieved successfully", progressMap); + + } catch (Exception e) { + logger.error("Error executing GetImageTransferProgressCommand", e); + return new GetImageTransferProgressAnswer(cmd, false, "Error getting transfer progress: " + e.getMessage()); + } + } +} diff --git a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java index daa28427467..97b28468bea 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java @@ -23,6 +23,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; import java.util.UUID; import java.util.stream.Collectors; @@ -41,6 +43,8 @@ import org.apache.cloudstack.api.response.ImageTransferResponse; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.ImageTransferDao; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -96,6 +100,8 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme @Inject EndPointSelector _epSelector; + private Timer imageTransferTimer; + private static final int NBD_PORT_RANGE_START = 10809; private static final int NBD_PORT_RANGE_END = 10909; private static final boolean DATAPLANE_PROXY_MODE = true; @@ -204,7 +210,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } catch (AgentUnavailableException | OperationTimedoutException e) { backupDao.remove(backup.getId()); - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } } @@ -269,7 +275,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme return true; } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } } @@ -324,7 +330,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme return imageTransfer; } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } } @@ -363,7 +369,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme try { nbdServerAnswer = (StartNBDServerAnswer) agentManager.send(host.getId(), nbdServerCmd); } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } if (!nbdServerAnswer.getResult()) { throw new CloudRuntimeException("Failed to start the NBD server"); @@ -392,7 +398,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme volume.getId(), host.getId(), nbdPort, - ImageTransferVO.Phase.initializing, + ImageTransferVO.Phase.transferring, ImageTransfer.Direction.upload, volume.getAccountId(), volume.getDomainId(), @@ -463,7 +469,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } } @@ -477,7 +483,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme try { answer = agentManager.send(imageTransfer.getHostId(), stopNbdServerCommand); } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to communicate with agent", e); } if (!answer.getResult()) { throw new CloudRuntimeException("Failed to stop the nbd server"); @@ -602,9 +608,131 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme Volume volume = volumeDao.findById(volumeId); response.setDiskId(volume.getUuid()); response.setTransferUrl(imageTransferVO.getTransferUrl()); - response.setPhase(ImageTransferVO.Phase.initializing.toString()); + response.setPhase(imageTransferVO.getPhase().toString()); + response.setProgress(imageTransferVO.getProgress()); response.setDirection(imageTransferVO.getDirection().toString()); response.setCreated(imageTransferVO.getCreated()); return response; } + + @Override + public boolean start() { + final TimerTask imageTransferPollTask = new ManagedContextTimerTask() { + @Override + protected void runInContext() { + try { + pollImageTransferProgress(); + } catch (final Throwable t) { + logger.warn("Catch throwable in image transfer poll task ", t); + } + } + }; + + imageTransferTimer = new Timer("ImageTransferPollTask"); + long pollingInterval = ImageTransferPollingInterval.value() * 1000L; + imageTransferTimer.schedule(imageTransferPollTask, pollingInterval, pollingInterval); + return true; + } + + @Override + public boolean stop() { + if (imageTransferTimer != null) { + imageTransferTimer.cancel(); + imageTransferTimer = null; + } + return true; + } + + private void pollImageTransferProgress() { + try { + List transferringTransfers = imageTransferDao.listByPhaseAndDirection( + ImageTransfer.Phase.transferring, ImageTransfer.Direction.upload); + if (transferringTransfers == null || transferringTransfers.isEmpty()) { + return; + } + + Map> transfersByHost = transferringTransfers.stream() + .collect(Collectors.groupingBy(ImageTransferVO::getHostId)); + + for (Map.Entry> entry : transfersByHost.entrySet()) { + Long hostId = entry.getKey(); + List hostTransfers = entry.getValue(); + + try { + List transferIds = new ArrayList<>(); + Map volumePaths = new HashMap<>(); + Map volumeSizes = new HashMap<>(); + + for (ImageTransferVO transfer : hostTransfers) { + VolumeVO volume = volumeDao.findById(transfer.getDiskId()); + if (volume == null) { + logger.warn("Volume not found for image transfer: " + transfer.getUuid()); + continue; + } + + String transferId = transfer.getUuid(); + transferIds.add(transferId); + + String volumePath = volume.getPath(); + if (volumePath == null) { + logger.warn("Volume path is null for image transfer: " + transfer.getUuid()); + continue; + } + + StoragePoolVO storagePool = primaryDataStoreDao.findById(volume.getPoolId()); + volumePath = String.format("/mnt/%s/%s", storagePool.getUuid(), volumePath); + + volumePaths.put(transferId, volumePath); + volumeSizes.put(transferId, volume.getSize()); + } + + if (transferIds.isEmpty()) { + continue; + } + + GetImageTransferProgressCommand cmd = new GetImageTransferProgressCommand(transferIds, volumePaths, volumeSizes); + GetImageTransferProgressAnswer answer = (GetImageTransferProgressAnswer) agentManager.send(hostId, cmd); + + if (answer != null && answer.getResult() && answer.getProgressMap() != null) { + for (ImageTransferVO transfer : hostTransfers) { + String transferId = transfer.getUuid(); + Integer progress = answer.getProgressMap().get(transferId); + if (progress != null) { + transfer.setProgress(progress); + if (progress == 100) { + transfer.setPhase(ImageTransfer.Phase.finished); + logger.debug("Updated phase for image transfer {} to finished", transferId); + } + imageTransferDao.update(transfer.getId(), transfer); + logger.debug("Updated progress for image transfer {}: {}%", transferId, progress); + } + } + } else { + logger.warn("Failed to get progress for transfers on host {}: {}", hostId, + answer != null ? answer.getDetails() : "null answer"); + } + + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.warn("Failed to communicate with host {} for image transfer progress", hostId); + } catch (Exception e) { + logger.error("Error polling image transfer progress for host " + hostId, e); + } + } + + } catch (Exception e) { + logger.error("Error in pollImageTransferProgress", e); + } + } + + @Override + public String getConfigComponentName() { + return IncrementalBackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + ImageTransferPollingInterval + }; + } } diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java index 449525f8328..b3e970b0c51 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java @@ -3826,7 +3826,7 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S // Open firewall port for image server if (_inSystemVM) { String rule = String.format("-p tcp -m state --state NEW -m tcp --dport %d -j ACCEPT", imageServerPort); - IpTablesHelper.addConditionally(IpTablesHelper.INPUT_CHAIN, true, rule, + IpTablesHelper.addConditionally(IpTablesHelper.INPUT_CHAIN, false, rule, String.format("Error in opening up image server port %d", imageServerPort)); }