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 8fca652518f2..36b1267f2d40 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -529,6 +529,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 = "serviceofferingcategoryid"; + public static final String SERVICE_OFFERING_CATEGORY_NAME = "serviceofferingcategoryname"; 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 ec109a2a4f37..543f56d0d9b9 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; @@ -289,6 +290,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 serviceOfferingCategoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -561,6 +570,10 @@ public Boolean getGpuDisplay() { return Boolean.TRUE.equals(gpuDisplay); } + public Long getServiceOfferingCategoryId() { + return serviceOfferingCategoryId; + } + ///////////////////////////////////////////////////// /////////////// 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 9d973dfc524b..92a1b456c263 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.Parameter; import org.apache.cloudstack.api.ServerApiException; 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; @@ -109,6 +110,14 @@ public class UpdateServiceOfferingCmd extends BaseCmd { 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 /////////////////////// ///////////////////////////////////////////////////// @@ -217,6 +226,8 @@ public boolean isCleanupExternalDetails() { return Boolean.TRUE.equals(cleanupExternalDetails); } + public Long getCategoryId() { return categoryId; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -249,4 +260,4 @@ public void execute() { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update service offering"); } } -} +} \ No newline at end of file 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 3b693fe57b7e..9923d6820005 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 69f80b54010e..5f0b9819f865 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 6a20c2fa2486..8b460264e9ce 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..c4eb67e3d5c5 --- /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); + } +} \ No newline at end of file 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 d308a9e5aaf9..95960dffdbe5 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-42200to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql index c1f1bb2c094d..afccef905f2f 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql @@ -18,3 +18,17 @@ --; -- Schema upgrade from 4.22.0.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()); \ No newline at end of file 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..29524e1b6939 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, @@ -134,4 +135,4 @@ FROM `cloud`.`service_offering_details` AS `lease_expiry_action_details` ON `lease_expiry_action_details`.`service_offering_id` = `service_offering`.`id` AND `lease_expiry_action_details`.`name` = 'leaseexpiryaction' GROUP BY - `service_offering`.`id`; + `service_offering`.`id`; \ No newline at end of file diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 8cc10ce41673..3b048bfc429e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -166,6 +166,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; @@ -373,6 +374,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; @@ -645,6 +647,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 4b469abe1fc2..11c2106eb994 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()); @@ -6231,6 +6247,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..bb60f06f67ef 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; } +} \ No newline at end of file diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index e2fc57b1b16d..dc86fd7c49f5 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -77,11 +77,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; @@ -254,6 +257,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; @@ -267,6 +271,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; @@ -363,6 +369,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject + ServiceOfferingCategoryDao _serviceOfferingCategoryDao; + @Inject DiskOfferingDao _diskOfferingDao; @Inject DiskOfferingDetailsDao diskOfferingDetailsDao; @@ -3461,6 +3469,12 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) } } + // validate optional category id + final Long serviceOfferingCategoryId = cmd.getServiceOfferingCategoryId(); + 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()); @@ -3476,7 +3490,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) { @@ -3506,7 +3520,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 = filterChildSubDomains(domainIds); @@ -3591,6 +3605,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) { @@ -3850,11 +3868,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) { @@ -3941,7 +3965,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) || @@ -3969,6 +3993,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); @@ -8595,4 +8623,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 e6032662e926..413aa9c607d9 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; @@ -3778,6 +3782,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 028406bbc682..c9fce45345c6 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -511,6 +511,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", @@ -1165,6 +1166,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 47aa3d2ddef1..d51e62b34d87 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -1027,6 +1027,9 @@ +