From 60af31c9c0ba9919b518ae0a18d622cef876ab4d Mon Sep 17 00:00:00 2001 From: Harikrishna Date: Mon, 3 Feb 2025 18:00:57 +0530 Subject: [PATCH 01/24] Decrypt zone, cluster, storage details for configuration values (#10237) Co-authored-by: dahn Co-authored-by: Bryan Lima <42067040+BryanMLima@users.noreply.github.com> --- .../java/com/cloud/dc/ClusterDetailsDao.java | 3 ++- .../com/cloud/dc/ClusterDetailsDaoImpl.java | 16 ++++++++---- .../java/com/cloud/dc/ClusterDetailsVO.java | 18 ++++++++----- .../dc/dao/DataCenterDetailsDaoImpl.java | 5 ++-- .../java/com/cloud/domain/DomainDetailVO.java | 18 ++++++++----- .../cloud/domain/dao/DomainDetailsDao.java | 5 ++-- .../domain/dao/DomainDetailsDaoImpl.java | 26 +++++++------------ .../dao/StoragePoolDetailsDaoImpl.java | 2 +- .../java/com/cloud/user/AccountDetailVO.java | 18 ++++++++----- .../com/cloud/user/AccountDetailsDao.java | 5 ++-- .../com/cloud/user/AccountDetailsDaoImpl.java | 26 +++++++------------ .../resourcedetail/ResourceDetailsDao.java | 2 ++ .../ResourceDetailsDaoBase.java | 18 +++++++++++++ .../db/ImageStoreDetailsDaoImpl.java | 4 +-- 14 files changed, 97 insertions(+), 69 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDao.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDao.java index 06c9c525504..b54cc8b3c21 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDao.java @@ -19,8 +19,9 @@ package com.cloud.dc; import java.util.Map; import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; -public interface ClusterDetailsDao extends GenericDao { +public interface ClusterDetailsDao extends GenericDao, ResourceDetailsDao { Map findDetails(long clusterId); void persist(long clusterId, Map details); diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java index c2058ad5644..37e10910978 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java @@ -26,12 +26,13 @@ import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; + +public class ClusterDetailsDaoImpl extends ResourceDetailsDaoBase implements ClusterDetailsDao, ScopedConfigStorage { -public class ClusterDetailsDaoImpl extends GenericDaoBase implements ClusterDetailsDao, ScopedConfigStorage { protected final SearchBuilder ClusterSearch; protected final SearchBuilder DetailSearch; @@ -42,11 +43,11 @@ public class ClusterDetailsDaoImpl extends GenericDaoBase findDetails(long clusterId) { SearchCriteria sc = ClusterSearch.create(); @@ -138,7 +144,7 @@ public class ClusterDetailsDaoImpl extends GenericDaoBase key) { ClusterDetailsVO vo = findDetail(id, key.key()); - return vo == null ? null : vo.getValue(); + return vo == null ? null : getActualValue(vo); } @Override diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java index 6eb9e7466a7..b213f8f2594 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java @@ -23,11 +23,11 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; -import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.api.ResourceDetail; @Entity @Table(name = "cluster_details") -public class ClusterDetailsVO implements InternalIdentity { +public class ClusterDetailsVO implements ResourceDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -35,7 +35,7 @@ public class ClusterDetailsVO implements InternalIdentity { private long id; @Column(name = "cluster_id") - private long clusterId; + private long resourceId; @Column(name = "name") private String name; @@ -47,13 +47,14 @@ public class ClusterDetailsVO implements InternalIdentity { } public ClusterDetailsVO(long clusterId, String name, String value) { - this.clusterId = clusterId; + this.resourceId = clusterId; this.name = name; this.value = value; } - public long getClusterId() { - return clusterId; + @Override + public long getResourceId() { + return resourceId; } public String getName() { @@ -64,6 +65,11 @@ public class ClusterDetailsVO implements InternalIdentity { return value; } + @Override + public boolean isDisplay() { + return true; + } + public void setValue(String value) { this.value = value; } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java index e36c8ebd6c7..27210dfcf0d 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java @@ -16,7 +16,6 @@ // under the License. package com.cloud.dc.dao; -import org.apache.cloudstack.api.ResourceDetail; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; @@ -45,8 +44,8 @@ public class DataCenterDetailsDaoImpl extends ResourceDetailsDaoBase key) { - ResourceDetail vo = findDetail(id, key.key()); - return vo == null ? null : vo.getValue(); + DataCenterDetailVO vo = findDetail(id, key.key()); + return vo == null ? null : getActualValue(vo); } @Override diff --git a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java index df5a2283baa..6f803cc9f2f 100644 --- a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java @@ -23,18 +23,18 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; -import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.api.ResourceDetail; @Entity @Table(name = "domain_details") -public class DomainDetailVO implements InternalIdentity { +public class DomainDetailVO implements ResourceDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private long id; @Column(name = "domain_id") - private long domainId; + private long resourceId; @Column(name = "name") private String name; @@ -46,13 +46,14 @@ public class DomainDetailVO implements InternalIdentity { } public DomainDetailVO(long domainId, String name, String value) { - this.domainId = domainId; + this.resourceId = domainId; this.name = name; this.value = value; } - public long getDomainId() { - return domainId; + @Override + public long getResourceId() { + return resourceId; } public String getName() { @@ -63,6 +64,11 @@ public class DomainDetailVO implements InternalIdentity { return value; } + @Override + public boolean isDisplay() { + return true; + } + public void setValue(String value) { this.value = value; } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDao.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDao.java index 6b53e49764e..ae149ff4381 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDao.java @@ -20,8 +20,9 @@ import java.util.Map; import com.cloud.domain.DomainDetailVO; import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; -public interface DomainDetailsDao extends GenericDao { +public interface DomainDetailsDao extends GenericDao, ResourceDetailsDao { Map findDetails(long domainId); void persist(long domainId, Map details); @@ -31,6 +32,4 @@ public interface DomainDetailsDao extends GenericDao { void deleteDetails(long domainId); void update(long domainId, Map details); - - String getActualValue(DomainDetailVO domainDetailVO); } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java index 50097d154f5..72532f4ea26 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java @@ -24,8 +24,6 @@ import javax.inject.Inject; import com.cloud.domain.DomainDetailVO; import com.cloud.domain.DomainVO; -import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -35,9 +33,9 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; -public class DomainDetailsDaoImpl extends GenericDaoBase implements DomainDetailsDao, ScopedConfigStorage { +public class DomainDetailsDaoImpl extends ResourceDetailsDaoBase implements DomainDetailsDao, ScopedConfigStorage { protected final SearchBuilder domainSearch; @Inject @@ -47,14 +45,14 @@ public class DomainDetailsDaoImpl extends GenericDaoBase i protected DomainDetailsDaoImpl() { domainSearch = createSearchBuilder(); - domainSearch.and("domainId", domainSearch.entity().getDomainId(), Op.EQ); + domainSearch.and("domainId", domainSearch.entity().getResourceId(), Op.EQ); domainSearch.done(); } @Override public Map findDetails(long domainId) { QueryBuilder sc = QueryBuilder.create(DomainDetailVO.class); - sc.and(sc.entity().getDomainId(), Op.EQ, domainId); + sc.and(sc.entity().getResourceId(), Op.EQ, domainId); List results = sc.list(); Map details = new HashMap(results.size()); for (DomainDetailVO r : results) { @@ -80,11 +78,16 @@ public class DomainDetailsDaoImpl extends GenericDaoBase i @Override public DomainDetailVO findDetail(long domainId, String name) { QueryBuilder sc = QueryBuilder.create(DomainDetailVO.class); - sc.and(sc.entity().getDomainId(), Op.EQ, domainId); + sc.and(sc.entity().getResourceId(), Op.EQ, domainId); sc.and(sc.entity().getName(), Op.EQ, name); return sc.find(); } + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new DomainDetailVO(resourceId, key, value)); + } + @Override public void deleteDetails(long domainId) { SearchCriteria sc = domainSearch.create(); @@ -129,13 +132,4 @@ public class DomainDetailsDaoImpl extends GenericDaoBase i } return vo == null ? null : getActualValue(vo); } - - @Override - public String getActualValue(DomainDetailVO domainDetailVO) { - ConfigurationVO configurationVO = _configDao.findByName(domainDetailVO.getName()); - if (configurationVO != null && configurationVO.isEncrypted()) { - return DBEncryptionUtil.decrypt(domainDetailVO.getValue()); - } - return domainDetailVO.getValue(); - } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java index 0c39a8c581a..559978ef284 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java @@ -45,7 +45,7 @@ public class StoragePoolDetailsDaoImpl extends ResourceDetailsDaoBase key) { StoragePoolDetailVO vo = findDetail(id, key.key()); - return vo == null ? null : vo.getValue(); + return vo == null ? null : getActualValue(vo); } @Override diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java index 863f6c96008..aa6e49666dd 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java @@ -23,18 +23,18 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; -import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.api.ResourceDetail; @Entity @Table(name = "account_details") -public class AccountDetailVO implements InternalIdentity { +public class AccountDetailVO implements ResourceDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private long id; @Column(name = "account_id") - private long accountId; + private long resourceId; @Column(name = "name") private String name; @@ -46,13 +46,14 @@ public class AccountDetailVO implements InternalIdentity { } public AccountDetailVO(long accountId, String name, String value) { - this.accountId = accountId; + this.resourceId = accountId; this.name = name; this.value = value; } - public long getAccountId() { - return accountId; + @Override + public long getResourceId() { + return resourceId; } public String getName() { @@ -63,6 +64,11 @@ public class AccountDetailVO implements InternalIdentity { return value; } + @Override + public boolean isDisplay() { + return true; + } + public void setValue(String value) { this.value = value; } diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDao.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDao.java index 514433e8068..65bbe1670a8 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDao.java @@ -19,8 +19,9 @@ package com.cloud.user; import java.util.Map; import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; -public interface AccountDetailsDao extends GenericDao { +public interface AccountDetailsDao extends GenericDao, ResourceDetailsDao { Map findDetails(long accountId); void persist(long accountId, Map details); @@ -34,6 +35,4 @@ public interface AccountDetailsDao extends GenericDao { * they will get created */ void update(long accountId, Map details); - - String getActualValue(AccountDetailVO accountDetailVO); } diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java index de562e27f9e..8cea616b97d 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java @@ -23,7 +23,6 @@ import java.util.Optional; import javax.inject.Inject; -import com.cloud.utils.crypt.DBEncryptionUtil; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; @@ -34,16 +33,15 @@ import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.domain.dao.DomainDao; import com.cloud.user.dao.AccountDao; -import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; -public class AccountDetailsDaoImpl extends GenericDaoBase implements AccountDetailsDao, ScopedConfigStorage { +public class AccountDetailsDaoImpl extends ResourceDetailsDaoBase implements AccountDetailsDao, ScopedConfigStorage { protected final SearchBuilder accountSearch; @Inject @@ -57,14 +55,14 @@ public class AccountDetailsDaoImpl extends GenericDaoBase protected AccountDetailsDaoImpl() { accountSearch = createSearchBuilder(); - accountSearch.and("accountId", accountSearch.entity().getAccountId(), Op.EQ); + accountSearch.and("accountId", accountSearch.entity().getResourceId(), Op.EQ); accountSearch.done(); } @Override public Map findDetails(long accountId) { QueryBuilder sc = QueryBuilder.create(AccountDetailVO.class); - sc.and(sc.entity().getAccountId(), Op.EQ, accountId); + sc.and(sc.entity().getResourceId(), Op.EQ, accountId); List results = sc.list(); Map details = new HashMap(results.size()); for (AccountDetailVO r : results) { @@ -90,11 +88,16 @@ public class AccountDetailsDaoImpl extends GenericDaoBase @Override public AccountDetailVO findDetail(long accountId, String name) { QueryBuilder sc = QueryBuilder.create(AccountDetailVO.class); - sc.and(sc.entity().getAccountId(), Op.EQ, accountId); + sc.and(sc.entity().getResourceId(), Op.EQ, accountId); sc.and(sc.entity().getName(), Op.EQ, name); return sc.find(); } + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new AccountDetailVO(resourceId, key, value)); + } + @Override public void deleteDetails(long accountId) { SearchCriteria sc = accountSearch.create(); @@ -154,13 +157,4 @@ public class AccountDetailsDaoImpl extends GenericDaoBase } return value; } - - @Override - public String getActualValue(AccountDetailVO accountDetailVO) { - ConfigurationVO configurationVO = _configDao.findByName(accountDetailVO.getName()); - if (configurationVO != null && configurationVO.isEncrypted()) { - return DBEncryptionUtil.decrypt(accountDetailVO.getValue()); - } - return accountDetailVO.getValue(); - } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java index 5a173191be1..6daf8f02231 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java @@ -97,4 +97,6 @@ public interface ResourceDetailsDao extends GenericDao public void addDetail(long resourceId, String key, String value, boolean display); public List findResourceIdsByNameAndValueIn(String name, Object[] values); + + String getActualValue(ResourceDetail resourceDetail); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java index 37ebfebf5dd..556c832e991 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.cloud.utils.crypt.DBEncryptionUtil; import org.apache.cloudstack.api.ResourceDetail; import com.cloud.utils.db.GenericDaoBase; @@ -28,8 +29,16 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.SearchCriteria.Op; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; + +import javax.inject.Inject; public abstract class ResourceDetailsDaoBase extends GenericDaoBase implements ResourceDetailsDao { + + @Inject + private ConfigurationDao configDao; + private SearchBuilder AllFieldsSearch; public ResourceDetailsDaoBase() { @@ -201,4 +210,13 @@ public abstract class ResourceDetailsDaoBase extends G return customSearch(sc, null); } + + @Override + public String getActualValue(ResourceDetail resourceDetail) { + ConfigurationVO configurationVO = configDao.findByName(resourceDetail.getName()); + if (configurationVO != null && configurationVO.isEncrypted()) { + return DBEncryptionUtil.decrypt(resourceDetail.getValue()); + } + return resourceDetail.getValue(); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java index 8e5ce770f45..1b0644820c5 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java @@ -37,7 +37,6 @@ import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; @Component public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase implements ImageStoreDetailsDao, ScopedConfigStorage { - protected final SearchBuilder storeSearch; public ImageStoreDetailsDaoImpl() { @@ -108,12 +107,11 @@ public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase key) { ImageStoreDetailVO vo = findDetail(id, key.key()); - return vo == null ? null : vo.getValue(); + return vo == null ? null : getActualValue(vo); } @Override public void addDetail(long resourceId, String key, String value, boolean display) { super.addDetail(new ImageStoreDetailVO(resourceId, key, value, display)); } - } From fa5c11e6b2eecc82c16b4c1f67ecfdd7e616fd37 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 3 Feb 2025 13:31:38 +0100 Subject: [PATCH 02/24] UI: list backup offerings by zoneid when assign vm to backup offering (#10217) --- ui/src/config/section/compute.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 37bccf86758..52567450f86 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -219,6 +219,10 @@ export default { args: ['virtualmachineid', 'backupofferingid'], show: (record) => { return !record.backupofferingid }, mapping: { + backupofferingid: { + api: 'listBackupOfferings', + params: (record) => { return { zoneid: record.zoneid } } + }, virtualmachineid: { value: (record, params) => { return record.id } } From c1bc57b844b64bb9f7602567156cfb25f41a8cee Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 3 Feb 2025 07:59:46 -0500 Subject: [PATCH 03/24] List default network offerings when multiple physical networks for guest traffic type exists (#10222) --- .../configuration/ConfigurationManagerImpl.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 9df33b47257..94a0fd2ea0d 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -6955,10 +6955,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati sc.addAnd("id", SearchCriteria.Op.EQ, id); } - if (tags != null) { - sc.addAnd("tags", SearchCriteria.Op.EQ, tags); - } - if (isTagged != null) { if (isTagged) { sc.addAnd("tags", SearchCriteria.Op.NNULL); @@ -6967,6 +6963,17 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + if (tags != null) { + if (GuestType.Shared.name().equalsIgnoreCase(guestIpType)) { + SearchCriteria tagsSc = networkOfferingJoinDao.createSearchCriteria(); + tagsSc.addAnd("tags", SearchCriteria.Op.EQ, tags); + tagsSc.addOr("isDefault", SearchCriteria.Op.EQ, true); + sc.addAnd("tags", SearchCriteria.Op.SC, tagsSc); + } else { + sc.addAnd("tags", SearchCriteria.Op.EQ, tags); + } + } + if (zoneId != null) { SearchBuilder sb = networkOfferingJoinDao.createSearchBuilder(); sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.FIND_IN_SET); @@ -7027,7 +7034,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati boolean addOffering = true; List checkForProviders = new ArrayList(); - if (checkForTags && ! checkNetworkOfferingTags(pNtwkTags, allowNullTag, offering.getTags())) { + if (checkForTags && !checkNetworkOfferingTags(pNtwkTags, allowNullTag, offering.getTags())) { continue; } From 238d0c5e30a8445082050c9f35ff00f1cc9d096f Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 4 Feb 2025 14:29:20 +0530 Subject: [PATCH 04/24] Fix NPE while checking for user data provider (#10255) --- .../com/cloud/network/NetworkModelImpl.java | 6 +++++- .../element/ConfigDriveNetworkElement.java | 19 ++++++++++++++++--- .../network/element/VirtualRouterElement.java | 7 ++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index 23018ab72fd..2f904a3275f 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -935,8 +935,12 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi @Override public UserDataServiceProvider getUserDataUpdateProvider(Network network) { - String userDataProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); + if (network == null) { + s_logger.warn("No network details, can't fetch user data provider"); + return null; + } + String userDataProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); if (userDataProvider == null) { s_logger.debug("Network " + network + " doesn't support service " + Service.UserData.getName()); return null; diff --git a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java index 38d71b9c507..15238dc1c3b 100644 --- a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -320,8 +320,12 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle try { final Network network = _networkMgr.getNetwork(nic.getNetworkId()); final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network); + if (userDataUpdateProvider == null) { + LOG.warn("Failed to get user data provider"); + return false; + } final Provider provider = userDataUpdateProvider.getProvider(); - if (provider.equals(Provider.ConfigDrive)) { + if (Provider.ConfigDrive.equals(provider)) { try { return deleteConfigDriveIso(vm); } catch (ResourceUnavailableException e) { @@ -336,7 +340,12 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle @Override public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) { - if (_networkModel.getUserDataUpdateProvider(network).getProvider().equals(Provider.ConfigDrive)) { + final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network); + if (userDataUpdateProvider == null) { + LOG.warn("Failed to prepare for migration, can't get user data provider"); + return false; + } + if (Provider.ConfigDrive.equals(userDataUpdateProvider.getProvider())) { LOG.trace(String.format("[prepareMigration] for vm: %s", vm.getInstanceName())); try { if (isConfigDriveIsoOnHostCache(vm.getId())) { @@ -384,7 +393,11 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle } private void recreateConfigDriveIso(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest) throws ResourceUnavailableException { - if (nic.isDefaultNic() && _networkModel.getUserDataUpdateProvider(network).getProvider().equals(Provider.ConfigDrive)) { + final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network); + if (userDataUpdateProvider == null) { + return; + } + if (nic.isDefaultNic() && Provider.ConfigDrive.equals(userDataUpdateProvider.getProvider())) { DiskTO diskToUse = null; for (DiskTO disk : vm.getDisks()) { if (disk.getType() == Volume.Type.ISO && disk.getPath() != null && disk.getPath().contains("configdrive")) { diff --git a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java index 5680111ca1a..334cfb59073 100644 --- a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java @@ -767,7 +767,12 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ @Override public boolean saveHypervisorHostname(NicProfile nicProfile, Network network, VirtualMachineProfile vm, DeployDestination dest) throws ResourceUnavailableException { - if (_networkModel.getUserDataUpdateProvider(network).getProvider().equals(Provider.VirtualRouter) && vm.getVirtualMachine().getType() == VirtualMachine.Type.User) { + final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network); + if (userDataUpdateProvider == null) { + s_logger.warn("Failed to update hypervisor host details, can't get user data provider"); + return false; + } + if (Provider.VirtualRouter.equals(userDataUpdateProvider.getProvider()) && vm.getVirtualMachine().getType() == VirtualMachine.Type.User) { VirtualMachine uvm = vm.getVirtualMachine(); UserVmVO destVm = _userVmDao.findById(uvm.getId()); VirtualMachineProfile profile = null; From 37c29f82eda886e87c0dcb342a1a03e699c480d7 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 4 Feb 2025 15:54:41 +0530 Subject: [PATCH 05/24] server: fix snapshot physical size (#10216) Signed-off-by: Abhishek Kumar Co-authored-by: Wei Zhou --- .../com/cloud/api/query/QueryManagerImpl.java | 76 ++++++------ .../cloud/api/query/dao/SnapshotJoinDao.java | 6 +- .../api/query/dao/SnapshotJoinDaoImpl.java | 34 ++++-- .../query/dao/SnapshotJoinDaoImplTest.java | 109 ++++++++++++++++++ 4 files changed, 171 insertions(+), 54 deletions(-) create mode 100644 server/src/test/java/com/cloud/api/query/dao/SnapshotJoinDaoImplTest.java diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 42128525782..3063b0d49a2 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.api.query; +import static com.cloud.vm.VmDetailConstants.SSH_PUBLIC_KEY; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -34,39 +36,6 @@ import java.util.stream.Stream; import javax.inject.Inject; -import com.cloud.network.dao.IPAddressDao; -import com.cloud.network.dao.IPAddressVO; -import com.cloud.storage.StoragePool; -import com.cloud.storage.StoragePoolHostVO; -import com.cloud.event.EventVO; -import com.cloud.event.dao.EventDao; -import com.cloud.host.HostVO; -import com.cloud.offering.ServiceOffering; -import com.cloud.service.ServiceOfferingDetailsVO; -import com.cloud.storage.VMTemplateStoragePoolVO; -import com.cloud.storage.dao.StoragePoolHostDao; -import com.cloud.storage.dao.VMTemplatePoolDao; -import com.cloud.host.Host; -import com.cloud.host.dao.HostDao; -import com.cloud.network.as.AutoScaleVmGroupVmMapVO; -import com.cloud.network.as.dao.AutoScaleVmGroupDao; -import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.dao.PublicIpQuarantineDao; -import com.cloud.network.PublicIpQuarantine; -import com.cloud.network.vo.PublicIpQuarantineVO; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.user.AccountVO; -import com.cloud.user.SSHKeyPairVO; -import com.cloud.user.dao.SSHKeyPairDao; -import com.cloud.vm.InstanceGroupVMMapVO; -import com.cloud.vm.NicVO; -import com.cloud.vm.UserVmDetailVO; -import com.cloud.vm.dao.InstanceGroupVMMapDao; -import com.cloud.vm.dao.NicDao; -import com.cloud.vm.dao.UserVmDetailsDao; -import com.cloud.storage.VolumeVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker; @@ -108,6 +77,7 @@ import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; import org.apache.cloudstack.api.command.user.address.ListQuarantinedIpsCmd; import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupsCmd; +import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; import org.apache.cloudstack.api.command.user.event.ListEventsCmd; import org.apache.cloudstack.api.command.user.iso.ListIsosCmd; import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; @@ -129,6 +99,7 @@ import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BucketResponse; import org.apache.cloudstack.api.response.DetailOptionsResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; @@ -255,22 +226,38 @@ import com.cloud.dc.dao.DedicatedResourceDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; +import com.cloud.event.EventVO; +import com.cloud.event.dao.EventDao; import com.cloud.event.dao.EventJoinDao; import com.cloud.exception.CloudAuthenticationException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.PublicIpQuarantine; import com.cloud.network.RouterHealthCheckResult; import com.cloud.network.VNF; import com.cloud.network.VpcVirtualNetworkApplianceService; +import com.cloud.network.as.AutoScaleVmGroupVmMapVO; +import com.cloud.network.as.dao.AutoScaleVmGroupDao; +import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PublicIpQuarantineDao; import com.cloud.network.dao.RouterHealthCheckResultDao; import com.cloud.network.dao.RouterHealthCheckResultVO; import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.security.SecurityGroupVMMapVO; import com.cloud.network.security.dao.SecurityGroupVMMapDao; +import com.cloud.network.vo.PublicIpQuarantineVO; import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; import com.cloud.org.Grouping; import com.cloud.projects.Project; import com.cloud.projects.Project.ListProjectResourcesCriteria; @@ -286,6 +273,7 @@ import com.cloud.server.ResourceManagerUtil; import com.cloud.server.ResourceMetaDataService; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; @@ -298,24 +286,34 @@ import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.TemplateType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiServiceImpl; +import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.BucketDao; import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplatePoolDao; +import com.cloud.storage.dao.VolumeDao; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate.State; import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; import com.cloud.user.DomainManager; +import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; @@ -331,18 +329,20 @@ import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.InstanceGroupVMMapVO; +import com.cloud.vm.NicVO; +import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.InstanceGroupVMMapDao; +import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; -import org.apache.cloudstack.api.response.BucketResponse; - -import static com.cloud.vm.VmDetailConstants.SSH_PUBLIC_KEY; @Component public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable { @@ -5627,7 +5627,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q Integer count = snapshotDataPair.second(); if (count == 0) { - // empty result return snapshotDataPair; } List snapshotData = snapshotDataPair.first(); @@ -5637,7 +5636,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } else { snapshots = snapshotJoinDao.searchBySnapshotStorePair(snapshotData.stream().map(SnapshotJoinVO::getSnapshotStorePair).toArray(String[]::new)); } - return new Pair<>(snapshots, count); } diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java index 4e916e66ae7..25dfbfe6714 100644 --- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java @@ -23,10 +23,7 @@ import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.SnapshotResponse; import com.cloud.api.query.vo.SnapshotJoinVO; -import com.cloud.utils.Pair; -import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; -import com.cloud.utils.db.SearchCriteria; public interface SnapshotJoinDao extends GenericDao { @@ -34,8 +31,7 @@ public interface SnapshotJoinDao extends GenericDao { SnapshotResponse setSnapshotResponse(SnapshotResponse snapshotResponse, SnapshotJoinVO snapshot); - Pair, Integer> searchIncludingRemovedAndCount(final SearchCriteria sc, final Filter filter); - List searchBySnapshotStorePair(String... pairs); + List findByDistinctIds(Long zoneId, Long... ids); } diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java index 8b951c174f4..fe462859310 100644 --- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java @@ -18,6 +18,8 @@ package com.cloud.api.query.dao; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,6 +35,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.query.QueryService; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import com.cloud.api.ApiDBUtils; @@ -45,7 +48,6 @@ import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeVO; import com.cloud.user.Account; import com.cloud.user.AccountService; -import com.cloud.utils.Pair; import com.cloud.utils.db.Filter; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -195,13 +197,6 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation, Integer> searchIncludingRemovedAndCount(final SearchCriteria sc, final Filter filter) { - List objects = searchIncludingRemoved(sc, filter, null, false); - Integer count = getDistinctCount(sc); - return new Pair<>(objects, count); - } - @Override public List searchBySnapshotStorePair(String... pairs) { // set detail batch query size @@ -246,14 +241,33 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation findById(Long zoneId, long id) { + SearchBuilder sb = createSearchBuilder(); + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("id", id); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + List snapshotJoinVOS = search(sc, null); + if (CollectionUtils.isEmpty(snapshotJoinVOS)) { + return null; + } + snapshotJoinVOS.sort(Comparator.comparing(SnapshotJoinVO::getSnapshotStorePair)); + return Collections.singletonList(snapshotJoinVOS.get(0)); + } + @Override public List findByDistinctIds(Long zoneId, Long... ids) { if (ids == null || ids.length == 0) { return new ArrayList<>(); } - + if (ids.length == 1) { + return findById(zoneId, ids[0]); + } Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", QueryService.SortKeyAscending.value(), null, null); - SearchCriteria sc = snapshotIdsSearch.create(); if (zoneId != null) { sc.setParameters("zoneId", zoneId); diff --git a/server/src/test/java/com/cloud/api/query/dao/SnapshotJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/SnapshotJoinDaoImplTest.java new file mode 100644 index 00000000000..c1159d3aa88 --- /dev/null +++ b/server/src/test/java/com/cloud/api/query/dao/SnapshotJoinDaoImplTest.java @@ -0,0 +1,109 @@ +// 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. +package com.cloud.api.query.dao; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.api.query.vo.SnapshotJoinVO; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class SnapshotJoinDaoImplTest { + + @Spy + @InjectMocks + SnapshotJoinDaoImpl snapshotJoinDao = new SnapshotJoinDaoImpl(); + + @Mock + SearchCriteria mockSearchCriteria; + + @Before + public void setUp() { + SnapshotJoinVO mockSnap = mock(SnapshotJoinVO.class); + SearchBuilder mockSearchBuilder = mock(SearchBuilder.class); + when(mockSearchBuilder.entity()).thenReturn(mockSnap); + doReturn(mockSearchBuilder).when(snapshotJoinDao).createSearchBuilder(); + when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria); + } + + @Test + public void testFindById_WithNullZoneId_EmptyResult() { + Long zoneId = null; + long id = 1L; + doReturn(Collections.emptyList()).when(snapshotJoinDao).search(mockSearchCriteria, null); + List result = snapshotJoinDao.findById(zoneId, id); + assertNull(result); + verify(mockSearchCriteria).setParameters("id", id); + verify(mockSearchCriteria, never()).setParameters("zoneId", zoneId); + } + + @Test + public void testFindById_WithValidZoneId_EmptyResult() { + Long zoneId = 1L; + long id = 1L; + doReturn(Collections.emptyList()).when(snapshotJoinDao).search(mockSearchCriteria, null); + List result = snapshotJoinDao.findById(zoneId, id); + assertNull(result); + verify(mockSearchCriteria).setParameters("id", id); + verify(mockSearchCriteria).setParameters("zoneId", zoneId); + } + + @Test + public void testFindById_WithValidResults() { + Long zoneId = 1L; + long id = 1L; + SnapshotJoinVO snapshot1 = mock(SnapshotJoinVO.class); + when(snapshot1.getSnapshotStorePair()).thenReturn("Primary_1"); + SnapshotJoinVO snapshot2 = mock(SnapshotJoinVO.class); + when(snapshot2.getSnapshotStorePair()).thenReturn("Image_1"); + doReturn(Arrays.asList(snapshot1, snapshot2)).when(snapshotJoinDao).search(mockSearchCriteria, null); + List result = snapshotJoinDao.findById(zoneId, id); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Image_1", result.get(0).getSnapshotStorePair()); + verify(mockSearchCriteria).setParameters("id", id); + verify(mockSearchCriteria).setParameters("zoneId", zoneId); + } + + @Test + public void testFindById_WithNullResults() { + long id = 1L; + doReturn(null).when(snapshotJoinDao).search(mockSearchCriteria, null); + List result = snapshotJoinDao.findById(null, id); + assertNull(result); + } +} From 1b2f6c999851d9482eb49ae44ba3066c9721dee4 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:03:07 +0530 Subject: [PATCH 06/24] Hide register template, create/upload volume and create vpc buttons when zone is not created. (#10243) --- ui/src/config/section/compute.js | 5 +++++ ui/src/config/section/image.js | 8 ++++++-- ui/src/config/section/network.js | 17 +++++++++-------- ui/src/config/section/storage.js | 5 ++++- ui/src/utils/zone.js | 25 +++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 ui/src/utils/zone.js diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 52567450f86..5ebcde5602d 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { isZoneCreated } from '@/utils/zone' export default { name: 'compute', @@ -99,6 +100,7 @@ export default { label: 'label.vm.add', docHelp: 'adminguide/virtual_machines.html#creating-vms', listView: true, + show: () => { isZoneCreated() }, component: () => import('@/views/compute/DeployVM.vue') }, { @@ -567,6 +569,7 @@ export default { docHelp: 'plugins/cloudstack-kubernetes-service.html#creating-a-new-kubernetes-cluster', listView: true, popup: true, + show: () => { isZoneCreated() }, component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateKubernetesCluster.vue'))) }, { @@ -695,6 +698,7 @@ export default { icon: 'plus-outlined', label: 'label.new.autoscale.vmgroup', listView: true, + show: () => { isZoneCreated() }, component: () => import('@/views/compute/CreateAutoScaleVmGroup.vue') }, { @@ -785,6 +789,7 @@ export default { icon: 'plus-outlined', label: 'label.new.instance.group', listView: true, + show: () => { isZoneCreated() }, args: ['name'] }, { diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js index aeeec2c3758..2b5153c5865 100644 --- a/ui/src/config/section/image.js +++ b/ui/src/config/section/image.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { isZoneCreated } from '@/utils/zone' export default { name: 'image', @@ -110,16 +111,17 @@ export default { docHelp: 'adminguide/templates.html#uploading-templates-from-a-remote-http-server', listView: true, popup: true, + show: () => { isZoneCreated() }, component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadTemplate.vue'))) }, { api: 'registerTemplate', icon: 'cloud-upload-outlined', label: 'label.upload.template.from.local', - show: () => { return 'getUploadParamsForTemplate' in store.getters.apis }, docHelp: 'adminguide/templates.html#uploading-templates-and-isos-from-a-local-computer', listView: true, popup: true, + show: () => { return isZoneCreated() && 'getUploadParamsForTemplate' in store.getters.apis }, component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadTemplate.vue'))) }, { @@ -270,13 +272,14 @@ export default { docHelp: 'adminguide/templates.html#id10', listView: true, popup: true, + show: () => { isZoneCreated() }, component: shallowRef(defineAsyncComponent(() => import('@/views/image/RegisterOrUploadIso.vue'))) }, { api: 'registerIso', icon: 'cloud-upload-outlined', label: 'label.upload.iso.from.local', - show: () => { return 'getUploadParamsForIso' in store.getters.apis }, + show: () => { return isZoneCreated() && 'getUploadParamsForIso' in store.getters.apis }, docHelp: 'adminguide/templates.html#id10', listView: true, popup: true, @@ -389,6 +392,7 @@ export default { label: 'label.kubernetes.version.add', listView: true, popup: true, + show: () => { isZoneCreated() }, component: shallowRef(defineAsyncComponent(() => import('@/views/image/AddKubernetesSupportedVersion.vue'))) }, { diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index edbe4bb37b7..f0415a783a5 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -19,6 +19,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' import tungsten from '@/assets/icons/tungsten.svg?inline' import { isAdmin } from '@/role' +import { isZoneCreated } from '@/utils/zone' export default { name: 'network', @@ -123,7 +124,7 @@ export default { listView: true, popup: true, show: () => { - if (!store.getters.zones || store.getters.zones.length === 0) { + if (!isZoneCreated()) { return false } const AdvancedZones = store.getters.zones.filter(zone => zone.networktype === 'Advanced') @@ -245,6 +246,7 @@ export default { icon: 'plus-outlined', label: 'label.add.vpc', docHelp: 'adminguide/networking_and_traffic.html#adding-a-virtual-private-cloud', + show: () => { isZoneCreated() }, listView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/network/CreateVpc.vue'))) @@ -306,7 +308,7 @@ export default { component: shallowRef(defineAsyncComponent(() => import('@/views/network/IngressEgressRuleConfigure.vue'))) }], show: () => { - if (!store.getters.zones || store.getters.zones.length === 0) { + if (!isZoneCreated()) { return false } const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true) @@ -394,6 +396,7 @@ export default { label: 'label.vnf.appliance.add', docHelp: 'adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances', listView: true, + show: () => { isZoneCreated() }, component: () => import('@/views/compute/DeployVnfAppliance.vue') }, { @@ -941,6 +944,7 @@ export default { label: 'label.add.vpn.gateway', docHelp: 'adminguide/networking_and_traffic.html#creating-a-vpn-gateway-for-the-vpc', listView: true, + show: () => { isZoneCreated() }, args: ['vpcid'] }, { @@ -1116,6 +1120,7 @@ export default { icon: 'plus-outlined', label: 'label.add.vpn.user', listView: true, + show: () => { isZoneCreated() }, args: (record, store) => { if (store.userInfo.roletype === 'User') { return ['username', 'password'] @@ -1195,6 +1200,7 @@ export default { docHelp: 'adminguide/networking_and_traffic.html#creating-and-updating-a-vpn-customer-gateway', listView: true, popup: true, + show: () => { isZoneCreated() }, component: shallowRef(defineAsyncComponent(() => import('@/views/network/CreateVpnCustomerGateway.vue'))) }, { @@ -1384,12 +1390,7 @@ export default { component: shallowRef(defineAsyncComponent(() => import('@/views/network/GuestVlanNetworksTab.vue'))), show: (record) => { return (record.allocationstate === 'Allocated') } }], - show: () => { - if (!store.getters.zones || store.getters.zones.length === 0) { - return false - } - return true - } + show: () => { isZoneCreated() } } ] } diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 28c451105a1..95894b33429 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -17,6 +17,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' +import { isZoneCreated } from '@/utils/zone' export default { name: 'storage', @@ -103,6 +104,7 @@ export default { icon: 'plus-outlined', docHelp: 'adminguide/storage.html#creating-a-new-volume', label: 'label.action.create.volume', + show: () => { isZoneCreated() }, listView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateVolume.vue'))) @@ -112,7 +114,7 @@ export default { icon: 'cloud-upload-outlined', docHelp: 'adminguide/storage.html#uploading-an-existing-volume-to-a-virtual-machine', label: 'label.upload.volume.from.local', - show: () => { return 'getUploadParamsForVolume' in store.getters.apis }, + show: () => { return isZoneCreated() && 'getUploadParamsForVolume' in store.getters.apis }, listView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UploadLocalVolume.vue'))) @@ -122,6 +124,7 @@ export default { icon: 'link-outlined', docHelp: 'adminguide/storage.html#uploading-an-existing-volume-to-a-virtual-machine', label: 'label.upload.volume.from.url', + show: () => { isZoneCreated() }, listView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UploadVolume.vue'))) diff --git a/ui/src/utils/zone.js b/ui/src/utils/zone.js new file mode 100644 index 00000000000..266bf9df445 --- /dev/null +++ b/ui/src/utils/zone.js @@ -0,0 +1,25 @@ +// 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. + +import store from '@/store' + +export function isZoneCreated () { + if (!store.getters.zones || store.getters.zones.length === 0) { + return false + } + return true +} From 55e8eaab89cad7053b3bf0cd2d82808d70f8ef70 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Tue, 4 Feb 2025 15:18:49 +0100 Subject: [PATCH 07/24] Linstor: encryption support (#10126) This introduces a new encryption mode, instead of a simple bool. Now also storage driver can just provide encrypted volumes to CloudStack. --- .../main/java/com/cloud/storage/Storage.java | 65 +++++---- .../resource/LibvirtComputingResource.java | 3 +- .../kvm/storage/KVMStorageProcessor.java | 4 +- plugins/storage/volume/linstor/CHANGELOG.md | 5 + .../LinstorBackupSnapshotCommandWrapper.java | 18 ++- .../kvm/storage/LinstorStorageAdaptor.java | 2 +- .../LinstorPrimaryDataStoreDriverImpl.java | 68 +++++++++- ...LinstorPrimaryDataStoreDriverImplTest.java | 87 ++++++++++++ .../datastore/util/LinstorUtilTest.java | 127 ++++++++++++++++++ pom.xml | 2 +- 10 files changed, 346 insertions(+), 35 deletions(-) create mode 100644 plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java create mode 100644 plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java index 1163fcc892f..0d7a3ed90c0 100644 --- a/api/src/main/java/com/cloud/storage/Storage.java +++ b/api/src/main/java/com/cloud/storage/Storage.java @@ -135,34 +135,49 @@ public class Storage { ISODISK /* Template corresponding to a iso (non root disk) present in an OVA */ } + public enum EncryptionSupport { + /** + * Encryption not supported. + */ + Unsupported, + /** + * Will use hypervisor encryption driver (qemu -> luks) + */ + Hypervisor, + /** + * Storage pool handles encryption and just provides an encrypted volume + */ + Storage + } + public static enum StoragePoolType { - Filesystem(false, true, true), // local directory - NetworkFilesystem(true, true, true), // NFS - IscsiLUN(true, false, false), // shared LUN, with a clusterfs overlay - Iscsi(true, false, false), // for e.g., ZFS Comstar - ISO(false, false, false), // for iso image - LVM(false, false, false), // XenServer local LVM SR - CLVM(true, false, false), - RBD(true, true, false), // http://libvirt.org/storage.html#StorageBackendRBD - SharedMountPoint(true, true, true), - VMFS(true, true, false), // VMware VMFS storage - PreSetup(true, true, false), // for XenServer, Storage Pool is set up by customers. - EXT(false, true, false), // XenServer local EXT SR - OCFS2(true, false, false), - SMB(true, false, false), - Gluster(true, false, false), - PowerFlex(true, true, true), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS) - ManagedNFS(true, false, false), - Linstor(true, true, false), - DatastoreCluster(true, true, false), // for VMware, to abstract pool of clusters - StorPool(true, true, true), - FiberChannel(true, true, false); // Fiber Channel Pool for KVM hypervisors is used to find the volume by WWN value (/dev/disk/by-id/wwn-) + Filesystem(false, true, EncryptionSupport.Hypervisor), // local directory + NetworkFilesystem(true, true, EncryptionSupport.Hypervisor), // NFS + IscsiLUN(true, false, EncryptionSupport.Unsupported), // shared LUN, with a clusterfs overlay + Iscsi(true, false, EncryptionSupport.Unsupported), // for e.g., ZFS Comstar + ISO(false, false, EncryptionSupport.Unsupported), // for iso image + LVM(false, false, EncryptionSupport.Unsupported), // XenServer local LVM SR + CLVM(true, false, EncryptionSupport.Unsupported), + RBD(true, true, EncryptionSupport.Unsupported), // http://libvirt.org/storage.html#StorageBackendRBD + SharedMountPoint(true, true, EncryptionSupport.Hypervisor), + VMFS(true, true, EncryptionSupport.Unsupported), // VMware VMFS storage + PreSetup(true, true, EncryptionSupport.Unsupported), // for XenServer, Storage Pool is set up by customers. + EXT(false, true, EncryptionSupport.Unsupported), // XenServer local EXT SR + OCFS2(true, false, EncryptionSupport.Unsupported), + SMB(true, false, EncryptionSupport.Unsupported), + Gluster(true, false, EncryptionSupport.Unsupported), + PowerFlex(true, true, EncryptionSupport.Hypervisor), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS) + ManagedNFS(true, false, EncryptionSupport.Unsupported), + Linstor(true, true, EncryptionSupport.Storage), + DatastoreCluster(true, true, EncryptionSupport.Unsupported), // for VMware, to abstract pool of clusters + StorPool(true, true, EncryptionSupport.Hypervisor), + FiberChannel(true, true, EncryptionSupport.Unsupported); // Fiber Channel Pool for KVM hypervisors is used to find the volume by WWN value (/dev/disk/by-id/wwn-) private final boolean shared; private final boolean overProvisioning; - private final boolean encryption; + private final EncryptionSupport encryption; - StoragePoolType(boolean shared, boolean overProvisioning, boolean encryption) { + StoragePoolType(boolean shared, boolean overProvisioning, EncryptionSupport encryption) { this.shared = shared; this.overProvisioning = overProvisioning; this.encryption = encryption; @@ -177,6 +192,10 @@ public class Storage { } public boolean supportsEncryption() { + return encryption == EncryptionSupport.Hypervisor || encryption == EncryptionSupport.Storage; + } + + public EncryptionSupport encryptionSupportMode() { return encryption; } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 7df170cd361..71f33d9be57 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -3180,7 +3180,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase())); } - if (volumeObjectTO.requiresEncryption()) { + if (volumeObjectTO.requiresEncryption() && + pool.getType().encryptionSupportMode() == Storage.EncryptionSupport.Hypervisor ) { String secretUuid = createLibvirtVolumeSecret(conn, volumeObjectTO.getPath(), volumeObjectTO.getPassphrase()); DiskDef.LibvirtDiskEncryptDetails encryptDetails = new DiskDef.LibvirtDiskEncryptDetails(secretUuid, QemuObject.EncryptFormat.enumValue(volumeObjectTO.getEncryptFormat())); disk.setLibvirtDiskEncryptDetails(encryptDetails); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 8cee8434b5e..d58cef8c79d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -50,6 +50,7 @@ import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper; import com.cloud.storage.JavaStorageLayer; import com.cloud.storage.MigrationOptions; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageLayer; @@ -1452,7 +1453,8 @@ public class KVMStorageProcessor implements StorageProcessor { } } - if (encryptDetails != null) { + if (encryptDetails != null && + attachingPool.getType().encryptionSupportMode() == Storage.EncryptionSupport.Hypervisor) { diskdef.setLibvirtDiskEncryptDetails(encryptDetails); } diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index 419a7f983ee..98fc8f69512 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Volume snapshots on zfs used the wrong dataset path to hide/unhide snapdev +## [2024-12-19] + +### Added +- Native CloudStack encryption support + ## [2024-12-13] ### Fixed diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java index a572759c35a..fac2ccd2589 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java @@ -96,18 +96,23 @@ public final class LinstorBackupSnapshotCommandWrapper // NOTE: the qemu img will also contain the drbd metadata at the end final QemuImg qemu = new QemuImg(waitMilliSeconds); qemu.convert(srcFile, dstFile); - s_logger.info("Backup snapshot " + srcFile + " to " + dstPath); + s_logger.info(String.format("Backup snapshot '%s' to '%s'", srcPath, dstPath)); return dstPath; } private SnapshotObjectTO setCorrectSnapshotSize(final SnapshotObjectTO dst, final String dstPath) { final File snapFile = new File(dstPath); - final long size = snapFile.exists() ? snapFile.length() : 0; + long size; + if (snapFile.exists()) { + size = snapFile.length(); + } else { + s_logger.warn(String.format("Snapshot file %s does not exist. Reporting size 0", dstPath)); + size = 0; + } - final SnapshotObjectTO snapshot = new SnapshotObjectTO(); - snapshot.setPath(dst.getPath() + File.separator + dst.getName()); - snapshot.setPhysicalSize(size); - return snapshot; + dst.setPath(dst.getPath() + File.separator + dst.getName()); + dst.setPhysicalSize(size); + return dst; } @Override @@ -157,6 +162,7 @@ public final class LinstorBackupSnapshotCommandWrapper s_logger.info("Backup shrunk " + dstPath + " to actual size " + src.getVolume().getSize()); SnapshotObjectTO snapshot = setCorrectSnapshotSize(dst, dstPath); + s_logger.info(String.format("Actual file size for '%s' is %d", dstPath, snapshot.getPhysicalSize())); return new CopyCmdAnswer(snapshot); } catch (final Exception e) { final String error = String.format("Failed to backup snapshot with id [%s] with a pool %s, due to %s", diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 9b7a376e8f2..6a4d6c7a349 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -407,7 +407,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor { if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS) && !rsc.getFlags().contains(ApiConsts.FLAG_TIE_BREAKER)) { - ApiCallRcList delAnswers = api.resourceDelete(rsc.getName(), localNodeName); + ApiCallRcList delAnswers = api.resourceDelete(rsc.getName(), localNodeName, true); logLinstorAnswers(delAnswers); } } catch (ApiException apiEx) { diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 27904ed441b..4132fbd278a 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -21,11 +21,14 @@ import com.linbit.linstor.api.CloneWaiter; import com.linbit.linstor.api.DevelopersApi; import com.linbit.linstor.api.model.ApiCallRc; import com.linbit.linstor.api.model.ApiCallRcList; +import com.linbit.linstor.api.model.AutoSelectFilter; +import com.linbit.linstor.api.model.LayerType; import com.linbit.linstor.api.model.Properties; import com.linbit.linstor.api.model.ResourceDefinition; import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest; import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted; import com.linbit.linstor.api.model.ResourceDefinitionCreate; +import com.linbit.linstor.api.model.ResourceGroup; import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.Snapshot; @@ -34,6 +37,7 @@ import com.linbit.linstor.api.model.VolumeDefinition; import com.linbit.linstor.api.model.VolumeDefinitionModify; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.inject.Inject; import java.util.Arrays; @@ -43,6 +47,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import com.cloud.agent.api.Answer; import com.cloud.agent.api.storage.ResizeVolumeAnswer; @@ -103,8 +108,11 @@ import org.apache.cloudstack.storage.snapshot.SnapshotObject; import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.volume.VolumeObject; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; +import java.nio.charset.StandardCharsets; + public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver { private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class); @Inject private PrimaryDataStoreDao _storagePoolDao; @@ -393,11 +401,56 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver storagePoolVO.getUserInfo() : "DfltRscGrp"; } + /** + * Returns the layerlist of the resourceGroup with encryption(LUKS) added above STORAGE. + * If the resourceGroup layer list already contains LUKS this layer list will be returned. + * @param api Linstor developers API + * @param resourceGroup Resource group to get the encryption layer list + * @return layer list with LUKS added + */ + public List getEncryptedLayerList(DevelopersApi api, String resourceGroup) { + try { + List rscGrps = api.resourceGroupList( + Collections.singletonList(resourceGroup), Collections.emptyList(), null, null); + + if (CollectionUtils.isEmpty(rscGrps)) { + throw new CloudRuntimeException( + String.format("Resource Group %s not found on Linstor cluster.", resourceGroup)); + } + + final ResourceGroup rscGrp = rscGrps.get(0); + List layers = Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE); + List curLayerStack = rscGrp.getSelectFilter() != null ? + rscGrp.getSelectFilter().getLayerStack() : Collections.emptyList(); + if (CollectionUtils.isNotEmpty(curLayerStack)) { + layers = curLayerStack.stream().map(LayerType::valueOf).collect(Collectors.toList()); + if (!layers.contains(LayerType.LUKS)) { + layers.add(layers.size() - 1, LayerType.LUKS); // lowest layer is STORAGE + } + } + return layers; + } catch (ApiException e) { + throw new CloudRuntimeException( + String.format("Resource Group %s not found on Linstor cluster.", resourceGroup)); + } + } + private String createResourceBase( - String rscName, long sizeInBytes, String volName, String vmName, DevelopersApi api, String rscGrp) { + String rscName, long sizeInBytes, String volName, String vmName, + @Nullable Long passPhraseId, @Nullable byte[] passPhrase, DevelopersApi api, String rscGrp) { ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn(); rscGrpSpawn.setResourceDefinitionName(rscName); rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024); + if (passPhraseId != null) { + AutoSelectFilter asf = new AutoSelectFilter(); + List luksLayers = getEncryptedLayerList(api, rscGrp); + asf.setLayerStack(luksLayers.stream().map(LayerType::toString).collect(Collectors.toList())); + rscGrpSpawn.setSelectFilter(asf); + if (passPhrase != null) { + String utf8Passphrase = new String(passPhrase, StandardCharsets.UTF_8); + rscGrpSpawn.setVolumePassphrases(Collections.singletonList(utf8Passphrase)); + } + } try { @@ -422,7 +475,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid(); String deviceName = createResourceBase( - rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), linstorApi, rscGrp); + rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), vol.getPassphraseId(), vol.getPassphrase(), + linstorApi, rscGrp); try { @@ -463,6 +517,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver s_logger.info("Clone resource definition " + cloneRes + " to " + rscName); ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest(); cloneRequest.setName(rscName); + if (volumeInfo.getPassphraseId() != null) { + List encryptionLayer = getEncryptedLayerList(linstorApi, getRscGrp(storagePoolVO)); + cloneRequest.setLayerList(encryptionLayer); + if (volumeInfo.getPassphrase() != null) { + String utf8Passphrase = new String(volumeInfo.getPassphrase(), StandardCharsets.UTF_8); + cloneRequest.setVolumePassphrases(Collections.singletonList(utf8Passphrase)); + } + } ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone( cloneRes, cloneRequest); @@ -915,6 +977,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver tInfo.getSize(), tInfo.getName(), "", + null, + null, api, getRscGrp(pool)); diff --git a/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java new file mode 100644 index 00000000000..75276739468 --- /dev/null +++ b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java @@ -0,0 +1,87 @@ +// 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. +package org.apache.cloudstack.storage.datastore.driver; + +import com.linbit.linstor.api.ApiException; +import com.linbit.linstor.api.DevelopersApi; +import com.linbit.linstor.api.model.AutoSelectFilter; +import com.linbit.linstor.api.model.LayerType; +import com.linbit.linstor.api.model.ResourceGroup; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LinstorPrimaryDataStoreDriverImplTest { + + private DevelopersApi api; + + @InjectMocks + private LinstorPrimaryDataStoreDriverImpl linstorPrimaryDataStoreDriver; + + @Before + public void setUp() { + api = mock(DevelopersApi.class); + } + + @Test + public void testGetEncryptedLayerList() throws ApiException { + ResourceGroup dfltRscGrp = new ResourceGroup(); + dfltRscGrp.setName("DfltRscGrp"); + + ResourceGroup bCacheRscGrp = new ResourceGroup(); + bCacheRscGrp.setName("BcacheGrp"); + AutoSelectFilter asf = new AutoSelectFilter(); + asf.setLayerStack(Arrays.asList(LayerType.DRBD.name(), LayerType.BCACHE.name(), LayerType.STORAGE.name())); + asf.setStoragePool("nvmePool"); + bCacheRscGrp.setSelectFilter(asf); + + ResourceGroup encryptedGrp = new ResourceGroup(); + encryptedGrp.setName("EncryptedGrp"); + AutoSelectFilter asf2 = new AutoSelectFilter(); + asf2.setLayerStack(Arrays.asList(LayerType.DRBD.name(), LayerType.LUKS.name(), LayerType.STORAGE.name())); + asf2.setStoragePool("ssdPool"); + encryptedGrp.setSelectFilter(asf2); + + when(api.resourceGroupList(Collections.singletonList("DfltRscGrp"), Collections.emptyList(), null, null)) + .thenReturn(Collections.singletonList(dfltRscGrp)); + when(api.resourceGroupList(Collections.singletonList("BcacheGrp"), Collections.emptyList(), null, null)) + .thenReturn(Collections.singletonList(bCacheRscGrp)); + when(api.resourceGroupList(Collections.singletonList("EncryptedGrp"), Collections.emptyList(), null, null)) + .thenReturn(Collections.singletonList(encryptedGrp)); + + List layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "DfltRscGrp"); + Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE), layers); + + layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "BcacheGrp"); + Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.BCACHE, LayerType.LUKS, LayerType.STORAGE), layers); + + layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "EncryptedGrp"); + Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE), layers); + } +} diff --git a/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java new file mode 100644 index 00000000000..55f0c6ebe6d --- /dev/null +++ b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java @@ -0,0 +1,127 @@ +// 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. +package org.apache.cloudstack.storage.datastore.util; + +import com.linbit.linstor.api.ApiException; +import com.linbit.linstor.api.DevelopersApi; +import com.linbit.linstor.api.model.AutoSelectFilter; +import com.linbit.linstor.api.model.Node; +import com.linbit.linstor.api.model.Properties; +import com.linbit.linstor.api.model.ProviderKind; +import com.linbit.linstor.api.model.ResourceGroup; +import com.linbit.linstor.api.model.StoragePool; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LinstorUtilTest { + + private static final String LINSTOR_URL_TEST = "devnull.com:3370"; + private DevelopersApi api; + + private Node mockNode(String name) { + Node nodeMock = new Node(); + nodeMock.setName(name); + + return nodeMock; + } + + private StoragePool mockStoragePool(String name, String node, ProviderKind kind) { + StoragePool sp = new StoragePool(); + sp.setStoragePoolName(name); + sp.setNodeName(node); + sp.setProviderKind(kind); + return sp; + } + + @Before + public void setUp() throws ApiException { + api = mock(DevelopersApi.class); + + when(api.nodeList(Collections.emptyList(), Collections.emptyList(), null, null)) + .thenReturn(Arrays.asList(mockNode("nodeA"), mockNode("nodeB"), mockNode("nodeC"))); + + ResourceGroup csGroup = new ResourceGroup(); + csGroup.setName("cloudstack"); + AutoSelectFilter asf = new AutoSelectFilter(); + asf.setPlaceCount(2); + csGroup.setSelectFilter(asf); + when(api.resourceGroupList(Collections.singletonList("cloudstack"), null, null, null)) + .thenReturn(Collections.singletonList(csGroup)); + + when(api.viewStoragePools(Collections.emptyList(), null, null, null, null, true)) + .thenReturn(Arrays.asList( + mockStoragePool("thinpool", "nodeA", ProviderKind.LVM_THIN), + mockStoragePool("thinpool", "nodeB", ProviderKind.LVM_THIN), + mockStoragePool("thinpool", "nodeC", ProviderKind.LVM_THIN) + )); + +// when(LinstorUtil.getLinstorAPI(LINSTOR_URL_TEST)).thenReturn(api); + } + + @Test + public void testGetLinstorNodeNames() throws ApiException { + List linstorNodes = LinstorUtil.getLinstorNodeNames(api); + Assert.assertEquals(Arrays.asList("nodeA", "nodeB", "nodeC"), linstorNodes); + } + + @Test + public void testGetSnapshotPath() { + { + StoragePool spLVMThin = new StoragePool(); + Properties lvmThinProps = new Properties(); + lvmThinProps.put("StorDriver/StorPoolName", "storage/storage-thin"); + spLVMThin.setProps(lvmThinProps); + spLVMThin.setProviderKind(ProviderKind.LVM_THIN); + String snapPath = LinstorUtil.getSnapshotPath(spLVMThin, "cs-cb32532a-dd8f-47e0-a81c-8a75573d3545", "snap3"); + Assert.assertEquals("/dev/mapper/storage-cs--cb32532a--dd8f--47e0--a81c--8a75573d3545_00000_snap3", snapPath); + } + + { + StoragePool spZFS = new StoragePool(); + Properties zfsProps = new Properties(); + zfsProps.put("StorDriver/StorPoolName", "linstorPool"); + spZFS.setProps(zfsProps); + spZFS.setProviderKind(ProviderKind.ZFS); + + String snapPath = LinstorUtil.getSnapshotPath(spZFS, "cs-cb32532a-dd8f-47e0-a81c-8a75573d3545", "snap2"); + Assert.assertEquals("zfs://linstorPool/cs-cb32532a-dd8f-47e0-a81c-8a75573d3545_00000@snap2", snapPath); + } + } + + @Test + public void testGetRscGroupStoragePools() throws ApiException { + List storagePools = LinstorUtil.getRscGroupStoragePools(api, "cloudstack"); + + List names = storagePools.stream() + .map(sp -> String.format("%s::%s", sp.getNodeName(), sp.getStoragePoolName())) + .collect(Collectors.toList()); + Assert.assertEquals(names, Arrays.asList("nodeA::thinpool", "nodeB::thinpool", "nodeC::thinpool")); + } +} diff --git a/pom.xml b/pom.xml index da0252acbb4..0771a124db2 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,7 @@ 10.1 2.6.6 0.6.0 - 0.5.2 + 0.6.0 0.10.2 3.4.4_1 4.0.1 From 90c960eeed9f7067961cd581064f7ca12459ad78 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 4 Feb 2025 16:00:58 +0100 Subject: [PATCH 08/24] VPC VR: fix ACL between tier and private gateway (#10268) --- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index 3cb782daf7a..3d6d1f6f722 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -542,8 +542,10 @@ class CsIP: (self.dev, guestNetworkCidr, self.address['gateway'], self.dev)]) if self.is_private_gateway(): - self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % + self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % (self.address['network'], self.dev, self.dev)]) + self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT" % + (self.address['network'], self.dev)]) self.fw.append(["filter", "", "-A ACL_INBOUND_%s -j DROP" % self.dev]) self.fw.append(["mangle", "", "-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" % From c5afee210181db2335f059267c7a2b74f91002d4 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:18:40 +0530 Subject: [PATCH 09/24] UI improvements (#9773) * Show Usage Server configuration in a separate pane * UI: Option to attach volume to an instance during create volume * Show service ip in management server details tab * change Schedule Snapshots to Recurring Snapshots * Change the hypervisor order so that kvm, vmware, xenserver show up first * Remove extra space in hypervisor names in config.java * Fix `updateTemplatePermission` when the UI is set to a language other than English (#9766) * Fix updateTemplatePermission UI in non-english language * Improve fix --------- Co-authored-by: Lucas Martins * Autofill vcenter details in add cluster form * UI: condition to display create vm-vol-snapshots to same as create vol-snapshots * Fix alignment on wrapping in global settings tabs * rename Autofill vCenter credentials to Autofill vCenter credentials from Zone * Rename Service Ip to Ip Address in management server response * Change description of kvm.snapshot.enabled to say that it applies to volume snapshots * Return error when kvm vm snapshot is taken withoutsnapshot memory * Minor naming changes and grammar * Fix tooltip for attach volume to instance button * Show Usage Server configuration in a separate pane * UI: Option to attach volume to an instance during create volume * Show service ip in management server details tab * change Schedule Snapshots to Recurring Snapshots * Change the hypervisor order so that kvm, vmware, xenserver show up first * Remove extra space in hypervisor names in config.java * Autofill vcenter details in add cluster form * UI: condition to display create vm-vol-snapshots to same as create vol-snapshots * Fix alignment on wrapping in global settings tabs * rename Autofill vCenter credentials to Autofill vCenter credentials from Zone * Rename Service Ip to Ip Address in management server response * Change description of kvm.snapshot.enabled to say that it applies to volume snapshots * Return error when kvm vm snapshot is taken withoutsnapshot memory * Minor naming changes and grammar * Fix tooltip for attach volume to instance button * Show Usage Server configuration in a separate pane * UI: Option to attach volume to an instance during create volume * Show service ip in management server details tab * change Schedule Snapshots to Recurring Snapshots * Change the hypervisor order so that kvm, vmware, xenserver show up first * Remove extra space in hypervisor names in config.java * Autofill vcenter details in add cluster form * UI: condition to display create vm-vol-snapshots to same as create vol-snapshots * Fix alignment on wrapping in global settings tabs * rename Autofill vCenter credentials to Autofill vCenter credentials from Zone * Rename Service Ip to Ip Address in management server response * Change description of kvm.snapshot.enabled to say that it applies to volume snapshots * Return error when kvm vm snapshot is taken withoutsnapshot memory * Minor naming changes and grammar * Fix tooltip for attach volume to instance button * UI: Option to attach volume to an instance during create volume * UI: condition to display create vm-vol-snapshots to same as create vol-snapshots * moved db changes from 41900to42000 to 42000to42010 * Update group_id in already present usage configuration settings * remove "schedule" from message in create Recurring Snapshots form * Update server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java --------- Co-authored-by: Daan Hoogland Co-authored-by: Lucas Martins <56271185+lucas-a-martins@users.noreply.github.com> Co-authored-by: Lucas Martins Co-authored-by: Boris Stoyanov - a.k.a Bobby Co-authored-by: Andrija Panic <45762285+andrijapanicsb@users.noreply.github.com> --- .../apache/cloudstack/api/ApiConstants.java | 1 - .../response/ManagementServerResponse.java | 12 +-- .../META-INF/db/schema-42000to42010.sql | 8 ++ .../com/cloud/api/query/QueryManagerImpl.java | 2 +- .../java/com/cloud/configuration/Config.java | 6 +- .../network/vpn/Site2SiteVpnManagerImpl.java | 4 +- .../vm/snapshot/VMSnapshotManagerImpl.java | 10 +- ui/public/locales/de_DE.json | 1 - ui/public/locales/el_GR.json | 1 - ui/public/locales/en.json | 6 +- ui/public/locales/ja_JP.json | 1 - ui/public/locales/ko_KR.json | 1 - ui/public/locales/pt_BR.json | 1 - ui/public/locales/zh_CN.json | 1 - ui/src/config/section/compute.js | 7 +- .../config/section/infra/managementServers.js | 4 +- ui/src/config/section/storage.js | 14 +-- ui/src/views/infra/ClusterAdd.vue | 102 +++++++++++------- .../views/setting/ConfigurationHierarchy.vue | 9 +- ui/src/views/storage/CreateVolume.vue | 100 ++++++++++++++++- .../views/storage/RecurringSnapshotVolume.vue | 2 +- 21 files changed, 214 insertions(+), 79 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index cf03f1d2699..efbf9d9d78e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -447,7 +447,6 @@ public class ApiConstants { public static final String SENT = "sent"; public static final String SENT_BYTES = "sentbytes"; public static final String SERIAL = "serial"; - public static final String SERVICE_IP = "serviceip"; public static final String SERVICE_OFFERING_ID = "serviceofferingid"; public static final String SESSIONKEY = "sessionkey"; public static final String SHOW_CAPACITIES = "showcapacities"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java index fc7d3b722ab..4165ea25778 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java @@ -74,9 +74,9 @@ public class ManagementServerResponse extends BaseResponse { @Param(description = "the running OS kernel version for this Management Server") private String kernelVersion; - @SerializedName(ApiConstants.SERVICE_IP) + @SerializedName(ApiConstants.IP_ADDRESS) @Param(description = "the IP Address for this Management Server") - private String serviceIp; + private String ipAddress; @SerializedName(ApiConstants.PEERS) @Param(description = "the Management Server Peers") @@ -122,8 +122,8 @@ public class ManagementServerResponse extends BaseResponse { return lastBoot; } - public String getServiceIp() { - return serviceIp; + public String getIpAddress() { + return ipAddress; } public void setId(String id) { @@ -170,8 +170,8 @@ public class ManagementServerResponse extends BaseResponse { this.kernelVersion = kernelVersion; } - public void setServiceIp(String serviceIp) { - this.serviceIp = serviceIp; + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; } public String getKernelVersion() { diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql index 92e0dbb5b2a..bf13e5eee1a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql @@ -24,6 +24,14 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user', 'api_key_access', 'boolean DE CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" '); CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.account', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" '); +-- Create a new group for Usage Server related configurations +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Usage Server', 'Usage Server related configuration', 9); +UPDATE `cloud`.`configuration_subgroup` set `group_id` = (SELECT `id` FROM `cloud`.`configuration_group` WHERE `name` = 'Usage Server'), `precedence` = 1 WHERE `name`='Usage'; +UPDATE `cloud`.`configuration` SET `group_id` = (SELECT `id` FROM `cloud`.`configuration_group` WHERE `name` = 'Usage Server') where `subgroup_id` = (SELECT `id` FROM `cloud`.`configuration_subgroup` WHERE `name` = 'Usage'); + +-- Update the description to indicate this setting applies only to volume snapshots on running instances +UPDATE `cloud`.`configuration` SET `description`='whether volume snapshot is enabled on running instances on KVM hosts' WHERE `name`='kvm.snapshot.enabled'; + -- Modify index for mshost_peer DELETE FROM `cloud`.`mshost_peer`; CALL `cloud`.`IDEMPOTENT_DROP_FOREIGN_KEY`('cloud.mshost_peer','fk_mshost_peer__owner_mshost'); diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 94a87a0b31c..40249dda17a 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -5413,13 +5413,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q mgmtResponse.setLastServerStart(mgmt.getLastJvmStart()); mgmtResponse.setLastServerStop(mgmt.getLastJvmStop()); mgmtResponse.setLastBoot(mgmt.getLastSystemBoot()); - mgmtResponse.setServiceIp(mgmt.getServiceIP()); if (listPeers) { List peers = mshostPeerJoinDao.listByOwnerMshostId(mgmt.getId()); for (ManagementServerHostPeerJoinVO peer: peers) { mgmtResponse.addPeer(createPeerManagementServerNodeResponse(peer)); } } + mgmtResponse.setIpAddress(mgmt.getServiceIP()); mgmtResponse.setObjectName("managementserver"); return mgmtResponse; } diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index b9de906ba46..8093af6afc1 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -506,7 +506,7 @@ public enum Config { "The time interval in seconds when the management server polls for snapshots to be scheduled.", null), SnapshotDeltaMax("Snapshots", SnapshotManager.class, Integer.class, "snapshot.delta.max", "16", "max delta snapshots between two full snapshots.", null), - KVMSnapshotEnabled("Hidden", SnapshotManager.class, Boolean.class, "kvm.snapshot.enabled", "false", "whether snapshot is enabled for KVM hosts", null), + KVMSnapshotEnabled("Hidden", SnapshotManager.class, Boolean.class, "kvm.snapshot.enabled", "false", "Whether volume snapshot is enabled on running instances on a KVM host", null), // Advanced EventPurgeInterval( @@ -665,8 +665,8 @@ public enum Config { ManagementServer.class, String.class, "hypervisor.list", - HypervisorType.Hyperv + "," + HypervisorType.KVM + "," + HypervisorType.XenServer + "," + HypervisorType.VMware + "," + HypervisorType.BareMetal + "," + - HypervisorType.Ovm + "," + HypervisorType.LXC + "," + HypervisorType.Ovm3, + HypervisorType.KVM + "," + HypervisorType.VMware + "," + HypervisorType.XenServer + "," + HypervisorType.Hyperv + "," + + HypervisorType.BareMetal + "," + HypervisorType.Ovm + "," + HypervisorType.LXC + "," + HypervisorType.Ovm3, "The list of hypervisors that this deployment will use.", "hypervisorList", ConfigKey.Kind.CSV, diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index ed83e396abf..8be97644bf5 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -343,7 +343,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn private void validatePrerequisiteVpnGateway(Site2SiteVpnGateway vpnGateway) { // check if gateway has been defined on the VPC if (_vpnGatewayDao.findByVpcId(vpnGateway.getVpcId()) == null) { - throw new InvalidParameterValueException("we can not create a VPN connection for a VPC that does not have a VPN gateway defined"); + throw new InvalidParameterValueException("We can not create a VPN connection for a VPC that does not have a VPN gateway defined"); } } @@ -590,7 +590,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn private void stopVpnConnection(Long id) throws ResourceUnavailableException { Site2SiteVpnConnectionVO conn = _vpnConnectionDao.acquireInLockTable(id); if (conn == null) { - throw new CloudRuntimeException("Unable to acquire lock for stopping of VPN connection with ID " + id); + throw new CloudRuntimeException("Unable to acquire lock for stopping VPN connection with ID " + id); } try { if (conn.getState() == State.Pending) { diff --git a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java index 8d43875190f..907182edc2a 100644 --- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.storage.snapshot.SnapshotManager; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; @@ -377,9 +378,14 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme //StorageVMSnapshotStrategy - allows volume snapshots without memory; VM has to be in Running state; No limitation of the image format if the storage plugin supports volume snapshots; "kvm.vmstoragesnapshot.enabled" has to be enabled //Other Storage volume plugins could integrate this with their own functionality for group snapshots VMSnapshotStrategy snapshotStrategy = storageStrategyFactory.getVmSnapshotStrategy(userVmVo.getId(), rootVolumePool.getId(), snapshotMemory); - if (snapshotStrategy == null) { - String message = "KVM does not support the type of snapshot requested"; + String message; + if (!SnapshotManager.VmStorageSnapshotKvm.value() && !snapshotMemory) { + message = "Creating a snapshot of a running KVM instance without memory is not supported"; + } else { + message = "KVM does not support the type of snapshot requested"; + } + logger.debug(message); throw new CloudRuntimeException(message); } diff --git a/ui/public/locales/de_DE.json b/ui/public/locales/de_DE.json index d9adc43469e..abf471ae17d 100644 --- a/ui/public/locales/de_DE.json +++ b/ui/public/locales/de_DE.json @@ -1265,7 +1265,6 @@ "label.save.new.rule": "Neue Regel Speichern", "label.schedule": "Zeitplan", "label.scheduled.backups": "geplante Backups", -"label.scheduled.snapshots": "geplante Schnappschüsse", "label.scope": "Geltungsbereich", "label.search": "Suche", "label.secondary.isolated.vlan.type.isolated": "Isoliert", diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json index 754cde89542..7b7f4b29e12 100644 --- a/ui/public/locales/el_GR.json +++ b/ui/public/locales/el_GR.json @@ -1517,7 +1517,6 @@ "label.scale.vm": "Κλίμακα εικονικής μηχανής", "label.schedule": "Πρόγραμμα", "label.scheduled.backups": "Προγραμματισμένα αντίγραφα ασφαλείας", -"label.scheduled.snapshots": "Προγραμματισμένα στιγμιότυπα", "label.scope": "Πεδίο εφαρμογής", "label.search": "Αναζήτηση", "label.secondary.isolated.vlan.type.isolated": "Απομονωμένες", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index a2d82d477aa..29c917b5174 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -54,6 +54,7 @@ "label.action": "Action", "label.action.attach.disk": "Attach disk", "label.action.attach.iso": "Attach ISO", +"label.action.attach.to.instance": "Attach to Instance", "label.action.bulk.delete.egress.firewall.rules": "Bulk delete egress firewall rules", "label.action.bulk.delete.firewall.rules": "Bulk delete firewall rules", "label.action.bulk.delete.ip.v6.firewall.rules": "Bulk remove IPv6 firewall rules", @@ -399,9 +400,11 @@ "label.associatednetworkid": "Associated Network ID", "label.associatednetworkname": "Network name", "label.asyncbackup": "Async backup", +"label.attach.vol.to.instance": "Attach the created Volume to an existing Instance", "label.attaching": "Attaching", "label.authentication.method": "Authentication Method", "label.authentication.sshkey": "System SSH Key", +"label.use.existing.vcenter.credentials.from.zone": "Use existing vCenter credentials from the Zone", "label.autoscale": "AutoScale", "label.autoscalevmgroupname": "AutoScaling Group", "label.author.email": "Author e-mail", @@ -1073,7 +1076,7 @@ "label.hastate": "HA state", "label.headers": "Headers", "label.header.backup.schedule": "You can set up recurring backup schedules by selecting from the available options below and applying your policy preference.", -"label.header.volume.snapshot": "You can set up recurring Snapshot schedules by selecting from the available options below and applying your policy preference.", +"label.header.volume.snapshot": "You can set up recurring Snapshots by selecting from the available options below and applying your policy preference.", "label.header.volume.take.snapshot": "Please confirm that you want to take a Snapshot of this volume.", "label.health.check": "Health check", "label.heapmemoryused": "Heap-memory used", @@ -1999,7 +2002,6 @@ "label.schedule": "Schedule", "label.schedule.add": "Add schedule", "label.scheduled.backups": "Scheduled backups", -"label.scheduled.snapshots": "Scheduled Snapshots", "label.schedules": "Schedules", "label.scope": "Scope", "label.scope.tooltip": "Primary Storage Pool Scope", diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json index f1518286f80..53009781500 100644 --- a/ui/public/locales/ja_JP.json +++ b/ui/public/locales/ja_JP.json @@ -1969,7 +1969,6 @@ "label.scaleup.policy": "スケールアップポリシー", "label.schedule": "スケジュール", "label.scheduled.backups": "スケジュールされたバックアップ", - "label.scheduled.snapshots": "スケジュールされたスナップショット", "label.scope": "スコープ", "label.search": "検索", "label.secondary.isolated.vlan.type.isolated": "隔離", diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index a34cdea767f..153bfd64cef 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -1322,7 +1322,6 @@ "label.scale.vm": "VM \ud655\uc7a5", "label.schedule": "\uc2a4\ucf00\uc904", "label.scheduled.backups": "\uc608\uc57d\ub41c \ubc31\uc5c5", -"label.scheduled.snapshots": "\uc608\uc57d\ub41c \uc2a4\ub0c5\uc0f7", "label.scope": "\ubc94\uc704", "label.search": "\uac80\uc0c9", "label.secondary.isolated.vlan.type.isolated": "isolated", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index a9c67baa32d..af4d5209017 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -1428,7 +1428,6 @@ "label.scale.vm": "Escalar VM", "label.schedule": "Programar", "label.scheduled.backups": "Backups programados", -"label.scheduled.snapshots": "Snapshots programados", "label.scope": "Escopo", "label.search": "Pesquisar", "label.secondary.isolated.vlan.type.isolated": "Isolada", diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json index 91353d2f81a..5337829b53d 100644 --- a/ui/public/locales/zh_CN.json +++ b/ui/public/locales/zh_CN.json @@ -2248,7 +2248,6 @@ "label.schedule": "\u65E5\u7A0B", "label.scheduled.backups": "\u5B9A\u65F6\u5907\u4EFD", - "label.scheduled.snapshots": "\u8BA1\u5212\u5FEB\u7167", "label.scope": "\u8303\u56F4", "label.search": "\u641C\u7D22", diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 5737126d463..5de6d78d263 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -207,9 +207,10 @@ export default { docHelp: 'adminguide/virtual_machines.html#virtual-machine-snapshots', dataView: true, popup: true, - show: (record) => { - return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') || - (['Stopped'].includes(record.state) && !['KVM', 'LXC'].includes(record.hypervisor))) + show: (record, store) => { + return (record.hypervisor !== 'KVM') || + ['Stopped', 'Destroyed'].includes(record.state) || + store.features.kvmsnapshotenabled }, disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' }, component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateSnapshotWizard.vue'))) diff --git a/ui/src/config/section/infra/managementServers.js b/ui/src/config/section/infra/managementServers.js index a8bf1be1a01..bc7b42d9cf0 100644 --- a/ui/src/config/section/infra/managementServers.js +++ b/ui/src/config/section/infra/managementServers.js @@ -26,14 +26,14 @@ export default { permission: ['listManagementServersMetrics'], resourceType: 'ManagementServer', columns: () => { - const fields = ['name', 'state', 'serviceip', 'version', 'osdistribution', 'agentcount'] + const fields = ['name', 'state', 'ipaddress', 'version', 'osdistribution', 'agentcount'] const metricsFields = ['collectiontime', 'availableprocessors', 'cpuload', 'heapmemoryused'] if (store.getters.metrics) { fields.push(...metricsFields) } return fields }, - details: ['collectiontime', 'usageislocal', 'dbislocal', 'lastserverstart', 'lastserverstop', 'lastboottime', 'version', 'loginfo', 'systemtotalcpucycles', 'systemloadaverages', 'systemcycleusage', 'systemmemorytotal', 'systemmemoryfree', 'systemmemoryvirtualsize', 'availableprocessors', 'javadistribution', 'javaversion', 'osdistribution', 'kernelversion', 'agentcount', 'sessions', 'heapmemoryused', 'heapmemorytotal', 'threadsblockedcount', 'threadsdeamoncount', 'threadsnewcount', 'threadsrunnablecount', 'threadsterminatedcount', 'threadstotalcount', 'threadswaitingcount'], + details: ['ipaddress', 'collectiontime', 'usageislocal', 'dbislocal', 'lastserverstart', 'lastserverstop', 'lastboottime', 'version', 'loginfo', 'systemtotalcpucycles', 'systemloadaverages', 'systemcycleusage', 'systemmemorytotal', 'systemmemoryfree', 'systemmemoryvirtualsize', 'availableprocessors', 'javadistribution', 'javaversion', 'osdistribution', 'kernelversion', 'agentcount', 'sessions', 'heapmemoryused', 'heapmemorytotal', 'threadsblockedcount', 'threadsdeamoncount', 'threadsnewcount', 'threadsrunnablecount', 'threadsterminatedcount', 'threadstotalcount', 'threadswaitingcount'], tabs: [ { name: 'details', diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index e0fa72dff8a..3ab890d530a 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -165,9 +165,10 @@ export default { label: 'label.action.take.snapshot', dataView: true, show: (record, store) => { - return record.state === 'Ready' && (record.hypervisor !== 'KVM' || - record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled || - record.hypervisor === 'KVM' && record.vmstate !== 'Running') + return record.state === 'Ready' && + (record.hypervisor !== 'KVM' || + ['Stopped', 'Destroyed'].includes(record.vmstate) || + store.features.kvmsnapshotenabled) }, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/TakeSnapshot.vue'))) @@ -179,9 +180,10 @@ export default { label: 'label.action.recurring.snapshot', dataView: true, show: (record, store) => { - return record.state === 'Ready' && (record.hypervisor !== 'KVM' || - record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled || - record.hypervisor === 'KVM' && record.vmstate !== 'Running') + return record.state === 'Ready' && + (record.hypervisor !== 'KVM' || + (['Stopped', 'Destroyed'].includes(record.vmstate)) || + (store.features.kvmsnapshotenabled)) }, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/storage/RecurringSnapshotVolume.vue'))), diff --git a/ui/src/views/infra/ClusterAdd.vue b/ui/src/views/infra/ClusterAdd.vue index 924722d59bb..e4f5d67a3aa 100644 --- a/ui/src/views/infra/ClusterAdd.vue +++ b/ui/src/views/infra/ClusterAdd.vue @@ -47,7 +47,7 @@
{{ $t('label.hypervisor') }}
-
-
{{ $t('label.vcenterusername') }}
- -
- -
-
{{ $t('label.vcenterpassword') }}
- -
-
{{ $t('label.vcenterdatacenter') }}
+ +
+
{{ $t('label.use.existing.vcenter.credentials.from.zone') }}
+ +
+ +
@@ -176,6 +185,7 @@ export default { username: null, password: null, url: null, + useDefaultVMwareCred: true, host: null, dataCenter: null, ovm3pool: null, @@ -257,6 +267,27 @@ export default { this.loading = false }) }, + fetchVMwareCred () { + this.loading = true + this.clustertype = 'ExternalManaged' + api('listVmwareDcs', { + zoneid: this.zoneId + }).then(response => { + var vmwaredcs = response.listvmwaredcsresponse.VMwareDC + if (vmwaredcs !== null) { + this.host = vmwaredcs[0].vcenter + this.dataCenter = vmwaredcs[0].name + } + }).catch(error => { + this.$notification.error({ + message: `${this.$t('label.error')} ${error.response.status}`, + description: error.response.data.listvmwaredcsresponse.errortext, + duration: 0 + }) + }).finally(() => { + this.loading = false + }) + }, toggleDedicated () { this.dedicatedDomainId = null this.dedicatedAccount = null @@ -270,35 +301,24 @@ export default { } this.$refs.requiredCluster.classList.remove('required-label--visible') + if (this.hypervisor === 'VMware' && this.useDefaultVMwareCred === false) { + if (!this.username) { + this.$refs.requiredUsername.classList.add('required-label--visible') + return + } + if (!this.password) { + this.$refs.requiredPassword.classList.add('required-label--visible') + return + } + this.$refs.requiredUsername.classList.remove('required-label--visible') + this.$refs.requiredPassword.classList.remove('required-label--visible') + } + if (this.hypervisor === 'Ovm3') { this.ovm3pool = 'on' this.ovm3cluster = 'undefined' this.ovm3vip = '' } - - if (this.hypervisor === 'VMware') { - this.clustertype = 'ExternalManaged' - if ((this.host === null || this.host.length === 0) && - (this.dataCenter === null || this.dataCenter.length === 0)) { - api('listVmwareDcs', { - zoneid: this.zoneId - }).then(response => { - var vmwaredcs = response.listvmwaredcsresponse.VMwareDC - if (vmwaredcs !== null) { - this.host = vmwaredcs[0].vcenter - this.dataCenter = vmwaredcs[0].name - } - this.addCluster() - }).catch(error => { - this.$notification.error({ - message: `${this.$t('label.error')} ${error.response.status}`, - description: error.response.data.listvmwaredcsresponse.errortext, - duration: 0 - }) - }) - return - } - } this.addCluster() }, addCluster () { @@ -387,7 +407,7 @@ export default { this.loading = false }) }, - resetAllFields () { + onChangeHypervisor (hypervisor) { this.clustertype = 'CloudManaged' this.username = null this.password = null @@ -397,6 +417,16 @@ export default { this.ovm3pool = null this.ovm3cluster = null this.ovm3vip = null + if (hypervisor === 'VMware') { + this.fetchVMwareCred() + } + }, + onChangeUseDefaultVMwareCred () { + this.useDefaultVMwareCred = !this.useDefaultVMwareCred + if (this.useDefaultVMwareCred) { + this.username = null + this.password = null + } }, returnPlaceholder (field) { this.params.find(i => { diff --git a/ui/src/views/setting/ConfigurationHierarchy.vue b/ui/src/views/setting/ConfigurationHierarchy.vue index 45060efa594..80b464e657c 100644 --- a/ui/src/views/setting/ConfigurationHierarchy.vue +++ b/ui/src/views/setting/ConfigurationHierarchy.vue @@ -28,11 +28,10 @@