From 90294a6bbbe10f176d5ac7a7c0ee7a26b032ea53 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 10 Apr 2025 12:19:19 +0530 Subject: [PATCH 01/45] ui,api,server: template categorization based on os Adds new interface for image selection (template/iso) for an instance in UI. Old interface can still be used and it can be configured using UI configuration (config.json) OS categories/Guest OS categories have been improved with ability to create new categories, delete an existing category, and marking a category as featured to allow it to show up in the UI in the image selection interface. New APIs added: - addOsCategory - deleteOsCategory - updateOsCategory APIs updated: - updateOsType - listTemplates - listOsCategories Several improvements in UI especially related to forms - DeloyVM, ReinstallVM, CreateVnfAppliance, AddAutoscaleGroup. DeployVM form can now be opened from template details view with query params. Signed-off-by: Abhishek Kumar --- .../com/cloud/server/ManagementService.java | 9 + .../java/com/cloud/server/ResourceTag.java | 1 + .../com/cloud/storage/GuestOsCategory.java | 2 + .../apache/cloudstack/api/ApiConstants.java | 1 + .../cloudstack/api/ResponseGenerator.java | 4 + .../admin/guest/AddGuestOsCategoryCmd.java | 87 +++ .../admin/guest/DeleteGuestOsCategoryCmd.java | 78 +++ .../admin/guest/UpdateGuestOsCategoryCmd.java | 102 +++ .../command/admin/guest/UpdateGuestOsCmd.java | 28 +- .../user/guest/ListGuestOsCategoriesCmd.java | 53 +- .../user/template/ListTemplatesCmd.java | 11 + .../api/response/GuestOSCategoryResponse.java | 19 +- .../com/cloud/storage/GuestOSCategoryVO.java | 23 + .../storage/dao/GuestOSCategoryDaoImpl.java | 2 +- .../com/cloud/storage/dao/GuestOSDao.java | 8 +- .../com/cloud/storage/dao/GuestOSDaoImpl.java | 21 +- .../com/cloud/storage/dao/VMTemplateDao.java | 2 + .../cloud/storage/dao/VMTemplateDaoImpl.java | 35 + .../META-INF/db/schema-42000to42010.sql | 5 + .../java/com/cloud/api/ApiResponseHelper.java | 18 + .../com/cloud/api/query/QueryManagerImpl.java | 20 +- .../cloud/server/ManagementServerImpl.java | 148 +++- .../cloud/tags/ResourceManagerUtilImpl.java | 2 + ui/public/config.json | 3 + ui/public/locales/en.json | 14 +- ui/src/components/header/UserMenu.vue | 5 + ui/src/components/view/InfoCard.vue | 27 +- ui/src/components/view/ListView.vue | 10 +- .../widgets/BlockRadioGroupSelect.vue | 154 +++++ ui/src/config/section/config.js | 42 +- ui/src/core/lazy_lib/components_use.js | 2 + ui/src/core/lazy_lib/icons_use.js | 2 + ui/src/store/modules/user.js | 3 +- ui/src/utils/plugins.js | 4 +- ui/src/views/AutogenView.vue | 3 + ui/src/views/auth/Login.vue | 4 +- .../views/compute/CreateAutoScaleVmGroup.vue | 434 +++++++----- ui/src/views/compute/DeployVM.vue | 641 +++++++++++------- ui/src/views/compute/DeployVnfAppliance.vue | 560 ++++++++------- ui/src/views/compute/ReinstallVm.vue | 177 +++-- ui/src/views/compute/ResetUserData.vue | 8 +- .../compute/wizard/NetworkConfiguration.vue | 1 + .../compute/wizard/OsBasedImageRadioGroup.vue | 197 ++++++ .../compute/wizard/OsBasedImageSelection.vue | 340 ++++++++++ .../OsBasedImageSelectionSearchView.vue | 203 ++++++ .../wizard/ZoneBlockRadioGroupSelect.vue | 51 ++ ui/src/views/image/TemplateZones.vue | 38 +- 47 files changed, 2808 insertions(+), 794 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java create mode 100644 ui/src/components/widgets/BlockRadioGroupSelect.vue create mode 100644 ui/src/views/compute/wizard/OsBasedImageRadioGroup.vue create mode 100644 ui/src/views/compute/wizard/OsBasedImageSelection.vue create mode 100644 ui/src/views/compute/wizard/OsBasedImageSelectionSearchView.vue create mode 100644 ui/src/views/compute/wizard/ZoneBlockRadioGroupSelect.vue diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index 18f3e901cd93..2627dbf024e4 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -25,12 +25,15 @@ import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; +import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; @@ -168,6 +171,12 @@ public interface ManagementService { */ Pair, Integer> listGuestOSCategoriesByCriteria(ListGuestOsCategoriesCmd cmd); + GuestOsCategory addGuestOsCategory(AddGuestOsCategoryCmd cmd); + + GuestOsCategory updateGuestOsCategory(UpdateGuestOsCategoryCmd cmd); + + boolean deleteGuestOsCategory(DeleteGuestOsCategoryCmd cmd); + /** * Obtains a list of all guest OS mappings * diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index 9bbb5d43eaeb..b3026deceff8 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -66,6 +66,7 @@ public enum ResourceObjectType { LBStickinessPolicy(false, true), LBHealthCheckPolicy(false, true), SnapshotPolicy(true, true), + GuestOsCategory(false, false, true), GuestOs(false, true), NetworkOffering(false, true), VpcOffering(true, false), diff --git a/api/src/main/java/com/cloud/storage/GuestOsCategory.java b/api/src/main/java/com/cloud/storage/GuestOsCategory.java index b46418d5c8f9..8c4639144960 100644 --- a/api/src/main/java/com/cloud/storage/GuestOsCategory.java +++ b/api/src/main/java/com/cloud/storage/GuestOsCategory.java @@ -27,4 +27,6 @@ public interface GuestOsCategory extends Identity, InternalIdentity { void setName(String name); + boolean isFeatured(); + } 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 2c729c3cbc7a..8ad4a8f38124 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -290,6 +290,7 @@ public class ApiConstants { public static final String IS_EXTRACTABLE = "isextractable"; public static final String IS_FEATURED = "isfeatured"; public static final String IS_IMPLICIT = "isimplicit"; + public static final String IS_ISO = "isiso"; public static final String IS_PORTABLE = "isportable"; public static final String IS_PUBLIC = "ispublic"; public static final String IS_PERSISTENT = "ispersistent"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index ea0d946ee417..b67e5197eb20 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -25,6 +25,7 @@ import com.cloud.bgp.ASNumber; import com.cloud.bgp.ASNumberRange; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; @@ -227,6 +228,7 @@ import com.cloud.server.ResourceIcon; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSHypervisor; +import com.cloud.storage.GuestOsCategory; import com.cloud.storage.ImageStore; import com.cloud.storage.Snapshot; import com.cloud.storage.StoragePool; @@ -481,6 +483,8 @@ List createTemplateResponses(ResponseView view, VirtualMachine AutoScaleVmGroupResponse createAutoScaleVmGroupResponse(AutoScaleVmGroup vmGroup); + GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory); + GuestOSResponse createGuestOSResponse(GuestOS os); GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java new file mode 100644 index 000000000000..e342d0bf4811 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.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.api.command.admin.guest; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; + +import com.cloud.storage.GuestOsCategory; +import com.cloud.user.Account; + +@APICommand(name = "addOsCategory", + description = "Adds a new OS category", + responseObject = GuestOSCategoryResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.20.1", + authorized = {RoleType.Admin}) +public class AddGuestOsCategoryCmd extends BaseCmd { + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the OS category", + required = true) + private String name; + + @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, + description = "Whether the category is featured or not") + private Boolean featured; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public boolean isFeatured() { + return Boolean.TRUE.equals(featured); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + GuestOsCategory guestOs = _mgr.addGuestOsCategory(this); + if (guestOs != null) { + GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add new OS category"); + } + + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java new file mode 100644 index 000000000000..b12ea8502584 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java @@ -0,0 +1,78 @@ +// 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.api.command.admin.guest; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import com.cloud.user.Account; + + +@APICommand(name = "deleteOsCategory", + description = "Deletes an OS category", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.20.1", + authorized = {RoleType.Admin}) +public class DeleteGuestOsCategoryCmd extends BaseCmd { + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + required = true, description = "ID of the guest OS") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + boolean result = _mgr.deleteGuestOsCategory(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove guest OS"); + } + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java new file mode 100644 index 000000000000..f3e1a14c1886 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// 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.api.command.admin.guest; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; + +import com.cloud.storage.GuestOsCategory; +import com.cloud.user.Account; + +@APICommand(name = "updateOsCategory", + description = "Updates an OS category", + responseObject = GuestOSCategoryResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.20.1", + authorized = {RoleType.Admin}) +public class UpdateGuestOsCategoryCmd extends BaseCmd { + + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + required = true, description = "ID of the OS Category") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name for the OS category") + private String name; + + @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, + description = "Whether the category is featured or not") + private Boolean featured; + + @Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER, + description = "sort key of the OS category for listing") + private Integer sortKey; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Boolean isFeatured() { + return featured; + } + + public Integer getSortKey() { + return sortKey; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + GuestOsCategory guestOs = _mgr.updateGuestOsCategory(this); + if (guestOs != null) { + GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update guest OS type"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java index c98cd149ef30..2a255553adeb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java @@ -16,8 +16,12 @@ // under the License. package org.apache.cloudstack.api.command.admin.guest; -import org.apache.commons.collections.MapUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -25,18 +29,14 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.GuestOSResponse; -import org.apache.cloudstack.acl.RoleType; +import org.apache.commons.collections.MapUtils; import com.cloud.event.EventTypes; import com.cloud.storage.GuestOS; import com.cloud.user.Account; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - @APICommand(name = "updateGuestOs", description = "Updates the information about Guest OS", responseObject = GuestOSResponse.class, since = "4.4.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class UpdateGuestOsCmd extends BaseAsyncCmd { @@ -50,7 +50,7 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, required = true, description = "UUID of the Guest OS") private Long id; - @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, required = true, description = "Unique display name for Guest OS") + @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, description = "Unique display name for Guest OS") private String osDisplayName; @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs)") @@ -59,6 +59,12 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd { @Parameter(name="forDisplay", type=CommandType.BOOLEAN, description="whether this guest OS is available for end users", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + description = "the ID of the OS category", since = "4.20.1") + private Long osCategoryId; + + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -71,7 +77,7 @@ public String getOsDisplayName() { return osDisplayName; } - public Map getDetails() { + public Map getDetails() { Map detailsMap = new HashMap<>();; if (MapUtils.isNotEmpty(detailsMap)) { Collection servicesCollection = details.values(); @@ -90,6 +96,10 @@ public Boolean getForDisplay() { return display; } + public Long getOsCategoryId() { + return osCategoryId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java index c74514d662ca..0cf71d188d7d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java @@ -26,7 +26,9 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import com.cloud.cpu.CPU; import com.cloud.storage.GuestOsCategory; import com.cloud.utils.Pair; @@ -39,12 +41,37 @@ public class ListGuestOsCategoriesCmd extends BaseListCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "list Os category by id") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List OS category by id") private Long id; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "list os category by name", since = "3.0.1") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List OS category by name", since = "3.0.1") private String name; + @Parameter(name = ApiConstants.IS_FEATURED, + type = CommandType.BOOLEAN, + description = "List available OS categories by featured or not", + since = "4.20.1") + private Boolean featured; + + @Parameter(name = ApiConstants.IS_ISO, + type = CommandType.BOOLEAN, + description = "List OS categories types for which an ISO is available", + since = "4.20.1") + private Boolean iso; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "List available OS categories types for the zone", + since = "4.20.1") + private Long zoneId; + + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + description = "List OS categories types available for given CPU architecture", + since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -57,6 +84,22 @@ public String getName() { return name; } + public Boolean isFeatured() { + return featured; + } + + public Boolean isIso() { + return iso; + } + + public Long getZoneId() { + return zoneId; + } + + public CPU.CPUArch getArch() { + return arch == null ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -67,11 +110,7 @@ public void execute() { ListResponse response = new ListResponse(); List osCatResponses = new ArrayList(); for (GuestOsCategory osCategory : result.first()) { - GuestOSCategoryResponse categoryResponse = new GuestOSCategoryResponse(); - categoryResponse.setId(osCategory.getUuid()); - categoryResponse.setName(osCategory.getName()); - - categoryResponse.setObjectName("oscategory"); + GuestOSCategoryResponse categoryResponse = _responseGenerator.createGuestOSCategoryResponse(osCategory); osCatResponses.add(categoryResponse); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index bff65ef70a92..bfa8c4bbde4f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -20,6 +20,8 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; + +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.commons.collections.CollectionUtils; @@ -111,6 +113,11 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User since = "4.20") private String arch; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType= GuestOSCategoryResponse.class, + description = "the ID of the OS category for the template", + since = "4.20.1") + private Long osCategoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -205,6 +212,10 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Long getOsCategoryId() { + return osCategoryId; + } + @Override public String getCommandName() { return s_name; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java index 7872bf220852..ef3f6eab7dda 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java @@ -26,7 +26,7 @@ import com.cloud.storage.GuestOsCategory; @EntityReference(value = GuestOsCategory.class) -public class GuestOSCategoryResponse extends BaseResponse { +public class GuestOSCategoryResponse extends BaseResponse implements SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the OS category") private String id; @@ -35,6 +35,14 @@ public class GuestOSCategoryResponse extends BaseResponse { @Param(description = "the name of the OS category") private String name; + @SerializedName(ApiConstants.IS_FEATURED) + @Param(description = "Whether the OS category is featured", since = "4.20.1") + private Boolean featured; + + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.20.1") + private ResourceIconResponse resourceIconResponse; + public String getId() { return id; } @@ -50,4 +58,13 @@ public String getName() { public void setName(String name) { this.name = name; } + + public void setFeatured(Boolean featured) { + this.featured = featured; + } + + @Override + public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { + this.resourceIconResponse = resourceIconResponse; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java index 36773e351e36..12a9ef53ea06 100644 --- a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java @@ -39,6 +39,12 @@ public class GuestOSCategoryVO implements GuestOsCategory { @Column(name = "uuid") String uuid = UUID.randomUUID().toString(); + @Column(name = "featured") + boolean featured; + + @Column(name = "sort_key") + private int sortKey; + @Override public long getId() { return id; @@ -62,4 +68,21 @@ public String getUuid() { public void setUuid(String uuid) { this.uuid = uuid; } + + @Override + public boolean isFeatured() { + return featured; + } + + public void setFeatured(Boolean featured) { + this.featured = featured; + } + + public void setSortKey(int key) { + sortKey = key; + } + + public int getSortKey() { + return sortKey; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java index 6fad6c5c47ee..539b50ab735e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java @@ -27,7 +27,7 @@ public class GuestOSCategoryDaoImpl extends GenericDaoBase implements GuestOSCategoryDao { protected GuestOSCategoryDaoImpl() { - + this._count = "select count(distinct id) from guest_os_category WHERE "; } @Override diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java index 13cd398073ad..1a2b098c40a7 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.storage.dao; +import java.util.List; +import java.util.Set; + import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; -import java.util.List; -import java.util.Set; - public interface GuestOSDao extends GenericDao { GuestOSVO findOneByDisplayName(String displayName); @@ -36,4 +36,6 @@ public interface GuestOSDao extends GenericDao { List listByDisplayName(String displayName); Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay); + + List listIdsByCategoryId(final long categoryId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java index efcaa482a676..881be207c1aa 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java @@ -25,19 +25,20 @@ import java.util.List; import java.util.Set; -import com.cloud.storage.GuestOS; -import com.cloud.utils.Pair; -import com.cloud.utils.db.DB; -import com.cloud.utils.db.TransactionLegacy; -import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; @Component public class GuestOSDaoImpl extends GenericDaoBase implements GuestOSDao { @@ -152,4 +153,14 @@ public Pair, Integer> listGuestOSByCriteria(Long startIn return new Pair<>(result.first(), result.second()); } + @Override + public List listIdsByCategoryId(final long categoryId) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.selectFields(sb.entity().getId()); + sb.and("categoryId", sb.entity().getCategoryId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("categoryId", categoryId); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java index 0b40366a8665..8d6d25796932 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java @@ -58,6 +58,8 @@ public interface VMTemplateDao extends GenericDao, StateDao< public List listInZoneByState(long dataCenterId, VirtualMachineTemplate.State... states); + public List listTemplateIsoByArchAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso); + public List listAllActive(); public List listByState(VirtualMachineTemplate.State... states); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java index 12c00a3209ae..627b4b9633fc 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -17,6 +17,7 @@ package com.cloud.storage.dao; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -520,6 +521,40 @@ public List listInZoneByState(long dataCenterId, VirtualMachineTem return listBy(sc); } + @Override + public List listTemplateIsoByArchAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.select(null, Func.DISTINCT, sb.entity().getGuestOSId()); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN); + sb.and("type", sb.entity().getTemplateType(), SearchCriteria.Op.IN); + sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ); + if (isIso != null) { + sb.and("isIso", sb.entity().getFormat(), isIso ? SearchCriteria.Op.EQ : SearchCriteria.Op.NEQ); + } + if (dataCenterId != null) { + SearchBuilder templateZoneSearch = _templateZoneDao.createSearchBuilder(); + templateZoneSearch.and("removed", templateZoneSearch.entity().getRemoved(), SearchCriteria.Op.NULL); + templateZoneSearch.and("zoneId", templateZoneSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + sb.join("templateZoneSearch", templateZoneSearch, templateZoneSearch.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER); + templateZoneSearch.done(); + } + sb.done(); + SearchCriteria sc = sb.create(); + List types = Arrays.asList(TemplateType.USER, TemplateType.BUILTIN, TemplateType.PERHOST); + sc.setParameters("type", types.toArray()); + sc.setParameters("state", VirtualMachineTemplate.State.Active); + if (dataCenterId != null) { + sc.setJoinParameters("templateZoneSearch", "zoneId", dataCenterId); + } + if (arch != null) { + sc.setParameters("arch", arch); + } + if (isIso != null) { + sc.setParameters("isIso", ImageFormat.ISO); + } + return customSearch(sc, null); + } + @Override public List listAllActive() { SearchCriteria sc = ActiveTmpltSearch.create(); 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 e60378a2dc8c..4a4481b2a312 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 @@ -75,3 +75,8 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only Admin - Default', 'va CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'setupUserTwoFactorAuthentication', 'ALLOW'); CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW'); + +-- Add featured column for guest_os_category +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'featured', 'tinyint(1) NOT NULL DEFAULT 0 COMMENT "whether the category is featured or not" AFTER `uuid`'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'sort_key', 'int NOT NULL DEFAULT 0 COMMENT "sort key used for customising sort method" AFTER `featured`'); +UPDATE `cloud`.`guest_os_category` SET `featured` = 1 WHERE `name` NOT IN ('Novel', 'None'); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index a193726eb649..42599bbbb0b1 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -48,6 +48,7 @@ import com.cloud.dc.dao.ASNumberRangeDao; import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.icon.ResourceIconVO; import com.cloud.storage.BucketVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -99,6 +100,7 @@ import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse; @@ -3878,6 +3880,22 @@ public Site2SiteVpnConnectionResponse createSite2SiteVpnConnectionResponse(Site2 return response; } + @Override + public GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory) { + GuestOSCategoryResponse categoryResponse = new GuestOSCategoryResponse(); + categoryResponse.setId(guestOsCategory.getUuid()); + categoryResponse.setName(guestOsCategory.getName()); + categoryResponse.setFeatured(guestOsCategory.isFeatured()); + ResourceIconVO resourceIcon = ApiDBUtils.getResourceIconByResourceUUID(guestOsCategory.getUuid(), + ResourceObjectType.GuestOsCategory); + if (resourceIcon != null) { + ResourceIconResponse iconResponse = ApiDBUtils.newResourceIconResponse(resourceIcon); + categoryResponse.setResourceIconResponse(iconResponse); + } + categoryResponse.setObjectName("oscategory"); + return categoryResponse; + } + @Override public GuestOSResponse createGuestOSResponse(GuestOS guestOS) { GuestOSResponse response = new GuestOSResponse(); 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 729c65cdc637..ca529fc36881 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -301,6 +301,7 @@ import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.BucketDao; import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateDao; @@ -613,6 +614,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject ManagementServerHostPeerJoinDao mshostPeerJoinDao; + @Inject + GuestOSDao guestOSDao; + private SearchCriteria getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) { SearchCriteria sc = _srvOfferingJoinDao.createSearchCriteria(); @@ -4563,7 +4567,7 @@ private Pair, Integer> searchForTemplatesInternal(ListTempl null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), - templateType, isVnf, cmd.getArch()); + templateType, isVnf, cmd.getArch(), cmd.getOsCategoryId()); } private Pair, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, @@ -4572,7 +4576,7 @@ private Pair, Integer> searchForTemplatesInternal(Long temp boolean showDomr, boolean onlyReady, List permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique, String templateType, - Boolean isVnf, CPU.CPUArch arch) { + Boolean isVnf, CPU.CPUArch arch, Long osCategoryId) { // check if zone is configured, if not, just return empty list List hypers = null; @@ -4600,10 +4604,13 @@ private Pair, Integer> searchForTemplatesInternal(Long temp if (storagePoolId != null) { SearchBuilder storagePoolSb = templatePoolDao.createSearchBuilder(); - storagePoolSb.and("pool_id", storagePoolSb.entity().getPoolId(), SearchCriteria.Op.EQ); sb.join("storagePool", storagePoolSb, storagePoolSb.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER); } + if (osCategoryId != null) { + sb.and("guestOsIdIN", sb.entity().getGuestOSId(), Op.IN); + } + SearchCriteria sc = sb.create(); if (imageStoreId != null) { @@ -4618,6 +4625,11 @@ private Pair, Integer> searchForTemplatesInternal(Long temp sc.setJoinParameters("storagePool", "pool_id", storagePoolId); } + if (osCategoryId != null) { + List guestOsIds = guestOSDao.listIdsByCategoryId(osCategoryId); + sc.setParameters("guestOsIdIN", guestOsIds.toArray()); + } + // verify templateId parameter and specially handle it if (templateId != null) { template = _templateDao.findByIdIncludingRemoved(templateId); // Done for backward compatibility - Bug-5221 @@ -5003,7 +5015,7 @@ private Pair, Integer> searchForIsosInternal(ListIsosCmd cm return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, - tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, cmd.getArch()); + tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, cmd.getArch(), null); } @Override diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 9e1f2332cac2..54bf55f62ed6 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -89,12 +89,15 @@ import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin; import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd; import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; +import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.host.AddHostCmd; @@ -640,6 +643,8 @@ import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.agent.AgentManager; @@ -2723,29 +2728,108 @@ public Pair, Integer> listGuestOSByCriteria(final ListGu @Override public Pair, Integer> listGuestOSCategoriesByCriteria(final ListGuestOsCategoriesCmd cmd) { - final Filter searchFilter = new Filter(GuestOSCategoryVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + final Filter searchFilter = new Filter(GuestOSCategoryVO.class, "sortKey", true, + cmd.getStartIndex(), cmd.getPageSizeVal()); + searchFilter.addOrderBy(GuestOSCategoryVO.class, "id", true); final Long id = cmd.getId(); final String name = cmd.getName(); final String keyword = cmd.getKeyword(); + final Boolean featured = cmd.isFeatured(); + final Long zoneId = cmd.getZoneId(); + final CPU.CPUArch arch = cmd.getArch(); + final Boolean isIso = cmd.isIso(); - final SearchCriteria sc = _guestOSCategoryDao.createSearchCriteria(); - + final SearchBuilder sb = _guestOSCategoryDao.createSearchBuilder(); + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("name", sb.entity().getId(), SearchCriteria.Op.LIKE); + sb.and("keyword", sb.entity().getId(), SearchCriteria.Op.LIKE); + sb.and("featured", sb.entity().isFeatured(), SearchCriteria.Op.EQ); + if (ObjectUtils.anyNotNull(zoneId, arch, isIso)) { + final SearchBuilder guestOsSearch = _guestOSDao.createSearchBuilder(); + guestOsSearch.and("ids", guestOsSearch.entity().getId(), SearchCriteria.Op.IN); + sb.join("guestOsSearch", guestOsSearch, guestOsSearch.entity().getCategoryId(), sb.entity().getId(), + JoinType.INNER); + guestOsSearch.done(); + sb.groupBy(sb.entity().getId()); + } + sb.done(); + SearchCriteria sc = sb.create(); if (id != null) { - sc.addAnd("id", SearchCriteria.Op.EQ, id); + sc.setParameters("id", id); } - if (name != null) { - sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + name + "%"); + sc.setParameters("name", "%" + name + "%"); } - if (keyword != null) { - sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + sc.setParameters("name", "%" + keyword + "%"); + } + if (featured != null) { + sc.setParameters("featured", featured); + } + if (ObjectUtils.anyNotNull(zoneId, arch, isIso)) { + List guestOsIds = templateDao.listTemplateIsoByArchAndZone(zoneId, arch, isIso); + if (CollectionUtils.isEmpty(guestOsIds)) { + return new Pair<>(Collections.emptyList(), 0); + } + sc.setJoinParameters("guestOsSearch", "ids", guestOsIds.toArray()); } - final Pair, Integer> result = _guestOSCategoryDao.searchAndCount(sc, searchFilter); return new Pair<>(result.first(), result.second()); } + @Override + public GuestOsCategory addGuestOsCategory(AddGuestOsCategoryCmd cmd) { + final String name = cmd.getName(); + final boolean featured = cmd.isFeatured(); + final GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO(); + guestOSCategory.setName(name); + guestOSCategory.setFeatured(featured); + return _guestOSCategoryDao.persist(guestOSCategory); + } + + @Override + public GuestOsCategory updateGuestOsCategory(UpdateGuestOsCategoryCmd cmd) { + final long id = cmd.getId(); + final String name = cmd.getName(); + final Boolean featured = cmd.isFeatured(); + Integer sortKey = cmd.getSortKey(); + final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(id); + if (guestOSCategory == null) { + throw new InvalidParameterValueException("Invalid OS category ID specified"); + } + if (ObjectUtils.allNull(name, featured, sortKey)) { + return guestOSCategory; + } + if (StringUtils.isNotBlank(name)) { + guestOSCategory.setName(name); + } + if (featured != null) { + guestOSCategory.setFeatured(featured); + } + if (sortKey != null) { + guestOSCategory.setSortKey(sortKey); + } + if (!_guestOSCategoryDao.update(id, guestOSCategory)) { + return null; + } + return guestOSCategory; + } + + @Override + public boolean deleteGuestOsCategory(DeleteGuestOsCategoryCmd cmd) { + final long id = cmd.getId(); + final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(id); + if (guestOSCategory == null) { + throw new InvalidParameterValueException("Invalid OS category ID specified"); + } + List guestOses = _guestOSDao.listIdsByCategoryId(id); + if (!guestOses.isEmpty()) { + throw new InvalidParameterValueException(String.format( + "Unable to delete the OS category. %d guest OS exist for it.", guestOses.size())); + } + return _guestOSCategoryDao.remove(id); + } + @Override public Pair, Integer> listGuestOSMappingByCriteria(final ListGuestOsMappingCmd cmd) { final String guestOsId = "guestOsId"; @@ -2963,6 +3047,10 @@ public GuestOS getAddedGuestOs(final Long guestOsId) { public GuestOS updateGuestOs(final UpdateGuestOsCmd cmd) { final Long id = cmd.getId(); final String displayName = cmd.getOsDisplayName(); + final Long osCategoryId = cmd.getOsCategoryId(); + final Boolean display = cmd.getForDisplay(); + final Map details = cmd.getDetails(); + boolean updateNeeded = false; //check if guest OS exists final GuestOS guestOsHandle = ApiDBUtils.findGuestOSById(id); @@ -2970,27 +3058,46 @@ public GuestOS updateGuestOs(final UpdateGuestOsCmd cmd) { throw new InvalidParameterValueException("Guest OS not found. Please specify a valid ID for the Guest OS"); } - if (!guestOsHandle.getIsUserDefined()) { + //Check if update is needed + if (StringUtils.isNotBlank(displayName) && !displayName.equals(guestOsHandle.getDisplayName())) { + //Check if another Guest OS by same name exists + final GuestOS duplicate = ApiDBUtils.findGuestOSByDisplayName(displayName); + if (duplicate != null) { + throw new InvalidParameterValueException("The specified Guest OS name : " + displayName + " already exists. Please specify a unique guest OS name"); + } + updateNeeded = true; + } + + if (osCategoryId != null) { + if (_guestOSCategoryDao.findById(osCategoryId) == null) { + throw new InvalidParameterValueException("Invalid OS category ID specified"); + } + updateNeeded = true; + } + + if (!guestOsHandle.getIsUserDefined() && (StringUtils.isNotBlank(displayName) || MapUtils.isNotEmpty(details) + || display != null)) { throw new InvalidParameterValueException("Unable to modify system defined guest OS"); } - persistGuestOsDetails(cmd.getDetails(), id); + if (MapUtils.isNotEmpty(details)) { + persistGuestOsDetails(details, id); + } - //Check if update is needed - if (displayName.equals(guestOsHandle.getDisplayName())) { + if (!updateNeeded) { return guestOsHandle; } - //Check if another Guest OS by same name exists - final GuestOS duplicate = ApiDBUtils.findGuestOSByDisplayName(displayName); - if (duplicate != null) { - throw new InvalidParameterValueException("The specified Guest OS name : " + displayName + " already exists. Please specify a unique guest OS name"); - } final GuestOSVO guestOs = _guestOSDao.createForUpdate(id); - guestOs.setDisplayName(displayName); + if (StringUtils.isNotBlank(displayName)) { + guestOs.setDisplayName(displayName); + } if (cmd.getForDisplay() != null) { guestOs.setDisplay(cmd.getForDisplay()); } + if (osCategoryId != null) { + guestOs.setCategoryId(osCategoryId); + } if (_guestOSDao.update(id, guestOs)) { return _guestOSDao.findById(id); } else { @@ -3691,6 +3798,9 @@ public List> getCommands() { cmdList.add(ListPortForwardingRulesCmd.class); cmdList.add(UpdatePortForwardingRuleCmd.class); cmdList.add(ListGuestOsCategoriesCmd.class); + cmdList.add(AddGuestOsCategoryCmd.class); + cmdList.add(UpdateGuestOsCategoryCmd.class); + cmdList.add(DeleteGuestOsCategoryCmd.class); cmdList.add(ListGuestOsCmd.class); cmdList.add(ListGuestOsMappingCmd.class); cmdList.add(AddGuestOsCmd.class); diff --git a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java index c02f41368634..e6d0b737bbe9 100644 --- a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java +++ b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java @@ -58,6 +58,7 @@ import com.cloud.server.ResourceTag; import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.GuestOsCategory; import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotVO; import com.cloud.storage.VMTemplateVO; @@ -117,6 +118,7 @@ public class ResourceManagerUtilImpl implements ResourceManagerUtil { s_typeMap.put(ResourceTag.ResourceObjectType.NetworkOffering, NetworkOfferingVO.class); s_typeMap.put(ResourceTag.ResourceObjectType.VpcOffering, VpcOfferingVO.class); s_typeMap.put(ResourceTag.ResourceObjectType.Domain, DomainVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.GuestOsCategory, GuestOsCategory.class); } @Inject diff --git a/ui/public/config.json b/ui/public/config.json index 38d1fd9bbe71..446256b5b189 100644 --- a/ui/public/config.json +++ b/ui/public/config.json @@ -97,5 +97,8 @@ "basicZoneEnabled": true, "multipleServer": false, "allowSettingTheme": true, + "imageSelectionInterface": "modern", + "showUserCategoryForModernImageSelection": true, + "showAllCategoryForModernImageSelection": false, "docHelpMappings": {} } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4db07fec4435..a3a473d39485 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -86,6 +86,7 @@ "label.action.delete.firewall": "Delete firewall rule", "label.action.delete.interface.static.route": "Remove Tungsten Fabric interface static route", "label.action.delete.guest.os": "Delete guest os", +"label.action.delete.guest.os.category": "Delete guest os category", "label.action.delete.guest.os.hypervisor.mapping": "Delete guest os hypervisor mapping", "label.action.delete.ip.range": "Delete IP range", "label.action.delete.iso": "Delete ISO", @@ -258,6 +259,7 @@ "label.add.firewallrule": "Add Firewall Rule", "label.add.guest.network": "Add guest Network", "label.add.guest.os": "Add guest os", +"label.add.guest.os.category": "Add guest os category", "label.add.guest.os.hypervisor.mapping": "Add guest os hypervisor mapping", "label.add.host": "Add host", "label.add.ingress.rule": "Add ingress rule", @@ -1049,6 +1051,7 @@ "label.guest.netmask": "Guest netmask", "label.guest.networks": "Guest Networks", "label.guest.os": "Guest OS", +"label.guest.os.categories": "Guest OS Categories", "label.guest.os.hypervisor.mappings": "Guest OS mappings", "label.guest.start.ip": "Guest start IP", "label.guest.traffic": "Guest traffic", @@ -1123,6 +1126,8 @@ "label.ikelifetime": "IKE lifetime (second)", "label.ikepolicy": "IKE policy", "label.ikeversion": "IKE version", +"label.image": "Image", +"label.image.type": "Image type", "label.images": "Images", "label.imagestoreid": "Secondary Storage", "label.import.backup.offering": "Import backup offering", @@ -1606,7 +1611,6 @@ "label.offeringtype": "Compute offering type", "label.ok": "OK", "label.only.end.date.and.time": "Only end date and time", -"label.only.start.date.and.time": "Only start date and time", "label.open.documentation": "Open documentation", "label.open.url": "Open URL in browser", "label.opendaylight": "OpenDaylight", @@ -1621,6 +1625,7 @@ "label.operator.equal": "Equals to", "label.optional": "Optional", "label.order": "Order", +"label.os": "Operating System", "label.oscategoryid": "OS category", "label.oscategoryname": "OS category name", "label.osname": "OS name", @@ -2082,6 +2087,8 @@ "label.sharedrouteripv6": "IPv6 address for the VR in this shared Network.", "label.sharewith": "Share with", "label.showing": "Showing", +"label.show.featured.only": "Show featured only", +"label.show.public.only": "Show public only", "label.show.usage.records": "Show usage records", "label.shrinkok": "Shrink OK", "label.shutdown": "Shutdown", @@ -2263,7 +2270,8 @@ "label.tariffvalue": "Tariff value", "label.tcp": "TCP", "label.tcp.proxy": "TCP proxy", -"label.template": "Select a template", +"label.template": "Template", +"label.template.select": "Select a template", "label.templatetag": "Tag", "label.template.select.existing": "Select an existing template", "label.template.temporary.import": "Use a temporary template for import", @@ -2647,6 +2655,7 @@ "message.action.delete.ingress.rule": "Please confirm that you want to delete this ingress rule.", "message.action.delete.ipv4.subnet": "Please confirm that you want to delete this IPv4 subnet.", "message.action.delete.guest.os": "Please confirm that you want to delete this guest os. System defined entry cannot be deleted.", +"message.action.delete.guest.os.category": "Please confirm that you want to delete this guest os category.", "message.action.delete.guest.os.hypervisor.mapping": "Please confirm that you want to delete this guest os hypervisor mapping. System defined entry cannot be deleted.", "message.action.delete.instance.group": "Please confirm that you want to delete the Instance group.", "message.action.delete.interface.static.route": "Please confirm that you want to remove this interface Static Route?", @@ -3248,6 +3257,7 @@ "message.installwizard.tooltip.tungsten.provider.name": "Tungsten provider name is required", "message.installwizard.tooltip.tungsten.provider.port": "Tungsten provider port is required", "message.installwizard.tooltip.tungsten.provider.vrouterport": "Tungsten provider vrouter port is required", +"message.instance.architecture": "Please select Instance architecture", "message.instances.managed": "Instances controlled by CloudStack.", "message.instances.unmanaged": "Instances not controlled by CloudStack.", "message.instances.migrate.vmware": "Instances that can be migrated from VMware.", diff --git a/ui/src/components/header/UserMenu.vue b/ui/src/components/header/UserMenu.vue index c1aa9d7baaa1..32597dffdfbd 100644 --- a/ui/src/components/header/UserMenu.vue +++ b/ui/src/components/header/UserMenu.vue @@ -139,6 +139,10 @@ export default { }, fetchResourceIcon (id) { return new Promise((resolve, reject) => { + if (this.$store.getters.avatar) { + this.image = this.$store.getters.avatar + resolve(this.image) + } api('listUsers', { id: id, showicon: true @@ -146,6 +150,7 @@ export default { const response = json.listusersresponse.user || [] if (response?.[0]) { this.image = response[0]?.icon?.base64image || '' + this.$store.commit('SET_AVATAR', this.image) resolve(this.image) } }).catch(error => { diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index 65c4d3ac0f5e..5f6755c79796 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -34,7 +34,7 @@ - + - + @@ -463,6 +463,9 @@ + + + +