This is an automated email from the ASF dual-hosted git repository. dahn pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new b8f8321f0f9 Allow listing of inactive offerings (#8821) b8f8321f0f9 is described below commit b8f8321f0f9eb41c66bcedf91527c13a14431928 Author: Vishesh <vishes...@gmail.com> AuthorDate: Thu Apr 4 17:16:44 2024 +0530 Allow listing of inactive offerings (#8821) --- .../admin/offering/UpdateDiskOfferingCmd.java | 12 ++++ .../admin/offering/UpdateServiceOfferingCmd.java | 15 ++++ .../user/offering/ListDiskOfferingsCmd.java | 21 ++++++ .../user/offering/ListServiceOfferingsCmd.java | 22 +++++- .../api/response/DiskOfferingResponse.java | 12 ++++ .../api/response/ServiceOfferingResponse.java | 12 ++++ .../META-INF/db/views/cloud.disk_offering_view.sql | 2 - .../db/views/cloud.service_offering_view.sql | 5 +- .../java/com/cloud/api/query/QueryManagerImpl.java | 21 ++++-- .../api/query/dao/DiskOfferingJoinDaoImpl.java | 1 + .../api/query/dao/ServiceOfferingJoinDaoImpl.java | 1 + .../cloud/api/query/vo/ServiceOfferingJoinVO.java | 11 +++ .../configuration/ConfigurationManagerImpl.java | 23 ++++-- .../configuration/ConfigurationManagerTest.java | 16 +++-- ui/public/locales/en.json | 19 +++-- ui/src/config/section/offering.js | 81 ++++++++++++++++++---- ui/src/views/AutogenView.vue | 9 ++- 17 files changed, 242 insertions(+), 41 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java index 1d5898ea4a0..424ee1609ad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.admin.offering; import java.util.ArrayList; import java.util.List; +import com.cloud.offering.DiskOffering.State; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -27,6 +28,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -123,6 +125,9 @@ public class UpdateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, description = "the cache mode to use for this disk offering", since = "4.15") private String cacheMode; + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the disk offering") + private String diskOfferingState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -262,6 +267,13 @@ public class UpdateDiskOfferingCmd extends BaseCmd { public Long getIopsWriteRateMaxLength() { return iopsWriteRateMaxLength; } + public State getState() { + State state = EnumUtils.getEnumIgnoreCase(State.class, diskOfferingState); + if (StringUtils.isNotBlank(diskOfferingState) && state == null) { + throw new InvalidParameterValueException("Invalid state value: " + diskOfferingState); + } + return state; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java index d86564a60c6..2f3dba4d9c5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.admin.offering; import java.util.ArrayList; import java.util.List; +import com.cloud.offering.ServiceOffering.State; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -27,6 +28,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -84,6 +86,11 @@ public class UpdateServiceOfferingCmd extends BaseCmd { since = "4.16") private String hostTags; + @Parameter(name = ApiConstants.STATE, + type = CommandType.STRING, + description = "state of the service offering") + private String serviceOfferingState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -172,6 +179,14 @@ public class UpdateServiceOfferingCmd extends BaseCmd { return hostTags; } + public State getState() { + State state = EnumUtils.getEnumIgnoreCase(State.class, serviceOfferingState); + if (StringUtils.isNotBlank(serviceOfferingState) && state == null) { + throw new InvalidParameterValueException("Invalid state value: " + serviceOfferingState); + } + return state; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java index e7284d515a2..6f32b58b733 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java @@ -16,10 +16,13 @@ // under the License. package org.apache.cloudstack.api.command.user.offering; +import com.cloud.offering.DiskOffering.State; import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -29,6 +32,8 @@ import org.apache.cloudstack.api.BaseCmd.CommandType; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.ListResponse; +import static com.cloud.offering.DiskOffering.State.Active; + @APICommand(name = "listDiskOfferings", description = "Lists all available disk offerings.", responseObject = DiskOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListDiskOfferingsCmd extends BaseListProjectAndAccountResourcesCmd { @@ -67,6 +72,11 @@ public class ListDiskOfferingsCmd extends BaseListProjectAndAccountResourcesCmd since = "4.19") private String storageType; + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, + description = "Filter by state of the disk offering. Defaults to 'Active'. If set to 'all' shows both Active & Inactive offerings.", + since = "4.19") + private String diskOfferingState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -95,6 +105,17 @@ public class ListDiskOfferingsCmd extends BaseListProjectAndAccountResourcesCmd return storageType; } + public State getState() { + if (StringUtils.isBlank(diskOfferingState)) { + return Active; + } + State state = EnumUtils.getEnumIgnoreCase(State.class, diskOfferingState); + if (!diskOfferingState.equalsIgnoreCase("all") && state == null) { + throw new IllegalArgumentException("Invalid state value: " + diskOfferingState); + } + return state; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java index a9a699ed3ef..246984aaada 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java @@ -16,18 +16,22 @@ // under the License. package org.apache.cloudstack.api.command.user.offering; +import com.cloud.offering.ServiceOffering.State; import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.BaseCmd.CommandType; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import static com.cloud.offering.ServiceOffering.State.Active; + @APICommand(name = "listServiceOfferings", description = "Lists all available service offerings.", responseObject = ServiceOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesCmd { @@ -95,6 +99,11 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC since = "4.19") private String storageType; + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, + description = "Filter by state of the service offering. Defaults to 'Active'. If set to 'all' shows both Active & Inactive offerings.", + since = "4.19") + private String serviceOfferingState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -141,6 +150,17 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC return storageType; } + public State getState() { + if (StringUtils.isBlank(serviceOfferingState)) { + return Active; + } + State state = EnumUtils.getEnumIgnoreCase(State.class, serviceOfferingState); + if (!serviceOfferingState.equalsIgnoreCase("all") && state == null) { + throw new IllegalArgumentException("Invalid state value: " + serviceOfferingState); + } + return state; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java index b8244aebc60..5b4434fbd8d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -53,6 +53,10 @@ public class DiskOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "the name of the disk offering") private String name; + @SerializedName(ApiConstants.STATE) + @Param(description = "state of the disk offering") + private String state; + @SerializedName(ApiConstants.DISPLAY_TEXT) @Param(description = "an alternate display text of the disk offering.") private String displayText; @@ -226,6 +230,14 @@ public class DiskOfferingResponse extends BaseResponseWithAnnotations { this.name = name; } + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + public String getDisplayText() { return displayText; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index 53767adf17d..c7740c19214 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -37,6 +37,10 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "the name of the service offering") private String name; + @SerializedName("state") + @Param(description = "state of the service offering") + private String state; + @SerializedName("displaytext") @Param(description = "an alternate display text of the service offering.") private String displayText; @@ -249,6 +253,14 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { this.name = name; } + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + public Boolean getIsSystem() { return isSystem; } diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql index 10dd3c2f9de..dffaec575ce 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql @@ -76,7 +76,5 @@ FROM LEFT JOIN `cloud`.`disk_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`offering_id` = `disk_offering`.`id` AND `vsphere_storage_policy`.`name` = 'storagepolicy' -WHERE - `disk_offering`.`state`='Active' GROUP BY `disk_offering`.`id`; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql index da5172e39cc..c894429adf8 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql @@ -24,6 +24,7 @@ SELECT `service_offering`.`id` AS `id`, `service_offering`.`uuid` AS `uuid`, `service_offering`.`name` AS `name`, + `service_offering`.`state` AS `state`, `service_offering`.`display_text` AS `display_text`, `disk_offering`.`provisioning_type` AS `provisioning_type`, `service_offering`.`created` AS `created`, @@ -84,7 +85,7 @@ SELECT FROM `cloud`.`service_offering` INNER JOIN - `cloud`.`disk_offering` ON service_offering.disk_offering_id = disk_offering.id AND `disk_offering`.`state`='Active' + `cloud`.`disk_offering` ON service_offering.disk_offering_id = disk_offering.id LEFT JOIN `cloud`.`service_offering_details` AS `domain_details` ON `domain_details`.`service_offering_id` = `service_offering`.`id` AND `domain_details`.`name`='domainid' LEFT JOIN @@ -108,7 +109,5 @@ FROM LEFT JOIN `cloud`.`service_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`service_offering_id` = `service_offering`.`id` AND `vsphere_storage_policy`.`name` = 'storagepolicy' -WHERE - `service_offering`.`state`='Active' GROUP BY `service_offering`.`id`; 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 db462373eed..d72e4760f57 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -3332,6 +3332,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q Long storagePoolId = cmd.getStoragePoolId(); Boolean encrypt = cmd.getEncrypt(); String storageType = cmd.getStorageType(); + DiskOffering.State state = cmd.getState(); Filter searchFilter = new Filter(DiskOfferingVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal()); searchFilter.addOrderBy(DiskOfferingVO.class, "id", true); @@ -3339,7 +3340,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q diskOfferingSearch.select(null, Func.DISTINCT, diskOfferingSearch.entity().getId()); // select distinct diskOfferingSearch.and("computeOnly", diskOfferingSearch.entity().isComputeOnly(), Op.EQ); - diskOfferingSearch.and("activeState", diskOfferingSearch.entity().getState(), Op.EQ); + + if (state != null) { + diskOfferingSearch.and("state", diskOfferingSearch.entity().getState(), Op.EQ); + } // Keeping this logic consistent with domain specific zones // if a domainId is provided, we just return the disk offering @@ -3452,7 +3456,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q SearchCriteria<DiskOfferingVO> sc = diskOfferingSearch.create(); sc.setParameters("computeOnly", false); - sc.setParameters("activeState", DiskOffering.State.Active); + + if (state != null) { + sc.setParameters("state", state); + } if (keyword != null) { sc.setParameters("keywordDisplayText", "%" + keyword + "%"); @@ -3595,6 +3602,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q Integer cpuSpeed = cmd.getCpuSpeed(); Boolean encryptRoot = cmd.getEncryptRoot(); String storageType = cmd.getStorageType(); + ServiceOffering.State state = cmd.getState(); final Account owner = accountMgr.finalizeOwner(caller, accountName, domainId, projectId); @@ -3629,7 +3637,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q SearchBuilder<ServiceOfferingVO> serviceOfferingSearch = _srvOfferingDao.createSearchBuilder(); serviceOfferingSearch.select(null, Func.DISTINCT, serviceOfferingSearch.entity().getId()); // select distinct - serviceOfferingSearch.and("activeState", serviceOfferingSearch.entity().getState(), Op.EQ); + + if (state != null) { + serviceOfferingSearch.and("state", serviceOfferingSearch.entity().getState(), Op.EQ); + } if (vmId != null) { currentVmOffering = _srvOfferingDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); @@ -3908,7 +3919,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } SearchCriteria<ServiceOfferingVO> sc = serviceOfferingSearch.create(); - sc.setParameters("activeState", ServiceOffering.State.Active); + if (state != null) { + sc.setParameters("state", state); + } if (vmId != null) { if (!currentVmOffering.isDynamic()) { diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java index 5341b3b56d7..14fc56cefc9 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java @@ -105,6 +105,7 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase<DiskOfferingJoinVO, DiskOfferingResponse diskOfferingResponse = new DiskOfferingResponse(); diskOfferingResponse.setId(offering.getUuid()); diskOfferingResponse.setName(offering.getName()); + diskOfferingResponse.setState(offering.getState().toString()); diskOfferingResponse.setDisplayText(offering.getDisplayText()); diskOfferingResponse.setProvisioningType(offering.getProvisioningType().toString()); diskOfferingResponse.setCreated(offering.getCreated()); diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java index 92175568cd9..1c7c2737756 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java @@ -115,6 +115,7 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo ServiceOfferingResponse offeringResponse = new ServiceOfferingResponse(); offeringResponse.setId(offering.getUuid()); offeringResponse.setName(offering.getName()); + offeringResponse.setState(offering.getState().toString()); offeringResponse.setIsSystemOffering(offering.isSystemUse()); offeringResponse.setDefaultUse(offering.isDefaultUse()); offeringResponse.setSystemVmType(offering.getSystemVmType()); diff --git a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java index e0fe3d29433..01811c878fe 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java @@ -20,9 +20,12 @@ import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.Table; +import com.cloud.offering.ServiceOffering.State; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -43,6 +46,10 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit @Column(name = "name") private String name; + @Column(name = "state") + @Enumerated(value = EnumType.STRING) + private State state; + @Column(name = "display_text") private String displayText; @@ -231,6 +238,10 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit return name; } + public State getState() { + return state; + } + public String getDisplayText() { return displayText; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index b47215e255f..9baf4df38ce 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -3456,6 +3456,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List<Long> zoneIds = cmd.getZoneIds(); String storageTags = cmd.getStorageTags(); String hostTags = cmd.getHostTags(); + ServiceOffering.State state = cmd.getState(); if (userId == null) { userId = Long.valueOf(User.UID_SYSTEM); @@ -3540,7 +3541,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException(String.format("Unable to update service offering: %s by id user: %s because it is not root-admin or domain-admin", offeringHandle.getUuid(), user.getUuid())); } - final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null; + final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null || state != null; final boolean detailsUpdateNeeded = !filteredDomainIds.equals(existingDomainIds) || !filteredZoneIds.equals(existingZoneIds); if (!updateNeeded && !detailsUpdateNeeded) { return _serviceOfferingDao.findById(id); @@ -3560,8 +3561,17 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati offering.setSortKey(sortKey); } + if (state != null) { + offering.setState(state); + } + DiskOfferingVO diskOffering = _diskOfferingDao.findById(offeringHandle.getDiskOfferingId()); updateOfferingTagsIfIsNotNull(storageTags, diskOffering); + + if (diskOffering.isComputeOnly() && state != null) { + diskOffering.setState(state == ServiceOffering.State.Active ? DiskOffering.State.Active : DiskOffering.State.Inactive); + } + _diskOfferingDao.update(diskOffering.getId(), diskOffering); updateServiceOfferingHostTagsIfNotNull(hostTags, offering); @@ -3916,6 +3926,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long iopsWriteRateMax = cmd.getIopsWriteRateMax(); Long iopsWriteRateMaxLength = cmd.getIopsWriteRateMaxLength(); String cacheMode = cmd.getCacheMode(); + DiskOffering.State state = cmd.getState(); // Check if diskOffering exists final DiskOffering diskOfferingHandle = _entityMgr.findById(DiskOffering.class, diskOfferingId); @@ -3969,7 +3980,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException(String.format("Unable to update disk offering: %s by id user: %s because it is not root-admin or domain-admin", diskOfferingHandle.getUuid(), user.getUuid())); } - boolean updateNeeded = shouldUpdateDiskOffering(name, displayText, sortKey, displayDiskOffering, tags, cacheMode) || + boolean updateNeeded = shouldUpdateDiskOffering(name, displayText, sortKey, displayDiskOffering, tags, cacheMode, state) || shouldUpdateIopsRateParameters(iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength) || shouldUpdateBytesRateParameters(bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength); @@ -3997,6 +4008,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati diskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase())); } + if (state != null) { + diskOffering.setState(state); + } + if (updateNeeded && !_diskOfferingDao.update(diskOfferingId, diskOffering)) { return null; } @@ -4217,8 +4232,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati * Check if it needs to update any parameter when updateDiskoffering is called * Verify if name or displayText are not blank, tags is not null, sortkey and displayDiskOffering is not null */ - protected boolean shouldUpdateDiskOffering(String name, String displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String cacheMode) { - return !StringUtils.isAllBlank(name, displayText, cacheMode) || tags != null || sortKey != null || displayDiskOffering != null; + protected boolean shouldUpdateDiskOffering(String name, String displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String cacheMode, DiskOffering.State state) { + return !StringUtils.isAllBlank(name, displayText, cacheMode) || tags != null || sortKey != null || displayDiskOffering != null || state != null; } protected boolean shouldUpdateBytesRateParameters(Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength, Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength) { diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java index c2d748ee587..4b9441dd2ea 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java @@ -56,6 +56,7 @@ import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.offering.DiskOffering; import com.cloud.projects.ProjectManager; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.StoragePoolTagVO; @@ -1074,17 +1075,18 @@ public class ConfigurationManagerTest { @Test public void shouldUpdateDiskOfferingTests(){ - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyString())); - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class))); - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class))); - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), nullable(String.class), nullable(String.class))); - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), Mockito.anyBoolean(), nullable(String.class), nullable(String.class))); - Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), nullable(Boolean.class), Mockito.anyString(), Mockito.anyString())); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyString(), Mockito.any(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), Mockito.any(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), Mockito.anyBoolean(), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class))); + Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), nullable(Boolean.class), Mockito.anyString(), Mockito.anyString(), nullable(DiskOffering.State.class))); } @Test public void shouldUpdateDiskOfferingTestFalse(){ - Assert.assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null, null, null, null, null)); + Assert.assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null, null, null, null, null, null)); } @Test diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 245e9efc23f..be9d4e72056 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -71,7 +71,6 @@ "label.action.delete.account": "Delete Account", "label.action.delete.backup.offering": "Delete backup offering", "label.action.delete.cluster": "Delete cluster", -"label.action.delete.disk.offering": "Delete disk offering", "label.action.delete.domain": "Delete domain", "label.action.delete.egress.firewall": "Delete egress firewall rule", "label.action.delete.firewall": "Delete firewall rule", @@ -91,9 +90,7 @@ "label.action.delete.primary.storage": "Delete primary storage", "label.action.delete.secondary.storage": "Delete secondary storage", "label.action.delete.security.group": "Delete security group", -"label.action.delete.service.offering": "Delete service offering", "label.action.delete.snapshot": "Delete Snapshot", -"label.action.delete.system.service.offering": "Delete system service offering", "label.action.delete.template": "Delete Template", "label.action.delete.tungsten.router.table": "Remove Tungsten Fabric route table from Network", "label.action.delete.user": "Delete User", @@ -106,9 +103,12 @@ "label.action.detach.iso": "Detach ISO", "label.action.disable.account": "Disable Account", "label.action.disable.cluster": "Disable cluster", +"label.action.disable.disk.offering": "Disable disk offering", "label.action.disable.physical.network": "Disable physical Network", "label.action.disable.pod": "Disable pod", "label.action.disable.static.nat": "Disable static NAT", +"label.action.disable.service.offering": "Disable service offering", +"label.action.disable.system.service.offering": "Disable system service offering", "label.action.disable.user": "Disable User", "label.action.disable.zone": "Disable zone", "label.action.download.iso": "Download ISO", @@ -124,9 +124,12 @@ "label.action.verify.two.factor.authentication": "Verified Two factor authentication", "label.action.enable.account": "Enable Account", "label.action.enable.cluster": "Enable cluster", +"label.action.enable.disk.offering": "Enable disk offering", "label.action.enable.maintenance.mode": "Enable maintenance mode", "label.action.enable.physical.network": "Enable physical Network", "label.action.enable.pod": "Enable pod", +"label.action.enable.service.offering": "Enable service offering", +"label.action.enable.system.service.offering": "Enable system service offering", "label.action.enable.static.nat": "Enable static NAT", "label.action.enable.user": "Enable User", "label.action.enable.zone": "Enable zone", @@ -1021,6 +1024,7 @@ "label.import.instance": "Import Instance", "label.import.offering": "Import offering", "label.import.role": "Import role", +"label.inactive": "Inactive", "label.in.progress": "in progress", "label.in.progress.for": "in progress for", "label.info": "Info", @@ -2396,7 +2400,6 @@ "message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscale Instance group.", "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?", "message.action.delete.cluster": "Please confirm that you want to delete this cluster.", -"message.action.delete.disk.offering": "Please confirm that you want to delete this disk offering.", "message.action.delete.domain": "Please confirm that you want to delete this domain.", "message.action.delete.external.firewall": "Please confirm that you would like to remove this external firewall. Warning: If you are planning to add back the same external firewall, you must reset usage data on the device.", "message.action.delete.external.load.balancer": "Please confirm that you would like to remove this external load balancer. Warning: If you are planning to add back the same external load balancer, you must reset usage data on the device.", @@ -2415,9 +2418,7 @@ "message.action.delete.pod": "Please confirm that you want to delete this pod.", "message.action.delete.secondary.storage": "Please confirm that you want to delete this secondary storage.", "message.action.delete.security.group": "Please confirm that you want to delete this security group.", -"message.action.delete.service.offering": "Please confirm that you want to delete this service offering.", "message.action.delete.snapshot": "Please confirm that you want to delete this Snapshot.", -"message.action.delete.system.service.offering": "Please confirm that you want to delete this system service offering.", "message.action.delete.template": "Please confirm that you want to delete this Template.", "message.action.delete.tungsten.router.table": "Please confirm that you want to remove Route Table from this Network?", "message.action.delete.volume": "Please confirm that you want to delete this volume. Note: this will not delete any Snapshots of this volume.", @@ -2430,6 +2431,9 @@ "message.action.disable.2FA.user.auth": "Please confirm that you want to disable User two factor authentication.", "message.action.about.mandate.and.disable.2FA.user.auth": "Two factor authentication is mandated for the User, if this is disabled now User will need to setup two factor authentication again during next login. <br><br>Please confirm that you want to disable.", "message.action.disable.cluster": "Please confirm that you want to disable this cluster.", +"message.action.disable.disk.offering": "Please confirm that you want to disable this disk offering.", +"message.action.disable.service.offering": "Please confirm that you want to disable this service offering.", +"message.action.disable.system.service.offering": "Please confirm that you want to disable this system service offering.", "message.action.disable.physical.network": "Please confirm that you want to disable this physical Network.", "message.action.disable.pod": "Please confirm that you want to disable this pod.", "message.action.disable.static.nat": "Please confirm that you want to disable static NAT.", @@ -2437,6 +2441,9 @@ "message.action.download.iso": "Please confirm that you want to download this ISO.", "message.action.download.template": "Please confirm that you want to download this Template.", "message.action.enable.cluster": "Please confirm that you want to enable this cluster.", +"message.action.enable.disk.offering": "Please confirm that you want to enable this disk offering.", +"message.action.enable.service.offering": "Please confirm that you want to enable this service offering.", +"message.action.enable.system.service.offering": "Please confirm that you want to enable this system service offering.", "message.action.enable.physical.network": "Please confirm that you want to enable this physical Network.", "message.action.enable.pod": "Please confirm that you want to enable this pod.", "message.action.enable.zone": "Please confirm that you want to enable this zone.", diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index 27e7d32073a..3f040924242 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -36,7 +36,8 @@ export default { } return params }, - columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], + filters: ['active', 'inactive'], + columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], details: () => { var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot'] if (store.getters.apis.createServiceOffering && @@ -89,15 +90,33 @@ export default { dataView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue'))) + }, + { + api: 'updateServiceOffering', + icon: 'play-circle-outlined', + label: 'label.action.enable.service.offering', + message: 'message.action.enable.service.offering', + dataView: true, + args: ['state'], + mapping: { + state: { + value: (record) => { return 'Active' } + } + }, + groupAction: true, + popup: true, + show: (record) => { return record.state !== 'Active' }, + groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) } }, { api: 'deleteServiceOffering', - icon: 'delete-outlined', - label: 'label.action.delete.service.offering', - message: 'message.action.delete.service.offering', + icon: 'pause-circle-outlined', + label: 'label.action.disable.service.offering', + message: 'message.action.disable.service.offering', docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering', dataView: true, groupAction: true, popup: true, + show: (record) => { return record.state === 'Active' }, groupMap: (selection) => { return selection.map(x => { return { id: x } }) } }] }, @@ -108,7 +127,8 @@ export default { docHelp: 'adminguide/service_offerings.html#system-service-offerings', permission: ['listServiceOfferings', 'listInfrastructure'], params: { issystem: 'true', isrecursive: 'true' }, - columns: ['name', 'systemvmtype', 'cpunumber', 'cpuspeed', 'memory', 'storagetype', 'order'], + columns: ['name', 'state', 'systemvmtype', 'cpunumber', 'cpuspeed', 'memory', 'storagetype', 'order'], + filters: ['active', 'inactive'], details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'storagetags', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'], actions: [{ api: 'createServiceOffering', @@ -127,16 +147,34 @@ export default { params: { issystem: 'true' }, docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering', args: ['name', 'displaytext', 'storagetags', 'hosttags'] + }, { + api: 'updateServiceOffering', + icon: 'play-circle-outlined', + label: 'label.action.enable.system.service.offering', + message: 'message.action.enable.system.service.offering', + dataView: true, + params: { issystem: 'true' }, + args: ['state'], + mapping: { + state: { + value: (record) => { return 'Active' } + } + }, + groupAction: true, + popup: true, + show: (record) => { return record.state !== 'Active' }, + groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) } }, { api: 'deleteServiceOffering', - icon: 'delete-outlined', - label: 'label.action.delete.system.service.offering', - message: 'message.action.delete.system.service.offering', + icon: 'pause-circle-outlined', + label: 'label.action.disable.system.service.offering', + message: 'message.action.disable.system.service.offering', docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering', dataView: true, params: { issystem: 'true' }, groupAction: true, popup: true, + show: (record) => { return record.state === 'Active' }, groupMap: (selection) => { return selection.map(x => { return { id: x } }) } }] }, @@ -153,7 +191,8 @@ export default { } return params }, - columns: ['name', 'displaytext', 'disksize', 'domain', 'zone', 'order'], + columns: ['name', 'displaytext', 'state', 'disksize', 'domain', 'zone', 'order'], + filters: ['active', 'inactive'], details: () => { var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'disksizestrictness', 'iscustomizediops', 'diskIopsReadRate', 'diskIopsWriteRate', 'diskBytesReadRate', 'diskBytesWriteRate', 'miniops', 'maxiops', 'tags', 'domain', 'zone', 'created', 'encrypt'] if (store.getters.apis.createDiskOffering && @@ -202,15 +241,33 @@ export default { dataView: true, popup: true, component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue'))) + }, { + api: 'updateDiskOffering', + icon: 'play-circle-outlined', + label: 'label.action.enable.disk.offering', + message: 'message.action.enable.disk.offering', + dataView: true, + params: { issystem: 'true' }, + args: ['state'], + mapping: { + state: { + value: (record) => { return 'Active' } + } + }, + groupAction: true, + popup: true, + show: (record) => { return record.state !== 'Active' }, + groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) } }, { api: 'deleteDiskOffering', - icon: 'delete-outlined', - label: 'label.action.delete.disk.offering', - message: 'message.action.delete.disk.offering', + icon: 'pause-circle-outlined', + label: 'label.action.disable.disk.offering', + message: 'message.action.disable.disk.offering', docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering', dataView: true, groupAction: true, popup: true, + show: (record) => { return record.state === 'Active' }, groupMap: (selection) => { return selection.map(x => { return { id: x } }) } }] }, diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index 77ee73d700c..be6c0f24cd1 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -51,7 +51,7 @@ <template #suffixIcon><filter-outlined class="ant-select-suffix" /></template> <a-select-option v-if="['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) && - ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool', 'kubernetes'].includes($route.name) || + ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool', 'kubernetes', 'computeoffering', 'systemoffering', 'diskoffering'].includes($route.name) || ['account'].includes($route.name)" key="all" :label="$t('label.all')"> @@ -690,7 +690,7 @@ export default { if (['volume'].includes(routeName)) { return 'user' } - if (['event'].includes(routeName)) { + if (['event', 'computeoffering', 'systemoffering', 'diskoffering'].includes(routeName)) { return 'active' } return 'self' @@ -769,6 +769,9 @@ export default { 'isofilter' in params && this.routeName === 'iso') { params.isofilter = 'all' } + if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) && ['computeoffering', 'systemoffering', 'diskoffering'].includes(this.routeName) && this.$route.params.id) { + params.state = 'all' + } if (Object.keys(this.$route.query).length > 0) { if ('page' in this.$route.query) { this.page = Number(this.$route.query.page) @@ -1757,6 +1760,8 @@ export default { } else { query.clustertype = filter === 'cloud.managed' ? 'CloudManaged' : 'ExternalManaged' } + } else if (['computeoffering', 'systemoffering', 'diskoffering'].includes(this.$route.name)) { + query.state = filter } query.filter = filter query.page = '1'