You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by sh...@apache.org on 2025/01/31 11:24:59 UTC

(cloudstack) branch 4.20 updated (4201a2dc11b -> ae2ffbe40bf)

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

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


    from 4201a2dc11b Fix Usage job not executing successfully (#10244)
     add 1c84ce4e23e server: fix attach uploaded volume (#10267)
     add 6f03f9e726e validate inserted values in numeric global settings (#10279)
     add 3313cc58295 ui: fix loading for hypervisor filter in serachview (#10292)
     add ee0dc5b2d64 list hosts API fix, when any stale entries exists on storage_pool_host_ref for the removed pools (#9852)
     add 0f544c9a3b0 api/ui: add specifyvlan to network response (#10236)
     add b9890875cc1 CKS: use --delete-emptydir-data instead of deprecated --delete-local-data (#10234)
     add b93589b5bdf server: reset 2fa user configuration on incomplete setup (#10247)
     add d9af9bdb36e fix SQL syntax erros and target 419 (#10273)
     add c70e4e29be4 fix npe on account creation (#10274)
     add 0fbf6379f98 UI: Fix domain view when opening details for a specific domainid (#10245)
     add bbe9c905926 packaging: support both mysql and mariadb on EL8/EL9 (#9941)
     add 641a60670e1 changed the kubernetestool url (#10295)
     add a335feab6a3 fix allocation of vmfs storage pools (#10201)
     add 97be6f2e5d1 ui: fix column filter for templates, isos (#10288)
     new ae2ffbe40bf Merge remote-tracking branch 'apache/4.19' into 4.20

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:
 .../cloudstack/api/response/NetworkResponse.java   |   8 +
 .../main/java/com/cloud/user/UserAccountVO.java    |  16 +-
 .../cloud.idempotent_update_api_permission.sql     |  52 +++++
 .../resources/META-INF/db/schema-41910to41920.sql  |  22 ++
 .../allocator/AbstractStoragePoolAllocator.java    |  16 +-
 packaging/el8/cloud.spec                           |   2 +-
 .../KubernetesClusterScaleWorker.java              |   2 +-
 .../contrail/management/MockAccountManager.java    |   4 +
 scripts/util/create-kubernetes-binaries-iso.sh     |   2 +-
 .../main/java/com/cloud/api/ApiResponseHelper.java |   1 +
 server/src/main/java/com/cloud/api/ApiServer.java  |   3 +-
 .../java/com/cloud/storage/StorageManagerImpl.java |  20 +-
 .../com/cloud/storage/VolumeApiServiceImpl.java    | 121 ++++++----
 .../main/java/com/cloud/user/AccountManager.java   |   7 +-
 .../java/com/cloud/user/AccountManagerImpl.java    |  37 ++-
 .../cloud/storage/VolumeApiServiceImplTest.java    | 252 ++++++++++++++++++++-
 .../com/cloud/user/AccountManagerImplTest.java     |  56 ++++-
 .../com/cloud/user/MockAccountManagerImpl.java     |  45 ++--
 ui/src/components/view/ListView.vue                |  16 +-
 ui/src/components/view/SearchView.vue              |   3 +
 ui/src/config/section/network.js                   |  15 +-
 ui/src/views/iam/DomainView.vue                    |  10 +
 ui/src/views/setting/ConfigurationValue.vue        |  23 ++
 23 files changed, 622 insertions(+), 111 deletions(-)
 create mode 100644 engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_update_api_permission.sql


(cloudstack) 01/01: Merge remote-tracking branch 'apache/4.19' into 4.20

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

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

commit ae2ffbe40bfc52ad32baf0b7296a04c14d4d9b4d
Merge: 4201a2dc11b 97be6f2e5d1
Author: Abhishek Kumar <ab...@gmail.com>
AuthorDate: Fri Jan 31 16:54:22 2025 +0530

    Merge remote-tracking branch 'apache/4.19' into 4.20

 .../cloudstack/api/response/NetworkResponse.java   |   8 +
 .../main/java/com/cloud/user/UserAccountVO.java    |  16 +-
 .../cloud.idempotent_update_api_permission.sql     |  52 +++++
 .../resources/META-INF/db/schema-41910to41920.sql  |  22 ++
 .../allocator/AbstractStoragePoolAllocator.java    |  16 +-
 packaging/el8/cloud.spec                           |   2 +-
 .../KubernetesClusterScaleWorker.java              |   2 +-
 .../contrail/management/MockAccountManager.java    |   4 +
 scripts/util/create-kubernetes-binaries-iso.sh     |   2 +-
 .../main/java/com/cloud/api/ApiResponseHelper.java |   1 +
 server/src/main/java/com/cloud/api/ApiServer.java  |   3 +-
 .../java/com/cloud/storage/StorageManagerImpl.java |  20 +-
 .../com/cloud/storage/VolumeApiServiceImpl.java    | 121 ++++++----
 .../main/java/com/cloud/user/AccountManager.java   |   7 +-
 .../java/com/cloud/user/AccountManagerImpl.java    |  37 ++-
 .../cloud/storage/VolumeApiServiceImplTest.java    | 252 ++++++++++++++++++++-
 .../com/cloud/user/AccountManagerImplTest.java     |  56 ++++-
 .../com/cloud/user/MockAccountManagerImpl.java     |  45 ++--
 ui/src/components/view/ListView.vue                |  16 +-
 ui/src/components/view/SearchView.vue              |   3 +
 ui/src/config/section/network.js                   |  15 +-
 ui/src/views/iam/DomainView.vue                    |  10 +
 ui/src/views/setting/ConfigurationValue.vue        |  23 ++
 23 files changed, 622 insertions(+), 111 deletions(-)

diff --cc engine/schema/src/main/java/com/cloud/user/UserAccountVO.java
index d204f67dc93,a9d4ca9a2b9..e4fcbad6b02
--- a/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java
+++ b/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java
@@@ -33,12 -32,13 +33,12 @@@ import javax.persistence.Table
  import javax.persistence.Transient;
  
  import org.apache.cloudstack.api.InternalIdentity;
++import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
++import org.apache.commons.lang3.StringUtils;
  
  import com.cloud.utils.db.Encrypt;
  import com.cloud.utils.db.GenericDao;
- import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
- import org.apache.commons.lang3.StringUtils;
  
 -import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 -import org.apache.commons.lang3.StringUtils;
 -
  @Entity
  @Table(name = "user")
  @SecondaryTable(name = "account", pkJoinColumns = {@PrimaryKeyJoinColumn(name = "account_id", referencedColumnName = "id")})
@@@ -379,4 -370,9 +373,10 @@@ public class UserAccountVO implements U
      public void setDetails(Map<String, String> details) {
          this.details = details;
      }
+ 
+     @Override
+     public String toString() {
 -        return String.format("User %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "name", "uuid"));
++        return String.format("UserAccount %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields
++                (this, "id", "uuid", "username", "accountName"));
+     }
  }
diff --cc engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
index 63524ccb6db,9e0a28875d1..2c034d8429a
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
@@@ -130,15 -131,22 +129,22 @@@ public abstract class AbstractStoragePo
              return null;
          }
  
-         if (pools.get(0).getPoolType().isShared()) {
+         short capacityType = Capacity.CAPACITY_TYPE_LOCAL_STORAGE;
+         String storageType = "local";
+         StoragePool storagePool = pools.get(0);
+         if (storagePool.isShared()) {
              capacityType = Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED;
-         } else {
-             capacityType = Capacity.CAPACITY_TYPE_LOCAL_STORAGE;
+             storageType = "shared";
          }
  
 -        s_logger.debug(String.format(
++        logger.debug(String.format(
+                 "Filtering storage pools by capacity type [%s] as the first storage pool of the list, with name [%s] and ID [%s], is a [%s] storage.",
+                 capacityType, storagePool.getName(), storagePool.getUuid(), storageType
+         ));
+ 
          List<Long> poolIdsByCapacity = capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType);
  
 -        s_logger.debug(String.format("List of pools in descending order of available capacity [%s].", poolIdsByCapacity));
 +        logger.debug(String.format("List of pools in descending order of available capacity [%s].", poolIdsByCapacity));
  
  
        //now filter the given list of Pools by this ordered list
@@@ -221,6 -229,8 +227,8 @@@
      }
  
      List<StoragePool> reorderStoragePoolsBasedOnAlgorithm(List<StoragePool> pools, DeploymentPlan plan, Account account) {
 -        s_logger.debug(String.format("Using allocation algorithm [%s] to reorder pools.", allocationAlgorithm));
++        logger.debug(String.format("Using allocation algorithm [%s] to reorder pools.", allocationAlgorithm));
+ 
          if (allocationAlgorithm.equals("random") || allocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
              reorderRandomPools(pools);
          } else if (StringUtils.equalsAny(allocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
diff --cc packaging/el8/cloud.spec
index fbbb7abe350,00000000000..244f4431a3b
mode 100644,000000..100644
--- a/packaging/el8/cloud.spec
+++ b/packaging/el8/cloud.spec
@@@ -1,712 -1,0 +1,712 @@@
 +# 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.
 +
 +%define __os_install_post %{nil}
 +%global debug_package %{nil}
 +%global __requires_exclude libc\\.so\\..*
 +%define _binaries_in_noarch_packages_terminate_build   0
 +
 +# DISABLE the post-percentinstall java repacking and line number stripping
 +# we need to find a way to just disable the java repacking and line number stripping, but not the autodeps
 +
 +Name:      cloudstack
 +Summary:   CloudStack IaaS Platform
 +#http://fedoraproject.org/wiki/PackageNamingGuidelines#Pre-Release_packages
 +%define _maventag %{_fullver}
 +Release:   %{_rel}
 +
 +Version:   %{_ver}
 +License:   ASL 2.0
 +Vendor:    Apache CloudStack <de...@cloudstack.apache.org>
 +Packager:  Apache CloudStack <de...@cloudstack.apache.org>
 +Group:     System Environment/Libraries
 +# FIXME do groups for every single one of the subpackages
 +Source0:   %{name}-%{_maventag}.tgz
 +BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build
 +BuildArch: noarch
 +
 +BuildRequires: (java-11-openjdk-devel or java-17-openjdk-devel)
 +#BuildRequires: ws-commons-util
 +BuildRequires: jpackage-utils
 +BuildRequires: gcc
 +BuildRequires: glibc-devel
 +BuildRequires: /usr/bin/mkisofs
 +BuildRequires: python3-setuptools
 +BuildRequires: wget
 +BuildRequires: nodejs
 +
 +%description
 +CloudStack is a highly-scalable elastic, open source,
 +intelligent IaaS cloud implementation.
 +
 +%package management
 +Summary:   CloudStack management server UI
 +Requires: java-17-openjdk
 +Requires: (tzdata-java or timezone-java)
 +Requires: python3
 +Requires: bash
 +Requires: gawk
 +Requires: which
 +Requires: file
 +Requires: tar
 +Requires: bzip2
 +Requires: gzip
 +Requires: unzip
 +Requires: /sbin/mount.nfs
 +Requires: (openssh-clients or openssh)
 +Requires: (nfs-utils or nfs-client)
 +Requires: iproute
 +Requires: wget
- Requires: mysql
++Requires: (mysql or mariadb)
 +Requires: sudo
 +Requires: /sbin/service
 +Requires: /sbin/chkconfig
 +Requires: /usr/bin/ssh-keygen
 +Requires: (genisoimage or mkisofs)
 +Requires: ipmitool
 +Requires: %{name}-common = %{_ver}
 +Requires: (iptables-services or iptables)
 +Requires: rng-tools
 +Requires: (qemu-img or qemu-tools)
 +Requires: python3-pip
 +Requires: python3-setuptools
 +Requires: (libgcrypt > 1.8.3 or libgcrypt20)
 +Group:     System Environment/Libraries
 +%description management
 +The CloudStack management server is the central point of coordination,
 +management, and intelligence in CloudStack.
 +
 +%package common
 +Summary: Apache CloudStack common files and scripts
 +Requires: python3
 +Group:   System Environment/Libraries
 +%description common
 +The Apache CloudStack files shared between agent and management server
 +%global __requires_exclude ^(libuuid\\.so\\.1|/usr/bin/python)$
 +
 +%package agent
 +Summary: CloudStack Agent for KVM hypervisors
 +Requires: (openssh-clients or openssh)
 +Requires: java-17-openjdk
 +Requires: tzdata-java
 +Requires: %{name}-common = %{_ver}
 +Requires: libvirt
 +Requires: libvirt-daemon-driver-storage-rbd
 +Requires: ebtables
 +Requires: iptables
 +Requires: ethtool
 +Requires: (net-tools or net-tools-deprecated)
 +Requires: iproute
 +Requires: ipset
 +Requires: perl
 +Requires: rsync
 +Requires: (python3-libvirt or python3-libvirt-python)
 +Requires: (qemu-img or qemu-tools)
 +Requires: qemu-kvm
 +Requires: cryptsetup
 +Requires: rng-tools
 +Requires: (libgcrypt > 1.8.3 or libgcrypt20)
 +Requires: (selinux-tools if selinux-tools)
 +Requires: sysstat
 +Provides: cloud-agent
 +Group: System Environment/Libraries
 +%description agent
 +The CloudStack agent for KVM hypervisors
 +
 +%package baremetal-agent
 +Summary: CloudStack baremetal agent
 +Requires: tftp-server
 +Requires: xinetd
 +Requires: syslinux
 +Requires: chkconfig
 +Requires: dhcp
 +Requires: httpd
 +Group:     System Environment/Libraries
 +%description baremetal-agent
 +The CloudStack baremetal agent
 +
 +%package usage
 +Summary: CloudStack Usage calculation server
 +Requires: java-17-openjdk
 +Requires: tzdata-java
 +Group: System Environment/Libraries
 +%description usage
 +The CloudStack usage calculation service
 +
 +%package ui
 +Summary: CloudStack UI
 +Group: System Environment/Libraries
 +%description ui
 +The CloudStack UI
 +
 +%package marvin
 +Summary: Apache CloudStack Marvin library
 +Requires: python3-pip
 +Requires: gcc
 +Requires: python3-devel
 +Requires: libffi-devel
 +Requires: openssl-devel
 +Group: System Environment/Libraries
 +%description marvin
 +Apache CloudStack Marvin library
 +
 +%package integration-tests
 +Summary: Apache CloudStack Marvin integration tests
 +Requires: %{name}-marvin = %{_ver}
 +Group: System Environment/Libraries
 +%description integration-tests
 +Apache CloudStack Marvin integration tests
 +
 +%if "%{_ossnoss}" == "noredist"
 +%package mysql-ha
 +Summary: Apache CloudStack Balancing Strategy for MySQL
 +Group: System Environmnet/Libraries
 +%description mysql-ha
 +Apache CloudStack Balancing Strategy for MySQL
 +
 +%endif
 +
 +%prep
 +echo Doing CloudStack build
 +
 +%setup -q -n %{name}-%{_maventag}
 +
 +%build
 +
 +cp packaging/el8/replace.properties build/replace.properties
 +echo VERSION=%{_maventag} >> build/replace.properties
 +echo PACKAGE=%{name} >> build/replace.properties
 +touch build/gitrev.txt
 +echo $(git rev-parse HEAD) > build/gitrev.txt
 +
 +if [ "%{_ossnoss}" == "NOREDIST" -o "%{_ossnoss}" == "noredist" ] ; then
 +   echo "Adding noredist flag to the maven build"
 +   FLAGS="$FLAGS -Dnoredist"
 +fi
 +
 +if [ "%{_sim}" == "SIMULATOR" -o "%{_sim}" == "simulator" ] ; then
 +   echo "Adding simulator flag to the maven build"
 +   FLAGS="$FLAGS -Dsimulator"
 +fi
 +
 +if [ \"%{_temp}\" != "" ]; then
 +    echo "Adding flags to package requested templates"
 +    FLAGS="$FLAGS `rpm --eval %{?_temp}`"
 +fi
 +
 +mvn -Psystemvm,developer $FLAGS clean package
 +cd ui && npm install && npm run build && cd ..
 +
 +%install
 +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 +# Common directories
 +mkdir -p ${RPM_BUILD_ROOT}%{_bindir}
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/ipallocator
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management
 +mkdir -p ${RPM_BUILD_ROOT}%{_initrddir}
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/default
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d
 +
 +# Common
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site
 +mkdir -p ${RPM_BUILD_ROOT}/usr/bin
 +cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
 +install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/
 +install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py
 +cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/
 +python3 -m py_compile ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py
 +python3 -m compileall ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloudutils
 +cp build/gitrev.txt ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
 +cp packaging/el8/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin
 +
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco
 +cp -r plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco
 +
 +# Management
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/run
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel
 +
 +# Setup Jetty
 +ln -sf /etc/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/conf
 +ln -sf /var/log/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/logs
 +
 +install -D client/target/utilities/bin/cloud-migrate-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-migrate-databases
 +install -D client/target/utilities/bin/cloud-set-guest-password ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-password
 +install -D client/target/utilities/bin/cloud-set-guest-sshkey ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-sshkey
 +install -D client/target/utilities/bin/cloud-setup-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-databases
 +install -D client/target/utilities/bin/cloud-setup-encryption ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-encryption
 +install -D client/target/utilities/bin/cloud-setup-management ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-management
 +install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal
 +install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm
 +install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses
 +# Bundle cmk in cloudstack-management
 +CMK_REL=$(wget -O - "https://api.github.com/repos/apache/cloudstack-cloudmonkey/releases" 2>/dev/null | jq -r '.[0].tag_name')
 +wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/$CMK_REL/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
 +chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk
 +
 +cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
 +
 +cp -r client/target/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/
 +cp -r client/target/classes/META-INF/webapp ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp
 +cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/
 +cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/
 +rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json
 +ln -sf /etc/%{name}/management/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json
 +mv ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/cloudstack-%{_maventag}.jar
 +cp client/target/lib/*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/
 +
 +# Don't package the scripts in the management webapp
 +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/scripts
 +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/vms
 +
 +for name in db.properties server.properties log4j-cloud.xml environment.properties java.security.ciphers
 +do
 +  cp client/target/conf/$name ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/$name
 +done
 +
 +ln -sf log4j-cloud.xml  ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/log4j2.xml
 +
 +install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py
 +install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
 +install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
 +
 +install -D packaging/el8/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator
 +install -D packaging/el8/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud
 +install -D packaging/el8/filelimit.conf ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
 +install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service
 +install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management
 +install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management
 +touch ${RPM_BUILD_ROOT}%{_localstatedir}/run/%{name}-management.pid
 +#install -D server/target/conf/cloudstack-catalina.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-catalina
 +install -D server/target/conf/cloudstack-management.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-management
 +
 +# SystemVM template
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm
 +cp -r engine/schema/dist/systemvm-templates/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm
 +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm/md5sum.txt
 +
 +# UI
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/
 +cp -r client/target/classes/META-INF/webapp/WEB-INF ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui
 +cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui/
 +cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/
 +rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json
 +ln -sf /etc/%{name}/ui/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json
 +
 +# Package mysql-connector-python
 +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/ee/ff/48bde5c0f013094d729fe4b0316ba2a24774b3ff1c52d924a8a4cb04078a/six-1.15.0-py2.py3-none-any.whl
 +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/e9/93/4860cebd5ad3ff2664ad3c966490ccb46e3b88458b2095145bca11727ca4/setuptools-47.3.1-py3-none-any.whl
 +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/32/27/1141a8232723dcb10a595cc0ce4321dcbbd5215300bf4acfc142343205bf/protobuf-3.19.6-py2.py3-none-any.whl
 +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl
 +
 +chmod 440 ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management
 +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
 +
 +# KVM Agent
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins
 +install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service
 +install -D packaging/systemd/cloudstack-rolling-maintenance@.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-rolling-maintenance@.service
 +install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent
 +install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties
 +install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties
 +install -D agent/target/transformed/log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/log4j-cloud.xml
 +install -D agent/target/transformed/cloud-setup-agent ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-agent
 +install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade
 +install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool
 +install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook
 +install -D agent/target/transformed/rolling-maintenance ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/rolling-maintenance
 +install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh
 +install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh
 +install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
 +install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar
 +cp plugins/hypervisors/kvm/target/dependencies/*  ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
 +cp plugins/storage/volume/storpool/target/*.jar  ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
 +cp plugins/storage/volume/linstor/target/*.jar  ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
 +
 +# Usage server
 +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib
 +install -D usage/target/cloud-usage-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/cloud-usage-%{_maventag}.jar
 +install -D usage/target/transformed/db.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/db.properties
 +install -D usage/target/transformed/log4j-cloud_usage.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/log4j-cloud.xml
 +cp usage/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/
 +cp client/target/lib/mysql*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/
 +install -D packaging/systemd/cloudstack-usage.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-usage.service
 +install -D packaging/systemd/cloudstack-usage.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-usage
 +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/usage/
 +install -D usage/target/transformed/cloudstack-usage.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-usage
 +
 +# Marvin
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin
 +cp tools/marvin/dist/Marvin-*.tar.gz ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin/
 +
 +# integration-tests
 +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests
 +cp -r test/integration/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests/
 +
 +# MYSQL HA
 +if [ "x%{_ossnoss}" == "xnoredist" ] ; then
 +  mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib
 +  cp -r plugins/database/mysql-ha/target/cloud-plugin-database-mysqlha-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib
 +fi
 +
 +#License files from whisker
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE
 +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE
 +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE
 +
 +%clean
 +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 +
 +%posttrans common
 +
 +unalias cp
 +python_dir=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")
 +if [ ! -z $python_dir ];then
 +  cp -f -r /usr/share/cloudstack-common/python-site/* $python_dir/
 +fi
 +
 +%preun management
 +/usr/bin/systemctl stop cloudstack-management || true
 +/usr/bin/systemctl disable cloudstack-management || true
 +
 +%pre management
 +id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \
 +     -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud || true
 +
 +rm -rf %{_localstatedir}/cache/cloudstack
 +
 +# in case of upgrade to 4.9+ copy commands.properties if not exists in /etc/cloudstack/management/
 +if [ "$1" == "2" ] ; then
 +    if [ -f "%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties" ] && [ ! -f "%{_sysconfdir}/%{name}/management/commands.properties" ] ; then
 +        cp -p %{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties %{_sysconfdir}/%{name}/management/commands.properties
 +    fi
 +fi
 +
 +# Remove old tomcat symlinks and env config file
 +if [ -L "%{_datadir}/%{name}-management/lib" ]
 +then
 +    rm -f %{_datadir}/%{name}-management/bin
 +    rm -f %{_datadir}/%{name}-management/lib
 +    rm -f %{_datadir}/%{name}-management/temp
 +    rm -f %{_datadir}/%{name}-management/work
 +    rm -f %{_sysconfdir}/default/%{name}-management
 +fi
 +
 +%post management
 +# Install mysql-connector-python
 +pip3 install %{_datadir}/%{name}-management/setup/wheel/six-1.15.0-py2.py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/setuptools-47.3.1-py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/protobuf-3.19.6-py2.py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.31-py2.py3-none-any.whl
 +
 +/usr/bin/systemctl enable cloudstack-management > /dev/null 2>&1 || true
 +/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true
 +
 +grep -s -q "db.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "db.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.usage.driver=jdbc:mysql"  "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "db.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties"
 +
 +# Update DB properties having master and slave(s), with source and replica(s) respectively (for inclusiveness)
 +grep -s -q "^db.cloud.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.slaves=/db.cloud.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "^db.cloud.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.secondsBeforeRetryMaster=/db.cloud.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "^db.cloud.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.queriesBeforeRetryMaster=/db.cloud.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "^db.usage.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.slaves=/db.usage.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "^db.usage.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.secondsBeforeRetryMaster=/db.usage.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +grep -s -q "^db.usage.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.queriesBeforeRetryMaster=/db.usage.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
 +
 +if [ ! -f %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/vhd-util ] ; then
 +    echo Please download vhd-util from http://download.cloudstack.org/tools/vhd-util and put it in
 +    echo %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/
 +fi
 +
 +if [ -f %{_sysconfdir}/sysconfig/%{name}-management ] ; then
 +    rm -f %{_sysconfdir}/sysconfig/%{name}-management
 +fi
 +
 +chown -R cloud:cloud /var/log/cloudstack/management
 +
 +systemctl daemon-reload
 +
 +%posttrans management
 +# Print help message
 +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
 +    sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
 +    /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text management
 +fi
 +
 +%preun agent
 +/sbin/service cloudstack-agent stop || true
 +if [ "$1" == "0" ] ; then
 +    /sbin/chkconfig --del cloudstack-agent > /dev/null 2>&1 || true
 +fi
 +
 +%pre agent
 +
 +# save old configs if they exist (for upgrade). Otherwise we may lose them
 +# when the old packages are erased. There are a lot of properties files here.
 +if [ -d "%{_sysconfdir}/cloud" ] ; then
 +    mv %{_sysconfdir}/cloud %{_sysconfdir}/cloud.rpmsave
 +fi
 +
 +%posttrans agent
 +
 +if [ "$1" == "2" ] ; then
 +    echo "Running %{_bindir}/%{name}-agent-upgrade to update bridge name for upgrade from CloudStack 4.0.x (and before) to CloudStack 4.1 (and later)"
 +    %{_bindir}/%{name}-agent-upgrade
 +fi
 +if [ ! -d %{_sysconfdir}/libvirt/hooks ] ; then
 +    mkdir %{_sysconfdir}/libvirt/hooks
 +fi
 +cp -a ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook %{_sysconfdir}/libvirt/hooks/qemu
 +mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp
 +/usr/bin/systemctl restart libvirtd
 +/usr/bin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true
 +/usr/bin/systemctl enable cloudstack-rolling-maintenance@p > /dev/null 2>&1 || true
 +/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true
 +
 +# if saved configs from upgrade exist, copy them over
 +if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then
 +    mv %{_sysconfdir}/%{name}/agent/agent.properties  %{_sysconfdir}/%{name}/agent/agent.properties.rpmnew
 +    cp -p %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/%{name}/agent
 +    # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
 +    mv %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/cloud.rpmsave/agent/agent.properties.rpmsave
 +fi
 +
 +systemctl daemon-reload
 +
 +# Print help message
 +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
 +    sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
 +    /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text agent
 +fi
 +
 +%pre usage
 +id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \
 +     -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud|| true
 +
 +%preun usage
 +/sbin/service cloudstack-usage stop || true
 +if [ "$1" == "0" ] ; then
 +    /sbin/chkconfig --del cloudstack-usage > /dev/null 2>&1 || true
 +fi
 +
 +%post usage
 +if [ -f "%{_sysconfdir}/%{name}/management/db.properties" ]; then
 +    echo "Replacing usage server's db.properties with a link to the management server's db.properties"
 +    rm -f %{_sysconfdir}/%{name}/usage/db.properties
 +    ln -s %{_sysconfdir}/%{name}/management/db.properties %{_sysconfdir}/%{name}/usage/db.properties
 +    /usr/bin/systemctl enable cloudstack-usage > /dev/null 2>&1 || true
 +fi
 +
 +if [ -f "%{_sysconfdir}/%{name}/management/key" ]; then
 +    echo "Replacing usage server's key with a link to the management server's key"
 +    rm -f %{_sysconfdir}/%{name}/usage/key
 +    ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key
 +fi
 +
 +if [ ! -f "%{_sysconfdir}/%{name}/usage/key" ]; then
 +    ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key
 +fi
 +
 +mkdir -p /usr/local/libexec
 +if [ ! -f "/usr/local/libexec/sanity-check-last-id" ]; then
 +    echo 1 > /usr/local/libexec/sanity-check-last-id
 +fi
 +chown cloud:cloud /usr/local/libexec/sanity-check-last-id
 +
 +%posttrans usage
 +# Print help message
 +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
 +    sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
 +    /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text usage
 +fi
 +
 +%post marvin
 +pip3 install --upgrade https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl
 +pip3 install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz
 +
 +#No default permission as the permission setup is complex
 +%files management
 +%defattr(-,root,root,-)
 +%dir %{_datadir}/%{name}-management
 +%dir %attr(0770,root,cloud) %{_localstatedir}/%{name}/mnt
 +%dir %attr(0770,cloud,cloud) %{_localstatedir}/%{name}/management
 +%dir %attr(0770,root,cloud) %{_localstatedir}/cache/%{name}/management
 +%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/management
 +%config(noreplace) %{_sysconfdir}/default/%{name}-management
 +%config(noreplace) %{_sysconfdir}/sudoers.d/%{name}-management
 +%config(noreplace) %{_sysconfdir}/security/limits.d/cloud
 +%config(noreplace) %{_sysconfdir}/systemd/system/%{name}-management.service.d
 +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/db.properties
 +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/server.properties
 +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/config.json
 +%config(noreplace) %{_sysconfdir}/%{name}/management/log4j-cloud.xml
 +%config(noreplace) %{_sysconfdir}/%{name}/management/log4j2.xml
 +%config(noreplace) %{_sysconfdir}/%{name}/management/environment.properties
 +%config(noreplace) %{_sysconfdir}/%{name}/management/java.security.ciphers
 +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-management
 +%attr(0644,root,root) %{_unitdir}/%{name}-management.service
 +%attr(0755,cloud,cloud) %{_localstatedir}/run/%{name}-management.pid
 +%attr(0755,root,root) %{_bindir}/%{name}-setup-management
 +%attr(0755,root,root) %{_bindir}/%{name}-update-xenserver-licenses
 +%{_datadir}/%{name}-management/conf
 +%{_datadir}/%{name}-management/lib/*.jar
 +%{_datadir}/%{name}-management/logs
 +%{_datadir}/%{name}-management/templates
 +%attr(0755,root,root) %{_bindir}/%{name}-setup-databases
 +%attr(0755,root,root) %{_bindir}/%{name}-migrate-databases
 +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-password
 +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey
 +%attr(0755,root,root) %{_bindir}/%{name}-sysvmadm
 +%attr(0755,root,root) %{_bindir}/%{name}-setup-encryption
 +%attr(0755,root,root) %{_bindir}/cmk
 +%{_datadir}/%{name}-management/setup/*.sql
 +%{_datadir}/%{name}-management/setup/*.sh
 +%{_datadir}/%{name}-management/setup/server-setup.xml
 +%{_datadir}/%{name}-management/webapp/*
 +%attr(0755,root,root) %{_bindir}/%{name}-external-ipallocator.py
 +%attr(0755,root,root) %{_initrddir}/%{name}-ipallocator
 +%dir %attr(0770,root,root) %{_localstatedir}/log/%{name}/ipallocator
 +%{_defaultdocdir}/%{name}-management-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-management-%{version}/NOTICE
 +%{_datadir}/%{name}-management/setup/wheel/*.whl
 +
 +%files agent
 +%attr(0755,root,root) %{_bindir}/%{name}-setup-agent
 +%attr(0755,root,root) %{_bindir}/%{name}-agent-upgrade
 +%attr(0755,root,root) %{_bindir}/%{name}-guest-tool
 +%attr(0755,root,root) %{_bindir}/%{name}-ssh
 +%attr(0644,root,root) %{_unitdir}/%{name}-agent.service
 +%attr(0644,root,root) %{_unitdir}/%{name}-rolling-maintenance@.service
 +%config(noreplace) %{_sysconfdir}/default/%{name}-agent
 +%attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh
 +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent
 +%attr(0755,root,root) %{_datadir}/%{name}-common/scripts/network/cisco
 +%config(noreplace) %{_sysconfdir}/%{name}/agent
 +%dir %{_localstatedir}/log/%{name}/agent
 +%attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar
 +%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook
 +%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/rolling-maintenance
 +%dir %{_datadir}/%{name}-agent/plugins
 +%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE
 +
 +%files common
 +%dir %attr(0755,root,root) %{_datadir}/%{name}-common/python-site/cloudutils
 +%dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms
 +%attr(0755,root,root) %{_datadir}/%{name}-common/scripts
 +%attr(0755,root,root) /usr/bin/cloudstack-sccs
 +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip
 +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz
 +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh
 +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloud_utils.py
 +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/__pycache__/*
 +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloudutils/*
 +%attr(0644, root, root) %{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
 +%attr(0644, root, root) %{_datadir}/%{name}-common/lib/%{name}-utils.jar
 +%{_defaultdocdir}/%{name}-common-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-common-%{version}/NOTICE
 +
 +%files ui
 +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/ui/config.json
 +%{_datadir}/%{name}-ui/*
 +%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE
 +
 +%files usage
 +%attr(0644,root,root) %{_unitdir}/%{name}-usage.service
 +%config(noreplace) %{_sysconfdir}/default/%{name}-usage
 +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-usage
 +%attr(0644,root,root) %{_datadir}/%{name}-usage/*.jar
 +%attr(0644,root,root) %{_datadir}/%{name}-usage/lib/*.jar
 +%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/usage
 +%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/db.properties
 +%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/log4j-cloud.xml
 +%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE
 +
 +%files marvin
 +%attr(0644,root,root) %{_datadir}/%{name}-marvin/Marvin*.tar.gz
 +%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE
 +
 +%files integration-tests
 +%attr(0755,root,root) %{_datadir}/%{name}-integration-tests/*
 +%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE
 +%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE
 +
 +%if "%{_ossnoss}" == "noredist"
 +%files mysql-ha
 +%defattr(0644,cloud,cloud,0755)
 +%attr(0644,root,root) %{_datadir}/%{name}-mysql-ha/lib/*
 +%endif
 +
 +%files baremetal-agent
 +%attr(0755,root,root) %{_bindir}/cloudstack-setup-baremetal
 +
 +%changelog
 +* Thu Dec 22 2022 Rohit Yadav <ro...@apache.org> 4.18.0
 +- Add support for EL9
 +
 +* Fri Oct 14 2022 Daan Hoogland <da...@gmail.com> 4.18.0
 +- initialising sanity check pointer file
 +
 +* Tue Jun 29 2021 David Jumani <dj...@gmail.com> 4.16.0
 +- Adding SUSE 15 support
 +
 +* Thu Apr 30 2015 Rohit Yadav <bh...@apache.org> 4.6.0
 +- Remove awsapi package
 +
 +* Wed Nov 19 2014 Hugo Trippaers <hu...@apache.org> 4.6.0
 +- Create a specific spec for CentOS 7
 +
 +* Fri Jul 4 2014 Hugo Trippaers <hu...@apache.org> 4.5.0
 +- Add a package for the mysql ha module
 +
 +* Fri Oct 5 2012 Hugo Trippaers <hu...@apache.org> 4.1.0
 +- new style spec file
diff --cc plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
index de85e6231f2,d666293d02a..4d50ef7e1f8
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java
@@@ -203,10 -203,10 +203,10 @@@ public class KubernetesClusterScaleWork
              retryCounter++;
              try {
                  Pair<Boolean, String> result = SshHelper.sshExecute(ipAddress, port, getControlNodeLoginUser(),
-                         pkFile, null, String.format("sudo /opt/bin/kubectl drain %s --ignore-daemonsets --delete-local-data", hostName),
+                         pkFile, null, String.format("sudo /opt/bin/kubectl drain %s --ignore-daemonsets --delete-emptydir-data", hostName),
                          10000, 10000, 60000);
                  if (!result.first()) {
 -                    LOGGER.warn(String.format("Draining node: %s on VM : %s in Kubernetes cluster : %s unsuccessful", hostName, userVm.getDisplayName(), kubernetesCluster.getName()));
 +                    logger.warn("Draining node: {} on VM: {} in Kubernetes cluster: {} unsuccessful", hostName, userVm, kubernetesCluster);
                  } else {
                      result = SshHelper.sshExecute(ipAddress, port, getControlNodeLoginUser(),
                              pkFile, null, String.format("sudo /opt/bin/kubectl delete node %s", hostName),
diff --cc plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index 7d27e6b77ce,a63c5b68e57..12741e0996a
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@@ -520,13 -517,8 +520,17 @@@ public class MockAccountManager extend
          return null;
      }
  
 +    public void validateUserPasswordAndUpdateIfNeeded(String newPassword, UserVO user,
 +                                               String currentPassword,
 +                                               boolean skipCurrentPassValidation) {
 +    }
 +
 +    @Override
 +    public void checkApiAccess(Account account, String command) throws PermissionDeniedException {
++    }
 +
+     @Override
+     public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) {
+         return null;
      }
  }
diff --cc server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index 4ca5bf2eb92,3a6d804a762..8e99e3429ba
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@@ -62,8 -62,8 +62,8 @@@ import org.apache.cloudstack.api.comman
  import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
  import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
  import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
--import org.apache.cloudstack.api.command.admin.storage.UpdateObjectStoragePoolCmd;
  import org.apache.cloudstack.api.command.admin.storage.UpdateImageStoreCmd;
++import org.apache.cloudstack.api.command.admin.storage.UpdateObjectStoragePoolCmd;
  import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
  import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd;
  import org.apache.cloudstack.api.command.admin.storage.heuristics.RemoveSecondaryStorageSelectorCmd;
@@@ -234,8 -235,8 +234,8 @@@ import com.cloud.user.dao.UserDao
  import com.cloud.utils.DateUtil;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.Pair;
--import com.cloud.utils.UriUtils;
  import com.cloud.utils.StringUtils;
++import com.cloud.utils.UriUtils;
  import com.cloud.utils.component.ComponentContext;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.concurrency.NamedThreadFactory;
diff --cc server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index 2b6c7a06709,68546f185b7..a371a064701
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@@ -346,7 -352,7 +351,9 @@@ public class VolumeApiServiceImpl exten
      @Inject
      protected StoragePoolDetailsDao storagePoolDetailsDao;
      @Inject
 +    private BackupDao backupDao;
++    @Inject
+     HostPodDao podDao;
  
  
      protected Gson _gson;
@@@ -2433,20 -2384,13 +2440,13 @@@
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
      public Volume attachVolumeToVM(AttachVolumeCmd command) {
 -        return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId());
 +        return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId(), false);
      }
  
-     private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
-         VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
- 
-         if (volumeToAttach.isAttachedVM()) {
-             throw new CloudRuntimeException("This volume is already attached to a VM.");
-         }
- 
-         UserVmVO vm = _userVmDao.findById(vmId);
+     protected VolumeVO getVmExistingVolumeForVolumeAttach(UserVmVO vm, VolumeInfo volumeToAttach) {
          VolumeVO existingVolumeOfVm = null;
          VMTemplateVO template = _templateDao.findById(vm.getTemplateId());
-         List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.ROOT);
+         List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
          if (rootVolumesOfVm.size() > 1 && template != null && !template.isDeployAsIs()) {
              throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state.");
          } else {
@@@ -2463,41 -2407,89 +2463,89 @@@
                  }
              }
          }
-         if (logger.isTraceEnabled()) {
-             if (existingVolumeOfVm != null) {
-                 logger.trace("attaching volume {} to a VM {} with an existing volume {} on primary storage {}",
-                         volumeToAttach, vm, existingVolumeOfVm, _storagePoolDao.findById(existingVolumeOfVm.getPoolId()));
+         if (existingVolumeOfVm == null) {
 -            if (s_logger.isTraceEnabled()) {
 -                s_logger.trace(String.format("No existing volume found for VM (%s/%s) to attach volume %s/%s",
++            if (logger.isTraceEnabled()) {
++                logger.trace(String.format("No existing volume found for VM (%s/%s) to attach volume %s/%s",
+                         vm.getName(), vm.getUuid(),
+                         volumeToAttach.getName(), volumeToAttach.getUuid()));
              }
+             return null;
          }
- 
-         HypervisorType rootDiskHyperType = vm.getHypervisorType();
-         HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());
- 
 -        if (s_logger.isTraceEnabled()) {
++        if (logger.isTraceEnabled()) {
+             String msg = "attaching volume %s/%s to a VM (%s/%s) with an existing volume %s/%s on primary storage %s";
 -            s_logger.trace(String.format(msg,
++            logger.trace(String.format(msg,
+                     volumeToAttach.getName(), volumeToAttach.getUuid(),
+                     vm.getName(), vm.getUuid(),
+                     existingVolumeOfVm.getName(), existingVolumeOfVm.getUuid(),
+                     existingVolumeOfVm.getPoolId()));
+         }
+         return existingVolumeOfVm;
+     }
+ 
+     protected StoragePool getPoolForAllocatedOrUploadedVolumeForAttach(final VolumeInfo volumeToAttach, final UserVmVO vm) {
+         DataCenter zone = _dcDao.findById(vm.getDataCenterId());
+         Pair<Long, Long> clusterHostId = virtualMachineManager.findClusterAndHostIdForVm(vm, false);
+         long podId = vm.getPodIdToDeployIn();
+         if (clusterHostId.first() != null) {
+             Cluster cluster = clusterDao.findById(clusterHostId.first());
+             podId = cluster.getPodId();
+         }
+         Pod pod = podDao.findById(podId);
+         DiskOfferingVO offering = _diskOfferingDao.findById(volumeToAttach.getDiskOfferingId());
+         DiskProfile diskProfile =  new DiskProfile(volumeToAttach.getId(), volumeToAttach.getVolumeType(),
+                 volumeToAttach.getName(), volumeToAttach.getId(), volumeToAttach.getSize(), offering.getTagsArray(),
+                 offering.isUseLocalStorage(), offering.isRecreatable(),
+                 volumeToAttach.getTemplateId());
+         diskProfile.setHyperType(vm.getHypervisorType());
+         StoragePool pool = _volumeMgr.findStoragePool(diskProfile, zone, pod, clusterHostId.first(),
+                 clusterHostId.second(), vm, Collections.emptySet());
+         if (pool == null) {
+             throw new CloudRuntimeException(String.format("Failed to find a primary storage for volume in state: %s", volumeToAttach.getState()));
+         }
+         return pool;
+     }
+ 
+     protected VolumeInfo createVolumeOnPrimaryForAttachIfNeeded(final VolumeInfo volumeToAttach, final UserVmVO vm, VolumeVO existingVolumeOfVm) {
          VolumeInfo newVolumeOnPrimaryStorage = volumeToAttach;
- 
+         boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded;
+         if (!Arrays.asList(Volume.State.Allocated, Volume.State.Uploaded).contains(volumeToAttach.getState())) {
+             return newVolumeOnPrimaryStorage;
+         }
          //don't create volume on primary storage if its being attached to the vm which Root's volume hasn't been created yet
-         StoragePoolVO destPrimaryStorage = null;
+         StoragePool destPrimaryStorage = null;
          if (existingVolumeOfVm != null && !existingVolumeOfVm.getState().equals(Volume.State.Allocated)) {
              destPrimaryStorage = _storagePoolDao.findById(existingVolumeOfVm.getPoolId());
 -            if (s_logger.isTraceEnabled() && destPrimaryStorage != null) {
 -                s_logger.trace(String.format("decided on target storage: %s/%s", destPrimaryStorage.getName(), destPrimaryStorage.getUuid()));
 +            if (logger.isTraceEnabled() && destPrimaryStorage != null) {
 +                logger.trace("decided on target storage: {}", destPrimaryStorage);
              }
          }
+         if (destPrimaryStorage == null) {
+             destPrimaryStorage = getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         }
+         try {
+             if (volumeOnSecondary && Storage.StoragePoolType.PowerFlex.equals(destPrimaryStorage.getPoolType())) {
+                 throw new InvalidParameterValueException("Cannot attach uploaded volume, this operation is unsupported on storage pool type " + destPrimaryStorage.getPoolType());
+             }
+             newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach,
+                     vm.getHypervisorType(), destPrimaryStorage);
+         } catch (NoTransitionException e) {
 -            s_logger.debug("Failed to create volume on primary storage", e);
++            logger.debug("Failed to create volume on primary storage", e);
+             throw new CloudRuntimeException("Failed to create volume on primary storage", e);
+         }
+         return newVolumeOnPrimaryStorage;
+     }
  
-         boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded;
+     private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
+         VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
  
-         if (destPrimaryStorage != null && (volumeToAttach.getState() == Volume.State.Allocated || volumeOnSecondary)) {
-             try {
-                 if (volumeOnSecondary && destPrimaryStorage.getPoolType() == Storage.StoragePoolType.PowerFlex) {
-                     throw new InvalidParameterValueException("Cannot attach uploaded volume, this operation is unsupported on storage pool type " + destPrimaryStorage.getPoolType());
-                 }
-                 newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, destPrimaryStorage);
-             } catch (NoTransitionException e) {
-                 logger.debug("Failed to create volume on primary storage", e);
-                 throw new CloudRuntimeException("Failed to create volume on primary storage", e);
-             }
+         if (volumeToAttach.isAttachedVM()) {
+             throw new CloudRuntimeException("This volume is already attached to a VM.");
          }
  
+         UserVmVO vm = _userVmDao.findById(vmId);
+         VolumeVO existingVolumeOfVm = getVmExistingVolumeForVolumeAttach(vm, volumeToAttach);
+         VolumeInfo newVolumeOnPrimaryStorage = createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, existingVolumeOfVm);
+ 
          // reload the volume from db
          newVolumeOnPrimaryStorage = volFactory.getVolume(newVolumeOnPrimaryStorage.getId());
          boolean moveVolumeNeeded = needMoveVolume(existingVolumeOfVm, newVolumeOnPrimaryStorage);
@@@ -2515,13 -2507,11 +2563,11 @@@
              StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(existingVolumeOfVm.getPoolId());
  
              try {
+                 HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());
                  newVolumeOnPrimaryStorage = _volumeMgr.moveVolume(newVolumeOnPrimaryStorage, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(),
                          volumeToAttachHyperType);
-             } catch (ConcurrentOperationException e) {
-                 logger.debug("move volume failed", e);
-                 throw new CloudRuntimeException("move volume failed", e);
-             } catch (StorageUnavailableException e) {
+             } catch (ConcurrentOperationException | StorageUnavailableException e) {
 -                s_logger.debug("move volume failed", e);
 +                logger.debug("move volume failed", e);
                  throw new CloudRuntimeException("move volume failed", e);
              }
          }
diff --cc server/src/main/java/com/cloud/user/AccountManager.java
index 1e5526688b7,1d7611d5b54..640eaa00076
--- a/server/src/main/java/com/cloud/user/AccountManager.java
+++ b/server/src/main/java/com/cloud/user/AccountManager.java
@@@ -20,7 -20,7 +20,6 @@@ import java.net.InetAddress
  import java.util.List;
  import java.util.Map;
  
--import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
  import org.apache.cloudstack.acl.ControlledEntity;
  import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
  import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
@@@ -31,6 -31,6 +30,7 @@@ import org.apache.cloudstack.auth.UserT
  import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.framework.config.Configurable;
  
++import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
  import com.cloud.api.query.vo.ControlledViewEntity;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.ResourceUnavailableException;
@@@ -198,9 -198,6 +198,12 @@@ public interface AccountManager extend
  
      UserTwoFactorAuthenticationSetupResponse setupUserTwoFactorAuthentication(SetupUserTwoFactorAuthenticationCmd cmd);
  
 +    List<String> getApiNameList();
 +
 +    void validateUserPasswordAndUpdateIfNeeded(String newPassword, UserVO user, String currentPassword, boolean skipCurrentPassValidation);
 +
-   void checkApiAccess(Account caller, String command);
++    void checkApiAccess(Account caller, String command);
++
+     UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user);
+ 
  }
diff --cc server/src/main/java/com/cloud/user/AccountManagerImpl.java
index ea2df5d1530,2358830e9a3..1422f39f1fe
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@@ -120,6 -117,6 +119,7 @@@ import com.cloud.exception.InvalidParam
  import com.cloud.exception.OperationTimedoutException;
  import com.cloud.exception.PermissionDeniedException;
  import com.cloud.exception.ResourceUnavailableException;
++import com.cloud.host.dao.HostDao;
  import com.cloud.network.IpAddress;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
@@@ -1377,8 -1340,8 +1377,8 @@@ public class AccountManagerImpl extend
       * if there is any permission under the requested role that is not permitted for the caller, refuse
       */
      private void checkRoleEscalation(Account caller, Account requested) {
 -        if (s_logger.isDebugEnabled()) {
 -            s_logger.debug(String.format("Checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]",
 +        if (logger.isDebugEnabled()) {
-             logger.debug(String.format("checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]",
++            logger.debug(String.format("Checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]",
                      caller.getAccountName(),
                      caller.getUuid(),
                      caller.getRoleId(),
@@@ -1391,13 -1354,14 +1391,14 @@@
              try {
                  checkApiAccess(apiCheckers, requested, command);
              } catch (PermissionDeniedException pde) {
 -                if (s_logger.isTraceEnabled()) {
 -                    s_logger.trace(String.format(
 +                if (logger.isTraceEnabled()) {
-                     logger.trace(String.format("checking for permission to \"%s\" is irrelevant as it is not requested for %s [%s]",
++                    logger.trace(String.format(
+                             "Checking for permission to \"%s\" is irrelevant as it is not requested for %s [%s]",
                              command,
-                             pde.getAccount().getAccountName(),
-                             pde.getAccount().getUuid(),
-                             pde.getEntitiesInViolation()
-                             ));
+                             requested.getAccountName(),
+                             requested.getUuid()
+                         )
+                     );
                  }
                  continue;
              }
@@@ -3566,4 -3476,26 +3567,26 @@@
          return userTwoFactorAuthenticationProvidersMap.get(name.toLowerCase());
      }
  
+     @Override
+     public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) {
+         return Transaction.execute((TransactionCallback<UserAccount>) status -> {
+             if (!user.isUser2faEnabled() && StringUtils.isBlank(user.getUser2faProvider())) {
+                 return user;
+             }
+             UserDetailVO userDetailVO = _userDetailsDao.findDetail(user.getId(), UserDetailVO.Setup2FADetail);
+             if (userDetailVO != null && UserAccountVO.Setup2FAstatus.VERIFIED.name().equals(userDetailVO.getValue())) {
+                 return user;
+             }
 -            s_logger.info(String.format("Clearing 2FA configurations for %s as it is still in setup on a new login request", user));
++            logger.info("Clearing 2FA configurations for {} as it is still in setup on a new login request", user);
+             if (userDetailVO != null) {
+                 _userDetailsDao.remove(userDetailVO.getId());
+             }
+             UserAccountVO userAccountVO = _userAccountDao.findById(user.getId());
+             userAccountVO.setUser2faEnabled(false);
+             userAccountVO.setUser2faProvider(null);
+             userAccountVO.setKeyFor2fa(null);
+             _userAccountDao.update(user.getId(), userAccountVO);
+             return userAccountVO;
+         });
+     }
  }
diff --cc server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
index 9630b341bc9,3e6f9ec63f9..23b79e4ab03
--- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
+++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
@@@ -91,11 -85,14 +90,15 @@@ import org.mockito.junit.MockitoJUnitRu
  import org.springframework.test.util.ReflectionTestUtils;
  
  import com.cloud.api.query.dao.ServiceOfferingJoinDao;
 +import com.cloud.configuration.ConfigurationManager;
  import com.cloud.configuration.Resource;
  import com.cloud.configuration.Resource.ResourceType;
+ import com.cloud.dc.ClusterVO;
  import com.cloud.dc.DataCenterVO;
+ import com.cloud.dc.HostPodVO;
+ import com.cloud.dc.dao.ClusterDao;
  import com.cloud.dc.dao.DataCenterDao;
+ import com.cloud.dc.dao.HostPodDao;
  import com.cloud.event.EventTypes;
  import com.cloud.event.UsageEventUtils;
  import com.cloud.exception.InvalidParameterValueException;
@@@ -108,6 -105,6 +111,7 @@@ import com.cloud.org.Grouping
  import com.cloud.projects.Project;
  import com.cloud.projects.ProjectManager;
  import com.cloud.serializer.GsonHelper;
++import com.cloud.server.ManagementService;
  import com.cloud.server.TaggedResourceService;
  import com.cloud.service.ServiceOfferingVO;
  import com.cloud.service.dao.ServiceOfferingDao;
@@@ -247,15 -251,6 +262,12 @@@ public class VolumeApiServiceImplTest 
      @Mock
      private StorageManager storageMgr;
  
 +    @Mock
 +    private ConfigurationManager _configMgr;
 +
-     @Mock
-     private VolumeOrchestrationService _volumeMgr;
- 
 +    @Mock
 +    private ManagementService managementService;
 +
      private long accountMockId = 456l;
      private long volumeMockId = 12313l;
      private long vmInstanceMockId = 1123l;
@@@ -1838,91 -1837,236 +1850,323 @@@
          volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
      }
  
 +    private void testResizeVolumeSetup() throws ExecutionException, InterruptedException {
 +        Long poolId = 11L;
 +
 +        when(volumeDaoMock.findById(volumeMockId)).thenReturn(volumeVoMock);
 +        when(volumeVoMock.getId()).thenReturn(volumeMockId);
 +        when(volumeDaoMock.getHypervisorType(volumeMockId)).thenReturn(HypervisorType.KVM);
 +        when(volumeVoMock.getState()).thenReturn(Volume.State.Ready);
 +        when(volumeVoMock.getDiskOfferingId()).thenReturn(diskOfferingMockId);
 +        when(_diskOfferingDao.findById(diskOfferingMockId)).thenReturn(diskOfferingMock);
 +        when(_diskOfferingDao.findById(newDiskOfferingMockId)).thenReturn(newDiskOfferingMock);
 +        when(newDiskOfferingMock.getRemoved()).thenReturn(null);
 +        when(diskOfferingMock.getDiskSizeStrictness()).thenReturn(false);
 +        when(newDiskOfferingMock.getDiskSizeStrictness()).thenReturn(false);
 +        when(volumeVoMock.getInstanceId()).thenReturn(null);
 +        when(volumeVoMock.getVolumeType()).thenReturn(Type.DATADISK);
 +        when(newDiskOfferingMock.getDiskSize()).thenReturn(newVolumeSizeMock);
 +
 +        VolumeInfo volInfo = Mockito.mock(VolumeInfo.class);
 +        when(volumeDataFactoryMock.getVolume(volumeMockId)).thenReturn(volInfo);
 +        DataStore dataStore = Mockito.mock(DataStore.class);
 +        when((volInfo.getDataStore())).thenReturn(dataStore);
 +
 +        when(volumeVoMock.getPoolId()).thenReturn(poolId);
 +        StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
 +        when(primaryDataStoreDaoMock.findById(poolId)).thenReturn(storagePool);
 +
 +        Mockito.lenient().doReturn(asyncCallFutureVolumeapiResultMock).when(volumeServiceMock).resize(any(VolumeInfo.class));
 +        Mockito.doReturn(Mockito.mock(VolumeApiResult.class)).when(asyncCallFutureVolumeapiResultMock).get();
 +    }
 +
 +    @Test
 +    public void testResizeVolumeWithEnoughCapacity() throws ResourceAllocationException, ExecutionException, InterruptedException {
 +        ResizeVolumeCmd cmd = new ResizeVolumeCmd();
 +        ReflectionTestUtils.setField(cmd, "id", volumeMockId);
 +        ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
 +
 +        testResizeVolumeSetup();
 +
 +        when(storageMgr.storagePoolHasEnoughSpaceForResize(any(), nullable(Long.class), nullable(Long.class))).thenReturn(true);
 +
 +        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
 +            volumeApiServiceImpl.resizeVolume(cmd);
 +
 +            verify(volumeServiceMock).resize(any(VolumeInfo.class));
 +        }
 +    }
 +
 +    @Test
 +    public void testResizeVolumeWithoutEnoughCapacity() throws ResourceAllocationException, ExecutionException, InterruptedException {
 +        ResizeVolumeCmd cmd = new ResizeVolumeCmd();
 +        ReflectionTestUtils.setField(cmd, "id", volumeMockId);
 +        ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
 +        ReflectionTestUtils.setField(cmd, "autoMigrate", true);
 +
 +        testResizeVolumeSetup();
 +
 +        when(storageMgr.storagePoolHasEnoughSpaceForResize(any(), nullable(Long.class), nullable(Long.class))).thenReturn(false).thenReturn(true);
 +        StoragePoolVO suitableStoragePool = Mockito.mock(StoragePoolVO.class);
 +        Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = new Pair<>(Arrays.asList(suitableStoragePool), Arrays.asList(suitableStoragePool));
 +        when(managementService.listStoragePoolsForSystemMigrationOfVolume(anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyBoolean(), anyBoolean())).thenReturn(poolsPair);
 +        doReturn(volumeInfoMock).when(volumeApiServiceImpl).migrateVolume(any());
 +        when(volumeInfoMock.getId()).thenReturn(volumeMockId);
 +
 +        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
 +            volumeApiServiceImpl.resizeVolume(cmd);
 +
 +            verify(volumeApiServiceImpl).migrateVolume(any());
 +            verify(volumeServiceMock).resize(any(VolumeInfo.class));
 +        }
 +    }
 +
 +    @Test
 +    public void testResizeVolumeInAllocateState() throws ResourceAllocationException, ExecutionException, InterruptedException {
 +        ResizeVolumeCmd cmd = new ResizeVolumeCmd();
 +        ReflectionTestUtils.setField(cmd, "id", volumeMockId);
 +        ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
 +
 +        testResizeVolumeSetup();
 +
 +        when(volumeVoMock.getState()).thenReturn(Volume.State.Allocated);
 +
 +        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
 +            volumeApiServiceImpl.resizeVolume(cmd);
 +
 +            verify(volumeServiceMock, times(0)).resize(any(VolumeInfo.class));
 +        }
 +    }
+     private UserVmVO getMockedVm() {
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         Mockito.when(vm.getId()).thenReturn(1L);
+         Mockito.when(vm.getTemplateId()).thenReturn(10L);
+         Mockito.when(vm.getHostName()).thenReturn("test-vm");
+         return vm;
+     }
+ 
+     private VMTemplateVO getMockedTemplate() {
+         VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+         Mockito.when(template.isDeployAsIs()).thenReturn(false);
+         return template;
+     }
+ 
+     @Test(expected = CloudRuntimeException.class)
+     public void testGetVmExistingVolumeForVolumeAttach_MultipleRootVolumes_ThrowsException() {
+         UserVmVO vm = getMockedVm();
+         VMTemplateVO template = getMockedTemplate();
+         when(templateDao.findById(10L)).thenReturn(template);
+         when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT))
+                 .thenReturn(Arrays.asList(Mockito.mock(VolumeVO.class), Mockito.mock(VolumeVO.class)));
+         volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, Mockito.mock(VolumeInfo.class));
+     }
+ 
+     @Test
+     public void testGetVmExistingVolumeForVolumeAttach_SingleRootVolume() {
+         UserVmVO vm = getMockedVm();
+         VMTemplateVO template = getMockedTemplate();
+         VolumeVO rootVolume = Mockito.mock(VolumeVO.class);
+         Mockito.when(rootVolume.getId()).thenReturn(20L);
+         Mockito.when(templateDao.findById(10L)).thenReturn(template);
+         Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT))
+                 .thenReturn(Collections.singletonList(rootVolume));
+         VolumeVO result = volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, Mockito.mock(VolumeInfo.class));
+         Assert.assertNotNull(result);
+         Assert.assertEquals(20L, result.getId());
+     }
+ 
+     private VolumeVO getMockedDataVolume() {
+         VolumeVO volume = Mockito.mock(VolumeVO.class);
+         Mockito.when(volume.getId()).thenReturn(30L);
+         Mockito.when(volume.getState()).thenReturn(Volume.State.Ready);
+         return volume;
+     }
+ 
+     @Test
+     public void testGetVmExistingVolumeForVolumeAttach_NoRootVolume_DataDiskAvailable() {
+         UserVmVO vm = getMockedVm();
+         VMTemplateVO template = getMockedTemplate();
+         VolumeVO dataDisk = getMockedDataVolume();
+         List<VolumeVO> rootVolumes = Collections.emptyList();
+         List<VolumeVO> dataVolumes = Collections.singletonList(dataDisk);
+         Mockito.when(templateDao.findById(10L)).thenReturn(template);
+         Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT)).thenReturn(rootVolumes);
+         Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.DATADISK)).thenReturn(dataVolumes);
+         VolumeVO result = volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, Mockito.mock(VolumeInfo.class));
+         Assert.assertNotNull(result);
+         Assert.assertEquals(30L, result.getId());
+     }
+ 
+     @Test
+     public void testGetVmExistingVolumeForVolumeAttach_NoVolumesAtAll() {
+         UserVmVO vm = getMockedVm();
+         VMTemplateVO template = getMockedTemplate();
+         Mockito.when(templateDao.findById(10L)).thenReturn(template);
+         Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT)).thenReturn(Collections.emptyList());
+         Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.DATADISK)).thenReturn(Collections.emptyList());
+         VolumeVO result = volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, Mockito.mock(VolumeInfo.class));
+         Assert.assertNull(result);
+     }
+ 
+     private void mockDiskOffering() {
+         DiskOfferingVO offering = Mockito.mock(DiskOfferingVO.class);
+         Mockito.when(_diskOfferingDao.findById(1L)).thenReturn(offering);
+         Mockito.when(offering.isUseLocalStorage()).thenReturn(true);
+         Mockito.when(offering.isRecreatable()).thenReturn(false);
+     }
+ 
+     private DataCenterVO mockZone() {
+         DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+         Mockito.when(_dcDao.findById(1L)).thenReturn(zone);
+         return zone;
+     }
+ 
+     @Test
+     public void testGetPoolForAllocatedOrUploadedVolumeForAttach_Success() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         ClusterVO cluster = Mockito.mock(ClusterVO.class);
+         HostPodVO pod = Mockito.mock(HostPodVO.class);
+         DataCenterVO zone = mockZone();
+         mockDiskOffering();
+         StoragePool pool = Mockito.mock(StoragePool.class);
+         when(vm.getDataCenterId()).thenReturn(1L);
+         when(virtualMachineManager.findClusterAndHostIdForVm(vm, false)).thenReturn(new Pair<>(1L, 2L));
+         when(clusterDao.findById(1L)).thenReturn(cluster);
+         when(cluster.getPodId()).thenReturn(1L);
+         when(podDao.findById(1L)).thenReturn(pod);
+         when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+         when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), eq(zone), eq(pod), eq(1L), eq(2L), eq(vm), eq(Collections.emptySet())))
+                 .thenReturn(pool);
+         StoragePool result = volumeApiServiceImpl.getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         Assert.assertNotNull(result);
+         Assert.assertEquals(pool, result);
+     }
+ 
+     @Test(expected = CloudRuntimeException.class)
+     public void testGetPoolForAllocatedOrUploadedVolumeForAttach_NoPoolFound_ThrowsException() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         DataCenterVO zone = mockZone();
+         Pair<Long, Long> clusterHostId = new Pair<>(1L, 2L);
+         ClusterVO cluster = Mockito.mock(ClusterVO.class);
+         HostPodVO pod = Mockito.mock(HostPodVO.class);
+         mockDiskOffering();
+         when(vm.getDataCenterId()).thenReturn(1L);
+         when(clusterDao.findById(1L)).thenReturn(cluster);
+         when(virtualMachineManager.findClusterAndHostIdForVm(vm, false)).thenReturn(clusterHostId);
+         when(podDao.findById(anyLong())).thenReturn(pod);
+         when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+         when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), eq(zone), eq(pod), eq(1L), eq(2L), eq(vm), eq(Collections.emptySet())))
+                 .thenReturn(null);
+         volumeApiServiceImpl.getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+     }
+ 
+     @Test
+     public void testGetPoolForAllocatedOrUploadedVolumeForAttach_NoCluster() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         DataCenterVO zone = mockZone();
+         HostPodVO pod = Mockito.mock(HostPodVO.class);
+         mockDiskOffering();
+         StoragePool pool = Mockito.mock(StoragePool.class);
+         when(vm.getDataCenterId()).thenReturn(1L);
+         when(vm.getPodIdToDeployIn()).thenReturn(2L);
+         when(virtualMachineManager.findClusterAndHostIdForVm(vm, false)).thenReturn(new Pair<>(null, 2L));
+         when(podDao.findById(2L)).thenReturn(pod);
+         when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+         when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), eq(zone), eq(pod), eq(null), eq(2L), eq(vm), eq(Collections.emptySet())))
+                 .thenReturn(pool);
+         StoragePool result = volumeApiServiceImpl.getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         Assert.assertNotNull(result);
+         Assert.assertEquals(pool, result);
+     }
+ 
+ 
+     @Test
+     public void testCreateVolumeOnSecondaryForAttachIfNeeded_VolumeNotAllocatedOrUploaded() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Ready);
+         VolumeInfo result = volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(
+                 volumeToAttach, Mockito.mock(UserVmVO.class), null);
+         Assert.assertSame(volumeToAttach, result);
+         Mockito.verifyNoInteractions(primaryDataStoreDaoMock, volumeOrchestrationService);
+     }
+ 
+     @Test
+     public void testCreateVolumeOnSecondaryForAttachIfNeeded_ExistingVolumeDeterminesStoragePool() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         VolumeVO existingVolume = Mockito.mock(VolumeVO.class);
+         Mockito.when(existingVolume.getState()).thenReturn(Volume.State.Ready);
+         when(existingVolume.getPoolId()).thenReturn(1L);
+         StoragePoolVO destPrimaryStorage = Mockito.mock(StoragePoolVO.class);
+         Mockito.when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+         Mockito.when(primaryDataStoreDaoMock.findById(1L)).thenReturn(destPrimaryStorage);
+         VolumeInfo newVolumeOnPrimaryStorage = Mockito.mock(VolumeInfo.class);
+         try {
+             Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(vm, volumeToAttach, vm.getHypervisorType(), destPrimaryStorage))
+                     .thenReturn(newVolumeOnPrimaryStorage);
+         } catch (NoTransitionException nte) {
+             Assert.fail(nte.getMessage());
+         }
+         VolumeInfo result = volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, existingVolume);
+         Assert.assertSame(newVolumeOnPrimaryStorage, result);
+         Mockito.verify(primaryDataStoreDaoMock).findById(1L);
+     }
+ 
+     @Test
+     public void testCreateVolumeOnPrimaryForAttachIfNeeded_UsesGetPoolForAttach() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Allocated);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+         Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                 .getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         VolumeInfo newVolumeOnPrimaryStorage = Mockito.mock(VolumeInfo.class);
+         try {
+             Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(
+                     vm, volumeToAttach, vm.getHypervisorType(), destPrimaryStorage))
+                 .thenReturn(newVolumeOnPrimaryStorage);
+         } catch (NoTransitionException nte) {
+             Assert.fail(nte.getMessage());
+         }
+         VolumeInfo result = volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, null);
+         Assert.assertSame(newVolumeOnPrimaryStorage, result);
+         verify(volumeApiServiceImpl).getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testCreateVolumeOnPrimaryForAttachIfNeeded_UnsupportedPoolType_ThrowsException() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+         when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
+         Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                 .getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, null);
+     }
+ 
+     @Test
+     public void testCreateVolumeOnSecondaryForAttachIfNeeded_CreateVolumeFails_ThrowsException() {
+         VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+         Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+         UserVmVO vm = Mockito.mock(UserVmVO.class);
+         StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+         Mockito.when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+         Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                 .getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+         try {
+             Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(vm, volumeToAttach, vm.getHypervisorType(), destPrimaryStorage))
+                     .thenThrow(new NoTransitionException("Mocked exception"));
+         } catch (NoTransitionException nte) {
+             Assert.fail(nte.getMessage());
+         }
+         CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, () ->
+                 volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, null)
+         );
+         Assert.assertTrue(exception.getMessage().contains("Failed to create volume on primary storage"));
+     }
  }
