You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by su...@apache.org on 2026/01/29 10:22:22 UTC

(cloudstack) branch 4.22 updated (a8f1e4a5bac -> d700e2db643)

This is an automated email from the ASF dual-hosted git repository.

sureshanaparti pushed a change to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


    from a8f1e4a5bac import network acl rules using csv (#12013)
     add 0dcbe57a478 Fix that Sensitive information logged in SshHelper.sshExecute method (#12026)
     add 6932cacabc1 Allow copy of templates from secondary storages of other zone when adding a new secondary storage (#12296)
     add ff7ec0cd229 Update alert id for VR public and private interface (#12527)
     add 83ce0067b82 Update the snapshot physical size for the primary storage resource after snapshot creation and during resource count recalculation (#12481)
     add 35e6d7c5ba8 fix that log sensitive infomation in cmd of script (#12024)
     add 95de88a8ffe Usage server should takeover immediately if the other Usage server has been stopped gracefully (#12507)
     add 1300fc5e91a Fix keyword parameter filtering in listBackupOfferings API (#12540)
     add 243872a7710 Use infinite scroll select (#11991)
     add b7fc7179fca Add batch deletion support to `removeRawUsageRecords` (#12522)
     add 664f76c7e4b Fix KvmSshToAgentEnabled setting description and make it dynamic (#12533)
     add 26b57655ece Deployment plan fixes for VM with last host, and last host in maintenance (#12062)
     new d700e2db643 Merge branch '4.20' into 4.22

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/cloudstack/alert/AlertService.java  |   4 +-
 .../command/admin/host/AddSecondaryStorageCmd.java |  24 ++-
 .../command/user/snapshot/CreateSnapshotCmd.java   |   3 +-
 .../service/StorageOrchestrationService.java       |   3 +-
 .../subsystem/api/storage/TemplateService.java     |   4 +-
 .../main/java/com/cloud/alert/AlertManager.java    |   1 -
 .../java/com/cloud/resource/ResourceManager.java   |   4 +-
 .../java/com/cloud/storage/StorageManager.java     |   5 +-
 .../engine/orchestration/StorageOrchestrator.java  |  45 +++--
 .../main/java/com/cloud/usage/dao/UsageDao.java    |   2 +-
 .../java/com/cloud/usage/dao/UsageDaoImpl.java     |  37 ++--
 .../main/java/com/cloud/usage/dao/UsageJobDao.java |   2 +
 .../java/com/cloud/usage/dao/UsageJobDaoImpl.java  |   3 +-
 .../storage/datastore/db/SnapshotDataStoreDao.java |  14 ++
 .../datastore/db/SnapshotDataStoreDaoImpl.java     |  42 +++-
 .../resources/META-INF/db/schema-42020to42030.sql  |   6 +
 .../resources/META-INF/db/schema-42200to42210.sql  |   6 +
 .../storage/image/TemplateServiceImpl.java         | 157 ++++++++++++--
 .../storage/image/TemplateServiceImplTest.java     | 171 +++++++++++++++-
 .../src/main/java/com/cloud/utils/db/Filter.java   |   3 +-
 .../java/com/cloud/utils/db/GenericDaoBase.java    |   4 +-
 .../LibvirtUpdateHostPasswordCommandWrapper.java   |   3 +-
 .../CitrixUpdateHostPasswordCommandWrapper.java    |   2 +-
 .../configuration/ConfigurationManagerImpl.java    |   2 +-
 .../deploy/DeploymentPlanningManagerImpl.java      |  82 ++++----
 .../main/java/com/cloud/event/AlertGenerator.java  |   5 +-
 .../com/cloud/ha/HighAvailabilityManagerImpl.java  |   4 +-
 .../com/cloud/resource/ResourceManagerImpl.java    |   5 +-
 .../resourcelimit/ResourceLimitManagerImpl.java    |  10 +-
 .../com/cloud/storage/ImageStoreDetailsUtil.java   |  11 +
 .../java/com/cloud/storage/StorageManagerImpl.java |   2 +-
 .../storage/snapshot/SnapshotManagerImpl.java      |  30 +--
 .../com/cloud/template/TemplateManagerImpl.java    |  14 +-
 .../java/com/cloud/usage/UsageServiceImpl.java     |   3 +-
 .../cloudstack/backup/BackupManagerImpl.java       |   8 +-
 .../cloud/resource/ResourceManagerImplTest.java    |  14 +-
 .../ResourceLimitManagerImplTest.java              |  10 +-
 ui/public/locales/en.json                          |   4 +-
 ui/src/components/view/DedicateDomain.vue          | 129 +++++-------
 ui/src/components/widgets/InfiniteScrollSelect.vue |  91 ++++++++-
 ui/src/views/iam/AddUser.vue                       | 121 ++++-------
 ui/src/views/infra/AddSecondaryStorage.vue         |  82 +++++++-
 ui/src/views/infra/UsageRecords.vue                | 112 +++++-----
 ui/src/views/infra/zone/ZoneWizardAddResources.vue |  25 ++-
 ui/src/views/infra/zone/ZoneWizardLaunchZone.vue   |   5 +
 ui/src/views/storage/CreateTemplate.vue            | 111 ++++------
 ui/src/views/storage/UploadLocalVolume.vue         | 225 ++++++++-------------
 ui/src/views/storage/UploadVolume.vue              | 218 ++++++++------------
 ui/src/views/tools/CreateWebhook.vue               | 126 ++++--------
 ui/src/views/tools/ManageVolumes.vue               | 157 ++++++--------
 .../java/com/cloud/usage/UsageManagerImpl.java     |   5 +
 .../main/java/com/cloud/utils/script/Script.java   |  99 +++++----
 .../main/java/com/cloud/utils/ssh/SshHelper.java   |  73 ++++++-
 .../java/com/cloud/utils/script/ScriptTest.java    |  30 +++
 .../java/com/cloud/utils/ssh/SshHelperTest.java    |  60 ++++++
 55 files changed, 1471 insertions(+), 947 deletions(-)


(cloudstack) 01/01: Merge branch '4.20' into 4.22

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sureshanaparti pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit d700e2db643c6d1adbda82957f21a6a8b696f704
Merge: a8f1e4a5bac 26b57655ece
Author: Suresh Kumar Anaparti <su...@gmail.com>
AuthorDate: Thu Jan 29 15:51:58 2026 +0530

    Merge branch '4.20' into 4.22

 .../org/apache/cloudstack/alert/AlertService.java  |   4 +-
 .../command/admin/host/AddSecondaryStorageCmd.java |  24 ++-
 .../command/user/snapshot/CreateSnapshotCmd.java   |   3 +-
 .../service/StorageOrchestrationService.java       |   3 +-
 .../subsystem/api/storage/TemplateService.java     |   4 +-
 .../main/java/com/cloud/alert/AlertManager.java    |   1 -
 .../java/com/cloud/resource/ResourceManager.java   |   4 +-
 .../java/com/cloud/storage/StorageManager.java     |   5 +-
 .../engine/orchestration/StorageOrchestrator.java  |  45 +++--
 .../main/java/com/cloud/usage/dao/UsageDao.java    |   2 +-
 .../java/com/cloud/usage/dao/UsageDaoImpl.java     |  37 ++--
 .../main/java/com/cloud/usage/dao/UsageJobDao.java |   2 +
 .../java/com/cloud/usage/dao/UsageJobDaoImpl.java  |   3 +-
 .../storage/datastore/db/SnapshotDataStoreDao.java |  14 ++
 .../datastore/db/SnapshotDataStoreDaoImpl.java     |  42 +++-
 .../resources/META-INF/db/schema-42020to42030.sql  |   6 +
 .../resources/META-INF/db/schema-42200to42210.sql  |   6 +
 .../storage/image/TemplateServiceImpl.java         | 157 ++++++++++++--
 .../storage/image/TemplateServiceImplTest.java     | 171 +++++++++++++++-
 .../src/main/java/com/cloud/utils/db/Filter.java   |   3 +-
 .../java/com/cloud/utils/db/GenericDaoBase.java    |   4 +-
 .../LibvirtUpdateHostPasswordCommandWrapper.java   |   3 +-
 .../CitrixUpdateHostPasswordCommandWrapper.java    |   2 +-
 .../configuration/ConfigurationManagerImpl.java    |   2 +-
 .../deploy/DeploymentPlanningManagerImpl.java      |  82 ++++----
 .../main/java/com/cloud/event/AlertGenerator.java  |   5 +-
 .../com/cloud/ha/HighAvailabilityManagerImpl.java  |   4 +-
 .../com/cloud/resource/ResourceManagerImpl.java    |   5 +-
 .../resourcelimit/ResourceLimitManagerImpl.java    |  10 +-
 .../com/cloud/storage/ImageStoreDetailsUtil.java   |  11 +
 .../java/com/cloud/storage/StorageManagerImpl.java |   2 +-
 .../storage/snapshot/SnapshotManagerImpl.java      |  30 +--
 .../com/cloud/template/TemplateManagerImpl.java    |  14 +-
 .../java/com/cloud/usage/UsageServiceImpl.java     |   3 +-
 .../cloudstack/backup/BackupManagerImpl.java       |   8 +-
 .../cloud/resource/ResourceManagerImplTest.java    |  14 +-
 .../ResourceLimitManagerImplTest.java              |  10 +-
 ui/public/locales/en.json                          |   4 +-
 ui/src/components/view/DedicateDomain.vue          | 129 +++++-------
 ui/src/components/widgets/InfiniteScrollSelect.vue |  91 ++++++++-
 ui/src/views/iam/AddUser.vue                       | 121 ++++-------
 ui/src/views/infra/AddSecondaryStorage.vue         |  82 +++++++-
 ui/src/views/infra/UsageRecords.vue                | 112 +++++-----
 ui/src/views/infra/zone/ZoneWizardAddResources.vue |  25 ++-
 ui/src/views/infra/zone/ZoneWizardLaunchZone.vue   |   5 +
 ui/src/views/storage/CreateTemplate.vue            | 111 ++++------
 ui/src/views/storage/UploadLocalVolume.vue         | 225 ++++++++-------------
 ui/src/views/storage/UploadVolume.vue              | 218 ++++++++------------
 ui/src/views/tools/CreateWebhook.vue               | 126 ++++--------
 ui/src/views/tools/ManageVolumes.vue               | 157 ++++++--------
 .../java/com/cloud/usage/UsageManagerImpl.java     |   5 +
 .../main/java/com/cloud/utils/script/Script.java   |  99 +++++----
 .../main/java/com/cloud/utils/ssh/SshHelper.java   |  73 ++++++-
 .../java/com/cloud/utils/script/ScriptTest.java    |  30 +++
 .../java/com/cloud/utils/ssh/SshHelperTest.java    |  60 ++++++
 55 files changed, 1471 insertions(+), 947 deletions(-)

diff --cc api/src/main/java/org/apache/cloudstack/alert/AlertService.java
index d8e471756a0,4ae6288efce..14223227c34
--- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java
+++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java
@@@ -71,11 -71,8 +71,11 @@@ public interface AlertService 
          public static final AlertType ALERT_TYPE_HA_ACTION = new AlertType((short)30, "ALERT.HA.ACTION", true);
          public static final AlertType ALERT_TYPE_CA_CERT = new AlertType((short)31, "ALERT.CA.CERT", true);
          public static final AlertType ALERT_TYPE_VM_SNAPSHOT = new AlertType((short)32, "ALERT.VM.SNAPSHOT", true);
-         public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PUBLIC.IFACE.MTU", true);
-         public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true);
+         public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)33, "ALERT.VR.PUBLIC.IFACE.MTU", true);
+         public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)34, "ALERT.VR.PRIVATE.IFACE.MTU", true);
 +        public static final AlertType ALERT_TYPE_EXTENSION_PATH_NOT_READY = new AlertType((short)33, "ALERT.TYPE.EXTENSION.PATH.NOT.READY", true);
 +        public static final AlertType ALERT_TYPE_BACKUP_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_BACKUP_STORAGE, "ALERT.STORAGE.BACKUP", true);
 +        public static final AlertType ALERT_TYPE_OBJECT_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_OBJECT_STORAGE, "ALERT.STORAGE.OBJECT", true);
  
          public short getType() {
              return type;
diff --cc api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
index 3a49bad8fcb,078d4517f95..1b9d3c59e5a
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
@@@ -270,9 -243,8 +270,8 @@@ public class CreateSnapshotCmd extends 
          }
      }
  
 -    private Snapshot.LocationType getLocationType() {
 +    public Snapshot.LocationType getLocationType() {
- 
-         if (Snapshot.LocationType.values() == null || Snapshot.LocationType.values().length == 0 || locationType == null) {
+         if (locationType == null) {
              return null;
          }
  
diff --cc engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
index 3e3901cb293,4ce1f4a9638..5c7348cbe6c
--- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
+++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
@@@ -220,16 -220,9 +220,17 @@@ public interface StorageManager extend
              "storage.pool.host.connect.workers", "1",
              "Number of worker threads to be used to connect hosts to a primary storage", true);
  
 +    ConfigKey<Float> ObjectStorageCapacityThreshold = new ConfigKey<>("Alert", Float.class,
 +            "objectStorage.capacity.notificationthreshold",
 +            "0.75",
 +            "Percentage (as a value between 0 and 1) of object storage utilization above which alerts will be sent about low storage available.",
 +            true,
 +            ConfigKey.Scope.Global,
 +            null);
 +
-     ConfigKey<Boolean> COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES = new ConfigKey<>(Boolean.class, "copy.public.templates.from.other.storages",
-             "Storage", "true", "Allow SSVMs to try copying public templates from one secondary storage to another instead of downloading them from the source.",
+     ConfigKey<Boolean> COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES = new ConfigKey<>(Boolean.class, "copy.templates.from.other.secondary.storages",
+             "Storage", "true", "When enabled, this feature allows templates to be copied from existing Secondary Storage servers (within the same zone or across zones) " +
+             "while adding a new Secondary Storage. If the copy operation fails, the system falls back to downloading the template from the source URL.",
              true, ConfigKey.Scope.Zone, null);
  
      /**
diff --cc engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java
index ac9601389bd,c68316dd1fe..cdf903407c1
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java
@@@ -92,10 -78,15 +92,19 @@@ public class SnapshotDataStoreDaoImpl e
              " order by created %s " +
              " limit 1";
  
 +    private static final String FIND_SNAPSHOT_IN_ZONE = "SELECT ssr.* FROM " +
 +            "snapshot_store_ref ssr, snapshots s " +
 +            "WHERE ssr.snapshot_id=? AND ssr.snapshot_id = s.id AND s.data_center_id=?;";
 +
+     private static final String GET_PHYSICAL_SIZE_OF_SNAPSHOTS_ON_PRIMARY_BY_ACCOUNT = "SELECT SUM(s.physical_size) " +
+             "FROM cloud.snapshot_store_ref s " +
+             "LEFT JOIN cloud.snapshots ON s.snapshot_id = snapshots.id " +
+             "WHERE snapshots.account_id = ? " +
+             "AND snapshots.removed IS NULL " +
+             "AND s.state = 'Ready' " +
+             "AND s.store_role = 'Primary' " +
+             "AND NOT EXISTS (SELECT 1 FROM cloud.snapshot_store_ref i WHERE i.snapshot_id = s.snapshot_id AND i.store_role = 'Image')";
+ 
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
@@@ -136,11 -127,10 +145,11 @@@
          stateSearch.and(STATE, stateSearch.entity().getState(), SearchCriteria.Op.IN);
          stateSearch.done();
  
 +
-         idStateNinSearch = createSearchBuilder();
-         idStateNinSearch.and(SNAPSHOT_ID, idStateNinSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
-         idStateNinSearch.and(STATE, idStateNinSearch.entity().getState(), SearchCriteria.Op.NOTIN);
-         idStateNinSearch.done();
+         idStateNeqSearch = createSearchBuilder();
+         idStateNeqSearch.and(SNAPSHOT_ID, idStateNeqSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
+         idStateNeqSearch.and(STATE, idStateNeqSearch.entity().getState(), SearchCriteria.Op.NEQ);
+         idStateNeqSearch.done();
  
          snapshotVOSearch = snapshotDao.createSearchBuilder();
          snapshotVOSearch.and(VOLUME_ID, snapshotVOSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
@@@ -471,17 -355,9 +480,17 @@@
  
      @Override
      public List<SnapshotDataStoreVO> findBySnapshotIdWithNonDestroyedState(long snapshotId) {
-         SearchCriteria<SnapshotDataStoreVO> sc = idStateNinSearch.create();
+         SearchCriteria<SnapshotDataStoreVO> sc = idStateNeqSearch.create();
          sc.setParameters(SNAPSHOT_ID, snapshotId);
 -        sc.setParameters(STATE, State.Destroyed);
 +        sc.setParameters(STATE, State.Destroyed.name());
 +        return listBy(sc);
 +    }
 +
 +    @Override
 +    public List<SnapshotDataStoreVO> findBySnapshotIdAndNotInDestroyedHiddenState(long snapshotId) {
-         SearchCriteria<SnapshotDataStoreVO> sc = idStateNinSearch.create();
++        SearchCriteria<SnapshotDataStoreVO> sc = idStateNeqSearch.create();
 +        sc.setParameters(SNAPSHOT_ID, snapshotId);
 +        sc.setParameters(STATE, State.Destroyed.name(), State.Hidden.name());
          return listBy(sc);
      }
  
diff --cc engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql
index f5543756ed6,00000000000..a8a3d3f7bd4
mode 100644,000000..100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql
@@@ -1,29 -1,0 +1,35 @@@
 +-- 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.
 +
 +--;
 +-- Schema upgrade from 4.22.0.0 to 4.22.1.0
 +--;
 +
 +-- Add vm_id column to usage_event table for volume usage events
 +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.usage_event','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with volume usage events"');
 +CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.usage_event','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with volume usage events"');
 +
 +-- Add vm_id column to cloud_usage.usage_volume table
 +CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.usage_volume','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with the volume usage"');
 +
 +ALTER TABLE `cloud`.`template_store_ref` MODIFY COLUMN `download_url` varchar(2048);
++
++UPDATE `cloud`.`alert` SET type = 33 WHERE name = 'ALERT.VR.PUBLIC.IFACE.MTU';
++UPDATE `cloud`.`alert` SET type = 34 WHERE name = 'ALERT.VR.PRIVATE.IFACE.MTU';
++
++-- Update configuration 'kvm.ssh.to.agent' description and is_dynamic fields
++UPDATE `cloud`.`configuration` SET description = 'True if the management server will restart the agent service via SSH into the KVM hosts after or during maintenance operations', is_dynamic = 1 WHERE name = 'kvm.ssh.to.agent';
diff --cc server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
index bb165e0529d,6881fbab98c..230f97f6fd3
--- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
+++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
@@@ -36,7 -36,7 +36,8 @@@ import java.util.stream.Collectors
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
 +import com.cloud.gpu.dao.VgpuProfileDao;
+ import com.cloud.resource.ResourceState;
  import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
  import org.apache.cloudstack.affinity.AffinityGroupProcessor;
  import org.apache.cloudstack.affinity.AffinityGroupService;
diff --cc server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index 6f289f8e6bb,d1dca0fa901..9f9928bfb66
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@@ -4613,8 -4206,7 +4613,8 @@@ public class StorageManagerImpl extend
                  DataStoreDownloadFollowRedirects,
                  AllowVolumeReSizeBeyondAllocation,
                  StoragePoolHostConnectWorkers,
 +                ObjectStorageCapacityThreshold,
-                 COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES
+                 COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES
          };
      }
  
diff --cc server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
index 886feea19f2,19cde4da0f1..900c59abb66
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
@@@ -1688,16 -1479,12 +1695,17 @@@ public class SnapshotManagerImpl extend
                  UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_CREATE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null,
                          snapshotStoreRef.getPhysicalSize(), volume.getSize(), snapshot.getClass().getName(), snapshot.getUuid());
  
+                 ResourceType storeResourceType = dataStoreRole == DataStoreRole.Image ? ResourceType.secondary_storage : ResourceType.primary_storage;
                  // Correct the resource count of snapshot in case of delta snapshots.
-                 _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize()));
+                 _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), storeResourceType, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize()));
  
 -                if (!payload.getAsyncBackup() && backupSnapToSecondary) {
 -                    copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds());
 +                if (!payload.getAsyncBackup()) {
 +                    if (backupSnapToSecondary) {
 +                        copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds());
 +                    }
 +                    if (CollectionUtils.isNotEmpty(payload.getStoragePoolIds())) {
 +                        copyNewSnapshotToZonesOnPrimary(payload, snapshot);
 +                    }
                  }
              } catch (Exception e) {
                  logger.debug("post process snapshot failed", e);
diff --cc server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
index 7e60c111ab2,1669d7a47d9..47472408404
--- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
@@@ -55,11 -45,8 +55,12 @@@ import com.cloud.vm.dao.VMInstanceDao
  import com.trilead.ssh2.Connection;
  import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd;
  import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
+ import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  import org.junit.After;
  import org.junit.Assert;
  import org.junit.Before;
diff --cc server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
index a968a2da0b7,53ccc830dd2..e04ccc0ca13
--- a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
@@@ -31,9 -26,9 +31,10 @@@ import org.apache.cloudstack.api.ApiCom
  import org.apache.cloudstack.api.response.AccountResponse;
  import org.apache.cloudstack.api.response.DomainResponse;
  import org.apache.cloudstack.api.response.TaggedResourceLimitAndCountResponse;
 +import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.reservation.dao.ReservationDao;
+ import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.LogManager;
@@@ -128,9 -120,8 +129,11 @@@ public class ResourceLimitManagerImplTe
      @Mock
      UserVmDao userVmDao;
      @Mock
 +    EntityManager entityManager;
++    @Mock
+     SnapshotDataStoreDao snapshotDataStoreDao;
  
 +    private CallContext callContext;
      private List<String> hostTags = List.of("htag1", "htag2", "htag3");
      private List<String> storageTags = List.of("stag1", "stag2");
  
diff --cc ui/public/locales/en.json
index bf5ea84b343,99873820d53..0a9539abff3
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@@ -649,9 -591,10 +649,11 @@@
  "label.copy.consoleurl": "Copy console URL to clipboard",
  "label.copyid": "Copy ID",
  "label.copy.password": "Copy password",
+ "label.copy.templates.from.other.secondary.storages": "Copy Templates from other storages instead of fetching from URLs",
+ "label.copy.templates.from.other.secondary.storages.add.zone": "Copy Templates from other storages",
  "label.core": "Core",
  "label.core.zone.type": "Core Zone type",
 +"label.count": "Count",
  "label.counter": "Counter",
  "label.counter.name": "Name of the counter for which the policy will be evaluated",
  "label.cpu": "CPU",
@@@ -3312,17 -3018,12 +3314,17 @@@
  "message.desc.import.shared.kvm.wizard": "Import QCOW2 image from selected Primary Storage Pool",
  "message.desc.import.unmanage.volume": "Please choose a storage pool that you want to import or unmanage volumes. The storage pool should be in Up status. <br>This feature only supports KVM.",
  "message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. Unmanaging an Instance removes CloudStack ability to manage it. In both cases, the Instance is left running and no changes are done to the VM on the hypervisor.<br><br>For KVM, managing a VM is an experimental feature.",
 +"message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested",
  "message.desc.importmigratefromvmwarewizard": "By selecting an existing or external VMware Datacenter and an instance to import, CloudStack migrates the selected instance from VMware to KVM on a conversion host using virt-v2v and imports it into a KVM Cluster",
  "message.desc.primary.storage": "Each Cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.",
 -"message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.",
 -"message.desc.secondary.storage": "Each Zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.<br/><br/> \"Copy templates from other secondary storages\" switch can be used to automatically copy existing templates from secondary storages in other zones i [...]
 +"message.desc.register.template": "Hosted on download.cloudstack.org, these templates can be easily registered directly within CloudStack. Simply click <strong>Register Template</strong> for the templates you wish to use.",
 +"message.desc.register.cni.config": "Please fill in the following data to register CNI Configuration as user data.",
  "message.desc.register.user.data": "Please fill in the following to register new User Data.",
  "message.desc.registered.user.data": "Registered a User Data.",
 +"message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.",
- "message.desc.secondary.storage": "Each Zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.",
++"message.desc.secondary.storage": "Each Zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.<br/><br/> \"Copy templates from other secondary storages\" switch can be used to automatically copy existing templates from secondary storages in other zones i [...]
 +"message.desc.validationformat": "Specifies the format used to validate the parameter value, such as EMAIL, URL, UUID, DECIMAL, etc.",
 +"message.desc.valueoptions": "Provide a comma-separated list of values that will appear as selectable options for this parameter",
  "message.desc.zone": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more Pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",
  "message.desc.zone.edge": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 Networks can be deployed in such zones and functionalities that require secondary storages are not supported.",
  "message.drs.plan.description": "The maximum number of live migrations allowed for DRS. Configure DRS under the settings tab before generating a plan or to enable automatic DRS for the Cluster.",
diff --cc ui/src/components/widgets/InfiniteScrollSelect.vue
index 122feafb2a0,da780b66b80..8db4d8d523a
--- a/ui/src/components/widgets/InfiniteScrollSelect.vue
+++ b/ui/src/components/widgets/InfiniteScrollSelect.vue
@@@ -41,9 -41,10 +41,11 @@@
    - optionValueKey (String, optional): Property to use as the value for options (e.g., 'name'). Default is 'id'
    - optionLabelKey (String, optional): Property to use as the label for options (e.g., 'name'). Default is 'name'
    - defaultOption (Object, optional): Preselected object to include initially
+   - allowClear (Boolean, optional): Whether to allow clearing the selection. Default is false
    - showIcon (Boolean, optional): Whether to show icon for the options. Default is true
    - defaultIcon (String, optional): Icon to be shown when there is no resource icon for the option. Default is 'cloud-outlined'
 +  - autoSelectFirstOption (Boolean, optional): Whether to automatically select the first option when options are loaded. Default is false
+   - selectFirstOption (Boolean, optional): Whether to automatically select the first option when options are loaded. Default is false
  
    Events:
    - @change-option-value (Function): Emits the selected option value(s) when value(s) changes. Do not use @change as it will give warnings and may not work
@@@ -76,9 -78,9 +79,9 @@@
          </div>
        </div>
      </template>
-     <a-select-option v-for="option in options" :key="option.id" :value="option[optionValueKey]">
+     <a-select-option v-for="option in selectableOptions" :key="option.id" :value="option[optionValueKey]">
        <span>
-         <span v-if="showIcon && option.showicon !== false">
 -        <span v-if="showIcon && option.id !== null && option.id !== undefined">
++        <span v-if="showIcon && option.showicon !== false && option.id !== null && option.id !== undefined">
            <resource-icon v-if="option.icon && option.icon.base64image" :image="option.icon.base64image" size="1x" style="margin-right: 5px"/>
            <render-icon v-else :icon="defaultIcon" style="margin-right: 5px" />
          </span>
@@@ -141,9 -143,9 +148,13 @@@ export default 
        type: Number,
        default: null
      },
 +    autoSelectFirstOption: {
 +      type: Boolean,
 +      default: false
++    },
+     selectFirstOption: {
+       type: Boolean,
+       default: false
      }
    },
    data () {
@@@ -157,7 -159,7 +168,8 @@@
        scrollHandlerAttached: false,
        preselectedOptionValue: null,
        successiveFetches: 0,
-       canSelectFirstOption: false
++      canSelectFirstOption: false,
+       hasAutoSelectedFirst: false
      }
    },
    created () {
@@@ -218,9 -250,9 +260,10 @@@
        }).catch(error => {
          this.$notifyError(error)
        }).finally(() => {
 +        this.canSelectFirstOption = true
          if (this.successiveFetches === 0) {
            this.loading = false
+           this.autoSelectFirstOptionIfNeeded()
          }
        })
      },
@@@ -229,19 -261,12 +272,18 @@@
          (Array.isArray(this.preselectedOptionValue) && this.preselectedOptionValue.length === 0) ||
          this.successiveFetches >= this.maxSuccessiveFetches) {
          this.resetPreselectedOptionValue()
 +        if (!this.canSelectFirstOption && this.autoSelectFirstOption && this.options.length > 0) {
 +          this.$nextTick(() => {
 +            this.preselectedOptionValue = this.options[0][this.optionValueKey]
 +            this.onChange(this.preselectedOptionValue)
 +          })
 +        }
          return
        }
-       const matchValue = Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
-       const match = this.options.find(entry => entry[this.optionValueKey] === matchValue)
-       if (!match) {
+       if (!this.preselectedMatch) {
          this.successiveFetches++
-         if (this.options.length < this.totalCount) {
+         // Exclude defaultOption from count when comparing with totalCount
+         if (this.apiOptionsCount < this.totalCount) {
            this.fetchItems()
          } else {
            this.resetPreselectedOptionValue()
diff --cc ui/src/views/iam/AddUser.vue
index 374d81c51d1,3f0bd018050..acde7583887
--- a/ui/src/views/iam/AddUser.vue
+++ b/ui/src/views/iam/AddUser.vue
@@@ -313,79 -274,77 +274,81 @@@ export default 
      isValidValueForKey (obj, key) {
        return key in obj && obj[key] != null
      },
 -    handleSubmit (e) {
 +    async handleSubmit (e) {
        e.preventDefault()
        if (this.loading) return
 -      this.formRef.value.validate().then(() => {
 -        const values = toRaw(this.form)
 -        this.loading = true
 -        const params = {
 -          username: values.username,
 -          password: values.password,
 -          email: values.email,
 -          firstname: values.firstname,
 -          lastname: values.lastname,
 -          accounttype: 0
 -        }
 -
 -        // Account: use route query account if available, otherwise use form value (which is the account name)
 -        if (this.account) {
 -          params.account = this.account
 -        } else if (values.account) {
 -          params.account = values.account
 -        }
  
 -        // Domain: use route query domainid if available, otherwise use form value
 -        if (this.domainid) {
 -          params.domainid = this.domainid
 -        } else if (values.domainid) {
 -          params.domainid = values.domainid
 -        }
 +      await this.formRef.value.validate()
 +        .catch(error => this.formRef.value.scrollToField(error.errorFields[0].name))
  
 -        if (this.isValidValueForKey(values, 'timezone') && values.timezone.length > 0) {
 -          params.timezone = values.timezone
 -        }
 +      this.loading = true
 +      const values = toRaw(this.form)
 +      try {
 +        const userCreationResponse = await this.createUser(values)
 +        this.$notification.success({
 +          message: this.$t('label.create.user'),
 +          description: `${this.$t('message.success.create.user')} ${values.username}`
 +        })
  
 -        api('createUser', {}, 'POST', params).then(response => {
 -          this.$emit('refresh-data')
 +        const user = userCreationResponse?.createuserresponse?.user
 +        if (values.samlenable && user) {
 +          await postAPI('authorizeSamlSso', {
 +            enable: values.samlenable,
 +            entityid: values.samlentity,
 +            userid: user.id
 +          })
            this.$notification.success({
 -            message: this.$t('label.create.user'),
 -            description: `${this.$t('message.success.create.user')} ${params.username}`
 +            message: this.$t('label.samlenable'),
 +            description: this.$t('message.success.enable.saml.auth')
            })
 -          const user = response.createuserresponse.user
 -          if (values.samlenable && user) {
 -            api('authorizeSamlSso', {
 -              enable: values.samlenable,
 -              entityid: values.samlentity,
 -              userid: user.id
 -            }).then(response => {
 -              this.$notification.success({
 -                message: this.$t('label.samlenable'),
 -                description: this.$t('message.success.enable.saml.auth')
 -              })
 -            }).catch(error => {
 -              this.$notification.error({
 -                message: this.$t('message.request.failed'),
 -                description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
 -                duration: 0
 -              })
 -            })
 -          }
 +        }
 +
 +        this.closeAction()
 +        this.$emit('refresh-data')
 +      } catch (error) {
 +        if (error?.config?.params?.command === 'authorizeSamlSso') {
            this.closeAction()
 -        }).catch(error => {
 -          this.$notification.error({
 -            message: this.$t('message.request.failed'),
 -            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
 -            duration: 0
 -          })
 -        }).finally(() => {
 -          this.loading = false
 +          this.$emit('refresh-data')
 +        }
 +
 +        this.$notification.error({
 +          message: this.$t('message.request.failed'),
 +          description: error?.response?.headers['x-description'] || error.message,
 +          duration: 0
          })
 -      }).catch(error => {
 -        this.formRef.value.scrollToField(error.errorFields[0].name)
 -      })
 +      } finally {
 +        this.loading = false
 +      }
 +    },
 +    async createUser (rawParams) {
 +      const params = {
 +        username: rawParams.username,
 +        password: rawParams.password,
 +        email: rawParams.email,
 +        firstname: rawParams.firstname,
 +        lastname: rawParams.lastname,
 +        accounttype: 0
 +      }
 +
++      // Account: use route query account if available, otherwise use form value (which is the account name)
 +      if (this.account) {
 +        params.account = this.account
-       } else if (this.accountList[rawParams.account]) {
-         params.account = this.accountList[rawParams.account].name
++      } else if (rawParams.account) {
++        params.account = rawParams.account
 +      }
 +
++      // Domain: use route query domainid if available, otherwise use form value
 +      if (this.domainid) {
 +        params.domainid = this.domainid
 +      } else if (rawParams.domainid) {
 +        params.domainid = rawParams.domainid
 +      }
 +
 +      if (this.isValidValueForKey(rawParams, 'timezone') && rawParams.timezone.length > 0) {
 +        params.timezone = rawParams.timezone
 +      }
 +
 +      return postAPI('createUser', params)
      },
      async validateConfirmPassword (rule, value) {
        if (!value || value.length === 0) {
diff --cc ui/src/views/infra/AddSecondaryStorage.vue
index 0a02401e8e5,db4893115a6..603801b41fb
--- a/ui/src/views/infra/AddSecondaryStorage.vue
+++ b/ui/src/views/infra/AddSecondaryStorage.vue
@@@ -229,16 -246,51 +246,51 @@@ export default 
      closeModal () {
        this.$emit('close-action')
      },
+     fetchCopyTemplatesConfig () {
+       if (!this.form.zone) {
+         return
+       }
+ 
 -      api('listConfigurations', {
++      getAPI('listConfigurations', {
+         name: 'copy.templates.from.other.secondary.storages',
+         zoneid: this.form.zone
+       }).then(json => {
+         const items =
+           json?.listconfigurationsresponse?.configuration || []
+ 
+         items.forEach(item => {
+           if (item.name === 'copy.templates.from.other.secondary.storages') {
+             this.form.copyTemplatesFromOtherSecondaryStorages =
+               item.value === 'true'
+           }
+         })
+       })
+     },
+     onZoneChange (val) {
+       this.form.zone = val
+       this.copyTemplatesTouched = false
+       this.fetchCopyTemplatesConfig()
+     },
      listZones () {
 -      api('listZones', { showicon: true }).then(json => {
 +      getAPI('listZones', { showicon: true }).then(json => {
-         if (json && json.listzonesresponse && json.listzonesresponse.zone) {
-           this.zones = json.listzonesresponse.zone
-           if (this.zones.length > 0) {
-             this.form.zone = this.zones[0].id || ''
-           }
+         this.zones = json.listzonesresponse.zone || []
+ 
+         if (this.zones.length > 0) {
+           this.form.zone = this.zones[0].id
+           this.fetchCopyTemplatesConfig()
          }
        })
      },
+     checkOtherSecondaryStorages () {
 -      api('listImageStores', { listall: true }).then(json => {
++      getAPI('listImageStores', { listall: true }).then(json => {
+         const stores = json?.listimagestoresresponse?.imagestore || []
+ 
+         this.showCopyTemplatesToggle = stores.length > 0
+       })
+     },
+     onCopyTemplatesToggleChanged (val) {
+       this.copyTemplatesTouched = true
+     },
      nfsURL (server, path) {
        var url
        if (path.substring(0, 1) !== '/') {
diff --cc ui/src/views/infra/zone/ZoneWizardAddResources.vue
index b2a273f4c88,298cc7fec9d..32b7b10ad6d
--- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue
+++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
@@@ -1116,9 -1117,23 +1125,23 @@@ export default 
          this.storageProviders = storageProviders
        })
      },
+     applyCopyTemplatesOptionFromGlobalSettingDuringSecondaryStorageAddition () {
 -      api('listConfigurations', {
++      getAPI('listConfigurations', {
+         name: 'copy.templates.from.other.secondary.storages'
+       }).then(json => {
+         const config = json?.listconfigurationsresponse?.configuration?.[0]
+ 
+         if (!config || config.value === undefined) {
+           return
+         }
+ 
+         const value = String(config.value).toLowerCase() === 'true'
+         this.copytemplate = value
+       })
+     },
      fetchPrimaryStorageProvider () {
        this.primaryStorageProviders = []
 -      api('listStorageProviders', { type: 'primary' }).then(json => {
 +      getAPI('listStorageProviders', { type: 'primary' }).then(json => {
          this.primaryStorageProviders = json.liststorageprovidersresponse.dataStoreProvider || []
          this.primaryStorageProviders.map((item, idx) => { this.primaryStorageProviders[idx].id = item.name })
        })
diff --cc ui/src/views/storage/UploadVolume.vue
index db8866caa0c,c2cbaabc225..12a39eae340
--- a/ui/src/views/storage/UploadVolume.vue
+++ b/ui/src/views/storage/UploadVolume.vue
@@@ -158,7 -142,7 +142,7 @@@
  
  <script>
  import { ref, reactive, toRaw } from 'vue'
- import { getAPI, postAPI } from '@/api'
 -import { api } from '@/api'
++import { postAPI } from '@/api'
  import { mixinForm } from '@/utils/mixin'
  import ResourceIcon from '@/components/view/ResourceIcon'
  import TooltipLabel from '@/components/widgets/TooltipLabel'
diff --cc ui/src/views/tools/CreateWebhook.vue
index 961acd20e18,ef07cc39ed0..5f7ed0204b4
--- a/ui/src/views/tools/CreateWebhook.vue
+++ b/ui/src/views/tools/CreateWebhook.vue
@@@ -155,8 -149,7 +149,7 @@@
  
  <script>
  import { ref, reactive, toRaw } from 'vue'
- import { getAPI, postAPI } from '@/api'
- import _ from 'lodash'
 -import { api } from '@/api'
++import { postAPI } from '@/api'
  import { mixinForm } from '@/utils/mixin'
  import TooltipLabel from '@/components/widgets/TooltipLabel'
  import TestWebhookDeliveryView from '@/components/view/TestWebhookDeliveryView'
@@@ -300,13 -263,11 +263,11 @@@ export default 
            return
          }
          if (values.account) {
-           const accountItem = _.find(this.accounts, (option) => option.id === values.account)
-           if (accountItem) {
-             params.account = accountItem.name
-           }
+           // values.account is the account name (optionValueKey="name")
+           params.account = values.account
          }
          this.loading = true
 -        api('createWebhook', params).then(json => {
 +        postAPI('createWebhook', params).then(json => {
            this.$emit('refresh-data')
            this.$notification.success({
              message: this.$t('label.create.webhook'),