diff --git a/api/src/main/java/com/cloud/configuration/ConfigurationService.java b/api/src/main/java/com/cloud/configuration/ConfigurationService.java index 438283136d2c..015d1ebd5ed5 100644 --- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java +++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java @@ -35,11 +35,14 @@ import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.IsAccountAllowedToCreateOfferingsWithTagsCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; @@ -72,6 +75,7 @@ import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; +import com.cloud.offering.ServiceOfferingCategory; import com.cloud.user.Account; import com.cloud.utils.Pair; @@ -127,6 +131,30 @@ public interface ConfigurationService { */ List getServiceOfferingZones(Long serviceOfferingId); + /** + * Creates a service offering category + * + * @param cmd - the command specifying name and sort key + * @return the newly created service offering category + */ + ServiceOfferingCategory createServiceOfferingCategory(CreateServiceOfferingCategoryCmd cmd); + + /** + * Deletes a service offering category + * + * @param cmd - the command specifying category id + * @return true if successful, false otherwise + */ + boolean deleteServiceOfferingCategory(DeleteServiceOfferingCategoryCmd cmd); + + /** + * Updates a service offering category + * + * @param cmd - the command specifying category id, name, and/or sort key + * @return updated service offering category + */ + ServiceOfferingCategory updateServiceOfferingCategory(UpdateServiceOfferingCategoryCmd cmd); + /** * Updates a disk offering * diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java index 532123e4373a..5c19efd9df7a 100644 --- a/api/src/main/java/com/cloud/offering/ServiceOffering.java +++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java @@ -146,4 +146,6 @@ enum StorageType { Long getVgpuProfileId(); Integer getGpuCount(); + + long getCategoryId(); } diff --git a/api/src/main/java/com/cloud/offering/ServiceOfferingCategory.java b/api/src/main/java/com/cloud/offering/ServiceOfferingCategory.java new file mode 100644 index 000000000000..311623838276 --- /dev/null +++ b/api/src/main/java/com/cloud/offering/ServiceOfferingCategory.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.offering; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ServiceOfferingCategory extends Identity, InternalIdentity { + + String getName(); + + void setName(String name); + + int getSortKey(); + + void setSortKey(int sortKey); +} 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 896031806d53..cb54cdb36b78 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -530,6 +530,8 @@ public class ApiConstants { public static final String SENT_BYTES = "sentbytes"; public static final String SERIAL = "serial"; public static final String SERVICE_IP = "serviceip"; + public static final String SERVICE_OFFERING_CATEGORY_ID = "categoryid"; + public static final String SERVICE_OFFERING_CATEGORY_NAME = "categoryname"; public static final String SERVICE_OFFERING_ID = "serviceofferingid"; public static final String SERVICE_OFFERING_NAME = "serviceofferingname"; public static final String SESSIONKEY = "sessionkey"; 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 8e92e877f5ca..acc8dca1618f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -112,6 +112,7 @@ import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.ServiceResponse; import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; @@ -220,6 +221,7 @@ import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; +import com.cloud.offering.ServiceOfferingCategory; import com.cloud.org.Cluster; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount; @@ -267,6 +269,8 @@ public interface ResponseGenerator { ServiceOfferingResponse createServiceOfferingResponse(ServiceOffering offering); + ServiceOfferingCategoryResponse createServiceOfferingCategoryResponse(ServiceOfferingCategory category); + ConfigurationResponse createConfigurationResponse(Configuration cfg); ConfigurationGroupResponse createConfigurationGroupResponse(ConfigurationGroup cfgGroup); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCategoryCmd.java new file mode 100644 index 000000000000..3e28bffcbfdf --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCategoryCmd.java @@ -0,0 +1,85 @@ +// 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.offering; + +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.ServiceOfferingCategoryResponse; + +import com.cloud.offering.ServiceOfferingCategory; +import com.cloud.user.Account; + +@APICommand(name = "createServiceOfferingCategory", + description = "Creates a service offering category.", + responseObject = ServiceOfferingCategoryResponse.class, + since = "4.23.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class CreateServiceOfferingCategoryCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the name of the service offering category") + private String name; + + @Parameter(name = ApiConstants.SORT_KEY, + type = CommandType.INTEGER, + description = "sort key of the service offering category, default is 0") + private Integer sortKey; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public Integer getSortKey() { + return sortKey; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ServiceOfferingCategory result = _configService.createServiceOfferingCategory(this); + if (result != null) { + ServiceOfferingCategoryResponse response = _responseGenerator.createServiceOfferingCategoryResponse(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create service offering category"); + } + } + + @Override + public long getEntityOwnerId() { + return Account.Type.ADMIN.ordinal(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 4363d6861ba1..6580283757e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.VgpuProfileResponse; import org.apache.cloudstack.api.response.VsphereStoragePoliciesResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -288,6 +289,14 @@ public class CreateServiceOfferingCmd extends BaseCmd { since = "4.21.0") private Map externalDetails; + @Parameter(name = ApiConstants.SERVICE_OFFERING_CATEGORY_ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse.class, + required = false, + description = "the ID of the service offering category to associate with this offering", + since = "4.23") + private Long categoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -560,6 +569,10 @@ public Boolean getGpuDisplay() { return Boolean.TRUE.equals(gpuDisplay); } + public Long getCategoryId() { + return categoryId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/DeleteServiceOfferingCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/DeleteServiceOfferingCategoryCmd.java new file mode 100644 index 000000000000..3357351872fc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/DeleteServiceOfferingCategoryCmd.java @@ -0,0 +1,76 @@ +// 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.offering; + +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.ServiceOfferingCategoryResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import com.cloud.user.Account; + +@APICommand(name = "deleteServiceOfferingCategory", + description = "Deletes a service offering category.", + responseObject = SuccessResponse.class, + since = "4.23.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class DeleteServiceOfferingCategoryCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse.class, + required = true, + description = "the ID of the service offering category") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + boolean result = _configService.deleteServiceOfferingCategory(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete service offering category"); + } + } + + @Override + public long getEntityOwnerId() { + return Account.Type.ADMIN.ordinal(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/ListServiceOfferingCategoriesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/ListServiceOfferingCategoriesCmd.java new file mode 100644 index 000000000000..fd94551dc2f4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/ListServiceOfferingCategoriesCmd.java @@ -0,0 +1,71 @@ +// 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.offering; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; + +@APICommand(name = "listServiceOfferingCategories", + description = "Lists service offering categories.", + responseObject = ServiceOfferingCategoryResponse.class, + since = "4.23.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class ListServiceOfferingCategoriesCmd extends BaseListCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse.class, + description = "ID of the service offering category") + private Long id; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "name of the service offering category") + private String name; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = _queryService.listServiceOfferingCategories(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCategoryCmd.java new file mode 100644 index 000000000000..1bb6b31ed7a0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCategoryCmd.java @@ -0,0 +1,95 @@ +// 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.offering; + +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.ServiceOfferingCategoryResponse; + +import com.cloud.offering.ServiceOfferingCategory; +import com.cloud.user.Account; + +@APICommand(name = "updateServiceOfferingCategory", + description = "Updates a service offering category", + responseObject = ServiceOfferingCategoryResponse.class, + since = "4.23.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class UpdateServiceOfferingCategoryCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse.class, + required = true, + description = "the ID of the service offering category") + private Long id; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "the name of the service offering category") + private String name; + + @Parameter(name = ApiConstants.SORT_KEY, + type = CommandType.INTEGER, + description = "sort key of the service offering category") + private Integer sortKey; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getSortKey() { + return sortKey; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + ServiceOfferingCategory result = _configService.updateServiceOfferingCategory(this); + if (result != null) { + ServiceOfferingCategoryResponse response = _responseGenerator.createServiceOfferingCategoryResponse(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update service offering category"); + } + } +} 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 3a6d6639a5b4..db353521a146 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 @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; @@ -107,6 +108,14 @@ public class UpdateServiceOfferingCmd extends BaseCmd implements DomainAndZoneId since = "4.22.0") protected Boolean cleanupExternalDetails; + @Parameter(name = ApiConstants.SERVICE_OFFERING_CATEGORY_ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse.class, + required = false, + description = "the ID of the service offering category to associate", + since = "4.23") + private Long categoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -163,6 +172,8 @@ public boolean isCleanupExternalDetails() { return Boolean.TRUE.equals(cleanupExternalDetails); } + public Long getCategoryId() { return categoryId; } + ///////////////////////////////////////////////////// /////////////// 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 5c5c8776bce3..a98f7c524bd9 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 @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserVmResponse; @@ -124,6 +125,13 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC since = "4.21.0") private Boolean gpuEnabled; + @Parameter(name = ApiConstants.SERVICE_OFFERING_CATEGORY_ID, + type = CommandType.UUID, + entityType = ServiceOfferingCategoryResponse .class, + description = "the ID of the service offering category", + since = "4.23.0") + private Long categoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -193,6 +201,10 @@ public Boolean getGpuEnabled() { return gpuEnabled; } + public Long getCategoryId() { + return categoryId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingCategoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingCategoryResponse.java new file mode 100644 index 000000000000..e198aa9163b8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingCategoryResponse.java @@ -0,0 +1,65 @@ +// 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.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.offering.ServiceOfferingCategory; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ServiceOfferingCategory.class) +public class ServiceOfferingCategoryResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the service offering category") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the service offering category") + private String name; + + @SerializedName(ApiConstants.SORT_KEY) + @Param(description = "sort key of the service offering category") + private Integer sortKey; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getSortKey() { + return sortKey; + } + + public void setSortKey(Integer sortKey) { + this.sortKey = sortKey; + } +} 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 99c50829f048..30fbd1809345 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 @@ -286,6 +286,14 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "Action to be taken once lease is over", since = "4.21.0") private String leaseExpiryAction; + @SerializedName("categoryid") + @Param(description = "the ID of the service offering category", since = "4.23") + private String categoryId; + + @SerializedName("category") + @Param(description = "the name of the service offering category", since = "4.23") + private String categoryName; + public ServiceOfferingResponse() { } @@ -707,4 +715,9 @@ public void setGpuDisplay(Boolean gpuDisplay) { public void setPurgeResources(Boolean purgeResources) { this.purgeResources = purgeResources; } + + public String getCategoryId() { return categoryId; } + public void setCategoryId(String categoryId) { this.categoryId = categoryId; } + public String getCategoryName() { return categoryName; } + public void setCategoryName(String categoryName) { this.categoryName = categoryName; } } diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 3c9110595a07..6f35b59ef1df 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -36,6 +36,7 @@ import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd; import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd; +import org.apache.cloudstack.api.command.admin.offering.ListServiceOfferingCategoriesCmd; import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; @@ -87,6 +88,7 @@ import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.SnapshotResponse; import org.apache.cloudstack.api.response.StorageAccessGroupResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; @@ -185,6 +187,8 @@ public interface QueryService { ListResponse searchForServiceOfferings(ListServiceOfferingsCmd cmd); + ListResponse listServiceOfferingCategories(ListServiceOfferingCategoriesCmd cmd); + ListResponse listDataCenters(ListZonesCmd cmd); ListResponse listTemplates(ListTemplatesCmd cmd); diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingCategoryVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingCategoryVO.java new file mode 100644 index 000000000000..82f26e6122be --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingCategoryVO.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.service; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import com.cloud.offering.ServiceOfferingCategory; + +@Entity +@Table(name = "service_offering_category") +public class ServiceOfferingCategoryVO implements ServiceOfferingCategory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "name") + private String name; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "sort_key") + private int sortKey; + + public ServiceOfferingCategoryVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public ServiceOfferingCategoryVO(String name) { + this.name = name; + this.uuid = UUID.randomUUID().toString(); + } + + public ServiceOfferingCategoryVO(String name, int sortKey) { + this.name = name; + this.sortKey = sortKey; + this.uuid = UUID.randomUUID().toString(); + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public int getSortKey() { + return sortKey; + } + + @Override + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } +} diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index cfe8049f5b2c..94c4f05f51f7 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -133,6 +133,9 @@ public class ServiceOfferingVO implements ServiceOffering { @Column(name = "gpu_display") private Boolean gpuDisplay; + @Column(name = "category_id") + private long categoryId = 1L; // Default category + // This is a delayed load value. If the value is null, // then this field has not been loaded yet. // Call service offering dao to load it. @@ -482,4 +485,13 @@ public Boolean getGpuDisplay() { public void setGpuDisplay(Boolean gpuDisplay) { this.gpuDisplay = gpuDisplay; } + + @Override + public long getCategoryId() { + return categoryId; + } + + public void setCategoryId(long categoryId) { + this.categoryId = categoryId; + } } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDao.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDao.java new file mode 100644 index 000000000000..af7980dd765c --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDao.java @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.service.dao; + +import java.util.List; + +import com.cloud.service.ServiceOfferingCategoryVO; +import com.cloud.utils.db.GenericDao; + +public interface ServiceOfferingCategoryDao extends GenericDao { + + ServiceOfferingCategoryVO findByName(String name); + + List listAll(); +} diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDaoImpl.java new file mode 100644 index 000000000000..0223cdd0d3ae --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingCategoryDaoImpl.java @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.service.dao; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.cloud.service.ServiceOfferingCategoryVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.DB; + +@Component +@DB() +public class ServiceOfferingCategoryDaoImpl extends GenericDaoBase implements ServiceOfferingCategoryDao { + + protected final SearchBuilder NameSearch; + + protected ServiceOfferingCategoryDaoImpl() { + NameSearch = createSearchBuilder(); + NameSearch.and("name", NameSearch.entity().getName(), SearchCriteria.Op.EQ); + NameSearch.done(); + } + + @Override + public ServiceOfferingCategoryVO findByName(String name) { + SearchCriteria sc = NameSearch.create(); + sc.setParameters("name", name); + return findOneBy(sc); + } + + @Override + public List listAll() { + return listAll(null); + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml index 1846c3c62a0e..bfade8bbc599 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml @@ -56,6 +56,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index d330ecd0c0d5..a2535129787e 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -19,6 +19,20 @@ -- Schema upgrade from 4.22.1.0 to 4.23.0.0 --; +CREATE TABLE IF NOT EXISTS `cloud`.`service_offering_category` ( + `id` bigint unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL, + `uuid` varchar(40), + `sort_key` int NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + CONSTRAINT `uc_service_offering_category__uuid` UNIQUE (`uuid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + + +ALTER TABLE `cloud`.`service_offering` ADD COLUMN `category_id` bigint unsigned NOT NULL DEFAULT 1; +ALTER TABLE `cloud`.`service_offering` ADD CONSTRAINT `fk_service_offering__category_id` FOREIGN KEY (`category_id`) REFERENCES `cloud`.`service_offering_category` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +INSERT INTO `cloud`.`service_offering_category` (id, name, uuid) VALUES (1, 'Default', UUID()); + CREATE TABLE `cloud`.`backup_offering_details` ( `id` bigint unsigned NOT NULL auto_increment, `backup_offering_id` bigint unsigned NOT NULL COMMENT 'Backup 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 eb987af3ffb6..fa01eaef01bc 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 @@ -85,6 +85,7 @@ SELECT `vgpu_profile`.`max_resolution_y` AS `vgpu_profile_max_resolution_y`, `service_offering`.`gpu_count` AS `gpu_count`, `service_offering`.`gpu_display` AS `gpu_display`, + `service_offering`.`category_id` AS `category_id`, GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id, GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid, GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name, diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ce794cf5388f..3dbee9e0d763 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -167,6 +167,7 @@ import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.ServiceResponse; import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; @@ -374,6 +375,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offering.ServiceOffering; +import com.cloud.offering.ServiceOfferingCategory; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.org.Cluster; @@ -648,6 +650,16 @@ public ServiceOfferingResponse createServiceOfferingResponse(ServiceOffering off return ApiDBUtils.newServiceOfferingResponse(vOffering); } + @Override + public ServiceOfferingCategoryResponse createServiceOfferingCategoryResponse(ServiceOfferingCategory category) { + ServiceOfferingCategoryResponse response = new ServiceOfferingCategoryResponse(); + response.setId(category.getUuid()); + response.setName(category.getName()); + response.setSortKey(category.getSortKey()); + response.setObjectName("serviceofferingcategory"); + return response; + } + @Override public ConfigurationResponse createConfigurationResponse(Configuration cfg) { ConfigurationResponse cfgResponse = new ConfigurationResponse(); 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 472be9a12b7f..ddadb01c9577 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -69,6 +69,7 @@ import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; import org.apache.cloudstack.api.command.admin.iso.ListIsosCmdByAdmin; import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; +import org.apache.cloudstack.api.command.admin.offering.ListServiceOfferingCategoriesCmd; import org.apache.cloudstack.api.command.admin.pod.ListPodsByCmd; import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd; @@ -138,6 +139,7 @@ import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ServiceOfferingCategoryResponse; import org.apache.cloudstack.api.response.SnapshotResponse; import org.apache.cloudstack.api.response.StorageAccessGroupResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; @@ -296,8 +298,10 @@ import com.cloud.server.ResourceMetaDataService; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.service.ServiceOfferingCategoryVO; import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingCategoryDao; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.BucketVO; @@ -476,6 +480,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject ServiceOfferingDetailsDao _srvOfferingDetailsDao; + @Inject + ServiceOfferingCategoryDao _serviceOfferingCategoryDao; + @Inject DiskOfferingDao _diskOfferingDao; @@ -4011,6 +4018,7 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic ServiceOffering.State state = cmd.getState(); final Long vgpuProfileId = cmd.getVgpuProfileId(); final Boolean gpuEnabled = cmd.getGpuEnabled(); + Long categoryId = cmd.getCategoryId(); final Account owner = accountMgr.finalizeOwner(caller, accountName, domainId, projectId); @@ -4058,6 +4066,10 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic _srvOfferingDao.addCheckForGpuEnabled(serviceOfferingSearch, gpuEnabled); } + if (categoryId != null) { + serviceOfferingSearch.and("categoryId", serviceOfferingSearch.entity().getCategoryId(), Op.EQ); + } + if (vmId != null) { currentVmOffering = _srvOfferingDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); diskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentVmOffering.getDiskOfferingId()); @@ -4348,6 +4360,10 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic sc.setParameters("vgpuProfileId", vgpuProfileId); } + if (categoryId != null) { + sc.setParameters("categoryId", categoryId); + } + if (vmId != null) { if (!currentVmOffering.isDynamic()) { sc.setParameters("idNEQ", currentVmOffering.getId()); @@ -6233,6 +6249,46 @@ private List searchForBucketsInternal(ListBucketsCmd cmd) { return bucketDao.searchByIds(bktIds); } + @Override + public ListResponse listServiceOfferingCategories(ListServiceOfferingCategoriesCmd cmd) { + Long id = cmd.getId(); + String name = cmd.getName(); + + Filter searchFilter = new Filter(ServiceOfferingCategoryVO.class, "sortKey", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + SearchBuilder sb = _serviceOfferingCategoryDao.createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + + if (name != null) { + sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + } + + sb.done(); + SearchCriteria sc = sb.create(); + + if (id != null) { + sc.setParameters("id", id); + } + + if (name != null) { + sc.setParameters("name", name); + } + + Pair, Integer> result = _serviceOfferingCategoryDao.searchAndCount(sc, searchFilter); + ListResponse response = new ListResponse<>(); + List responses = new ArrayList<>(); + + for (ServiceOfferingCategoryVO category : result.first()) { + ServiceOfferingCategoryResponse categoryResponse = responseGenerator.createServiceOfferingCategoryResponse(category); + responses.add(categoryResponse); + } + + response.setResponses(responses, result.second()); + return response; + } + @Override public String getConfigComponentName() { return QueryService.class.getSimpleName(); 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 579425a68c13..b351d8de15f4 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 @@ -44,6 +44,8 @@ import com.cloud.offering.ServiceOffering; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.storage.DiskOfferingVO; +import com.cloud.service.dao.ServiceOfferingCategoryDao; +import com.cloud.service.ServiceOfferingCategoryVO; import com.cloud.user.AccountManager; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.Filter; @@ -64,6 +66,8 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase sofIdSearch; @@ -147,6 +151,16 @@ public ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offeringResponse.setMaxResolutionY(offering.getMaxResolutionY()); offeringResponse.setGpuCount(offering.getGpuCount()); offeringResponse.setGpuDisplay(offering.getGpuDisplay()); + + // Set category information if available + if (offering.getCategoryId() != null) { + ServiceOfferingCategoryVO category = _serviceOfferingCategoryDao.findById(offering.getCategoryId()); + if (category != null) { + offeringResponse.setCategoryId(category.getUuid()); + offeringResponse.setCategoryName(category.getName()); + } + } + offeringResponse.setNetworkRate(offering.getRateMbps()); offeringResponse.setHostTag(offering.getHostTag()); offeringResponse.setDeploymentPlanner(offering.getDeploymentPlanner()); 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 1d86d14cf63a..6105bbcc8761 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 @@ -263,6 +263,9 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit @Column(name = "gpu_display") private Boolean gpuDisplay; + @Column(name = "category_id") + private Long categoryId; + public ServiceOfferingJoinVO() { } @@ -557,4 +560,6 @@ public Integer getGpuCount() { public Boolean getGpuDisplay() { return gpuDisplay; } + + public Long getCategoryId() { return categoryId; } } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 5331cb10ed6d..28d71cca4307 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -78,11 +78,14 @@ import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.IsAccountAllowedToCreateOfferingsWithTagsCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; @@ -256,6 +259,7 @@ import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offering.ServiceOffering; +import com.cloud.offering.ServiceOfferingCategory; import com.cloud.offerings.NetworkOfferingDetailsVO; import com.cloud.offerings.NetworkOfferingServiceMapVO; import com.cloud.offerings.NetworkOfferingVO; @@ -269,6 +273,8 @@ import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.ServiceOfferingCategoryVO; +import com.cloud.service.dao.ServiceOfferingCategoryDao; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; @@ -365,6 +371,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject + ServiceOfferingCategoryDao _serviceOfferingCategoryDao; + @Inject DiskOfferingDao _diskOfferingDao; @Inject DiskOfferingDetailsDao diskOfferingDetailsDao; @@ -3474,6 +3482,12 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) } } + // validate optional category id + final Long serviceOfferingCategoryId = cmd.getCategoryId(); + if (serviceOfferingCategoryId != null && _serviceOfferingCategoryDao.findById(serviceOfferingCategoryId) == null) { + throw new InvalidParameterValueException("Please specify a valid service offering category id"); + } + // validate lease properties and set leaseExpiryAction Integer leaseDuration = cmd.getLeaseDuration(); VMLeaseManager.ExpiryAction leaseExpiryAction = validateAndGetLeaseExpiryAction(leaseDuration, cmd.getLeaseExpiryAction()); @@ -3489,7 +3503,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(), cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId, - cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), vgpuProfileId, gpuCount, cmd.getGpuDisplay(), cmd.isPurgeResources(), leaseDuration, leaseExpiryAction); + cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), vgpuProfileId, gpuCount, cmd.getGpuDisplay(), cmd.isPurgeResources(), leaseDuration, leaseExpiryAction, serviceOfferingCategoryId); } private Integer validateVgpuProfileAndGetGpuCount(final Long vgpuProfileId, Integer gpuCount) { @@ -3519,7 +3533,7 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength, final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID, final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness, - final boolean isCustomized, final boolean encryptRoot, Long vgpuProfileId, Integer gpuCount, Boolean gpuDisplay, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) { + final boolean isCustomized, final boolean encryptRoot, Long vgpuProfileId, Integer gpuCount, Boolean gpuDisplay, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction, final Long categoryId) { // Filter child domains when both parent and child domains are present List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); @@ -3604,6 +3618,10 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole serviceOffering.setVgpuProfileId(vgpuProfileId); serviceOffering.setGpuCount(gpuCount); serviceOffering.setGpuDisplay(gpuDisplay); + // Set category if provided (categoryId was validated in caller) + if (categoryId != null) { + serviceOffering.setCategoryId(categoryId); + } DiskOfferingVO diskOffering = null; if (diskOfferingId == null) { @@ -3863,11 +3881,17 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) boolean purgeResources = cmd.isPurgeResources(); final Map externalDetails = cmd.getExternalDetails(); final boolean cleanupExternalDetails = cmd.isCleanupExternalDetails(); + final Long categoryId = cmd.getCategoryId(); if (userId == null) { userId = Long.valueOf(User.UID_SYSTEM); } + // Validate category if provided + if (categoryId != null && _serviceOfferingCategoryDao.findById(categoryId) == null) { + throw new InvalidParameterValueException("Please specify a valid service offering category id"); + } + // Verify input parameters final ServiceOffering offeringHandle = _entityMgr.findById(ServiceOffering.class, id); if (offeringHandle == null) { @@ -3960,7 +3984,7 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) 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, user)); } - final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null || state != null; + final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null || state != null || categoryId != null; final boolean serviceOfferingExternalDetailsNeedUpdate = serviceOfferingExternalDetailsNeedUpdate(offeringDetails, externalDetails, cleanupExternalDetails); final boolean detailsUpdateNeeded = !filteredDomainIds.equals(existingDomainIds) || @@ -3988,6 +4012,10 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) offering.setState(state); } + if (categoryId != null) { + offering.setCategoryId(categoryId); + } + DiskOfferingVO diskOffering = _diskOfferingDao.findById(offeringHandle.getDiskOfferingId()); updateOfferingTagsIfIsNotNull(storageTags, diskOffering); @@ -8590,4 +8618,91 @@ public void setScope(String scope) { this.scope = scope; } } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering category") + public ServiceOfferingCategory createServiceOfferingCategory(CreateServiceOfferingCategoryCmd cmd) { + String name = cmd.getName(); + Integer sortKey = cmd.getSortKey(); + + // Check if category with same name already exists + ServiceOfferingCategoryVO existingCategory = _serviceOfferingCategoryDao.findByName(name); + if (existingCategory != null) { + throw new InvalidParameterValueException("Service offering category with name " + name + " already exists"); + } + + ServiceOfferingCategoryVO category = new ServiceOfferingCategoryVO(name); + if (sortKey != null) { + category.setSortKey(sortKey); + } + + category = _serviceOfferingCategoryDao.persist(category); + CallContext.current().setEventDetails("Service offering category id=" + category.getId()); + return category; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_DELETE, eventDescription = "deleting service offering category") + public boolean deleteServiceOfferingCategory(DeleteServiceOfferingCategoryCmd cmd) { + Long categoryId = cmd.getId(); + + ServiceOfferingCategoryVO category = _serviceOfferingCategoryDao.findById(categoryId); + if (category == null) { + throw new InvalidParameterValueException("Unable to find service offering category with id " + categoryId); + } + + // Check if any service offering is using this category + // For now we'll just check if it's the default category (id=1) + if (categoryId == 1L) { + throw new InvalidParameterValueException("Cannot delete the default service offering category"); + } + + boolean result = _serviceOfferingCategoryDao.remove(categoryId); + if (result) { + CallContext.current().setEventDetails("Service offering category id=" + categoryId); + } + return result; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_EDIT, eventDescription = "updating service offering category") + public ServiceOfferingCategory updateServiceOfferingCategory(UpdateServiceOfferingCategoryCmd cmd) { + Long categoryId = cmd.getId(); + String name = cmd.getName(); + Integer sortKey = cmd.getSortKey(); + + // Validate category exists + ServiceOfferingCategoryVO category = _serviceOfferingCategoryDao.findById(categoryId); + if (category == null) { + throw new InvalidParameterValueException("Unable to find service offering category with id " + categoryId); + } + + // Check if at least one parameter is being updated + if (name == null && sortKey == null) { + throw new InvalidParameterValueException("Please specify at least one parameter to update (name or sortKey)"); + } + + // If name is being updated, check for duplicates + if (name != null && !name.equals(category.getName())) { + ServiceOfferingCategoryVO existingCategory = _serviceOfferingCategoryDao.findByName(name); + if (existingCategory != null) { + throw new InvalidParameterValueException("A service offering category with name '" + name + "' already exists"); + } + category.setName(name); + } + + // Update sort key if provided + if (sortKey != null) { + category.setSortKey(sortKey); + } + + // Persist changes + boolean updated = _serviceOfferingCategoryDao.update(categoryId, category); + if (!updated) { + throw new CloudRuntimeException("Failed to update service offering category"); + } + + CallContext.current().setEventDetails("Service offering category id=" + categoryId); + return _serviceOfferingCategoryDao.findById(categoryId); + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 8ee35cc35ec0..3a1d7a67258a 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -162,11 +162,15 @@ import org.apache.cloudstack.api.command.admin.network.UpdateStorageNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.IsAccountAllowedToCreateOfferingsWithTagsCmd; +import org.apache.cloudstack.api.command.admin.offering.ListServiceOfferingCategoriesCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.outofbandmanagement.ChangeOutOfBandManagementPasswordCmd; import org.apache.cloudstack.api.command.admin.outofbandmanagement.ConfigureOutOfBandManagementCmd; import org.apache.cloudstack.api.command.admin.outofbandmanagement.DisableOutOfBandManagementForClusterCmd; @@ -3868,6 +3872,10 @@ public List> getCommands() { cmdList.add(IsAccountAllowedToCreateOfferingsWithTagsCmd.class); cmdList.add(UpdateDiskOfferingCmd.class); cmdList.add(UpdateServiceOfferingCmd.class); + cmdList.add(CreateServiceOfferingCategoryCmd.class); + cmdList.add(ListServiceOfferingCategoriesCmd.class); + cmdList.add(UpdateServiceOfferingCategoryCmd.class); + cmdList.add(DeleteServiceOfferingCategoryCmd.class); cmdList.add(CreatePodCmd.class); cmdList.add(DeletePodCmd.class); cmdList.add(ListPodsByCmd.class); diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index 2982c19ccdd4..0fb6b358d538 100644 --- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -42,6 +42,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.ServiceOffering; +import com.cloud.offering.ServiceOfferingCategory; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDaoImpl; import com.cloud.org.Grouping.AllocationState; @@ -62,11 +63,14 @@ import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.offering.IsAccountAllowedToCreateOfferingsWithTagsCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCategoryCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; @@ -147,6 +151,24 @@ public List getServiceOfferingZones(Long serviceOfferingId) { return null; } + @Override + public ServiceOfferingCategory createServiceOfferingCategory(CreateServiceOfferingCategoryCmd cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean deleteServiceOfferingCategory(DeleteServiceOfferingCategoryCmd cmd) { + // TODO Auto-generated method stub + return false; + } + + @Override + public ServiceOfferingCategory updateServiceOfferingCategory(UpdateServiceOfferingCategoryCmd cmd) { + // TODO Auto-generated method stub + return null; + } + /* (non-Javadoc) * @see com.cloud.configuration.ConfigurationService#updateDiskOffering(org.apache.cloudstack.api.commands.UpdateDiskOfferingCmd) */ diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index c20827f2100b..5c87e810a695 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -515,6 +515,7 @@ "label.capacitybytes": "Capacity bytes", "label.capacityiops": "IOPS total", "label.category": "Category", +"label.categoryname": "Category name", "label.certchain": "Chain", "label.certificate": "Certificate", "label.certificate.chain": "Certificate chain", @@ -1175,6 +1176,10 @@ "label.guest.os.category": "Guest OS Category", "label.guest.os.categories": "Guest OS Categories", "label.guest.os.hypervisor.mappings": "Guest OS mappings", +"label.service.offering.categories": "Service Offering Categories", +"label.add.service.offering.category": "Add Service Offering Category", +"label.action.delete.service.offering.category": "Delete Service Offering Category", +"message.action.delete.service.offering.category": "Please confirm that you want to delete this service offering category", "label.guest.start.ip": "Guest start IP", "label.guest.traffic": "Guest traffic", "label.guestcidraddress": "Guest CIDR", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 375fddcb55c6..a4942d67fd82 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -1034,6 +1034,9 @@ +