diff --git a/api/src/main/java/com/cloud/configuration/ConfigurationService.java b/api/src/main/java/com/cloud/configuration/ConfigurationService.java index 729f72b23ca2..05275a287dd9 100644 --- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java +++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java @@ -38,11 +38,14 @@ import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd; 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; @@ -75,6 +78,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; @@ -157,6 +161,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 3d827641358b..b6a34d4c3be1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -535,6 +535,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 338c1e738dfc..0680fb387c6d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -116,6 +116,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; @@ -224,6 +225,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; @@ -271,6 +273,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 8e37499c95ed..deeae2c95d99 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; @@ -108,6 +109,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 /////////////////////// ///////////////////////////////////////////////////// @@ -164,6 +173,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 5b053aafd84b..19ce2ad19913 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; @@ -187,6 +189,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 d69b524b85d9..4346483981cc 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 cf98df0da243..c5a9b021c4f5 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -164,6 +164,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; @@ -383,6 +384,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; @@ -668,6 +670,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 fb97e2f3d8dd..45963278e8df 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -76,6 +76,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; @@ -145,6 +146,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; @@ -301,8 +303,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; @@ -484,6 +488,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject ServiceOfferingDetailsDao _srvOfferingDetailsDao; + @Inject + ServiceOfferingCategoryDao _serviceOfferingCategoryDao; + @Inject DiskOfferingDao _diskOfferingDao; @@ -4090,6 +4097,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); @@ -4137,6 +4145,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()); @@ -4427,6 +4439,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()); @@ -6325,6 +6341,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 6cc816a81dec..56baba265347 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -82,11 +82,14 @@ import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd; 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; @@ -261,6 +264,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; @@ -274,6 +278,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; @@ -370,6 +376,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject + ServiceOfferingCategoryDao _serviceOfferingCategoryDao; + @Inject DiskOfferingDao _diskOfferingDao; @Inject DiskOfferingDetailsDao diskOfferingDetailsDao; @@ -3485,6 +3493,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()); @@ -3500,7 +3514,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) { @@ -3530,7 +3544,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); @@ -3615,6 +3629,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) { @@ -3925,7 +3943,7 @@ public ServiceOffering cloneServiceOffering(final CloneServiceOfferingCmd cmd) { diskParams.iopsWriteRate, diskParams.iopsWriteRateMax, diskParams.iopsWriteRateMaxLength, diskParams.hypervisorSnapshotReserve, diskParams.cacheMode, customParams.storagePolicy, dynamicScalingEnabled, diskOfferingId, diskOfferingStrictness, isCustomized, encryptRoot, - vgpuProfileId, finalGpuCount, gpuDisplay, purgeResources, leaseParams.leaseDuration, leaseParams.leaseExpiryAction); + vgpuProfileId, finalGpuCount, gpuDisplay, purgeResources, leaseParams.leaseDuration, leaseParams.leaseExpiryAction, sourceOffering.getCategoryId()); } private ServiceOfferingVO getAndValidateSourceOffering(Long sourceOfferingId) { @@ -4327,11 +4345,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) { @@ -4424,7 +4448,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) || @@ -4452,6 +4476,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); @@ -9481,4 +9509,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 f8c4d1d44d4c..97affcfd7596 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -167,11 +167,15 @@ import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd; 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; @@ -3893,6 +3897,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/configuration/ConfigurationManagerCloneIntegrationTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java index 67d43bf6933b..06090af677b1 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java @@ -251,7 +251,7 @@ public void testCloneServiceOfferingInheritsAllPropertiesFromSource() { anyString(), anyMap(), anyLong(), any(Boolean.class), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), any(Integer.class), anyString(), anyLong(), anyBoolean(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), - anyLong(), any(Integer.class), any(Boolean.class), anyBoolean(), any(Integer.class), any(VMLeaseManager.ExpiryAction.class) + anyLong(), any(Integer.class), any(Boolean.class), anyBoolean(), any(Integer.class), any(VMLeaseManager.ExpiryAction.class), anyLong() ); ServiceOffering result = configurationManager.cloneServiceOffering(cmd); @@ -328,7 +328,7 @@ public void testCloneServiceOfferingOverridesProvidedParameters() { anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), anyString(), anyLong(), anyBoolean(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), anyLong(), anyInt(), anyBoolean(), - anyBoolean(), anyInt(), any()); + anyBoolean(), anyInt(), any(), anyLong()); ServiceOffering result = configurationManager.cloneServiceOffering(cmd); @@ -553,7 +553,7 @@ public void testCloneServiceOfferingCanInheritDetailsFromSource() { anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), anyString(), anyLong(), anyBoolean(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), anyLong(), anyInt(), anyBoolean(), - anyBoolean(), anyInt(), any()); + anyBoolean(), anyInt(), any(), anyLong()); ServiceOffering result = configurationManager.cloneServiceOffering(cmd); diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index a8d3927f910e..04c868539d92 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; @@ -65,11 +66,14 @@ import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd; 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; @@ -168,6 +172,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 513dfcdaa368..83c26857189c 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -524,6 +524,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", @@ -1204,6 +1205,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/offering/ComputeOfferingForm.vue b/ui/src/components/offering/ComputeOfferingForm.vue index c621324f108e..fef48037fa9e 100644 --- a/ui/src/components/offering/ComputeOfferingForm.vue +++ b/ui/src/components/offering/ComputeOfferingForm.vue @@ -39,6 +39,25 @@ v-model:value="form.displaytext" :placeholder="$t('label.displaytext')"/> + + + + + {{ category.name }} + + +