diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java index ce8f96c464db..50f3c90bc0cf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.api.BaseAsyncCmd; 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.VpcOfferingResponse; import org.apache.log4j.Logger; @@ -72,6 +73,12 @@ public class UpdateVPCOfferingCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER, description = "sort key of the VPC offering, integer") private Integer sortKey; + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + description = "the ID of the service offering for the VPC router appliance") + private Long serviceOfferingId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -156,6 +163,10 @@ public Integer getSortKey() { return sortKey; } + public Long getServiceOfferingId() { + return serviceOfferingId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java index b8483c3e3d2b..044ac435884f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java @@ -82,6 +82,10 @@ public class VpcOfferingResponse extends BaseResponse { @Param(description = "the zone name(s) this disk offering belongs to. Ignore this information as it is not currently applicable.", since = "4.13.0") private String zone; + @SerializedName(ApiConstants.SERVICE_OFFERING_ID) + @Param(description = "the service offering which was used to create vpc offering") + private String serviceOfferingId; + public void setId(String id) { this.id = id; } @@ -149,4 +153,8 @@ public String getZone() { public void setZone(String zone) { this.zone = zone; } + + public void setServiceOfferingId(String serviceOfferingId) { + this.serviceOfferingId = serviceOfferingId; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java index aa26f16568a9..22196bd315c5 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java @@ -181,6 +181,10 @@ public Long getServiceOfferingId() { return serviceOfferingId; } + public void setServiceOfferingId(Long serviceOfferingId) { + this.serviceOfferingId = serviceOfferingId; + } + @Override public boolean isSupportsDistributedRouter() { return supportsDistributedRouter; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 9bec40894c93..a2f33c883d1e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -32,6 +32,7 @@ import javax.inject.Inject; import com.cloud.resource.RollingMaintenanceManager; +import com.cloud.service.dao.ServiceOfferingDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -391,6 +392,8 @@ public class ApiResponseHelper implements ResponseGenerator { private VMSnapshotDao vmSnapshotDao; @Inject private BackupOfferingDao backupOfferingDao; + @Inject + private ServiceOfferingDao serviceOfferingDao; @Override public UserResponse createUserResponse(User user) { @@ -2887,6 +2890,8 @@ public VpcOfferingResponse createVpcOfferingResponse(VpcOffering offering) { serviceResponses.add(svcRsp); } response.setServices(serviceResponses); + if(offering.getServiceOfferingId() != null) + response.setServiceOfferingId(serviceOfferingDao.findById(offering.getServiceOfferingId()).getUuid()); return response; } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index f1198ad4602e..eb7f82fe5017 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -804,7 +804,7 @@ public boolean deleteVpcOffering(final long offId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_OFFERING_UPDATE, eventDescription = "updating vpc offering") public VpcOffering updateVpcOffering(long vpcOffId, String vpcOfferingName, String displayText, String state) { - return updateVpcOfferingInternal(vpcOffId, vpcOfferingName, displayText, state, null, null, null); + return updateVpcOfferingInternal(vpcOffId, vpcOfferingName, displayText, state, null, null, null, null); } @Override @@ -817,6 +817,7 @@ public VpcOffering updateVpcOffering(final UpdateVPCOfferingCmd cmd) { final List domainIds = cmd.getDomainIds(); final List zoneIds = cmd.getZoneIds(); final Integer sortKey = cmd.getSortKey(); + final Long serviceOfferingId = cmd.getServiceOfferingId(); // check if valid domain if (CollectionUtils.isNotEmpty(domainIds)) { @@ -835,10 +836,10 @@ public VpcOffering updateVpcOffering(final UpdateVPCOfferingCmd cmd) { } } - return updateVpcOfferingInternal(offeringId, vpcOfferingName, displayText, state, sortKey, domainIds, zoneIds); + return updateVpcOfferingInternal(offeringId, vpcOfferingName, displayText, state, sortKey, domainIds, zoneIds, serviceOfferingId); } - private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingName, String displayText, String state, Integer sortKey, final List domainIds, final List zoneIds) { + private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingName, String displayText, String state, Integer sortKey, final List domainIds, final List zoneIds, final Long serviceOfferingId) { CallContext.current().setEventDetails(" Id: " + vpcOffId); // Verify input parameters @@ -875,6 +876,7 @@ private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingN if (displayText != null) { offering.setDisplayText(displayText); } + offering.setServiceOfferingId(serviceOfferingId); if (state != null) { boolean validState = false; for (final VpcOffering.State st : VpcOffering.State.values()) { diff --git a/test/integration/component/test_vpc_offerings.py b/test/integration/component/test_vpc_offerings.py index 5424cbeaba58..9c9f8674187c 100644 --- a/test/integration/component/test_vpc_offerings.py +++ b/test/integration/component/test_vpc_offerings.py @@ -53,6 +53,15 @@ def __init__(self): "cpuspeed": 100, "memory": 128, }, + "vpc_service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 128, + "systemvmtype": "domainrouter", + "issystem": True, + }, "network_offering": { "name": 'VPC Network offering', "displaytext": 'VPC Network off', @@ -189,8 +198,13 @@ def setUpClass(cls): cls.api_client, cls.services["service_offering"] ) + cls.system_offering = ServiceOffering.create( + cls.api_client, + cls.services["vpc_service_offering"] + ) cls._cleanup = [ cls.service_offering, + cls.system_offering ] return @@ -269,6 +283,36 @@ def validate_vpc_network(self, network): self.logger.debug("VPC network created successfully - %s" % network.name) return + def validate_vpc_offering_with_selected_service_offering(self, vpc_offering): + """Validates the VPC offering with selected service offering""" + + self.logger.debug("Check if the VPC offering is created successfully?") + self.logger.debug(vpc_offering.serviceofferingid) + vpc_offs = VpcOffering.list( + self.apiclient, + id=vpc_offering.id + ) + self.assertEqual( + isinstance(vpc_offs, list), + True, + "List VPC offerings should return a valid list" + ) + self.assertEqual( + vpc_offering.name, + vpc_offs[0].name, + "Name of the VPC offering should match with listVPCOff data" + ) + self.assertEqual( + vpc_offering.serviceofferingid, + vpc_offs[0].serviceofferingid, + "Service offering of the VPC offering should match with listVPCOff data" + ) + self.logger.debug( + "VPC offering is created successfully - %s" % + vpc_offering.name + ) + return + @attr(tags=["advanced", "intervlan"], required_hardware="false") def test_01_create_vpc_offering(self): """ Test create VPC offering @@ -1201,3 +1245,23 @@ def test_09_create_redundant_vpc_offering(self): ) return + + @attr(tags=["advanced", "intervlan"], required_hardware="false") + def test_10_create_vpc_offering_with_selected_service_offering(self): + """ Test create VPC offering with selected service offering + """ + + # Steps for validation + # 1. Create VPC Offering by specifying all supported Services + # 2. VPC offering should be created successfully. + + self.logger.debug("Creating inter VPC offering with selected service offering") + vpc_off = VpcOffering.create( + self.api_client, + services=self.services["vpc_offering"], + serviceofferingid=self.system_offering.id + ) + self.logger.debug("Check if the VPC offering is created successfully?") + self.cleanup.append(vpc_off) + self.validate_vpc_offering_with_selected_service_offering(vpc_off) + return diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index df38bb54a2b4..5fb957fad13a 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4409,7 +4409,7 @@ def __init__(self, items): self.__dict__.update(items) @classmethod - def create(cls, apiclient, services): + def create(cls, apiclient, services, serviceofferingid=None): """Create vpc offering""" import logging @@ -4418,6 +4418,7 @@ def create(cls, apiclient, services): cmd.name = "-".join([services["name"], random_gen()]) cmd.displaytext = services["displaytext"] cmd.supportedServices = services["supportedservices"] + cmd.serviceofferingid = serviceofferingid if "serviceProviderList" in services: for service, provider in services["serviceProviderList"].items(): providers = provider diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 3d3783ddd4fc..14b36fa65d0f 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -5249,6 +5249,38 @@ } }, //end of supportedservices field + serviceofferingid: { + label: 'label.service.offering', + select: function(args) { + $.ajax({ + url: createURL('listServiceOfferings'), + dataType: "json", + data: { + issystem: true, + listAll: true, + systemvmtype: 'domainrouter' + }, + success: function (json) { + serviceofferings = json.listserviceofferingsresponse.serviceoffering; + var items =[]; + items.push({ + id: "", + description: "" + }) + $(serviceofferings).each(function () { + items.push({ + id : this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } + }); + } + }, + "service.Connectivity.regionLevelVpcCapabilityCheckbox": { label: 'label.regionlevelvpc', isHidden: true, @@ -5470,12 +5502,20 @@ displaytext: args.data.displaytext, availability: args.data.availability }; + if(args.data.serviceofferingid !== "") + { + serviceofferingid: args.data.serviceofferingid + } $.ajax({ url: createURL('updateVPCOffering'), data: data, success: function(json) { var item = json.updatevpcofferingresponse.vpcoffering; + if(args.context.vpcOfferings[0].serviceofferingid !== args.data.serviceofferingid) + { + alert("Please restart the management server in order to apply the new VPC Service Offering."); + } args.response.success({ data: item }); @@ -5811,6 +5851,40 @@ converter: cloudStack.converters.toBooleanText }, + serviceofferingid: { + label: 'label.service.offering', + isEditable: true, + select: function(args) { + var serviceofferings; + $.ajax({ + url: createURL('listServiceOfferings'), + dataType: "json", + data: { + issystem: true, + listAll: true, + systemvmtype: 'domainrouter' + }, + success: function (json) { + serviceofferings = json.listserviceofferingsresponse.serviceoffering; + var items =[]; + items.push({ + id: "", + description: "" + }) + $(serviceofferings).each(function () { + items.push({ + id : this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } + }); + } + }, + supportedServices: { label: 'label.supported.services' }, @@ -5847,20 +5921,43 @@ async: true, success: function(json) { var item = json.listvpcofferingsresponse.vpcoffering[0]; - args.response.success({ - actionFilter: vpcOfferingActionfilter, - data: $.extend(item, { - supportedServices: $.map(item.service, function(service) { - return service.name; - }).join(', '), - - serviceCapabilities: $.map(item.service, function(service) { - return service.provider ? $.map(service.provider, function(capability) { - return service.name + ': ' + capability.name; - }).join(', ') : null; - }).join(', ') - }) - }); + if(args.context.vpcOfferings[0].serviceofferingid !== undefined && args.context.vpcOfferings[0].serviceofferingid !== "") + $.ajax({ + url: createURL('listServiceOfferings&issystem=true&id=' + args.context.vpcOfferings[0].serviceofferingid), + dataType: "json", + async: true, + success: function(json) { + var itemService = json.listserviceofferingsresponse.serviceoffering[0]; + args.response.success({ + data: $.extend(item, { + serviceofferingid: itemService.id, + supportedServices: $.map(item.service, function(service) { + return service.name; + }).join(', '), + + serviceCapabilities: $.map(item.service, function(service) { + return service.provider ? $.map(service.provider, function(capability) { + return service.name + ': ' + capability.name; + }).join(', ') : null; + }).join(', ') + }) + }); + } + }); + else + args.response.success({ + data: $.extend(item, { + supportedServices: $.map(item.service, function(service) { + return service.name; + }).join(', '), + + serviceCapabilities: $.map(item.service, function(service) { + return service.provider ? $.map(service.provider, function(capability) { + return service.name + ': ' + capability.name; + }).join(', ') : null; + }).join(', ') + }) + }); } }); }