diff --cc server/src/test/java/com/cloud/user/AccountManagerImplTest.java
index 0f53b907251,e68de194f01..61a273c8946
--- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
+++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
@@@ -43,23 -39,21 +41,25 @@@ import org.apache.cloudstack.auth.UserA
  import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.framework.config.ConfigKey;
+ import org.apache.cloudstack.resourcedetail.UserDetailVO;
 +import org.apache.cloudstack.webhook.WebhookHelper;
  import org.junit.Assert;
  import org.junit.Before;
  import org.junit.Test;
  import org.junit.runner.RunWith;
+ import org.mockito.ArgumentCaptor;
  import org.mockito.InOrder;
  import org.mockito.Mock;
 +import org.mockito.MockedStatic;
  import org.mockito.Mockito;
  import org.mockito.junit.MockitoJUnitRunner;
- 
 +import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  
  import com.cloud.acl.DomainChecker;
  import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
  import com.cloud.domain.Domain;
  import com.cloud.domain.DomainVO;
++import com.cloud.event.ActionEventUtils;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.PermissionDeniedException;
diff --cc server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
index 30324b41986,96b73cc63dd..62a95380a15
--- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
+++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
@@@ -16,6 -16,6 +16,27 @@@
  // under the License.
  package com.cloud.user;
  
++import java.net.InetAddress;
++import java.util.List;
++import java.util.Map;
++
++import javax.naming.ConfigurationException;
++
++import org.apache.cloudstack.acl.ControlledEntity;
++import org.apache.cloudstack.acl.RoleType;
++import org.apache.cloudstack.acl.SecurityChecker.AccessType;
++import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
++import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
++import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
++import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
++import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
++import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
++import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
++import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
++import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
++import org.apache.cloudstack.framework.config.ConfigKey;
++import org.springframework.stereotype.Component;
++
  import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
  import com.cloud.api.query.vo.ControlledViewEntity;
  import com.cloud.dc.DataCenter;
@@@ -34,25 -34,25 +55,6 @@@ import com.cloud.utils.component.Manage
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
--import org.apache.cloudstack.acl.ControlledEntity;
--import org.apache.cloudstack.acl.RoleType;
--import org.apache.cloudstack.acl.SecurityChecker.AccessType;
--import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
--import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
--import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
--import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
--import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
--import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
--import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
--import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
--import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
--import org.apache.cloudstack.framework.config.ConfigKey;
--import org.springframework.stereotype.Component;
--
--import javax.naming.ConfigurationException;
--import java.net.InetAddress;
--import java.util.List;
--import java.util.Map;
  
  @Component
  public class MockAccountManagerImpl extends ManagerBase implements Manager, AccountManager {
@@@ -488,12 -484,8 +490,17 @@@
          return null;
      }
  
 +    @Override
 +    public List<String> getApiNameList() {
 +        return null;
 +    }
 +
 +    @Override
 +    public void validateUserPasswordAndUpdateIfNeeded(String newPassword, UserVO user, String currentPassword, boolean skipCurrentPassValidation) {
 +    }
++
+     @Override
+     public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) {
+         return null;
+     }
  }
diff --cc ui/src/components/view/ListView.vue
index 37c22edba77,0c19ea8edab..4541e1687ea
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@@ -999,22 -911,19 +999,22 @@@ export default 
        return host.state
      },
      getColumnKey (name) {
-       if (typeof name === 'object') {
-         name = Object.keys(name).includes('field') ? name.field : name.customTitle
+       if (typeof name !== 'object' || name === null) {
+         return name
        }
-       return name
+       return name.field ?? name.customTitle ?? Object.keys(name)[0]
      },
-     getColumTitle (name) {
-       if (typeof name === 'object') {
-         name = Object.keys(name).includes('customTitle') ? name.customTitle : name.field
+     getColumnTitle (name) {
+       if (typeof name !== 'object' || name === null) {
+         return name
        }
-       return name
+       return name.customTitle ?? name.field ?? Object.keys(name)[0]
      },
 +    handleResizeColumn (w, col) {
 +      col.width = w
 +    },
      updateSelectedColumns (name) {
 -      this.$emit('update-selected-columns', name)
 +      this.$emit('update-selected-columns', this.getColumnKey(name))
      },
      getVmRouteUsingType (record) {
        switch (record.virtualmachinetype) {