From 3690b05aa485ddf9221b8f44e1eab16846378026 Mon Sep 17 00:00:00 2001 From: Vishesh Date: Thu, 24 Apr 2025 11:59:38 +0530 Subject: [PATCH 01/25] GPU support for KVM --- .../com/cloud/agent/api/VgpuTypesInfo.java | 95 ++ .../com/cloud/agent/api/to/GPUDeviceTO.java | 33 +- .../main/java/com/cloud/event/EventTypes.java | 33 + .../com/cloud/offering/ServiceOffering.java | 4 + .../annotation/AnnotationService.java | 3 +- .../api/ApiCommandResourceType.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 15 +- .../command/admin/gpu/CreateGpuCardCmd.java | 131 +++ .../admin/gpu/CreateGpuOfferingCmd.java | 106 ++ .../admin/gpu/CreateVgpuProfileCmd.java | 100 ++ .../command/admin/gpu/DeleteGpuCardCmd.java | 80 ++ .../admin/gpu/DeleteVgpuProfileCmd.java | 80 ++ .../admin/gpu/DisableGpuDeviceCmd.java | 85 ++ .../admin/gpu/DiscoverGpuDevicesCmd.java | 68 ++ .../command/admin/gpu/EnableGpuDeviceCmd.java | 85 ++ .../command/admin/gpu/ListGpuDevicesCmd.java | 92 ++ .../command/admin/gpu/UpdateGpuCardCmd.java | 109 ++ .../admin/gpu/UpdateGpuOfferingCmd.java | 139 +++ .../admin/gpu/UpdateVgpuProfileCmd.java | 99 ++ .../offering/CreateServiceOfferingCmd.java | 22 + .../api/command/user/gpu/ListGpuCardsCmd.java | 107 ++ .../command/user/gpu/ListGpuOfferingsCmd.java | 90 ++ .../command/user/gpu/ListVgpuProfilesCmd.java | 82 ++ .../offering/ListServiceOfferingsCmd.java | 12 + .../api/response/GpuCardResponse.java | 122 +++ .../api/response/GpuDeviceResponse.java | 166 +++ .../api/response/GpuOfferingResponse.java | 116 +++ .../api/response/ServiceOfferingResponse.java | 36 + .../api/response/UserVmResponse.java | 38 +- .../api/response/VgpuProfileResponse.java | 67 ++ .../org/apache/cloudstack/gpu/GpuCard.java | 67 ++ .../org/apache/cloudstack/gpu/GpuDevice.java | 42 + .../apache/cloudstack/gpu/GpuOffering.java | 55 + .../org/apache/cloudstack/gpu/GpuService.java | 171 +++ .../apache/cloudstack/gpu/VgpuProfile.java | 54 + .../cloud/agent/api/GetGPUStatsAnswer.java | 17 + .../agent/api/StartupRoutingCommand.java | 11 +- .../java/com/cloud/resource/Discoverer.java | 1 - .../com/cloud/resource/ResourceManager.java | 24 +- .../cloud/vm/VirtualMachineManagerImpl.java | 19 +- .../main/java/com/cloud/gpu/GpuCardVO.java | 140 +++ .../main/java/com/cloud/gpu/GpuDeviceVO.java | 164 +++ .../com/cloud/gpu/GpuOfferingDetailVO.java | 97 ++ .../java/com/cloud/gpu/GpuOfferingVO.java | 188 ++++ .../main/java/com/cloud/gpu/VGPUTypesVO.java | 32 +- .../java/com/cloud/gpu/VgpuProfileVO.java | 113 ++ .../java/com/cloud/gpu/dao/GpuCardDao.java | 50 + .../com/cloud/gpu/dao/GpuCardDaoImpl.java | 125 +++ .../java/com/cloud/gpu/dao/GpuDeviceDao.java | 79 ++ .../com/cloud/gpu/dao/GpuDeviceDaoImpl.java | 207 ++++ .../com/cloud/gpu/dao/GpuOfferingDao.java | 42 + .../com/cloud/gpu/dao/GpuOfferingDaoImpl.java | 120 +++ .../cloud/gpu/dao/GpuOfferingDetailsDao.java | 65 ++ .../gpu/dao/GpuOfferingDetailsDaoImpl.java | 93 ++ .../com/cloud/gpu/dao/HostGpuGroupsDao.java | 13 +- .../cloud/gpu/dao/HostGpuGroupsDaoImpl.java | 8 +- .../java/com/cloud/gpu/dao/VGPUTypesDao.java | 14 +- .../com/cloud/gpu/dao/VGPUTypesDaoImpl.java | 37 +- .../com/cloud/gpu/dao/VgpuProfileDao.java | 42 + .../com/cloud/gpu/dao/VgpuProfileDaoImpl.java | 103 ++ .../com/cloud/service/ServiceOfferingVO.java | 24 + .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 2 +- .../annotation/dao/AnnotationDaoImpl.java | 4 +- ...spring-engine-schema-core-daos-context.xml | 5 + .../resources/META-INF/db/schema-20to21.sql | 2 +- .../META-INF/db/schema-42010to42100.sql | 82 ++ .../db/views/cloud.service_offering_view.sql | 6 + .../META-INF/db/views/cloud.user_vm_view.sql | 7 +- .../resource/LibvirtComputingResource.java | 131 ++- .../kvm/resource/LibvirtGpuDef.java | 91 ++ .../LibvirtGetGPUStatsCommandWrapper.java | 97 ++ .../wrapper/LibvirtStartCommandWrapper.java | 2 + .../kvm/resource/LibvirtGpuDefTest.java | 125 +++ .../cloud/agent/manager/MockAgentManager.java | 7 + .../agent/manager/MockAgentManagerImpl.java | 235 ++++- .../agent/manager/MockVmManagerImpl.java | 27 +- .../agent/manager/SimulatorManagerImpl.java | 5 +- .../cloud/resource/SimulatorDiscoverer.java | 25 + .../com/cloud/simulator/MockGpuDevice.java | 43 + .../com/cloud/simulator/MockGpuDeviceVO.java | 237 +++++ .../cloud/simulator/dao/MockGpuDeviceDao.java | 56 + .../simulator/dao/MockGpuDeviceDaoImpl.java | 92 ++ .../core/spring-simulator-core-context.xml | 1 + scripts/vm/hypervisor/kvm/gpudiscovery.sh | 596 +++++++++++ .../allocator/impl/FirstFitAllocator.java | 38 +- .../java/com/cloud/api/ApiResponseHelper.java | 1 + .../com/cloud/api/query/QueryManagerImpl.java | 9 + .../query/dao/ServiceOfferingJoinDaoImpl.java | 3 + .../api/query/dao/UserVmJoinDaoImpl.java | 91 +- .../api/query/vo/ServiceOfferingJoinVO.java | 28 + .../com/cloud/api/query/vo/UserVmJoinVO.java | 28 + .../ConfigurationManagerImpl.java | 27 +- .../deploy/DeploymentPlanningManagerImpl.java | 35 +- .../com/cloud/deploy/FirstFitPlanner.java | 9 +- .../cloud/hypervisor/HypervisorGuruBase.java | 15 +- .../cloud/resource/ResourceManagerImpl.java | 101 +- .../annotation/AnnotationManagerImpl.java | 1 + .../apache/cloudstack/gpu/GpuServiceImpl.java | 974 ++++++++++++++++++ .../spring-server-core-managers-context.xml | 3 + .../resource/MockResourceManagerImpl.java | 24 + .../com/cloud/server/StatsCollectorTest.java | 16 + setup/db/create-schema-simulator.sql | 21 + tools/apidoc/gen_toc.py | 4 +- ui/public/locales/en.json | 10 + ui/src/components/view/GPUTab.vue | 147 +++ ui/src/components/view/ListView.vue | 593 ++++++++--- ui/src/components/view/VgpuProfilesTab.vue | 96 ++ ui/src/config/section/compute.js | 4 +- ui/src/config/section/config.js | 98 ++ ui/src/config/section/infra.js | 10 + ui/src/config/section/infra/hosts.js | 13 + ui/src/config/section/offering.js | 99 +- ui/src/core/lazy_lib/icons_use.js | 2 + ui/src/views/AutogenView.vue | 15 +- ui/src/views/offering/AddComputeOffering.vue | 130 +-- 115 files changed, 8603 insertions(+), 353 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuOfferingCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DisableGpuDeviceCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/EnableGpuDeviceCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuOfferingCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuOfferingsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/GpuOfferingResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java create mode 100644 api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java create mode 100644 api/src/main/java/org/apache/cloudstack/gpu/GpuOffering.java create mode 100644 api/src/main/java/org/apache/cloudstack/gpu/GpuService.java create mode 100644 api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/GpuOfferingDetailVO.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/GpuOfferingVO.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDao.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDaoImpl.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDao.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDaoImpl.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java create mode 100644 engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDef.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetGPUStatsCommandWrapper.java create mode 100644 plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDefTest.java create mode 100644 plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDevice.java create mode 100644 plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDeviceVO.java create mode 100644 plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDao.java create mode 100644 plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDaoImpl.java create mode 100755 scripts/vm/hypervisor/kvm/gpudiscovery.sh create mode 100644 server/src/main/java/org/apache/cloudstack/gpu/GpuServiceImpl.java create mode 100644 ui/src/components/view/GPUTab.vue create mode 100644 ui/src/components/view/VgpuProfilesTab.vue diff --git a/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java b/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java index 85ffc1898209..da18dbb106b0 100644 --- a/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java +++ b/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java @@ -15,10 +15,22 @@ // specific language governing permissions and limitations // under the License. package com.cloud.agent.api; + +import org.apache.cloudstack.gpu.GpuDevice; + public class VgpuTypesInfo { + private boolean passthroughEnabled = true; + private GpuDevice.DeviceType deviceType; + private String parentBusAddress; + private String busAddress; + private String deviceId; + private String deviceName; + private String vendorId; + private String vendorName; private String modelName; private String groupName; + private String vmName; private Long maxHeads; private Long videoRam; private Long maxResolutionX; @@ -71,6 +83,89 @@ public void setMaxVmCapacity(Long maxCapacity) { this.maxCapacity = maxCapacity; } + public boolean isPassthroughEnabled() { + return passthroughEnabled; + } + + public void setPassthroughEnabled(boolean passthroughEnabled) { + this.passthroughEnabled = passthroughEnabled; + } + + public GpuDevice.DeviceType getDeviceType() { + return deviceType; + } + + public void setDeviceType(GpuDevice.DeviceType deviceType) { + this.deviceType = deviceType; + } + + public String getParentBusAddress() { + return parentBusAddress; + } + + public void setParentBusAddress(String parentBusAddress) { + this.parentBusAddress = parentBusAddress; + } + + public String getBusAddress() { + return busAddress; + } + + public void setBusAddress(String busAddress) { + this.busAddress = busAddress; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } + + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public VgpuTypesInfo(GpuDevice.DeviceType deviceType, String groupName, String modelName, String busAddress, String vendorId, String vendorName, String deviceId, String deviceName) { + this.deviceType = deviceType; + this.groupName = groupName; + this.modelName = modelName; + this.busAddress = busAddress; + this.deviceId = deviceId; + this.deviceName = deviceName; + this.vendorId = vendorId; + this.vendorName = vendorName; + } + public VgpuTypesInfo(String groupName, String modelName, Long videoRam, Long maxHeads, Long maxResolutionX, Long maxResolutionY, Long maxVgpuPerGpu, Long remainingCapacity, Long maxCapacity) { this.groupName = groupName; diff --git a/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java b/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java index 4afe080477b7..6e9cee06dd38 100644 --- a/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java @@ -16,7 +16,9 @@ // under the License. package com.cloud.agent.api.to; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import com.cloud.agent.api.VgpuTypesInfo; @@ -24,9 +26,23 @@ public class GPUDeviceTO { private String gpuGroup; private String vgpuType; + private int gpuCount; private HashMap> groupDetails = new HashMap>(); + private List gpuDevices = new ArrayList<>(); - public GPUDeviceTO( String gpuGroup, String vgpuType, HashMap> groupDetails) { + public GPUDeviceTO(String gpuGroup, String vgpuType, int gpuCount, + HashMap> groupDetails, + List gpuDevices) { + this.gpuGroup = gpuGroup; + this.vgpuType = vgpuType; + this.groupDetails = groupDetails; + this.gpuCount = gpuCount; + this.gpuDevices = gpuDevices; + + } + + public GPUDeviceTO(String gpuGroup, String vgpuType, + HashMap> groupDetails) { this.gpuGroup = gpuGroup; this.vgpuType = vgpuType; this.groupDetails = groupDetails; @@ -48,6 +64,14 @@ public void setVgpuType(String vgpuType) { this.vgpuType = vgpuType; } + public int getGpuCount() { + return gpuCount; + } + + public void setGpuCount(int gpuCount) { + this.gpuCount = gpuCount; + } + public HashMap> getGroupDetails() { return groupDetails; } @@ -56,4 +80,11 @@ public void setGroupDetails(HashMap> grou this.groupDetails = groupDetails; } + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } } diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 27f507d3a75b..08eed8e1f6f1 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -29,6 +29,9 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.GpuOffering; +import org.apache.cloudstack.gpu.VgpuProfile; import org.apache.cloudstack.ha.HAConfig; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap; @@ -376,6 +379,21 @@ public class EventTypes { public static final String EVENT_DISK_OFFERING_EDIT = "DISK.OFFERING.EDIT"; public static final String EVENT_DISK_OFFERING_DELETE = "DISK.OFFERING.DELETE"; + // GPU Offerings + public static final String EVENT_GPU_OFFERING_CREATE = "GPU.OFFERING.CREATE"; + public static final String EVENT_GPU_OFFERING_EDIT = "GPU.OFFERING.EDIT"; + public static final String EVENT_GPU_OFFERING_DELETE = "GPU.OFFERING.DELETE"; + + // GPU Cards + public static final String EVENT_GPU_CARD_CREATE = "GPU.CARD.CREATE"; + public static final String EVENT_GPU_CARD_EDIT = "GPU.CARD.EDIT"; + public static final String EVENT_GPU_CARD_DELETE = "GPU.CARD.DELETE"; + + // vGPU Profile + public static final String EVENT_VGPU_PROFILE_CREATE = "VGPU.PROFILE.CREATE"; + public static final String EVENT_VGPU_PROFILE_EDIT = "VGPU.PROFILE.EDIT"; + public static final String EVENT_VGPU_PROFILE_DELETE = "VGPU.PROFILE.DELETE"; + // Network offerings public static final String EVENT_NETWORK_OFFERING_CREATE = "NETWORK.OFFERING.CREATE"; public static final String EVENT_NETWORK_OFFERING_ASSIGN = "NETWORK.OFFERING.ASSIGN"; @@ -1008,6 +1026,21 @@ public class EventTypes { entityEventDetails.put(EVENT_DISK_OFFERING_EDIT, DiskOffering.class); entityEventDetails.put(EVENT_DISK_OFFERING_DELETE, DiskOffering.class); + // GPU Offerings + entityEventDetails.put(EVENT_GPU_OFFERING_CREATE, GpuOffering.class); + entityEventDetails.put(EVENT_GPU_OFFERING_EDIT, GpuOffering.class); + entityEventDetails.put(EVENT_GPU_OFFERING_DELETE, GpuOffering.class); + + // GPU Cards + entityEventDetails.put(EVENT_GPU_CARD_CREATE, GpuCard.class); + entityEventDetails.put(EVENT_GPU_CARD_EDIT, GpuCard.class); + entityEventDetails.put(EVENT_GPU_CARD_DELETE, GpuCard.class); + + // vGPU Profiles + entityEventDetails.put(EVENT_VGPU_PROFILE_CREATE, VgpuProfile.class); + entityEventDetails.put(EVENT_VGPU_PROFILE_EDIT, VgpuProfile.class); + entityEventDetails.put(EVENT_VGPU_PROFILE_DELETE, VgpuProfile.class); + // Network offerings entityEventDetails.put(EVENT_NETWORK_OFFERING_CREATE, NetworkOffering.class); entityEventDetails.put(EVENT_NETWORK_OFFERING_ASSIGN, NetworkOffering.class); diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java index acb7a9f1cf91..866090c8a29d 100644 --- a/api/src/main/java/com/cloud/offering/ServiceOffering.java +++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java @@ -36,6 +36,8 @@ public interface ServiceOffering extends InfrastructureEntity, InternalIdentity, static final String PURGE_DB_ENTITIES_KEY = "purge.db.entities"; + Integer getGpuCount(); + enum State { Inactive, Active, } @@ -142,4 +144,6 @@ enum StorageType { Boolean getDiskOfferingStrictness(); void setDiskOfferingStrictness(boolean diskOfferingStrictness); + + Long getGpuOfferingId(); } diff --git a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java index 64bd96d9a9bd..5e97b87ef1a3 100644 --- a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java +++ b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java @@ -42,7 +42,7 @@ enum EntityType { VM_SNAPSHOT(true), INSTANCE_GROUP(true), SSH_KEYPAIR(true), USER_DATA(true), NETWORK(true), VPC(true), PUBLIC_IP_ADDRESS(true), VPN_CUSTOMER_GATEWAY(true), TEMPLATE(true), ISO(true), KUBERNETES_CLUSTER(true), - SERVICE_OFFERING(false), DISK_OFFERING(false), NETWORK_OFFERING(false), + SERVICE_OFFERING(false), DISK_OFFERING(false), NETWORK_OFFERING(false), GPU_OFFERING(false), ZONE(false), POD(false), CLUSTER(false), HOST(false), DOMAIN(false), PRIMARY_STORAGE(false), SECONDARY_STORAGE(false), VR(false), SYSTEM_VM(false), AUTOSCALE_VM_GROUP(true), MANAGEMENT_SERVER(false), OBJECT_STORAGE(false); @@ -83,6 +83,7 @@ static public List getNotAllowedTypesForNonAdmins(RoleType roleType) list.add(EntityType.DOMAIN); list.add(EntityType.SERVICE_OFFERING); list.add(EntityType.DISK_OFFERING); + list.add(EntityType.GPU_OFFERING); } return list; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java index 4cd89c877b24..3ee477e45efb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java @@ -70,6 +70,7 @@ public enum ApiCommandResourceType { Project(com.cloud.projects.Project.class), Domain(com.cloud.domain.Domain.class), DiskOffering(com.cloud.offering.DiskOffering.class), + GpuOffering(org.apache.cloudstack.gpu.GpuOffering.class), ServiceOffering(com.cloud.offering.ServiceOffering.class), NetworkOffering(com.cloud.offering.NetworkOffering.class), VpcOffering(com.cloud.network.vpc.VpcOffering.class), 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 f5a1636c30e6..520d4615e7c4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -68,6 +68,7 @@ public class ApiConstants { public static final String BOOTABLE = "bootable"; public static final String BIND_DN = "binddn"; public static final String BIND_PASSWORD = "bindpass"; + public static final String BUS_ADDRESS = "busaddress"; public static final String BYTES_READ_RATE = "bytesreadrate"; public static final String BYTES_READ_RATE_MAX = "bytesreadratemax"; public static final String BYTES_READ_RATE_MAX_LENGTH = "bytesreadratemaxlength"; @@ -160,6 +161,7 @@ public class ApiConstants { public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; public static final String DEVICE_ID = "deviceid"; + public static final String DEVICE_NAME = "devicename"; public static final String DIRECT_DOWNLOAD = "directdownload"; public static final String DISK = "disk"; public static final String DISK_OFFERING_ID = "diskofferingid"; @@ -388,6 +390,11 @@ public class ApiConstants { public static final String OS_TYPE_ID = "ostypeid"; public static final String OS_DISPLAY_NAME = "osdisplayname"; public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor"; + public static final String GPU_CARD_ID = "gpucardid"; + public static final String GPU_CARD_NAME = "gpucardname"; + public static final String GPU_COUNT = "gpucount"; + public static final String GPU_OFFERING_ID = "gpuofferingid"; + public static final String GPU_OFFERING_NAME = "gpuofferingname"; public static final String GUEST_OS_LIST = "guestoslist"; public static final String GUEST_OS_COUNT = "guestoscount"; public static final String OS_MAPPING_CHECK_ENABLED = "osmappingcheckenabled"; @@ -564,6 +571,11 @@ public class ApiConstants { public static final String USE_VIRTUAL_ROUTER_IP_RESOLVER = "userouteripresolver"; public static final String UPDATE_IN_SEQUENCE = "updateinsequence"; public static final String VALUE = "value"; + public static final String VENDOR_ID = "vendorid"; + public static final String VENDOR_NAME = "vendorname"; + public static final String VGPU_PROFILE_ID = "vgpuprofileid"; + public static final String VGPU_PROFILE_NAME = "vgpuprofilename"; + public static final String VGPU_PROFILE_IDS = "vgpuprofileids"; public static final String VIRTUAL_MACHINE_ID = "virtualmachineid"; public static final String VIRTUAL_MACHINE_IDS = "virtualmachineids"; public static final String VIRTUAL_MACHINE_NAME = "virtualmachinename"; @@ -572,6 +584,7 @@ public class ApiConstants { public static final String VIRTUAL_MACHINE_TYPE = "virtualmachinetype"; public static final String VIRTUAL_MACHINE_STATE = "vmstate"; public static final String VIRTUAL_MACHINES = "virtualmachines"; + public static final String VRAM_SIZE = "vramsize"; public static final String USAGE_ID = "usageid"; public static final String USAGE_TYPE = "usagetype"; public static final String INCLUDE_TAGS = "includetags"; @@ -1322,7 +1335,7 @@ public enum HostDetails { } public enum VMDetails { - all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp, vnfnics; + all, group, nics, stats, secgrp, tmpl, servoff, diskoff, gpuoff, backoff, iso, volume, min, affgrp, vnfnics; } public enum DomainDetails { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java new file mode 100644 index 000000000000..d14d20bac632 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java @@ -0,0 +1,131 @@ +// 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.gpu; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +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.GpuCardResponse; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "createGpuCard", description = "Creates a GPU card definition in the system", + responseObject = GpuCardResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0") +public class CreateGpuCardCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.DEVICE_ID, type = CommandType.STRING, required = true, + description = "the device ID of the GPU card") + private String deviceId; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, required = true, + description = "the device name of the GPU card") + private String deviceName; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description + = "the display name of the GPU card") + private String name; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, required = true, + description = "the vendor name of the GPU card") + private String vendorName; + + @Parameter(name = ApiConstants.VENDOR_ID, type = CommandType.STRING, required = true, + description = "the vendor ID of the GPU card") + private String vendorId; + + @Parameter(name = ApiConstants.VRAM_SIZE, type = CommandType.LONG, description = "the VRAM " + + "size of " + + "the GPU " + + "card in MB") + private Long vramSize; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public String getName() { + return name; + } + + public String getVendorName() { + return vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public Long getVramSize() { + return vramSize; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, + ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + GpuCard gpuCard = gpuService.createGpuCard(this); + if (gpuCard != null) { + GpuCardResponse response = new GpuCardResponse(gpuCard); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create GPU card: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuOfferingCmd.java new file mode 100644 index 000000000000..4ff986207277 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuOfferingCmd.java @@ -0,0 +1,106 @@ +// 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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +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.GpuOfferingResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "createGpuOffering", description = "Creates a GPU offering", + responseObject = GpuOfferingResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class CreateGpuOfferingCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the name of the GPU offering") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, + description = "the description of the GPU offering") + private String description; + + @Parameter(name = ApiConstants.VGPU_PROFILE_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = VgpuProfileResponse.class, + description = "the list of vGPU profile IDs to include in the offering") + private List vgpuProfileIds; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getVgpuProfileIds() { + return vgpuProfileIds; + } + + @Override + public void execute() { + try { + GpuOfferingResponse response = gpuService.createGpuOffering(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create GPU offering"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create GPU offering: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.GpuOffering; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java new file mode 100644 index 000000000000..801d195da2a0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.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 org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "createVgpuProfile", description = "Creates a vGPU profile in the system", responseObject = VgpuProfileResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class CreateVgpuProfileCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the vGPU profile") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "the description of the vGPU profile") + private String description; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + required = true, description = "the GPU card ID associated with this GPU device") + private Long cardId; + + @Parameter(name = ApiConstants.VRAM_SIZE, type = CommandType.LONG, description = "the VRAM size in MB") + private Long vramSize; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Long getCardId() { + return cardId; + } + + public Long getVramSize() { + return vramSize; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + VgpuProfileResponse response = gpuService.createVgpuProfile(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create vGPU profile: " + e.getMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java new file mode 100644 index 000000000000..c08947371e9f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java @@ -0,0 +1,80 @@ +// 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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "deleteGpuCard", description = "Deletes a GPU card definition from the system", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class DeleteGpuCardCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + required = true, description = "the ID of the GPU card") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + boolean success = gpuService.deleteGpuCard(this); + if (success) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete GPU card: " + e.getMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java new file mode 100644 index 000000000000..8338964ab4b6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java @@ -0,0 +1,80 @@ +// 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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "deleteVgpuProfile", description = "Deletes a vGPU profile from the system", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class DeleteVgpuProfileCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + required = true, description = "the ID of the vGPU profile") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + boolean success = gpuService.deleteVgpuProfile(this); + if (success) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete vGPU profile: " + e.getMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DisableGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DisableGpuDeviceCmd.java new file mode 100644 index 000000000000..6decd482a45b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DisableGpuDeviceCmd.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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "disableGpuDevice", description = "Disables a GPU device", responseObject = + SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class DisableGpuDeviceCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = + GpuDeviceResponse.class, + required = true, description = "the ID of the GPU device") + private Long id; + + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + @Override + public void execute() { + try { + if (gpuService.disableGpuDevice(this)) { + SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to disable GPU device"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to disable GPU device: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java new file mode 100644 index 000000000000..211af65809fd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java @@ -0,0 +1,68 @@ +// 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.gpu; + +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.GpuDeviceResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "discoverGpuDevices", + description = "Discovers available GPU devices on a host", + responseObject = GpuDeviceResponse.class, requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, since = "4.21.0") +public class DiscoverGpuDevicesCmd extends BaseListCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HostResponse.class, + required = true, description = "ID of the host") + private Long id; + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation ////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + CallContext.current().setEventDetails("Discovering GPU Devices on host id: " + getId()); + ListResponse response = gpuService.discoverGpuDevices(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/EnableGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/EnableGpuDeviceCmd.java new file mode 100644 index 000000000000..eff1a9f03202 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/EnableGpuDeviceCmd.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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "enableGpuDevice", description = "Enables a GPU device", responseObject = + SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class EnableGpuDeviceCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = + GpuDeviceResponse.class, + required = true, description = "the ID of the GPU device") + private Long id; + + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + @Override + public void execute() { + try { + if (gpuService.enableGpuDevice(this)) { + SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to enable GPU device"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to enable GPU device: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmd.java new file mode 100644 index 000000000000..ddd0938eae5c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmd.java @@ -0,0 +1,92 @@ +// 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.gpu; + +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.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "listGpuDevices", description = "Lists all available GPU devices", responseObject = GpuDeviceResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0") +public class ListGpuDevicesCmd extends BaseListCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuDeviceResponse.class, + description = "ID of the GPU device") + private Long id; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, + description = "the host ID where the GPU device is attached") + private Long hostId; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "the GPU card ID associated with this GPU device") + private Long gpuCardId; + + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + description = "the vGPU profile ID assigned to this GPU device") + private Long vgpuProfileId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getHostId() { + return hostId; + } + + public Long getGpuCardId() { + return gpuCardId; + } + + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation ////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + CallContext.current().setEventDetails("Listing GPU devices"); + ListResponse response = gpuService.listGpuDevices(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java new file mode 100644 index 000000000000..9c95a015e1b7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java @@ -0,0 +1,109 @@ +// 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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "updateGpuCard", description = "Updates a GPU card definition in the system", responseObject = GpuCardResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class UpdateGpuCardCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + required = true, description = "the ID of the GPU card") + private Long id; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, description = "the device name of the GPU card") + private String deviceName; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the display name of the GPU card") + private String name; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the GPU card") + private String vendorName; + + @Parameter(name = ApiConstants.VRAM_SIZE, type = CommandType.LONG, description = "the VRAM size of the GPU card in MB") + private Long vramSize; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getDeviceName() { + return deviceName; + } + + public String getName() { + return name; + } + + public String getVendorName() { + return vendorName; + } + + public Long getVramSize() { + return vramSize; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + GpuCard gpuCard = gpuService.updateGpuCard(this); + if (gpuCard != null) { + GpuCardResponse response = new GpuCardResponse(gpuCard); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update GPU card: " + e.getMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuOfferingCmd.java new file mode 100644 index 000000000000..bbfebbcdd837 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuOfferingCmd.java @@ -0,0 +1,139 @@ +// 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.gpu; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +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.GpuOfferingResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuOffering; +import org.apache.cloudstack.gpu.GpuService; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "updateGpuOffering", description = "Updates a GPU offering", + responseObject = GpuOfferingResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class UpdateGpuOfferingCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = + GpuOfferingResponse.class, + required = true, description = "the ID of the GPU offering") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, + description = "the name of the GPU offering") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, + description = "the description of the GPU offering") + private String description; + + @Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER, description = "sort key of the disk offering, integer") + private Integer sortKey; + + @Parameter(name = ApiConstants.VGPU_PROFILE_IDS, type = CommandType.LIST, + collectionType = CommandType.UUID, entityType = VgpuProfileResponse.class, + description = "the list of vGPU profile IDs to include in the offering") + private List vgpuProfileIds; + + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the GPU offering") + private String state; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Integer getSortKey() { + return sortKey; + } + + public List getVgpuProfileIds() { + return vgpuProfileIds; + } + + public GpuOffering.State getState() { + GpuOffering.State gpuOfferingState = EnumUtils.getEnumIgnoreCase(GpuOffering.State.class, state); + if (StringUtils.isNotBlank(state) && gpuOfferingState == null) { + throw new InvalidParameterValueException("Invalid state value: " + state); + } + return gpuOfferingState; + } + + @Override + public void execute() { + try { + GpuOfferingResponse response = gpuService.updateGpuOffering(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to update GPU offering"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to update GPU offering: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.GpuOffering; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java new file mode 100644 index 000000000000..1bb4ab5e67b1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java @@ -0,0 +1,99 @@ +// 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.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "updateVgpuProfile", description = "Updates a vGPU profile in the system", responseObject = VgpuProfileResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class UpdateVgpuProfileCmd extends BaseCmd { + + @Inject + private GpuService gpuService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + required = true, description = "the ID of the vGPU profile") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the vGPU profile") + private String profileName; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "the description of the vGPU profile") + private String description; + + @Parameter(name = ApiConstants.VRAM_SIZE, type = CommandType.LONG, description = "the VRAM size in MB") + private Long vramSize; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getProfileName() { + return profileName; + } + + public String getDescription() { + return description; + } + + public Long getVramSize() { + return vramSize; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + VgpuProfileResponse response = gpuService.updateVgpuProfile(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update vGPU profile: " + e.getMessage()); + } + } +} 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 4cadaad0e47e..0021946567a3 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 @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.GpuOfferingResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.VsphereStoragePoliciesResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -263,6 +264,19 @@ public class CreateServiceOfferingCmd extends BaseCmd { description = "Lease expiry action, valid values are STOP and DESTROY") private String leaseExpiryAction; + @Parameter(name = ApiConstants.GPU_OFFERING_ID, + type = CommandType.UUID, + entityType = GpuOfferingResponse .class, + description = "the ID of the GPU offering to which service offering should be mapped", + since = "4.21") + private Long gpuOfferingId; + + @Parameter(name = ApiConstants.GPU_COUNT, + type = CommandType.INTEGER, + description = "Count of GPUs to be assigned to the VM. This is applicable only for GPU enabled service offerings", + since = "4.21") + private Integer gpuCount; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -517,6 +531,14 @@ public boolean isPurgeResources() { return Boolean.TRUE.equals(purgeResources); } + public Long getGpuOfferingId() { + return gpuOfferingId; + } + + public Integer getGpuCount() { + return gpuCount; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java new file mode 100644 index 000000000000..f9910ac4c5be --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java @@ -0,0 +1,107 @@ +// 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.user.gpu; + +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.GpuCardResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "listGpuCards", description = "Lists all available GPU cards", responseObject + = GpuCardResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0") +public class ListGpuCardsCmd extends BaseListCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "ID of the GPU card") + private Long id; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "vendor " + + "name " + + "of " + + "the " + + "GPU " + + "card") + private String vendorName; + + @Parameter(name = ApiConstants.VENDOR_ID, type = CommandType.STRING, description = "vendor ID" + + " of the" + + " GPU " + + "card") + private String vendorId; + + @Parameter(name = ApiConstants.DEVICE_ID, type = CommandType.STRING, description = "device ID" + + " of the" + + " GPU " + + "card") + private String deviceId; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, description = "device " + + "name " + + "of " + + "the " + + "GPU " + + "card") + private String deviceName; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getVendorName() { + return vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = gpuService.listGpuCards(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuOfferingsCmd.java new file mode 100644 index 000000000000..381adc925ece --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuOfferingsCmd.java @@ -0,0 +1,90 @@ +// 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.user.gpu; + +import com.cloud.utils.StringUtils; +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.GpuOfferingResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.gpu.GpuOffering; +import org.apache.cloudstack.gpu.GpuService; +import org.apache.commons.lang3.EnumUtils; + +import javax.inject.Inject; + +import static org.apache.cloudstack.gpu.GpuOffering.State.Active; + +@APICommand(name = "listGpuOfferings", description = "Lists all available GPU offerings", + responseObject = GpuOfferingResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0") +public class ListGpuOfferingsCmd extends BaseListCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuOfferingResponse.class, + description = "ID of the GPU offering") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the GPU offering") + private String name; + + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, + description = "Filter by state of the gpu offering. Defaults to 'Active'. If set to 'all' shows both Active & Inactive offerings.") + private String state; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public GpuOffering.State getState() { + if (StringUtils.isBlank(state)) { + return Active; + } + GpuOffering.State gpuOfferingState = EnumUtils.getEnumIgnoreCase(GpuOffering.State.class, this.state); + if (!this.state.equalsIgnoreCase("all") && gpuOfferingState == null) { + throw new IllegalArgumentException("Invalid state value: " + this.state); + } + return gpuOfferingState; + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = gpuService.listGpuOfferings(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java new file mode 100644 index 000000000000..351c8fd572bb --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java @@ -0,0 +1,82 @@ +// 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.user.gpu; + +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.GpuCardResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuService; + +import javax.inject.Inject; + +@APICommand(name = "listVgpuProfiles", description = "Lists all available vGPU profiles", + responseObject = VgpuProfileResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0") +public class ListVgpuProfilesCmd extends BaseListCmd { + + @Inject + private GpuService gpuService; + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = + VgpuProfileResponse.class, + description = "ID of the vGPU profile") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the " + + "vGPU profile") + private String name; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = + GpuCardResponse.class, + description = "the GPU card ID associated with this GPU device") + private Long cardId; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Long getCardId() { + return cardId; + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = gpuService.listVgpuProfiles(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} 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 1b3f531e370d..3fe2b77208be 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 @@ -22,6 +22,7 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GpuOfferingResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.TemplateResponse; @@ -110,6 +111,13 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC since = "4.20.0") private Long templateId; + @Parameter(name = ApiConstants.GPU_OFFERING_ID, + type = CommandType.UUID, + entityType = GpuOfferingResponse.class, + description = "The ID of the GPU offering that listed offerings must support", + since = "4.21.0") + private Long gpuOfferingId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -171,6 +179,10 @@ public Long getTemplateId() { return templateId; } + public Long getGpuOfferingId() { + return gpuOfferingId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java new file mode 100644 index 000000000000..9e554f6d6b14 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java @@ -0,0 +1,122 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuCard; + +@EntityReference(value = GpuCard.class) +public class GpuCardResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the GPU card") + protected String id; + + @SerializedName("deviceid") + @Param(description = "the device ID of the GPU card") + protected String deviceId; + + @SerializedName("devicename") + @Param(description = "the device name of the GPU card") + protected String deviceName; + + @SerializedName("name") + @Param(description = "the display name of the GPU card") + protected String name; + + @SerializedName("vendorname") + @Param(description = "the vendor name of the GPU card") + protected String vendorName; + + @SerializedName("vendorid") + @Param(description = "the vendor ID of the GPU card") + protected String vendorId; + + @SerializedName("vramsize") + @Param(description = "the VRAM size of the GPU card in MB") + protected Long vramSize; + + public GpuCardResponse(GpuCard gpuCard) { + super("gpucard"); + id = gpuCard.getUuid(); + deviceId = gpuCard.getDeviceId(); + deviceName = gpuCard.getDeviceName(); + name = gpuCard.getName(); + vendorName = gpuCard.getVendorName(); + vendorId = gpuCard.getVendorId(); + vramSize = gpuCard.getVramSize(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } + + public Long getVramSize() { + return vramSize; + } + + public void setVramSize(Long vramSize) { + this.vramSize = vramSize; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java new file mode 100644 index 000000000000..901f1134b28f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java @@ -0,0 +1,166 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuDevice; + +@EntityReference(value = GpuDevice.class) +public class GpuDeviceResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the GPU device") + private String id; + + @SerializedName(ApiConstants.BUS_ADDRESS) + @Param(description = "bus address of the GPU device") + private String bussAddress; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "the host ID where the GPU device is attached") + private String hostId; + + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "the host name where the GPU device is attached") + private String hostName; + + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the GPU card ID associated with this GPU device") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the GPU card name associated with this GPU device") + private String gpuCardName; + + @SerializedName(ApiConstants.VGPU_PROFILE_ID) + @Param(description = "the vGPU profile ID assigned to this GPU device") + private String vgpuProfileId; + + @SerializedName(ApiConstants.VGPU_PROFILE_NAME) + @Param(description = "the vGPU profile name assigned to this GPU device") + private String vgpuProfileName; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the vGPU profile ID assigned to this GPU device") + private String vmId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "the vGPU profile name assigned to this GPU device") + private String vmName; + + @SerializedName(ApiConstants.STATE) + @Param(description = "the vGPU profile name assigned to this GPU device") + private GpuDevice.State state; + + + public GpuDeviceResponse() { + // Empty constructor for serialization + super("gpudevice"); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBussAddress() { + return bussAddress; + } + + public void setBussAddress(String bussAddress) { + this.bussAddress = bussAddress; + } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getGpuCardId() { + return gpuCardId; + } + + public void setGpuCardId(String gpuCardId) { + this.gpuCardId = gpuCardId; + } + + public String getGpuCardName() { + return gpuCardName; + } + + public void setGpuCardName(String gpuCardName) { + this.gpuCardName = gpuCardName; + } + + public String getVgpuProfileId() { + return vgpuProfileId; + } + + public void setVgpuProfileId(String vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + public String getVgpuProfileName() { + return vgpuProfileName; + } + + public void setVgpuProfileName(String vgpuProfileName) { + this.vgpuProfileName = vgpuProfileName; + } + + public String getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public GpuDevice.State getState() { + return state; + } + + public void setState(GpuDevice.State state) { + this.state = state; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GpuOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GpuOfferingResponse.java new file mode 100644 index 000000000000..f1f3e2bc9970 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GpuOfferingResponse.java @@ -0,0 +1,116 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuOffering; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@EntityReference(value = GpuOffering.class) +public class GpuOfferingResponse extends BaseResponseWithAnnotations { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the GPU offering") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the GPU offering") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the GPU offering") + private String description; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the date this GPU offering was created") + private Date created; + + @SerializedName("state") + @Param(description = "indicates if the GPU offering is enabled") + private GpuOffering.State state; + + @SerializedName("vgpuprofiles") + @Param(description = "the list of vGPU profiles included in this offering", responseObject = VgpuProfileResponse.class) + private List vgpuProfiles; + + public GpuOfferingResponse() { + } + + public GpuOfferingResponse(GpuOffering gpuOffering) { + setObjectName("gpuoffering"); + this.id = gpuOffering.getUuid(); + this.name = gpuOffering.getName(); + this.description = gpuOffering.getDescription(); + this.created = gpuOffering.getCreated(); + this.state = gpuOffering.getState(); + this.vgpuProfiles = new ArrayList<>(); + } + + 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 String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public GpuOffering.State getState() { + return state; + } + + public void setState(GpuOffering.State state) { + this.state = state; + } + + public List getVgpuProfiles() { + return vgpuProfiles; + } + + public void setVgpuProfiles(List vgpuProfiles) { + this.vgpuProfiles = vgpuProfiles; + } +} 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 ca9358e2e5e8..aca8aea5c219 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 @@ -234,6 +234,18 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "true if virtual machine root disk will be encrypted on storage", since = "4.18") private Boolean encryptRoot; + @SerializedName(ApiConstants.GPU_OFFERING_ID) + @Param(description = "the ID of the gpu offering to which service offering is linked", since = "4.21") + private String gpuOfferingId; + + @SerializedName(ApiConstants.GPU_OFFERING_NAME) + @Param(description = "the name of the gpu offering", since = "4.21") + private String gpuOfferingName; + + @SerializedName(ApiConstants.GPU_COUNT) + @Param(description = "the count of GPUs to attach ", since = "4.21") + private Integer gpuCount; + @SerializedName(ApiConstants.PURGE_RESOURCES) @Param(description = "Whether to cleanup VM and its associated resource upon expunge", since = "4.20") private Boolean purgeResources; @@ -584,6 +596,30 @@ public String getDiskOfferingDisplayText() { public void setEncryptRoot(Boolean encrypt) { this.encryptRoot = encrypt; } + public String getGpuOfferingName() { + return gpuOfferingName; + } + + public void setGpuOfferingName(String gpuOfferingName) { + this.gpuOfferingName = gpuOfferingName; + } + + public String getGpuOfferingId() { + return gpuOfferingId; + } + + public void setGpuOfferingId(String gpuOfferingId) { + this.gpuOfferingId = gpuOfferingId; + } + + public Integer getGpuCount() { + return gpuCount; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } + public void setPurgeResources(Boolean purgeResources) { this.purgeResources = purgeResources; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index d873bc65709b..61b637ebf39f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -1,4 +1,4 @@ -// Licensed to the Apache Software Foundation (ASF) under one + // 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 @@ -182,6 +182,18 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the name of the disk offering of the virtual machine. This parameter should not be used for retrieving disk offering details of DATA volumes. Use listVolumes API instead", since = "4.4") private String diskOfferingName; + @SerializedName(ApiConstants.GPU_OFFERING_ID) + @Param(description = "the ID of the GPU offering of the virtual machine.", since = "4.21") + private String gpuOfferingId; + + @SerializedName(ApiConstants.GPU_OFFERING_NAME) + @Param(description = "the name of the GPU offering of the virtual machine.", since = "4.21") + private String gpuOfferingName; + + @SerializedName(ApiConstants.GPU_COUNT) + @Param(description = "the count of GPUs on the virtual machine", since = "4.21") + private Integer gpuCount; + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) @Param(description = "the ID of the backup offering of the virtual machine", since = "4.14") private String backupOfferingId; @@ -565,6 +577,18 @@ public String getDiskOfferingName() { return diskOfferingName; } + public String getGpuOfferingId() { + return gpuOfferingId; + } + + public String getGpuOfferingName() { + return gpuOfferingName; + } + + public Integer getGpuCount() { + return gpuCount; + } + public String getBackupOfferingId() { return backupOfferingId; } @@ -847,6 +871,18 @@ public void setDiskOfferingName(String diskOfferingName) { this.diskOfferingName = diskOfferingName; } + public void setGpuOfferingId(String gpuOfferingId) { + this.gpuOfferingId = gpuOfferingId; + } + + public void setGpuOfferingName(String gpuOfferingName) { + this.gpuOfferingName = gpuOfferingName; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } + public void setBackupOfferingId(String backupOfferingId) { this.backupOfferingId = backupOfferingId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java new file mode 100644 index 000000000000..f40fc0a94883 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java @@ -0,0 +1,67 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.VgpuProfile; + +@EntityReference(value = VgpuProfile.class) +public class VgpuProfileResponse extends GpuCardResponse { + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the vGPU profile") + private String description; + + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the ID of the GPU card associated with this vGPU profile") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the name of the vGPU profile") + private String gpuCardName; + + public VgpuProfileResponse(VgpuProfile vgpuProfile, GpuCard gpuCard) { + super(gpuCard); + id = vgpuProfile.getUuid(); + name = vgpuProfile.getName(); + description = vgpuProfile.getDescription(); + gpuCardId = gpuCard.getUuid(); + gpuCardName = gpuCard.getName(); + vramSize = vgpuProfile.getVramSize(); + setObjectName("vgpuprofile"); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java new file mode 100644 index 000000000000..5c2dd19733c3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java @@ -0,0 +1,67 @@ +// 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.gpu; + +import java.util.Date; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +/** + * GPU card interface representing a physical GPU card model + */ +public interface GpuCard extends InternalIdentity, Identity { + /** + * @return the UUID of the GPU card + */ + String getUuid(); + + /** + * @return the device ID of the GPU card + */ + String getDeviceId(); + + /** + * @return the device name of the GPU card + */ + String getDeviceName(); + + /** + * @return the name of the GPU card + */ + String getName(); + + /** + * @return the vendor name of the GPU card + */ + String getVendorName(); + + /** + * @return the vendor ID of the GPU card + */ + String getVendorId(); + + /** + * @return the VRAM size of the GPU card in MB + */ + Long getVramSize(); + + /** + * @return the date when the GPU card was created + */ + Date getCreated(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java new file mode 100644 index 000000000000..ad308e85a0a1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java @@ -0,0 +1,42 @@ +// 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.gpu; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +/** + * GPU device interface representing a physical GPU device + */ +public interface GpuDevice extends InternalIdentity, Identity { + + enum State { + Allocated, + Free, + Disabled, + HasVGPUs, + } + + enum DeviceType { + PCI, + MDEV, + } + + long getHostId(); + + State getState(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuOffering.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuOffering.java new file mode 100644 index 000000000000..b38b1b6c2994 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuOffering.java @@ -0,0 +1,55 @@ +// 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.gpu; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +/** + * GPU offering interface representing a collection of vGPU profiles and GPU cards + */ +public interface GpuOffering extends InternalIdentity, Identity { + + enum State { + Inactive, Active, + } + + /** + * @return the name of the GPU offering + */ + String getName(); + + /** + * @return the description of the GPU offering + */ + String getDescription(); + + State getState(); + + /** + * @return list of vGPU profiles included in this offering + */ + List getVgpuProfiles(); + + /** + * @return the date when the GPU offering was created + */ + Date getCreated(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java new file mode 100644 index 000000000000..1cecf536f746 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java @@ -0,0 +1,171 @@ +// 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.gpu; + +import com.cloud.agent.api.VgpuTypesInfo; +import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.host.Host; +import com.cloud.utils.component.Manager; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuOfferingCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.DisableGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.DiscoverGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.EnableGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.ListGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuOfferingCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateVgpuProfileCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuCardsCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuOfferingsCmd; +import org.apache.cloudstack.api.command.user.gpu.ListVgpuProfilesCmd; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.GpuOfferingResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.framework.config.ConfigKey; + +import java.util.HashMap; +import java.util.List; + +public interface GpuService extends Manager { + + ConfigKey GpuDetachOnStop = new ConfigKey<>(Boolean.class, + "gpu.detach.on.stop", + "Advanced", + "false", + "Whether to detach GPU devices from VM on stop or keep them allocated", + true, ConfigKey.Scope.Domain, null); + + /** + * Creates a GPU card in the database + * + * @param cmd the API command + * @return the created GPU card object + */ + GpuCard createGpuCard(CreateGpuCardCmd cmd); + + /** + * Updates a GPU card in the database + * + * @param cmd the API command + * @return the updated GPU card object + */ + GpuCard updateGpuCard(UpdateGpuCardCmd cmd); + + /** + * Deletes a GPU card from the database + * + * @param cmd the API command + * @return true if successful, false otherwise + */ + boolean deleteGpuCard(DeleteGpuCardCmd cmd); + + /** + * Creates a vGPU profile in the database + * + * @param cmd the API command + * @return the created vGPU profile object + */ + VgpuProfileResponse createVgpuProfile(CreateVgpuProfileCmd cmd); + + /** + * Updates a vGPU profile in the database + * + * @param cmd the API command + * @return the updated vGPU profile object + */ + VgpuProfileResponse updateVgpuProfile(UpdateVgpuProfileCmd cmd); + + /** + * Deletes a vGPU profile from the database + * + * @param cmd the API command + * @return true if successful, false otherwise + */ + boolean deleteVgpuProfile(DeleteVgpuProfileCmd cmd); + + /** + * Lists GPU cards based on criteria + * + * @param cmd the API command + * @return a list of GPU card responses + */ + ListResponse listGpuCards(ListGpuCardsCmd cmd); + + /** + * Lists vGPU profiles based on criteria + * + * @param cmd the API command + * @return a list of vGPU profile responses + */ + ListResponse listVgpuProfiles(ListVgpuProfilesCmd cmd); + + /** + * Lists GPU devices based on criteria + * + * @param cmd the API command + * @return a list of GPU device responses + */ + ListResponse listGpuDevices(ListGpuDevicesCmd cmd); + + boolean disableGpuDevice(DisableGpuDeviceCmd cmd); + + boolean enableGpuDevice(EnableGpuDeviceCmd cmd); + + void deallocateGpuDevicesForVmOnHost(long vm, GpuDevice.State state); + + void assignGpuDevicesToVmOnHost(long vmId, long hostId, List gpuDevices); + + ListResponse discoverGpuDevices(DiscoverGpuDevicesCmd cmd); + + /** + * Creates a GPU offering in the database + * + * @param cmd the API command + * @return the created GPU offering + */ + GpuOfferingResponse createGpuOffering(CreateGpuOfferingCmd cmd); + + /** + * Updates a GPU offering in the database + * + * @param cmd the API command + * @return the updated GPU offering + */ + GpuOfferingResponse updateGpuOffering(UpdateGpuOfferingCmd cmd); + + /** + * Lists GPU offerings based on criteria + * + * @param listGpuOfferingsCmd the API command + * @return a list of GPU offering responses + */ + ListResponse listGpuOfferings(ListGpuOfferingsCmd listGpuOfferingsCmd); + + boolean isGPUDeviceAvailable(Host host, Long vmId, GpuOffering gpuOffering, int gpuCount); + + GPUDeviceTO getGPUDevice(VirtualMachine vm, GpuOffering gpuOffering, int gpuCount); + + HashMap> getGpuGroupDetailsFromGpuDevices(Host host); + + void addGpuDevicesToHost(Host host, List newGpuDevicesInfo); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java b/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java new file mode 100644 index 000000000000..543cec4d9986 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java @@ -0,0 +1,54 @@ +// 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.gpu; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +/** + * vGPU profile interface representing a virtualized GPU profile + */ +public interface VgpuProfile extends InternalIdentity, Identity { + /** + * @return the UUID of the vGPU profile + */ + String getUuid(); + + /** + * @return the name of the vGPU profile + */ + String getName(); + + /** + * @return the description of the vGPU profile + */ + String getDescription(); + + /** + * @return the date when the vGPU profile was created + */ + Date getCreated(); + + Long getCardId(); + + /** + * @return the VRAM size in MB + */ + Long getVramSize(); +} diff --git a/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java b/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java index 8b3cd44e207f..5bf70ed086f6 100644 --- a/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java @@ -19,7 +19,9 @@ package com.cloud.agent.api; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import com.cloud.agent.api.LogLevel.Log4jLevel; @@ -27,6 +29,7 @@ public class GetGPUStatsAnswer extends Answer { private HashMap> groupDetails; + private List gpuDevices = new ArrayList<>(); public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final HashMap> groupDetails) { super(cmd); @@ -37,7 +40,21 @@ public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final boolean success, fi super(cmd, success, details); } + public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final List gpuDevices) { + super(cmd); + this.gpuDevices = gpuDevices; + } + + public HashMap> getGroupDetails() { return groupDetails; } + + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } } diff --git a/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java b/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java index 286fced0c58a..068196aabe54 100644 --- a/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java @@ -45,6 +45,7 @@ public class StartupRoutingCommand extends StartupCommand { List hostTags = new ArrayList(); String hypervisorVersion; HashMap> groupDetails = new HashMap>(); + List gpuDevices = new ArrayList<>(); private Boolean hostHealthCheckResult; public StartupRoutingCommand() { @@ -179,7 +180,7 @@ public void setHostTags(List hostTags) { this.hostTags = hostTags; } - public HashMap> getGpuGroupDetails() { + public HashMap> getGpuGroupDetails() { return groupDetails; } @@ -187,6 +188,14 @@ public void setGpuGroupDetails(HashMap> g this.groupDetails = groupDetails; } + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } + public boolean getSupportsClonedVolumes() { return supportsClonedVolumes; } diff --git a/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java b/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java index a2bb5945a9d1..60b0167758c7 100644 --- a/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java +++ b/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java @@ -50,5 +50,4 @@ Map> find(long dcId, Long podId, L public void putParam(Map params); ServerResource reloadResource(HostVO host); - } diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 83f9768a62ac..9c448afad98e 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -21,8 +21,11 @@ import java.util.List; import java.util.Map; + import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -196,20 +199,27 @@ public interface ResourceManager extends ResourceService, Configurable { /** * Check if host has GPU devices available - * @param hostId the host to be checked + * @param host the host to be checked * @param groupName: gpuCard name * @param vgpuType the VGPU type + * @param gpuCount the number of GPUs requested * @return true when the host has the capacity with given VGPU type */ boolean isGPUDeviceAvailable(Host host, String groupName, String vgpuType); + + boolean isGPUDeviceAvailable(Host host, Long vmId, GpuOfferingVO gpuOffering, int gpuCount); + /** * Get available GPU device - * @param hostId the host to be checked + * @param vm the vm for which GPU device is requested * @param groupName: gpuCard name * @param vgpuType the VGPU type + * @param gpuCount the number of GPUs requested * @return GPUDeviceTO[] */ + GPUDeviceTO getGPUDevice(VirtualMachine vm, GpuOfferingVO gpuOffering, int gpuCount); + GPUDeviceTO getGPUDevice(long hostId, String groupName, String vgpuType); /** @@ -217,6 +227,7 @@ public interface ResourceManager extends ResourceService, Configurable { * @param hostId, the host to be checked * @param groupName: gpuCard name * @param vgpuType the VGPU type + * @param the number of GPUs requested * @return List of HostGpuGroupsVO. */ List listAvailableGPUDevice(long hostId, String groupName, String vgpuType); @@ -228,6 +239,15 @@ public interface ResourceManager extends ResourceService, Configurable { */ void updateGPUDetails(long hostId, HashMap> groupDetails); + /** + * Update GPU device details (post VM deployment) + * @param hostId, the dest host Id + * @param gpuDeviceTO, GPU device details + */ + void updateGPUDetailsForVmStop(VirtualMachine vm, GPUDeviceTO gpuDeviceTO); + + void updateGPUDetailsForVmStart(long hostId, long vmId, GPUDeviceTO gpuDevice); + /** * Get GPU details for a host * @param host, the Host object diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index e439872dfb55..e2b1fe7b08a4 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -84,6 +84,8 @@ import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageDispatcher; import org.apache.cloudstack.framework.messagebus.MessageHandler; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.cloudstack.gpu.GpuService; import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.reservation.dao.ReservationDao; @@ -385,6 +387,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private VolumeOrchestrationService volumeMgr; @Inject + private GpuService gpuService; + @Inject private DeploymentPlanningManager _dpMgr; @Inject private MessageBus _messageBus; @@ -1367,7 +1371,7 @@ public void orchestrateStart(final String vmUuid, final Map() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException { @@ -3741,6 +3743,9 @@ private void orchestrateReboot(final String vmUuid, final Map vgpuProfiles = null; + + public GpuOfferingVO() { + this.uuid = UUID.randomUUID().toString(); + } + + /** + * Constructor for creating a new GPU offering + */ + public GpuOfferingVO(String name, String description) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.created = new Date(); + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getSortKey() { + return sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + @Override + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + @Override + public List getVgpuProfiles() { + return vgpuProfiles; + } + + public void setVgpuProfiles(List vgpuProfiles) { + this.vgpuProfiles = vgpuProfiles; + } + + /** + * Add a vGPU profile to this offering + */ + public void addVgpuProfile(VgpuProfile vgpuProfile) { + if (vgpuProfiles == null) { + vgpuProfiles = new ArrayList<>(); + } + if (!vgpuProfiles.contains(vgpuProfile)) { + vgpuProfiles.add(vgpuProfile); + } + } + + /** + * Remove a vGPU profile from this offering + */ + public void removeVgpuProfile(VgpuProfile vgpuProfile) { + if (vgpuProfile != null) { + vgpuProfiles.remove(vgpuProfile); + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java index 5bbf90854ee6..4944d51f1b44 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java @@ -40,19 +40,19 @@ public class VGPUTypesVO implements InternalIdentity { private String vgpuType; @Column(name="video_ram") - private long videoRam; + private Long videoRam; @Column(name="max_heads") - private long maxHeads; + private Long maxHeads; @Column(name="max_resolution_x") - private long maxResolutionX; + private Long maxResolutionX; @Column(name="max_resolution_y") - private long maxResolutionY; + private Long maxResolutionY; @Column(name="max_vgpu_per_pgpu") - private long maxVgpuPerPgpu; + private Long maxVgpuPerPgpu; @Column(name="remaining_capacity") private long remainingCapacity; @@ -63,7 +63,7 @@ public class VGPUTypesVO implements InternalIdentity { protected VGPUTypesVO() { } - public VGPUTypesVO(long gpuGroupId, String vgpuType, long videoRam, long maxHeads, long maxResolutionX, long maxResolutionY, long maxVgpuPerPgpu, + public VGPUTypesVO(long gpuGroupId, String vgpuType, Long videoRam, Long maxHeads, Long maxResolutionX, Long maxResolutionY, Long maxVgpuPerPgpu, long remainingCapacity, long maxCapacity) { this.gpuGroupId = gpuGroupId; this.vgpuType = vgpuType; @@ -92,43 +92,43 @@ public void setVgpuType(String vgpuType) { this.vgpuType = vgpuType; } - public long getVideoRam() { + public Long getVideoRam() { return videoRam; } - public void setVideoRam(long videoRam) { + public void setVideoRam(Long videoRam) { this.videoRam = videoRam; } - public long getMaxHeads() { + public Long getMaxHeads() { return maxHeads; } - public void setMaxHeads(long maxHeads) { + public void setMaxHeads(Long maxHeads) { this.maxHeads = maxHeads; } - public long getMaxResolutionX() { + public Long getMaxResolutionX() { return maxResolutionX; } - public void setMaxResolutionX(long maxResolutionX) { + public void setMaxResolutionX(Long maxResolutionX) { this.maxResolutionX = maxResolutionX; } - public long getMaxResolutionY() { + public Long getMaxResolutionY() { return maxResolutionY; } - public void setMaxResolutionY(long maxResolutionY) { + public void setMaxResolutionY(Long maxResolutionY) { this.maxResolutionY = maxResolutionY; } - public long getMaxVgpuPerPgpu() { + public Long getMaxVgpuPerPgpu() { return maxVgpuPerPgpu; } - public void setMaxVgpuPerPgpu(long maxVgpuPerPgpu) { + public void setMaxVgpuPerPgpu(Long maxVgpuPerPgpu) { this.maxVgpuPerPgpu = maxVgpuPerPgpu; } diff --git a/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java new file mode 100644 index 000000000000..994f5cd858cb --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java @@ -0,0 +1,113 @@ +package com.cloud.gpu; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gpu.VgpuProfile; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +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 java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "vgpu_profile") +public class VgpuProfileVO implements VgpuProfile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "card_id") + private Long cardId; + + @Column(name = "vram_size") + private Long vramSize; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + public VgpuProfileVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public VgpuProfileVO(String name, String description, Long gpuCardId, Long vramSize) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.cardId = gpuCardId; + this.vramSize = vramSize; + this.created = new Date(); + } + + @Override + public String toString() { + return String.format("VgpuProfile %s", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "name", "cardId")); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Long getCardId() { + return cardId; + } + + public void setCardId(Long cardId) { + this.cardId = cardId; + } + + @Override + public Long getVramSize() { + return vramSize; + } + + public void setVramSize(Long vramSize) { + this.vramSize = vramSize; + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java new file mode 100644 index 000000000000..2cac4b5236b2 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java @@ -0,0 +1,50 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface GpuCardDao extends GenericDao { + + /** + * Find GPU card by vendor and device id + * + * @param vendorId the vendor id + * @param deviceId the device id + * @return GpuCardVO + */ + GpuCardVO findByVendorIdAndDeviceId(String vendorId, String deviceId); + + /** + * List all GPU cards by vendor name + * + * @param vendorName the vendor name + * @return list of GpuCardVO + */ + List listByVendorName(String vendorName); + + List listByDeviceName(String deviceName); + + Pair, Integer> searchAndCountGpuCards( + Long id, String keyword, String vendorId, String vendorName, String deviceId, + String deviceName, Long startIndex, Long pageSize + ); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java new file mode 100644 index 000000000000..1304ff4aa19f --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java @@ -0,0 +1,125 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class GpuCardDaoImpl extends GenericDaoBase implements GpuCardDao { + + private final SearchBuilder allFieldSearch; + + public GpuCardDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorId", allFieldSearch.entity().getVendorId(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorName", allFieldSearch.entity().getVendorName(), + SearchCriteria.Op.EQ); + allFieldSearch.and("deviceId", allFieldSearch.entity().getDeviceId(), SearchCriteria.Op.EQ); + allFieldSearch.and("deviceName", allFieldSearch.entity().getDeviceName(), + SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public GpuCardVO findByVendorIdAndDeviceId(String vendorId, String deviceId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("vendorId", vendorId); + sc.setParameters("deviceId", deviceId); + return findOneBy(sc); + } + + @Override + public List listByVendorName(String vendorName) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("vendorName", vendorName); + return listBy(sc); + } + + @Override + public List listByDeviceName(String deviceName) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("deviceName", deviceName); + return listBy(sc); + } + + @Override + public Pair, Integer> searchAndCountGpuCards( + Long id, String keyword, String vendorId, String vendorName, String deviceId, + String deviceName, Long startIndex, Long pageSize + ) { + + Filter searchFilter = new Filter(GpuCardVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + sb.op("nameKeyword", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("deviceNameKeyword", sb.entity().getDeviceName(), SearchCriteria.Op.LIKE); + sb.and("vendorNameKeyword", sb.entity().getVendorName(), SearchCriteria.Op.LIKE); + sb.cp(); + } + if (vendorId != null) { + sb.and("vendorId", sb.entity().getVendorId(), SearchCriteria.Op.EQ); + } + if (vendorName != null) { + sb.and("vendorName", sb.entity().getVendorName(), SearchCriteria.Op.EQ); + } + if (deviceId != null) { + sb.and("deviceId", sb.entity().getDeviceId(), SearchCriteria.Op.EQ); + } + if (deviceName != null) { + sb.and("deviceName", sb.entity().getDeviceName(), SearchCriteria.Op.EQ); + } + sb.done(); + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (keyword != null) { + sc.setParameters("nameKeyword", "%" + keyword + "%"); + sc.setParameters("deviceNameKeyword", "%" + keyword + "%"); + sc.setParameters("vendorNameKeyword", "%" + keyword + "%"); + } + if (vendorId != null) { + sc.setParameters("vendorId", vendorId); + } + if (vendorName != null) { + sc.setParameters("vendorName", vendorName); + } + if (deviceId != null) { + sc.setParameters("deviceId", deviceId); + } + if (deviceName != null) { + sc.setParameters("deviceName", deviceName); + } + + return searchAndCount(sc, searchFilter); + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java new file mode 100644 index 000000000000..b920ec74e9e5 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java @@ -0,0 +1,79 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface GpuDeviceDao extends GenericDao { + + /** + * Find GPU device by host ID and bus address + * + * @param hostId the host ID + * @param busAddress the PCI bus address + * @return GpuDeviceVO + */ + GpuDeviceVO findByHostIdAndBusAddress(long hostId, String busAddress); + + /** + * List GPU devices by host ID + * + * @param hostId the ID of the host + * @return a list of GPU devices for the host + */ + List listByHostId(long hostId); + + /** + * List GPU devices by VM ID + * + * @param vmId the VM ID + * @return list of GpuDeviceVO + */ + List listByVmId(long vmId); + + /** + * List GPU devices by card ID + * + * @param cardId the GPU card ID + * @return list of GpuDeviceVO + */ + List listByCardId(long cardId); + + /** + * List vGPU devices by parent GPU device ID + * + * @param parentGpuDeviceId the parent GPU device ID + * @return list of GpuDeviceVO + */ + List listByParentGpuDeviceId(long parentGpuDeviceId); + + boolean isVgpuProfileInUse(long vgpuProfileId); + + boolean isGpuCardInUse(long cardId); + + List listByHostAndVm(Long hostId, long vmId); + + List listDevicesForAllocation(Long hostId, List vgpuProfileIdList); + + Pair, Integer> searchAndCountGpuDevices( + Long id, String keyword, Long hostId, Long gpuCardId, Long vgpuProfileId, + Long startIndex, Long pageSize); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java new file mode 100644 index 000000000000..bce5e82e5d6b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java @@ -0,0 +1,207 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.gpu.GpuDevice; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.List; + +@Component +public class GpuDeviceDaoImpl extends GenericDaoBase implements GpuDeviceDao { + + private static final String HOST_ID = "hostId"; + private static final String VM_ID = "vmId"; + private static final String BUS_ADDRESS = "busAddress"; + private static final String CARD_ID = "cardId"; + private static final String VGPU_PROFILE_ID = "vgpuProfileId"; + private static final String PARENT_GPU_DEVICE_ID = "parentGpuDeviceId"; + private static final String STATE = "state"; + private final SearchBuilder allFieldSearch; + private final SearchBuilder gpuDevicesForAllocationSearch; + @Inject + private GpuCardDao gpuCardDao; + @Inject + private VgpuProfileDao vgpuProfileDao; + + public GpuDeviceDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and(HOST_ID, allFieldSearch.entity().getHostId(), SearchCriteria.Op.EQ); + allFieldSearch.and(BUS_ADDRESS, allFieldSearch.entity().getBusAddress(), + SearchCriteria.Op.EQ); + allFieldSearch.and(STATE, allFieldSearch.entity().getState(), SearchCriteria.Op.EQ); + allFieldSearch.and(VGPU_PROFILE_ID, allFieldSearch.entity().getVgpuProfileId(), + SearchCriteria.Op.EQ); + allFieldSearch.and(PARENT_GPU_DEVICE_ID, allFieldSearch.entity().getParentGpuDeviceId(), + SearchCriteria.Op.EQ); + allFieldSearch.and(VM_ID, allFieldSearch.entity().getVmId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + + gpuDevicesForAllocationSearch = createSearchBuilder(); + gpuDevicesForAllocationSearch.and(HOST_ID, + gpuDevicesForAllocationSearch.entity().getHostId(), SearchCriteria.Op.EQ); + gpuDevicesForAllocationSearch.and(VGPU_PROFILE_ID, + gpuDevicesForAllocationSearch.entity().getVgpuProfileId(), SearchCriteria.Op.IN); + gpuDevicesForAllocationSearch.and(STATE, + gpuDevicesForAllocationSearch.entity().getState(), SearchCriteria.Op.EQ); + gpuDevicesForAllocationSearch.done(); + + } + + @Override + public GpuDeviceVO findByHostIdAndBusAddress(long hostId, String busAddress) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(BUS_ADDRESS, busAddress); + return findOneBy(sc); + } + + @Override + public List listByHostId(long hostId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + return listBy(sc); + } + + @Override + public List listByVmId(long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(VM_ID, vmId); + return listBy(sc); + } + + @Override + public List listByCardId(long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(CARD_ID, cardId); + return listBy(sc); + } + + @Override + public List listByParentGpuDeviceId(long parentGpuDeviceId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(PARENT_GPU_DEVICE_ID, parentGpuDeviceId); + return listBy(sc); + } + + @Override + public boolean isVgpuProfileInUse(long vgpuProfileId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(VGPU_PROFILE_ID, vgpuProfileId); + return getCount(sc) > 0; + } + + @Override + public boolean isGpuCardInUse(long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(CARD_ID, cardId); + return getCount(sc) > 0; + } + + @Override + public List listByHostAndVm(Long hostId, long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(VM_ID, vmId); + return search(sc, null); + } + + @Override + public List listDevicesForAllocation(Long hostId, List vgpuProfileIdList) { + SearchCriteria sc = gpuDevicesForAllocationSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(VGPU_PROFILE_ID, vgpuProfileIdList.toArray()); + sc.setParameters(STATE, GpuDevice.State.Free); + return search(sc, null); + } + + @Override + public Pair, Integer> searchAndCountGpuDevices( + Long id, String keyword, Long hostId, Long gpuCardId, Long vgpuProfileId, + Long startIndex, Long pageSize + ) { + Filter searchFilter = new Filter(GpuDeviceVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (hostId != null) { + sb.and("hostId", sb.entity().getHostId(), SearchCriteria.Op.EQ); + } + if (gpuCardId != null) { + sb.and("cardId", sb.entity().getCardId(), SearchCriteria.Op.EQ); + } + if (vgpuProfileId != null) { + sb.and("vgpuProfileId", sb.entity().getVgpuProfileId(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + SearchBuilder cardSb = gpuCardDao.createSearchBuilder(); + SearchBuilder profileSb = vgpuProfileDao.createSearchBuilder(); + sb.join("cardJoin", cardSb, sb.entity().getCardId(), cardSb.entity().getId(), + JoinBuilder.JoinType.INNER); + sb.join("profileJoin", profileSb, sb.entity().getCardId(), profileSb.entity().getId() + , JoinBuilder.JoinType.INNER); + + sb.op("cardNameKeyword", cardSb.entity().getName(), SearchCriteria.Op.LIKE); + sb.or("cardNameKeyword", cardSb.entity().getVendorName(), SearchCriteria.Op.LIKE); + sb.or("cardNameKeyword", cardSb.entity().getDeviceName(), SearchCriteria.Op.LIKE); + + sb.op("profileNameKeyword", profileSb.entity().getName(), SearchCriteria.Op.LIKE); + sb.op("profileDescriptionKeyword", profileSb.entity().getDescription(), + SearchCriteria.Op.LIKE); + sb.cp(); + } + + sb.done(); + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (hostId != null) { + sc.setParameters("hostId", hostId); + } + if (gpuCardId != null) { + sc.setParameters("cardId", gpuCardId); + } + if (vgpuProfileId != null) { + sc.setParameters("vgpuProfileId", vgpuProfileId); + } + + if (keyword != null) { + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("profileJoin", "profileNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("profileJoin", "profileDescriptionKeyword", "%" + keyword + "%"); + } + + return searchAndCount(sc, searchFilter); + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDao.java new file mode 100644 index 000000000000..613c2379ccca --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDao.java @@ -0,0 +1,42 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gpu.GpuOffering.State; + +import java.util.List; + +/** + * Data Access Object for GPU offerings + */ +public interface GpuOfferingDao extends GenericDao { + /** + * Find GPU offering by name + * + * @param name the name of the GPU offering + * @param state + * @return GpuOffering + */ + Pair, Integer> searchAndCountGpuOfferings(Long id, String keyword, String name, State state, Long startIndex, Long pageSize); + + GpuOfferingVO findByName(String name); + + void loadVgpuProfiles(GpuOfferingVO gpuOffering); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDaoImpl.java new file mode 100644 index 000000000000..a1afaaa6fd62 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDaoImpl.java @@ -0,0 +1,120 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuOfferingDetailVO; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.gpu.GpuOffering; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.List; + +import static org.apache.cloudstack.query.QueryService.SortKeyAscending; + +@Component +public class GpuOfferingDaoImpl extends GenericDaoBase implements GpuOfferingDao { + + @Inject + protected GpuOfferingDetailsDao gpuOfferingDetailsDao; + private SearchBuilder allFieldSearch; + @Inject + private VgpuProfileDao vgpuProfileDao; + @Inject + private GpuCardDao gpuCardDao; + + public GpuOfferingDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("description", allFieldSearch.entity().getDescription(), + SearchCriteria.Op.EQ); + allFieldSearch.and("state", allFieldSearch.entity().getState(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public Pair, Integer> searchAndCountGpuOfferings(Long id, String keyword, + String name, + GpuOffering.State state, Long startIndex, + Long pageSize) { + SearchBuilder sb = createSearchBuilder(); + + Filter searchFilter = new Filter(GpuOfferingVO.class, "sortKey", SortKeyAscending.value(), startIndex, pageSize); + searchFilter.addOrderBy(GpuOfferingVO.class, "id", true); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + sb.op("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.or("keywordDescription", sb.entity().getDescription(), SearchCriteria.Op.LIKE); + sb.cp(); + } + if (name != null) { + sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + } + if (state != null) { + sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); + } + sb.done(); + + SearchCriteria sc = sb.create(); + + if (id != null) { + sc.setParameters("id", id); + } + if (keyword != null) { + sc.setJoinParameters("keywordName", "keywordName", "%" + keyword + "%"); + sc.setJoinParameters("keywordDescription", "keywordDescription", "%" + keyword + "%"); + } + if (name != null) { + sc.setParameters("name", name); + } + if (state != null) { + sc.setParameters("state", state); + } + return searchAndCount(sc, searchFilter); + + + } + + @Override + public GpuOfferingVO findByName(String name) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", name); + return findOneBy(sc); + } + + @Override + public void loadVgpuProfiles(GpuOfferingVO gpuOffering) { + List details = gpuOfferingDetailsDao.findDetails( + gpuOffering.getId(), GpuOfferingDetailVO.VgpuProfileId); + + for (GpuOfferingDetailVO detail : details) { + if (detail.getName().equals(GpuOfferingDetailVO.VgpuProfileId)) { + VgpuProfileVO profile = vgpuProfileDao.findById(Long.parseLong(detail.getValue())); + gpuOffering.addVgpuProfile(profile); + } + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDao.java new file mode 100644 index 000000000000..72aa1a9bd4b1 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDao.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 com.cloud.gpu.dao; + +import com.cloud.gpu.GpuOfferingDetailVO; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import java.util.Map; + +/** + * Data Access Object for GPU offering details + */ +public interface GpuOfferingDetailsDao extends GenericDao, + ResourceDetailsDao { + /** + * Get details for a GPU offering + * + * @param gpuOfferingId GPU offering ID + * @return a map of all details for the GPU offering + */ + Map getDetailsMap(long gpuOfferingId); + + /** + * Update a detail for a GPU offering + * + * @param gpuOfferingId GPU offering ID + * @param name detail name + * @param value detail value + * @param display whether the detail should be displayed to the user + * @return true if the detail was updated, false otherwise + */ + boolean update(long gpuOfferingId, String name, String value, boolean display); + + /** + * Add vGPU profile IDs to a GPU offering + * + * @param gpuOfferingId GPU offering ID + * @param vgpuProfileIds comma-separated list of vGPU profile IDs + * @param display whether the detail should be displayed to the user + */ + void addVgpuProfileIds(long gpuOfferingId, String vgpuProfileIds, boolean display); + + /** + * Get vGPU profile IDs for a GPU offering + * + * @param gpuOfferingId GPU offering ID + * @return comma-separated list of vGPU profile IDs + */ + String getVgpuProfileIds(long gpuOfferingId); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDaoImpl.java new file mode 100644 index 000000000000..ced5ae6747e9 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuOfferingDetailsDaoImpl.java @@ -0,0 +1,93 @@ +// 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.gpu.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.cloud.gpu.GpuOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +public class GpuOfferingDetailsDaoImpl extends ResourceDetailsDaoBase implements GpuOfferingDetailsDao { + + private SearchBuilder offeringDetailsSearch; + private SearchBuilder detailSearch; + + public GpuOfferingDetailsDaoImpl() { + offeringDetailsSearch = createSearchBuilder(); + offeringDetailsSearch.and("gpuOfferingId", offeringDetailsSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + offeringDetailsSearch.done(); + + detailSearch = createSearchBuilder(); + detailSearch.and("gpuOfferingId", detailSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + detailSearch.and("name", detailSearch.entity().getName(), SearchCriteria.Op.EQ); + detailSearch.done(); + } + + @Override + public void addDetail(long gpuOfferingId, String name, String value, boolean display) { + GpuOfferingDetailVO detail = findDetail(gpuOfferingId, name); + if (detail == null) { + detail = new GpuOfferingDetailVO(gpuOfferingId, name, value, display); + persist(detail); + } else { + detail.setValue(value); + detail.setDisplay(display); + update(detail.getId(), detail); + } + } + + + @Override + public Map getDetailsMap(long gpuOfferingId) { + Map details = new HashMap<>(); + List detailList = listDetails(gpuOfferingId); + for (GpuOfferingDetailVO detail : detailList) { + details.put(detail.getName(), detail.getValue()); + } + return details; + } + + @Override + public boolean update(long gpuOfferingId, String name, String value, boolean display) { + GpuOfferingDetailVO detail = findDetail(gpuOfferingId, name); + if (detail != null) { + detail.setValue(value); + detail.setDisplay(display); + return update(detail.getId(), detail); + } + return false; + } + + @Override + public void addVgpuProfileIds(long gpuOfferingId, String vgpuProfileIds, boolean display) { + // This method specifically adds vGPU profile IDs to a GPU offering + addDetail(gpuOfferingId, GpuOfferingDetailVO.VgpuProfileId, vgpuProfileIds, display); + } + + @Override + public String getVgpuProfileIds(long gpuOfferingId) { + GpuOfferingDetailVO detail = findDetail(gpuOfferingId, GpuOfferingDetailVO.VgpuProfileId); + return detail != null ? detail.getValue() : null; + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java index 8e4f2f742ac5..8e46b804d111 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java @@ -16,16 +16,17 @@ // under the License. package com.cloud.gpu.dao; -import java.util.List; - import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.utils.db.GenericDao; +import java.util.List; + public interface HostGpuGroupsDao extends GenericDao { /** * Find host device by hostId and groupName - * @param hostId the host + * + * @param hostId the host * @param groupName GPU group * @return HostGpuGroupsVO */ @@ -33,12 +34,14 @@ public interface HostGpuGroupsDao extends GenericDao { /** * List all the host Ids, that are GPU enabled. + * * @return list of hostIds */ List listHostIds(); /** * Return a list by hostId. + * * @param hostId the host * @return HostGpuGroupsVO */ @@ -46,13 +49,15 @@ public interface HostGpuGroupsDao extends GenericDao { /** * Delete entries by hostId. + * * @param hostId the host */ void deleteGpuEntries(long hostId); /** * Save the list of GPU groups belonging to a host - * @param hostId the host + * + * @param hostId the host * @param gpuGroups the list of GPU groups to save */ void persist(long hostId, List gpuGroups); diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java index 30535c7e27d5..ef747b8ca4e7 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java @@ -16,16 +16,14 @@ // under the License. package com.cloud.gpu.dao; -import java.util.List; - - -import org.springframework.stereotype.Component; - import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +import java.util.List; @Component public class HostGpuGroupsDaoImpl extends GenericDaoBase implements HostGpuGroupsDao { diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDao.java index 94d97c007364..79bcba9cf563 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDao.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDao.java @@ -16,17 +16,18 @@ //under the License. package com.cloud.gpu.dao; -import java.util.HashMap; -import java.util.List; - import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.gpu.VGPUTypesVO; import com.cloud.utils.db.GenericDao; +import java.util.HashMap; +import java.util.List; + public interface VGPUTypesDao extends GenericDao { /** * List zonewide/podwide/clusterwide GPU card capacities. + * * @param zoneId * @param podId * @param clusterId @@ -36,6 +37,7 @@ public interface VGPUTypesDao extends GenericDao { /** * Find VGPU types by group Id + * * @param groupId of the GPU group * @return list of VGPUTypesVO */ @@ -43,7 +45,8 @@ public interface VGPUTypesDao extends GenericDao { /** * Find VGPU type by group Id and VGPU type - * @param groupId of the GPU group + * + * @param groupId of the GPU group * @param vgpuType name of VGPU type * @return VGPUTypesVO */ @@ -51,7 +54,8 @@ public interface VGPUTypesDao extends GenericDao { /** * Save the list of enabled VGPU types - * @param hostId the host + * + * @param hostId the host * @param groupDetails with enabled VGPU types */ void persist(long hostId, HashMap> groupDetails); diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java index edc5e1f67c86..646ccc12b763 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java @@ -16,19 +16,6 @@ //under the License. package com.cloud.gpu.dao; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; - -import javax.inject.Inject; - -import org.springframework.stereotype.Component; - import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.gpu.VGPUTypesVO; @@ -37,19 +24,29 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; @Component public class VGPUTypesDaoImpl extends GenericDaoBase implements VGPUTypesDao { - private final SearchBuilder _searchByGroupId; - private final SearchBuilder _searchByGroupIdVGPUType; - - @Inject protected HostGpuGroupsDao _hostGpuGroupsDao; - private static final String LIST_ZONE_POD_CLUSTER_WIDE_GPU_CAPACITIES = "SELECT host_gpu_groups.group_name, vgpu_type, max_vgpu_per_pgpu, SUM(remaining_capacity) AS remaining_capacity, SUM(max_capacity) AS total_capacity FROM" + - " `cloud`.`vgpu_types` INNER JOIN `cloud`.`host_gpu_groups` ON vgpu_types.gpu_group_id = host_gpu_groups.id INNER JOIN `cloud`.`host`" + - " ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND host.data_center_id = ?"; + " `cloud`.`vgpu_types` INNER JOIN `cloud`.`host_gpu_groups` ON vgpu_types.gpu_group_id = host_gpu_groups.id INNER JOIN `cloud`.`host`" + + " ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND host.data_center_id = ?"; + private final SearchBuilder _searchByGroupId; + private final SearchBuilder _searchByGroupIdVGPUType; + @Inject + protected HostGpuGroupsDao _hostGpuGroupsDao; public VGPUTypesDaoImpl() { diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java new file mode 100644 index 000000000000..09566f3bdf16 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java @@ -0,0 +1,42 @@ +// 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.gpu.dao; + +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface VgpuProfileDao extends GenericDao { + + /** + * Find vGPU profile by name + * + * @param profileName the vGPU profile name + * @return VgpuProfileVO + */ + VgpuProfileVO findByName(String profileName); + + VgpuProfileVO findByNameAndCardId(String name, long cardId); + + List findByNameAndCardIds(String name, List cardIdList); + + Pair, Integer> searchAndCountVgpuProfiles( + Long id, String name, String keyword, Long gpuCardId, Long startIndex, Long pageSize + ); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java new file mode 100644 index 000000000000..c8b93b9245ba --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java @@ -0,0 +1,103 @@ +// 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.gpu.dao; + +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class VgpuProfileDaoImpl extends GenericDaoBase implements VgpuProfileDao { + + private final SearchBuilder allFieldSearch; + + public VgpuProfileDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("cardId", allFieldSearch.entity().getCardId(), SearchCriteria.Op.IN); + allFieldSearch.done(); + } + + @Override + public VgpuProfileVO findByName(String profileName) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", profileName); + return findOneBy(sc); + } + + @Override + public VgpuProfileVO findByNameAndCardId(String name, long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", name); + sc.setParameters("cardId", cardId); + return findOneBy(sc); + } + + @Override + public List findByNameAndCardIds(String name, List cardIdList) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", name); + sc.setParameters("cardId", cardIdList.toArray()); + return listBy(sc); + } + + @Override + public Pair, Integer> searchAndCountVgpuProfiles( + Long id, String name, String keyword, Long gpuCardId, Long startIndex, Long pageSize + ) { + Filter searchFilter = new Filter(VgpuProfileVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = 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); + } + if (keyword != null) { + sb.and("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("keywordDescription", sb.entity().getDescription(), SearchCriteria.Op.LIKE); + } + if (gpuCardId != null) { + sb.and("cardId", sb.entity().getCardId(), SearchCriteria.Op.EQ); + } + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (name != null) { + sc.setParameters("name", name); + } + if (keyword != null) { + sc.setParameters("keywordName", "%" + keyword + "%"); + sc.setParameters("keywordDescription", "%" + keyword + "%"); + } + if (gpuCardId != null) { + sc.setParameters("cardId", gpuCardId); + } + + return searchAndCount(sc, searchFilter); + } +} 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 7f5c1a7afa19..f1ff258b90c6 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -124,6 +124,12 @@ public class ServiceOfferingVO implements ServiceOffering { @Column(name = "dynamic_scaling_enabled") private boolean dynamicScalingEnabled = true; + @Column(name = "gpu_offering_id") + private Long gpuOfferingId; + + @Column(name = "gpu_count") + private Integer gpuCount; + // 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. @@ -445,4 +451,22 @@ public Boolean getDiskOfferingStrictness() { public void setDiskOfferingStrictness(boolean diskOfferingStrictness) { this.diskOfferingStrictness = diskOfferingStrictness; } + + @Override + public Long getGpuOfferingId() { + return gpuOfferingId; + } + + public void setGpuOfferingId(Long gpuOfferingId) { + this.gpuOfferingId = gpuOfferingId; + } + + @Override + public Integer getGpuCount() { + return gpuCount; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index ef10af63bae0..9f2d89ca1225 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -136,7 +136,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem "AS type, COUNT(DISTINCT vm.id) AS vmcount FROM service_offering_details offering INNER JOIN vm_instance vm ON offering.service_offering_id = vm.service_offering_id " + "INNER JOIN `cloud`.`host` ON vm.host_id = host.id WHERE vm.state = 'Running' AND host.data_center_id = ? "; private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2 = - "GROUP BY offering.service_offering_id) results GROUP BY pci, type"; + "GROUP BY pci, type) results GROUP BY pci, type"; private static final String UPDATE_SYSTEM_VM_TEMPLATE_ID_FOR_HYPERVISOR = "UPDATE `cloud`.`vm_instance` SET vm_template_id = ? WHERE type <> 'User' AND hypervisor_type = ? AND removed is NULL"; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java index 32607897a381..6ae3ac2ec266 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java @@ -64,12 +64,12 @@ public List listByEntityType(String entityType, String userUuid, b sc.addAnd("userUuid", SearchCriteria.Op.EQ, userUuid); } if (!isCallerAdmin) { - List adminOnlyTypes = Arrays.asList(EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, + List adminOnlyTypes = Arrays.asList(EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, EntityType.GPU_OFFERING, EntityType.NETWORK_OFFERING, EntityType.ZONE, EntityType.POD, EntityType.CLUSTER, EntityType.HOST, EntityType.DOMAIN, EntityType.PRIMARY_STORAGE, EntityType.SECONDARY_STORAGE, EntityType.VR, EntityType.SYSTEM_VM); if (StringUtils.isBlank(entityType)) { - sc.setParameters("entityTypeNotIn", EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, + sc.setParameters("entityTypeNotIn", EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, EntityType.GPU_OFFERING, EntityType.NETWORK_OFFERING, EntityType.ZONE, EntityType.POD, EntityType.CLUSTER, EntityType.HOST, EntityType.DOMAIN, EntityType.PRIMARY_STORAGE, EntityType.SECONDARY_STORAGE, EntityType.VR, EntityType.SYSTEM_VM); diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 474569f49243..d51a86bca983 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -304,4 +304,9 @@ + + + + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql b/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql index 000d0f077cc3..9bdcdb762988 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql @@ -148,7 +148,7 @@ ALTER TABLE `cloud`.`service_offering` ADD COLUMN `tags` varchar(255); ALTER TABLE `cloud`.`user_vm` MODIFY COLUMN `domain_router_id` bigint unsigned; -- change from NOT NULL to NULL -ALTER TABLE `cloud`.`event` ADD COLUMN `state` varchar(32) NOT NULL DEFAULT 'Completed'; + ALTER TABLE `cloud`.`event` ADD COLUMN `state` varchar(32) NOT NULL DEFAULT 'Completed'; ALTER TABLE `cloud`.`event` ADD COLUMN `start_id` bigint unsigned NOT NULL DEFAULT 0; ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `tags` varchar(4096); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 3fd4914b2e27..c4d4028b3257 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -233,3 +233,85 @@ CREATE TABLE IF NOT EXISTS `cloud`.`gui_themes_details` ( PRIMARY KEY (`id`), CONSTRAINT `fk_gui_themes_details__gui_theme_id` FOREIGN KEY (`gui_theme_id`) REFERENCES `gui_themes`(`id`) ); + +-- Create the GPU card table to hold the GPU card information +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_card` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `device_id` varchar(255) NOT NULL COMMENT 'device id of the GPU card', + `device_name` varchar(255) NOT NULL COMMENT 'device name of the GPU card', + `name` varchar(255) NOT NULL COMMENT 'name of the GPU card', + `vendor_name` varchar(255) NOT NULL COMMENT 'vendor name of the GPU card', + `vendor_id` varchar(255) NOT NULL COMMENT 'vendor id of the GPU card', + `vram_size` bigint unsigned COMMENT 'VRAM size in MB', + `created` datetime NOT NULL COMMENT 'date created', + PRIMARY KEY (`id`), + UNIQUE KEY (`vendor_id`, `device_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GPU cards supported by CloudStack'; + +-- Create the vGPU profile table to hold the vGPU profile information. +CREATE TABLE IF NOT EXISTS `cloud`.`vgpu_profile` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL COMMENT 'name of the vGPU profile', + `description` varchar(255) DEFAULT NULL COMMENT 'description of the vGPU profile', + `card_id` bigint unsigned NOT NULL COMMENT 'id of the GPU card', + `vram_size` bigint unsigned DEFAULT NULL COMMENT 'VRAM size in MB', + `created` datetime NOT NULL COMMENT 'date created', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `card_id`), + CONSTRAINT `fk_vgpu_profile_card_id` FOREIGN KEY (`card_id`) REFERENCES `gpu_card`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='vGPU profiles supported by CloudStack'; + + +-- Create the GPU device table to hold the GPU device information on different hosts +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_device` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `card_id` bigint unsigned NOT NULL COMMENT 'id of the GPU card', + `vgpu_profile_id` bigint unsigned DEFAULT NULL COMMENT 'id of the vGPU profile.', + `bus_address` varchar(255) NOT NULL COMMENT 'PCI bus address of the GPU device', + `type` varchar(32) NOT NULL COMMENT 'type of the GPU device. PCI or MDEV', + `host_id` bigint unsigned NOT NULL COMMENT 'id of the host where GPU is installed', + `vm_id` bigint unsigned DEFAULT NULL COMMENT 'id of the VM using this GPU device', + `parent_gpu_device_id` bigint unsigned DEFAULT NULL COMMENT 'id of the parent GPU device. null if it is a physical GPU device and for vGPUs points to the actual GPU', + `state` varchar(32) NOT NULL COMMENT 'state of the GPU device', + PRIMARY KEY (`id`), + UNIQUE KEY (`bus_address`, `host_id`), + CONSTRAINT `fk_gpu_devices__card_id` FOREIGN KEY (`card_id`) REFERENCES `gpu_card` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_gpu_devices__host_id` FOREIGN KEY (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_gpu_devices__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_gpu_devices__parent_gpu_device_id` FOREIGN KEY (`parent_gpu_device_id`) REFERENCES `gpu_device` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GPU devices installed on hosts'; + +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_offering` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL COMMENT 'name of the GPU offering', + `description` varchar(1024) COMMENT 'description of the GPU Offering', + `state` CHAR(40) NOT NULL DEFAULT 'Active' COMMENT 'state of service offering either Active or Inactive', + `sort_key` int(32) NOT NULL default 0 COMMENT 'sort key used for customising sort method', + `created` datetime NOT NULL COMMENT 'date created', + `removed` datetime COMMENT 'date removed if not null', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gpu_offering__uuid` (`uuid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_offering_detail` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `gpu_offering_id` bigint unsigned NOT NULL COMMENT 'GPU offering ID', + `name` varchar(255) NOT NULL COMMENT 'detail name', + `value` varchar(1024) NOT NULL COMMENT 'detail value', + `display` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'True if the detail can be displayed to the end user', + PRIMARY KEY (`id`), + KEY `fk_gpu_offering_detail__gpu_offering_id` (`gpu_offering_id`), + KEY `i_gpu_offering_detail__name` (`name`), + CONSTRAINT `fk_gpu_offering_detail__gpu_offering_id` FOREIGN KEY (`gpu_offering_id`) REFERENCES `gpu_offering` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Details for gpu offerings'; + + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.service_offering', 'gpu_offering_id', 'bigint unsigned DEFAULT NULL COMMENT "GPU offering ID"'); +CALL `cloud`.`IDEMPOTENT_DROP_FOREIGN_KEY`('cloud.service_offering','fk_service_offering__gpu_offering_id'); +CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.service_offering', 'fk_service_offering__gpu_offering_id', '(gpu_offering_id)', '`gpu_offering`(`id`)'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.service_offering', 'gpu_count', 'int unsigned DEFAULT NULL COMMENT "Number of GPUs"'); + 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 18e6231ef89a..4c7bedd8a0e1 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 @@ -55,6 +55,10 @@ SELECT `disk_offering`.`cache_mode` AS `cache_mode`, `disk_offering`.`disk_size` AS `root_disk_size`, `disk_offering`.`encrypt` AS `encrypt_root`, + `gpu_offering`.`id` AS `gpu_offering_id`, + `gpu_offering`.`uuid` AS `gpu_offering_uuid`, + `gpu_offering`.`name` AS `gpu_offering_name`, + `service_offering`.`gpu_count` AS `gpu_count`, `service_offering`.`cpu` AS `cpu`, `service_offering`.`speed` AS `speed`, `service_offering`.`ram_size` AS `ram_size`, @@ -89,6 +93,8 @@ FROM INNER JOIN `cloud`.`disk_offering` ON service_offering.disk_offering_id = disk_offering.id LEFT JOIN + `cloud`.`gpu_offering` ON service_offering.gpu_offering_id = gpu_offering.id + LEFT JOIN `cloud`.`service_offering_details` AS `domain_details` ON `domain_details`.`service_offering_id` = `service_offering`.`id` AND `domain_details`.`name`='domainid' LEFT JOIN `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`) diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index a0c2720fc630..afab29dc29c5 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -104,6 +104,10 @@ SELECT `backup_offering`.`id` AS `backup_offering_id`, `service_offering`.`name` AS `service_offering_name`, `disk_offering`.`name` AS `disk_offering_name`, + `gpu_offering`.`id` AS `gpu_offering_id`, + `gpu_offering`.`uuid` AS `gpu_offering_uuid`, + `gpu_offering`.`name` AS `gpu_offering_name`, + `service_offering`.`gpu_count` AS `gpu_count`, `backup_offering`.`name` AS `backup_offering_name`, `storage_pool`.`id` AS `pool_id`, `storage_pool`.`uuid` AS `pool_uuid`, @@ -174,7 +178,7 @@ SELECT `lease_expiry_action`.`value` AS `lease_expiry_action`, `lease_action_execution`.`value` AS `lease_action_execution` FROM - (((((((((((((((((((((((((((((((((((`user_vm` + ((((((((((((((((((((((((((((((((((((`user_vm` JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`) AND ISNULL(`vm_instance`.`removed`)))) JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`))) @@ -190,6 +194,7 @@ FROM LEFT JOIN `vm_template` `iso` ON ((`iso`.`id` = `user_vm`.`iso_id`))) LEFT JOIN `volumes` ON ((`vm_instance`.`id` = `volumes`.`instance_id`))) LEFT JOIN `service_offering` ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`))) + LEFT JOIN `gpu_offering` ON ((`service_offering`.`gpu_offering_id` = `gpu_offering`.`id`))) LEFT JOIN `disk_offering` `svc_disk_offering` ON ((`volumes`.`disk_offering_id` = `svc_disk_offering`.`id`))) LEFT JOIN `disk_offering` ON ((`volumes`.`disk_offering_id` = `disk_offering`.`id`))) LEFT JOIN `backup_offering` ON ((`vm_instance`.`backup_offering_id` = `backup_offering`.`id`))) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index d96290a86acb..fc3191f1fcb4 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -74,6 +74,7 @@ import org.apache.cloudstack.command.ReconcileCommandService; import org.apache.cloudstack.command.ReconcileCommandUtils; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.gpu.GpuDevice; import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand; import org.apache.cloudstack.storage.configdrive.ConfigDrive; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; @@ -103,9 +104,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.Logger; import org.apache.xerces.impl.xpath.regex.Match; import org.joda.time.Duration; import org.libvirt.Connect; @@ -130,7 +131,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; - import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.HostVmStateReportEntry; @@ -143,6 +143,7 @@ import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.StartupStorageCommand; +import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; import com.cloud.agent.api.VmStatsEntry; @@ -213,8 +214,8 @@ import com.cloud.network.Networks.RouterPrivateIpStrategy; import com.cloud.network.Networks.TrafficType; import com.cloud.resource.AgentStatusUpdater; -import com.cloud.resource.ResourceStatusUpdater; import com.cloud.resource.RequestWrapper; +import com.cloud.resource.ResourceStatusUpdater; import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResourceBase; import com.cloud.storage.JavaStorageLayer; @@ -241,6 +242,10 @@ import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; /** * LibvirtComputingResource execute requests on the computing/routing host using @@ -379,6 +384,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String modifyVlanPath; private String versionStringPath; + private String gpuDiscoveryPath; private String patchScriptPath; private String createVmPath; private String manageSnapshotPath; @@ -1039,6 +1045,11 @@ public boolean configure(final String name, final Map params) th throw new ConfigurationException("Unable to find versions.sh"); } + gpuDiscoveryPath = Script.findScript(kvmScriptsDir, "gpudiscovery.sh"); + if (gpuDiscoveryPath == null) { + throw new ConfigurationException("Unable to find gpudiscovery.sh"); + } + patchScriptPath = Script.findScript(kvmScriptsDir, "patch.sh"); if (patchScriptPath == null) { throw new ConfigurationException("Unable to find patch.sh"); @@ -1955,6 +1966,102 @@ public boolean passCmdLine(final String vmName, final String cmdLine) throws Int return true; } + public List getGpuDevices() { + LOGGER.debug("Executing GPU discovery script at: {}", gpuDiscoveryPath); + final Script command = new Script(gpuDiscoveryPath, Duration.standardSeconds(30), LOGGER); + + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = command.execute(parser); + if (result == null) { + LOGGER.debug("GPU discovery command executed successfully"); + result = parser.getLines(); + } + + if (result == null || result.trim().isEmpty()) { + LOGGER.error("GPU discovery failed: command returned null or empty result. Script path: {}, Exit code: {}", + gpuDiscoveryPath, command.getExitValue()); + return Collections.emptyList(); + } + + LOGGER.debug("GPU discovery result: {}", result); + + // TODO: Add running VMs to the list of GPU devices + // This will be used to update the GPU device list when agent on a host is unavailable or the VM is imported. + List gpuDevices = new ArrayList<>(); + try { + JsonParser jsonParser = new JsonParser(); + JsonArray jsonArray = jsonParser.parse(result).getAsJsonObject().get("gpus").getAsJsonArray(); + + for (JsonElement jsonElement : jsonArray) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + String busAddress = jsonObject.get("pci_address").getAsString(); + String vendorId = jsonObject.get("vendor_id").getAsString(); + String vendorName = jsonObject.get("vendor").getAsString(); + String deviceId = jsonObject.get("device_id").getAsString(); + String deviceName = jsonObject.get("device").getAsString(); + + // vgpu instances uses mdev uuid + // vf instances uses vf_pci_address + + JsonArray vgpuInstances = jsonObject.get("vgpu_instances").getAsJsonArray(); + JsonArray vfInstances = jsonObject.get("vf_instances").getAsJsonArray(); + + JsonObject fullPassthrough = jsonObject.get("full_passthrough").getAsJsonObject(); + boolean fullPassthroughEnabled = fullPassthrough.get("enabled").getAsInt() == 1; + + VgpuTypesInfo vgpuType = new VgpuTypesInfo(GpuDevice.DeviceType.PCI, "passthrough", "passthrough", busAddress, vendorId, + vendorName, deviceId, deviceName); + if (fullPassthroughEnabled) { + vgpuType.setPassthroughEnabled(true); + } else { + vgpuType.setPassthroughEnabled(false); + } + vgpuType.setVmName(getJsonStringValueOrNull(fullPassthrough, "used_by_vm")); + + gpuDevices.add(vgpuType); + + for (JsonElement vgpuInstance : vgpuInstances) { + String mdevUuid = vgpuInstance.getAsJsonObject().get("mdev_uuid").getAsString(); + String profileName = vgpuInstance.getAsJsonObject().get("profile_name").getAsString(); + Long availableInstances = vgpuInstance.getAsJsonObject().get("available_instances").getAsLong(); + VgpuTypesInfo device = new VgpuTypesInfo(GpuDevice.DeviceType.MDEV, profileName, profileName, mdevUuid, vendorId, vendorName, deviceId, deviceName); + device.setParentBusAddress(busAddress); + device.setRemainingCapacity(availableInstances); + device.setVmName(getJsonStringValueOrNull(vgpuInstance.getAsJsonObject(), "used_by_vm")); + gpuDevices.add(device); + } + + for (JsonElement vfInstance : vfInstances) { + String vfPciAddress = vfInstance.getAsJsonObject().get("vf_pci_address").getAsString(); + String vfProfile = vfInstance.getAsJsonObject().get("vf_profile").getAsString(); + VgpuTypesInfo device = new VgpuTypesInfo(GpuDevice.DeviceType.PCI, vfProfile, vfProfile, vfPciAddress, vendorId, vendorName, deviceId, deviceName); + device.setParentBusAddress(busAddress); + device.setVmName(getJsonStringValueOrNull(vfInstance.getAsJsonObject(), "used_by_vm")); + gpuDevices.add(device); + } + } + + } catch (Exception e) { + LOGGER.error("Failed to parse GPU discovery result: {}", e.getMessage(), e); + } + return gpuDevices; + } + + /** + * Safely extracts a string value from a JSON object, returning null if the field is missing or null. + * + * @param jsonObject the JSON object to extract from + * @param fieldName the name of the field to extract + * @return the string value of the field, or null if the field is missing or null + */ + private String getJsonStringValueOrNull(JsonObject jsonObject, String fieldName) { + JsonElement element = jsonObject.get(fieldName); + if (element == null || element.isJsonNull()) { + return null; + } + return element.getAsString(); + } + boolean isDirectAttachedNetwork(final String type) { if ("untagged".equalsIgnoreCase(type)) { return true; @@ -3947,6 +4054,8 @@ public StartupCommand[] initialize() { hostDistro = cmd.getHostDetails().get("Host.OS"); } + cmd.setGpuDevices(getGpuDevices()); + List startupCommands = new ArrayList<>(); startupCommands.add(cmd); for (int i = 0; i < localStoragePaths.size(); i++) { @@ -6215,4 +6324,20 @@ public String getGuestCpuArch() { return guestCpuArch; } + public void attachGpuDevices(final VirtualMachineTO vmSpec, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException { + // GPU device is not set for the VM + if (vmSpec.getGpuDevice() == null || CollectionUtils.isEmpty(vmSpec.getGpuDevice().getGpuDevices())) { + return; + } + List gpuDevices = vmSpec.getGpuDevice().getGpuDevices(); + for (VgpuTypesInfo gpuDevice : gpuDevices) { + LibvirtGpuDef gpu = new LibvirtGpuDef(); + + // TODO: Handle GPU devices + gpu.defGpu(gpuDevice); + + vm.getDevices().addDevice(gpu); + LOGGER.info("Attached GPU device " + gpuDevice.getDeviceName() + " to VM " + vmSpec.getName()); + } + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDef.java new file mode 100644 index 000000000000..c850c9c225d2 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDef.java @@ -0,0 +1,91 @@ +// 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.hypervisor.kvm.resource; + +import com.cloud.agent.api.VgpuTypesInfo; +import org.apache.cloudstack.gpu.GpuDevice; + +public class LibvirtGpuDef { + + private VgpuTypesInfo vgpuType; + + public LibvirtGpuDef() {} + + public void defGpu(VgpuTypesInfo vgpuType) { + this.vgpuType = vgpuType; + } + + @Override + public String toString() { + StringBuilder gpuBuilder = new StringBuilder(); + GpuDevice.DeviceType deviceType = vgpuType.getDeviceType(); + + if (deviceType == GpuDevice.DeviceType.MDEV) { + // Generate XML for MDEV device (vGPU) + generateMdevXml(gpuBuilder); + } else { + // Generate XML for PCI device (passthrough GPU or VF) + generatePciXml(gpuBuilder); + } + + return gpuBuilder.toString(); + } + + private void generateMdevXml(StringBuilder gpuBuilder) { + String mdevUuid = vgpuType.getBusAddress(); // For MDEV devices, busAddress contains the UUID + + gpuBuilder.append("\n"); + gpuBuilder.append(" \n"); + gpuBuilder.append("
\n"); + gpuBuilder.append(" \n"); + gpuBuilder.append("\n"); + } + + private void generatePciXml(StringBuilder gpuBuilder) { + String busAddress = vgpuType.getBusAddress(); + + gpuBuilder.append("\n"); + gpuBuilder.append(" \n"); + gpuBuilder.append(" \n"); + + // Parse the bus address (e.g., 00:02.0) into domain, bus, slot, function + String domain = "0x0000"; + String bus = "0x00"; + String slot = "0x00"; + String function = "0x0"; + + if (busAddress != null && !busAddress.isEmpty()) { + String[] parts = busAddress.split(":"); + if (parts.length > 1) { + bus = "0x" + parts[0]; + String[] slotFunctionParts = parts[1].split("\\."); + if (slotFunctionParts.length > 0) { + slot = "0x" + slotFunctionParts[0]; + if (slotFunctionParts.length > 1) { + function = "0x" + slotFunctionParts[1].trim(); + } + } + } + } + + gpuBuilder.append("
\n"); + gpuBuilder.append(" \n"); + gpuBuilder.append("\n"); + } +} + diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetGPUStatsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetGPUStatsCommandWrapper.java new file mode 100644 index 000000000000..68894271ded8 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetGPUStatsCommandWrapper.java @@ -0,0 +1,97 @@ +// +// 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.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetGPUStatsAnswer; +import com.cloud.agent.api.GetGPUStatsCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = GetGPUStatsCommand.class) +public final class LibvirtGetGPUStatsCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final GetGPUStatsCommand command, final LibvirtComputingResource libvirtComputingResource) { + return new GetGPUStatsAnswer(command, libvirtComputingResource.getGpuDevices()); + } + +// +// private List getGPUDevices() { +// Script cmd = new Script("/bin/bash", logger); +// cmd.add("-c"); +// cmd.add("lspci -nn -m"); +// +// OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); +// String result = cmd.execute(parser); +// if (result == null && parser.getLines() != null) { +// String[] lines = parser.getLines().split("\\n"); +// List gpuDevices = new ArrayList<>(); +// /* Sample output +// +// 00:02.0 "VGA compatible controller [0300]" "Cirrus Logic [1013]" "GD 5446 [00b8]" "Red Hat, Inc. [1af4]" +// "QEMU Virtual Machine [1100]" +// 00:08.0 "3D controller [0302]" "NVIDIA Corporation [10de]" "GA107M [GeForce RTX 3050 Ti Mobile] [25a0]" -ra1 +// "Dell [1028]" "Device [0b19]" +// +// For example, the first line contains: +// Bus Address: 00:02.0 +// Vendor ID: 1013 +// Device ID: 00b8 +// Vendor Name: Cirrus Logic +// Device Name: GD 5446 +// Model Name: passthrough +// The second line contains: +// Bus Address: 00:08.0 +// Vendor ID: 10de +// Device ID: 25a0 +// Vendor Name: NVIDIA Corporation +// Device Name: GA107M [GeForce RTX 3050 Ti Mobile] +// Model Name: passthrough +// */ +// for (String line : lines) { +// if (line.toLowerCase().contains("vga") || line.toLowerCase().contains("3d") +// || line.toLowerCase().contains("nvidia") || line.toLowerCase().contains("amd") +// || line.toLowerCase().contains("gpu")) { +// String[] gpuDeviceDetails = line.split("\""); +// String busAddress = gpuDeviceDetails[0]; +// +// String vendorId = StringUtils.right(gpuDeviceDetails[3], 5).split("]")[0]; +// String vendorName = StringUtils.left(gpuDeviceDetails[3], gpuDeviceDetails[3].length() - 6).trim(); +// +// String deviceId = StringUtils.right(gpuDeviceDetails[5], 5).split("]")[0]; +// String deviceName = StringUtils.left(gpuDeviceDetails[5], gpuDeviceDetails[5].length() - 6).trim(); +// +// String modelName = "passthrough"; +// if (line.toLowerCase().contains("mig") || line.toLowerCase().contains("vgpu")) { +// modelName = "vgpu"; +// } +// gpuDevices.add(new VgpuTypesInfo(modelName, modelName, busAddress, +// vendorId, vendorName, deviceId, deviceName)); +// } +// } +// return gpuDevices; +// } else { +// logger.debug("Failed to get GPU devices: " + result); +// return new ArrayList<>(); +// } +// } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java index 09e4ec380278..e88c26d3712a 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java @@ -85,6 +85,8 @@ public Answer execute(final StartCommand command, final LibvirtComputingResource libvirtComputingResource.createVifs(vmSpec, vm); + libvirtComputingResource.attachGpuDevices(vmSpec, vm); + logger.debug("starting " + vmName + ": " + vm.toString()); String vmInitialSpecification = vm.toString(); String vmFinalSpecification = performXmlTransformHook(vmInitialSpecification, libvirtComputingResource); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDefTest.java new file mode 100644 index 000000000000..120a60c677d6 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtGpuDefTest.java @@ -0,0 +1,125 @@ +package com.cloud.hypervisor.kvm.resource; + +import com.cloud.agent.api.VgpuTypesInfo; +import junit.framework.TestCase; +import org.apache.cloudstack.gpu.GpuDevice; +import org.junit.Test; + +public class LibvirtGpuDefTest extends TestCase { + + @Test + public void testGpuDefWithPciPassthrough() { + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); + VgpuTypesInfo pciGpuInfo = new VgpuTypesInfo( + GpuDevice.DeviceType.PCI, + "passthrough", + "passthrough", + "00:02.0", + "10de", + "NVIDIA Corporation", + "1b38", + "Tesla T4" + ); + gpuDef.defGpu(pciGpuInfo); + + String gpuXml = gpuDef.toString(); + + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("
")); + assertTrue(gpuXml.contains("")); + } + + @Test + public void testGpuDefWithMdevDevice() { + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); + VgpuTypesInfo mdevGpuInfo = new VgpuTypesInfo( + GpuDevice.DeviceType.MDEV, + "nvidia-63", + "GRID T4-2Q", + "4b20d080-1b54-4048-85b3-a6a62d165c01", + "10de", + "NVIDIA Corporation", + "1eb8", + "Tesla T4" + ); + gpuDef.defGpu(mdevGpuInfo); + + String gpuXml = gpuDef.toString(); + + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("
")); + assertTrue(gpuXml.contains("")); + assertFalse(gpuXml.contains("vfio")); // MDEV should not contain vfio driver element + } + + @Test + public void testGpuDefWithSriovVirtualFunction() { + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); + VgpuTypesInfo vfGpuInfo = new VgpuTypesInfo( + GpuDevice.DeviceType.PCI, + "VF-Profile", + "VF-Profile", + "00:10.1", + "8086", + "Intel Corporation", + "1515", + "X710 Virtual Function" + ); + gpuDef.defGpu(vfGpuInfo); + + String gpuXml = gpuDef.toString(); + + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("
")); + assertTrue(gpuXml.contains("")); + } + + @Test + public void testGpuDefWithComplexPciAddress() { + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); + VgpuTypesInfo complexPciGpuInfo = new VgpuTypesInfo( + GpuDevice.DeviceType.PCI, + "passthrough", + "passthrough", + "81:00.0", + "1002", + "Advanced Micro Devices", + "73a3", + "Navi 21" + ); + gpuDef.defGpu(complexPciGpuInfo); + + String gpuXml = gpuDef.toString(); + + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("
")); + assertTrue(gpuXml.contains("")); + } + + @Test + public void testGpuDefWithNullDeviceType() { + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); + VgpuTypesInfo nullTypeGpuInfo = new VgpuTypesInfo( + null, // null device type should default to PCI behavior + "passthrough", + "passthrough", + "00:05.0", + "10de", + "NVIDIA Corporation", + "1db4", + "V100" + ); + gpuDef.defGpu(nullTypeGpuInfo); + + String gpuXml = gpuDef.toString(); + + // Should default to PCI behavior when device type is null + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("")); + assertTrue(gpuXml.contains("
")); + assertTrue(gpuXml.contains("")); + } +} diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java index 260ffe594f3c..2c3ebcbd1eba 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java @@ -16,10 +16,13 @@ // under the License. package com.cloud.agent.manager; +import java.util.List; import java.util.Map; import javax.naming.ConfigurationException; +import com.cloud.agent.api.GetGPUStatsCommand; +import com.cloud.agent.api.VgpuTypesInfo; import org.apache.cloudstack.ca.SetupCertificateCommand; import org.apache.cloudstack.ca.SetupKeyStoreCommand; @@ -55,6 +58,10 @@ boolean handleSystemVMStart(long vmId, String privateIpAddress, String privateMa Answer checkHealth(CheckHealthCommand cmd); + Answer getGpuStats(GetGPUStatsCommand cmd, long hostId); + + List getGPUDevices(long hostId); + Answer pingTest(PingTestCommand cmd); Answer setupKeyStore(SetupKeyStoreCommand cmd); diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java index d3d6f646a7e7..d494a0e1e3c8 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java @@ -21,11 +21,14 @@ import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.CheckNetworkAnswer; import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.GetGPUStatsAnswer; +import com.cloud.agent.api.GetGPUStatsCommand; import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.HostStatsEntry; import com.cloud.agent.api.MaintainAnswer; import com.cloud.agent.api.PingTestCommand; +import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.api.commands.SimulatorAddSecondaryAgent; import com.cloud.dc.DataCenter; @@ -40,10 +43,14 @@ import com.cloud.resource.AgentStorageResource; import com.cloud.resource.Discoverer; import com.cloud.resource.ResourceManager; +import com.cloud.resource.SimulatorDiscoverer; import com.cloud.resource.SimulatorSecondaryDiscoverer; +import com.cloud.simulator.MockGpuDevice; +import com.cloud.simulator.MockGpuDeviceVO; import com.cloud.simulator.MockHost; import com.cloud.simulator.MockHostVO; import com.cloud.simulator.MockVMVO; +import com.cloud.simulator.dao.MockGpuDeviceDao; import com.cloud.simulator.dao.MockHostDao; import com.cloud.simulator.dao.MockVMDao; import com.cloud.user.AccountManager; @@ -61,12 +68,14 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.diagnostics.DiagnosticsAnswer; import org.apache.cloudstack.diagnostics.DiagnosticsCommand; +import org.apache.cloudstack.gpu.GpuDevice; import org.springframework.stereotype.Component; import javax.inject.Inject; import javax.naming.ConfigurationException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -88,6 +97,8 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage @Inject MockVMDao _mockVmDao = null; @Inject + MockGpuDeviceDao _mockGpuDeviceDao = null; + @Inject SimulatorManager _simulatorMgr = null; @Inject AgentManager _agentMgr = null; @@ -97,7 +108,8 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage ResourceManager _resourceMgr; @Inject private AccountManager _accountMgr; - SimulatorSecondaryDiscoverer discoverer; + SimulatorDiscoverer discoverer; + SimulatorSecondaryDiscoverer ssDiscoverer; @Inject HostDao hostDao; @@ -184,10 +196,19 @@ public Map> createServerResources(Map> createServerResources(Map params) throws ConfigurationException { try { @@ -371,7 +537,7 @@ public void run() { storageResource.configure("secondaryStorage", params); storageResource.start(); _resources.put(this.guid, storageResource); - discoverer.setResource(storageResource); + ssDiscoverer.setResource(storageResource); SimulatorAddSecondaryAgent cmd = new SimulatorAddSecondaryAgent("sim://" + this.guid, this.dcId); try { _resourceMgr.discoverHosts(cmd); @@ -469,6 +635,63 @@ public Answer checkHealth(CheckHealthCommand cmd) { return new Answer(cmd); } + @Override + public Answer getGpuStats(GetGPUStatsCommand cmd, long hostId) { + return new GetGPUStatsAnswer(cmd, getGPUDevices(hostId)); + } + + @Override + public List getGPUDevices(long hostId) { + List gpuDevices = new ArrayList<>(); + List mockGpuDevices; + // List all mock GPU devices from database + TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.SIMULATOR_DB); + try { + txn.start(); + mockGpuDevices = _mockGpuDeviceDao.listByHostId(hostId); + + if (mockGpuDevices != null && !mockGpuDevices.isEmpty()) { + logger.debug("Found {} mock GPU devices in the database", mockGpuDevices.size()); + + for (MockGpuDeviceVO mockGpuDevice : mockGpuDevices) { + String busAddress = mockGpuDevice.getBusAddress(); + String vendorId = mockGpuDevice.getVendorId(); + String deviceId = mockGpuDevice.getDeviceId(); + String vendorName = mockGpuDevice.getVendorName(); + String deviceName = mockGpuDevice.getDeviceName(); + String modelName = mockGpuDevice.getProfileName(); + boolean isPassthrough = mockGpuDevice.isPassthroughEnabled(); + + VgpuTypesInfo vgpuTypesInfo = new VgpuTypesInfo(mockGpuDevice.getDeviceType(), modelName, modelName, busAddress, + vendorId, vendorName, deviceId, deviceName); + vgpuTypesInfo.setPassthroughEnabled(isPassthrough); + + if (mockGpuDevice.getVmId() != null) { + MockVMVO mockVm = _mockVmDao.findById(mockGpuDevice.getVmId()); + vgpuTypesInfo.setVmName(mockVm.getName()); + } + + if (mockGpuDevice.getParentDeviceId() != null) { + MockGpuDeviceVO parentDevice = _mockGpuDeviceDao.findById(mockGpuDevice.getParentDeviceId()); + if (parentDevice != null) { + vgpuTypesInfo.setParentBusAddress(parentDevice.getBusAddress()); + } + } + gpuDevices.add(vgpuTypesInfo); + } + } + txn.commit(); + } catch (Exception ex) { + txn.rollback(); + throw new CloudRuntimeException("Unable to get GPU devices on hostId " + hostId + " due to " + ex.getMessage(), ex); + } finally { + txn.close(); + txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB); + txn.close(); + } + return gpuDevices; + } + @Override public Answer pingTest(PingTestCommand cmd) { return new Answer(cmd); @@ -497,12 +720,14 @@ public Answer setupCertificate(SetupCertificateCommand cmd) { public boolean start() { for (Discoverer discoverer : discoverers) { if (discoverer instanceof SimulatorSecondaryDiscoverer) { - this.discoverer = (SimulatorSecondaryDiscoverer)discoverer; - break; + this.ssDiscoverer = (SimulatorSecondaryDiscoverer)discoverer; + } + if (discoverer instanceof SimulatorDiscoverer) { + this.discoverer = (SimulatorDiscoverer)discoverer; } } - if (this.discoverer == null) { + if (this.ssDiscoverer == null) { throw new IllegalStateException("Failed to find SimulatorSecondaryDiscoverer"); } diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockVmManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockVmManagerImpl.java index 953c5792a500..aae957ce6ec7 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockVmManagerImpl.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockVmManagerImpl.java @@ -25,6 +25,11 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.api.VgpuTypesInfo; +import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.simulator.MockGpuDevice; +import com.cloud.simulator.MockGpuDeviceVO; +import com.cloud.simulator.dao.MockGpuDeviceDao; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.springframework.stereotype.Component; @@ -99,6 +104,8 @@ public class MockVmManagerImpl extends ManagerBase implements MockVmManager { @Inject MockHostDao _mockHostDao = null; @Inject + MockGpuDeviceDao _mockGpuDeviceDao = null; + @Inject MockSecurityRulesDao _mockSecurityDao = null; private final Map>> _securityRules = new ConcurrentHashMap>>(); @@ -111,7 +118,7 @@ public boolean configure(final String name, final Map params) th return true; } - public String startVM(final String vmName, final NicTO[] nics, final int cpuHz, final long ramSize, final String bootArgs, final String hostGuid) { + public String startVM(final String vmName, final NicTO[] nics, GPUDeviceTO gpuDeviceTO, final int cpuHz, final long ramSize, final String bootArgs, final String hostGuid) { TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.SIMULATOR_DB); MockHost host = null; @@ -157,6 +164,22 @@ public String startVM(final String vmName, final NicTO[] nics, final int cpuHz, try { txn.start(); vm = _mockVmDao.persist((MockVMVO)vm); + if (gpuDeviceTO != null) { + List gpuDevices = gpuDeviceTO.getGpuDevices(); + for (VgpuTypesInfo gpuDevice : gpuDevices) { + MockGpuDeviceVO mockGpuDevice = _mockGpuDeviceDao.listByHostIdAndBusAddress(host.getId(), gpuDevice.getBusAddress()); + mockGpuDevice.setVmId(vm.getId()); + mockGpuDevice.setState(MockGpuDevice.State.Allocated); + _mockGpuDeviceDao.persist(mockGpuDevice); + } + } else { + List mockGpuDevices = _mockGpuDeviceDao.listByVmId(vm.getId()); + for (MockGpuDeviceVO mockGpuDevice : mockGpuDevices) { + mockGpuDevice.setVmId(null); + mockGpuDevice.setState(MockGpuDevice.State.Available); + _mockGpuDeviceDao.persist(mockGpuDevice); + } + } txn.commit(); } catch (final Exception ex) { txn.rollback(); @@ -331,7 +354,7 @@ public CheckVirtualMachineAnswer checkVmState(final CheckVirtualMachineCommand c @Override public StartAnswer startVM(final StartCommand cmd, final SimulatorInfo info) { final VirtualMachineTO vm = cmd.getVirtualMachine(); - final String result = startVM(vm.getName(), vm.getNics(), vm.getCpus() * vm.getMaxSpeed(), vm.getMaxRam(), vm.getBootArgs(), info.getHostUuid()); + final String result = startVM(vm.getName(), vm.getNics(), vm.getGpuDevice(), vm.getCpus() * vm.getMaxSpeed(), vm.getMaxRam(), vm.getBootArgs(), info.getHostUuid()); if (result != null) { return new StartAnswer(cmd, result); } else { diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java index cb8d71985e34..39e504d7f952 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java @@ -26,6 +26,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.api.GetGPUStatsCommand; import org.apache.cloudstack.ca.SetupCertificateCommand; import org.apache.cloudstack.ca.SetupKeyStoreCommand; import org.apache.cloudstack.diagnostics.DiagnosticsCommand; @@ -287,8 +288,10 @@ public Answer simulate(final Command cmd, final String hostGuid) { if (answer == null) { if (cmd instanceof GetHostStatsCommand) { answer = _mockAgentMgr.getHostStatistic((GetHostStatsCommand)cmd); + } else if (cmd instanceof GetGPUStatsCommand) { + answer = _mockAgentMgr.getGpuStats((GetGPUStatsCommand) cmd, host.getId()); } else if (cmd instanceof CheckHealthCommand) { - answer = _mockAgentMgr.checkHealth((CheckHealthCommand)cmd); + answer = _mockAgentMgr.checkHealth((CheckHealthCommand) cmd); } else if (cmd instanceof PingTestCommand) { answer = _mockAgentMgr.pingTest((PingTestCommand)cmd); } else if (cmd instanceof SetupKeyStoreCommand) { diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/resource/SimulatorDiscoverer.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/resource/SimulatorDiscoverer.java index 332ac6098033..1b5156eb5e6f 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/resource/SimulatorDiscoverer.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/resource/SimulatorDiscoverer.java @@ -300,9 +300,34 @@ public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] st return null; } + ssCmd.setGpuDevices(_mockAgentMgr.getGPUDevices(host.getId())); + return _resourceMgr.fillRoutingHostVO(host, ssCmd, HypervisorType.Simulator, details, hostTags); } +// public List getGPUDevices() { +// List gpuDevices = new ArrayList<>(); +// // Fallback to the hardcoded implementation if DAO is not available +// logger.warn("MockGpuDeviceDao is not available, using fallback hardcoded GPU devices"); +// List lspciOutput = new ArrayList<>(); +// lspciOutput.add("00:01.0 \"3D controller [0302]\" \"Apache Cloudstack Simulator [0000]\" \"Simulated GPU [ACS Simulated GPU] [ffff]\" -ra1 -p00 \"XXXX [0000]\" \"Device [0001]\""); +// lspciOutput.add("00:02.0 \"3D controller [0302]\" \"Apache Cloudstack Simulator [0000]\" \"Simulated GPU [ACS Simulated GPU] [ffff]\" -ra1 -p00 \"XXXX [0000]\" \"Device [0001]\""); +// +// for (String gpuDevice : lspciOutput) { +// String[] gpuDeviceDetails = gpuDevice.split(" \""); +// String busAddress = gpuDeviceDetails[0]; +// String vendorId = gpuDeviceDetails[2].split("\\[")[1].split("]")[0]; +// String deviceId = gpuDeviceDetails[3].split("\\[")[2].split("]")[0]; +// String vendorName = gpuDeviceDetails[2].split(" \\[")[0]; +// String deviceName = gpuDeviceDetails[3].split(" \\[")[0]; +// String modelName = "passthrough"; +// +// gpuDevices.add(new VgpuTypesInfo(modelName, modelName, busAddress, +// vendorId, vendorName, deviceId, deviceName)); +// } +// return gpuDevices; +// } + @Override public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { return null; diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDevice.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDevice.java new file mode 100644 index 000000000000..614e6c86753d --- /dev/null +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDevice.java @@ -0,0 +1,43 @@ +// 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.simulator; + +public interface MockGpuDevice { + + enum State { + Allocated, + Available + } + + long getId(); + + String getBusAddress(); + + String getVendorId(); + + String getDeviceId(); + + String getVendorName(); + + String getDeviceName(); + + Long getHostId(); + + Long getVmId(); + + State getState(); +} diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDeviceVO.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDeviceVO.java new file mode 100644 index 000000000000..efd503464fef --- /dev/null +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/MockGpuDeviceVO.java @@ -0,0 +1,237 @@ +// 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.simulator; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.gpu.GpuDevice; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "mockgpudevice") +public class MockGpuDeviceVO implements MockGpuDevice, InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "bus_address", nullable = false) + private String busAddress; // PCI address for parent devices, MDEV UUID for MDEV devices, VF PCI address for VF devices + + @Column(name = "vendor_id", nullable = false) + private String vendorId; + + @Column(name = "device_id", nullable = false) + private String deviceId; + + @Column(name = "vendor_name", nullable = false) + private String vendorName; + + @Column(name = "device_name", nullable = false) + private String deviceName; + + @Column(name = "host_id") + private Long hostId; + + @Column(name = "vm_id") + private Long vmId; + + @Column(name = "state") + @Enumerated(EnumType.STRING) + private State state; + + // New fields to support KVM-like behavior + @Column(name = "device_type") + @Enumerated(EnumType.STRING) + private GpuDevice.DeviceType deviceType = GpuDevice.DeviceType.PCI; + + @Column(name = "parent_device_id") + private Long parentDeviceId; + + @Column(name = "profile_name") + private String profileName; + + @Column(name = "passthrough_enabled") + private boolean passthroughEnabled = true; + + public MockGpuDeviceVO() { + } + + public MockGpuDeviceVO(String busAddress, String vendorId, String deviceId, + String vendorName, String deviceName, Long hostId) { + this.busAddress = busAddress; + this.vendorId = vendorId; + this.deviceId = deviceId; + this.vendorName = vendorName; + this.deviceName = deviceName; + this.hostId = hostId; + this.state = State.Available; + this.deviceType = GpuDevice.DeviceType.PCI; + this.profileName = "passthrough"; + this.passthroughEnabled = true; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getBusAddress() { + return busAddress; + } + + public void setBusAddress(String busAddress) { + this.busAddress = busAddress; + } + + @Override + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } + + @Override + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + @Override + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + @Override + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } + + @Override + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + @Override + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + // New getters and setters for enhanced fields + public GpuDevice.DeviceType getDeviceType() { + return deviceType; + } + + public void setDeviceType(GpuDevice.DeviceType deviceType) { + this.deviceType = deviceType; + } + + public Long getParentDeviceId() { + return parentDeviceId; + } + + public void setParentDeviceId(Long parentDeviceId) { + this.parentDeviceId = parentDeviceId; + } + + public String getProfileName() { + return profileName; + } + + public void setProfileName(String profileName) { + this.profileName = profileName; + } + + public boolean isPassthroughEnabled() { + return passthroughEnabled; + } + + public void setPassthroughEnabled(boolean passthroughEnabled) { + this.passthroughEnabled = passthroughEnabled; + } + + /** + * Helper method to get the MDEV UUID (when device_type is MDEV) + * + * @return MDEV UUID or null if not an MDEV device + */ + public String getMdevUuid() { + return GpuDevice.DeviceType.MDEV.equals(this.deviceType) ? this.busAddress : null; + } + + /** + * Helper method to get the VF PCI address (when device_type is PCI and has + * parent) + * + * @return VF PCI address or null if not a VF device + */ + public String getVfPciAddress() { + return GpuDevice.DeviceType.PCI.equals(this.deviceType) && this.parentDeviceId != null ? this.busAddress : null; + } + + /** + * Helper method to get the parent PCI bus address (when device_type is PCI and + * no parent) + * + * @return Parent PCI bus address or null if not a parent device + */ + public String getParentPciBusAddress() { + return GpuDevice.DeviceType.PCI.equals(this.deviceType) && this.parentDeviceId == null ? this.busAddress : null; + } +} diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDao.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDao.java new file mode 100644 index 000000000000..1bab3d5fba57 --- /dev/null +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDao.java @@ -0,0 +1,56 @@ +// 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.simulator.dao; + +import com.cloud.simulator.MockGpuDeviceVO; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface MockGpuDeviceDao extends GenericDao { + + /** + * Find GPU device by bus address + * @param busAddress the bus address + * @return the GPU device or null if not found + */ + MockGpuDeviceVO listByHostIdAndBusAddress(long hostId, String busAddress); + + /** + * List GPU devices by host ID + * @param hostId the host ID + * @return list of GPU devices + */ + List listByHostId(Long hostId); + + List listByVmId(Long vmId); + + /** + * List GPU devices by host ID and VM ID + * @param hostId the host ID + * @param vmId the VM ID + * @return list of GPU devices + */ + List listByHostIdAndVmId(Long hostId, Long vmId); + + /** + * List GPU devices available for allocation on a host + * @param hostId the host ID + * @return list of available GPU devices + */ + List listAvailableByHost(Long hostId); +} diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDaoImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDaoImpl.java new file mode 100644 index 000000000000..7e7b4b8f30a6 --- /dev/null +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockGpuDeviceDaoImpl.java @@ -0,0 +1,92 @@ +// 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.simulator.dao; + +import com.cloud.simulator.MockGpuDevice; +import com.cloud.simulator.MockGpuDeviceVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Filter; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MockGpuDeviceDaoImpl extends GenericDaoBase implements MockGpuDeviceDao { + + private final SearchBuilder allFieldSearch; + private final SearchBuilder hostAndAvailableSearch; + + public MockGpuDeviceDaoImpl() { + + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("busAddress", allFieldSearch.entity().getBusAddress(), SearchCriteria.Op.EQ); + allFieldSearch.and("hostId", allFieldSearch.entity().getHostId(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorName", allFieldSearch.entity().getVendorName(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorId", allFieldSearch.entity().getVendorId(), SearchCriteria.Op.EQ); + allFieldSearch.and("deviceId", allFieldSearch.entity().getDeviceId(), SearchCriteria.Op.EQ); + allFieldSearch.and("deviceName", allFieldSearch.entity().getDeviceName(), SearchCriteria.Op.EQ); + allFieldSearch.and("state", allFieldSearch.entity().getState(), SearchCriteria.Op.EQ); + allFieldSearch.and("vmId", allFieldSearch.entity().getVmId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + + hostAndAvailableSearch = createSearchBuilder(); + hostAndAvailableSearch.and("hostId", hostAndAvailableSearch.entity().getHostId(), SearchCriteria.Op.EQ); + hostAndAvailableSearch.and("state", hostAndAvailableSearch.entity().getState(), SearchCriteria.Op.EQ); + hostAndAvailableSearch.and("vmId", hostAndAvailableSearch.entity().getVmId(), SearchCriteria.Op.NULL); + hostAndAvailableSearch.done(); + } + + @Override + public MockGpuDeviceVO listByHostIdAndBusAddress(long hostId, String busAddress) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("busAddress", busAddress); + return findOneBy(sc); + } + + @Override + public List listByHostId(Long hostId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("hostId", hostId); + return search(sc, new Filter(MockGpuDeviceVO.class, "id", true)); + } + + @Override + public List listByVmId(Long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("vmId", vmId); + return search(sc, null); + } + + @Override + public List listByHostIdAndVmId(Long hostId, Long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("vmId", vmId); + return search(sc, null); + } + + @Override + public List listAvailableByHost(Long hostId) { + SearchCriteria sc = hostAndAvailableSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("state", MockGpuDevice.State.Available); + return search(sc, null); + } +} diff --git a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/core/spring-simulator-core-context.xml b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/core/spring-simulator-core-context.xml index e0ed6066bc52..c085e309aac6 100644 --- a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/core/spring-simulator-core-context.xml +++ b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/core/spring-simulator-core-context.xml @@ -29,6 +29,7 @@ + diff --git a/scripts/vm/hypervisor/kvm/gpudiscovery.sh b/scripts/vm/hypervisor/kvm/gpudiscovery.sh new file mode 100755 index 000000000000..fd2261140237 --- /dev/null +++ b/scripts/vm/hypervisor/kvm/gpudiscovery.sh @@ -0,0 +1,596 @@ +#!/bin/bash +# 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. + +# +# Enumerate GPUs (NVIDIA, Intel, AMD) and output JSON for libvirt: +# - PCI metadata (address, vendor/device IDs, driver, pci_class) +# - IOMMU group +# - SR-IOV VF counts +# - full_passthrough block +# - vGPU (MDEV) instances with available_instances +# - VF (SR-IOV/MIG) instances with actual profile names +# +# Uses `lspci -nnm` for GPU discovery and `virsh` to detect VM attachments. +# Compatible with Ubuntu (20.04+, 22.04+) and RHEL/CentOS (7/8), Bash ≥4. +# +# Sample JSON: +# { +# "gpus": [ +# { +# "pci_address": "00:03.0", +# "vendor_id": "10de", +# "device_id": "2484", +# "vendor": "NVIDIA Corporation", +# "device": "GeForce RTX 3070", +# "driver": "nvidia", +# "pci_class": "VGA compatible controller", +# "iommu_group": "8", +# "sriov_totalvfs": 0, +# "sriov_numvfs": 0, + +# "full_passthrough": { +# "enabled": true, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x03", +# "function": "0x0" +# }, +# "used_by_vm": "win10" +# }, + +# "vgpu_instances": [], + +# "vf_instances": [] +# }, +# { +# "pci_address": "00:AF.0", +# "vendor_id": "10de", +# "device_id": "1EB8", +# "vendor": "NVIDIA Corporation", +# "device": "Tesla T4", +# "driver": "nvidia", +# "pci_class": "3D controller", +# "iommu_group": "12", +# "sriov_totalvfs": 0, +# "sriov_numvfs": 0, + +# "full_passthrough": { +# "enabled": false, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0xAF", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, + +# "vgpu_instances": [ +# { +# "mdev_uuid": "a1b2c3d4-5678-4e9a-8b0c-d1e2f3a4b5c6", +# "profile_name": "grid_t4-16c", +# "available_instances": 4, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0xAF", +# "function": "0x0" +# }, +# "used_by_vm": "vm1" +# }, +# { +# "mdev_uuid": "b2c3d4e5-6789-4f0a-9c1d-e2f3a4b5c6d7", +# "profile_name": "grid_t4-8c", +# "available_instances": 8, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0xAF", +# "function": "0x1" +# }, +# "used_by_vm": "vm2" +# } +# ], + +# "vf_instances": [] +# }, +# { +# "pci_address": "00:65.0", +# "vendor_id": "10de", +# "device_id": "20B0", +# "vendor": "NVIDIA Corporation", +# "device": "A100-SXM4-40GB", +# "driver": "nvidia", +# "pci_class": "VGA compatible controller", +# "iommu_group": "15", +# "sriov_totalvfs": 7, +# "sriov_numvfs": 7, + +# "full_passthrough": { +# "enabled": false, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x65", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, + +# "vgpu_instances": [ +# { +# "mdev_uuid": "f4a2c8de-1234-4b3a-8c9d-0a1b2c3d4e5f", +# "profile_name": "grid_a100-8c", +# "available_instances": 8, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x65", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, +# { +# "mdev_uuid": "e5b3d9ef-5678-4c2b-9d0e-1b2c3d4e5f6a", +# "profile_name": "grid_a100-5c", +# "available_instances": 5, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x65", +# "function": "0x1" +# }, +# "used_by_vm": null +# } +# ], + +# "vf_instances": [ +# { +# "vf_pci_address": "65:00.2", +# "vf_profile": "1g.5gb", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x65", +# "function": "0x2" +# }, +# "used_by_vm": "ml" +# }, +# { +# "vf_pci_address": "65:00.3", +# "vf_profile": "2g.10gb", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x65", +# "function": "0x3" +# }, +# "used_by_vm": null +# } +# ] +# }, +# { +# "pci_address": "00:02.0", +# "vendor_id": "8086", +# "device_id": "46A6", +# "vendor": "Intel Corporation", +# "device": "Alder Lake-P GT2 [Iris Xe Graphics]", +# "driver": "i915", +# "pci_class": "VGA compatible controller", +# "iommu_group": "0", +# "sriov_totalvfs": 4, +# "sriov_numvfs": 4, + +# "full_passthrough": { +# "enabled": false, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x02", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, + +# "vgpu_instances": [ +# { +# "mdev_uuid": "b7c8d9fe-1111-2222-3333-444455556666", +# "profile_name": "i915-GVTg_V5_4", +# "available_instances": 4, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x02", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, +# { +# "mdev_uuid": "c8d9e0af-7777-8888-9999-000011112222", +# "profile_name": "i915-GVTg_V5_8", +# "available_instances": 8, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x02", +# "function": "0x1" +# }, +# "used_by_vm": null +# } +# ], + +# "vf_instances": [ +# { +# "vf_pci_address": "00:02.1", +# "vf_profile": "Intel SR-IOV VF 1", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x02", +# "function": "0x1" +# }, +# "used_by_vm": "linux01" +# }, +# { +# "vf_pci_address": "00:02.2", +# "vf_profile": "Intel SR-IOV VF 2", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x02", +# "function": "0x2" +# }, +# "used_by_vm": null +# } +# ] +# }, +# { +# "pci_address": "00:03.0", +# "vendor_id": "1002", +# "device_id": "7340", +# "vendor": "AMD", +# "device": "Instinct MI210", +# "driver": "amdgpu", +# "pci_class": "3D controller", +# "iommu_group": "8", +# "sriov_totalvfs": 8, +# "sriov_numvfs": 8, + +# "full_passthrough": { +# "enabled": false, +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x03", +# "function": "0x0" +# }, +# "used_by_vm": null +# }, + +# "vgpu_instances": [], + +# "vf_instances": [ +# { +# "vf_pci_address": "03:00.1", +# "vf_profile": "mi210-4c", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x03", +# "function": "0x1" +# }, +# "used_by_vm": null +# }, +# { +# "vf_pci_address": "03:00.2", +# "vf_profile": "mi210-2c", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x03", +# "function": "0x2" +# }, +# "used_by_vm": null +# }, +# { +# "vf_pci_address": "03:00.3", +# "vf_profile": "mi210-1c", +# "libvirt_address": { +# "domain": "0x0000", +# "bus": "0x00", +# "slot": "0x03", +# "function": "0x3" +# }, +# "used_by_vm": null +# } +# ] +# } +# ] +# } +# + +set -euo pipefail + +# === Utility Functions === + +# Escape a string for JSON +json_escape() { + local str=${1//\\/\\\\} + str=${str//\"/\\\"} + str=${str//$'\n'/\\n} + printf '"%s"' "$str" +} + +# Given a PCI address (e.g. "00:02.0"), return its IOMMU group or "null" +get_iommu_group() { + local addr="$1" + for grp in /sys/kernel/iommu_groups/*/devices/*; do + if [[ "${grp##*/}" == "0000:$addr" ]]; then + echo "${grp#*/iommu_groups/}" | cut -d/ -f1 + return + fi + done + echo "null" +} + +# Given a PCI address, output "TOTALVFS NUMVFS" +get_sriov_counts() { + local addr="$1" + local path="/sys/bus/pci/devices/0000:$addr" + if [[ -f "$path/sriov_totalvfs" ]]; then + echo "$(<"$path/sriov_totalvfs") $(<"$path/sriov_numvfs")" + else + echo "0 0" + fi +} + +# Build VM → hostdev maps: +# pci_to_vm[BDF] = VM name that attaches that BDF +# mdev_to_vm[UUID] = VM name that attaches that MDEV UUID +declare -A pci_to_vm mdev_to_vm + +# Gather all VM names (including inactive) +mapfile -t VMS < <(virsh list --all --name | grep -v '^$') +for VM in "${VMS[@]}"; do + # Skip if dumpxml fails + if ! xml=$(virsh dumpxml "$VM" 2>/dev/null); then + continue + fi + + # -- PCI hostdevs: locate blocks and extract BDF -- + while IFS= read -r line; do + if [[ $line =~ \ line + while IFS= read -r sub; do + if [[ $sub =~ bus=\'0x([0-9A-Fa-f]{2})\'[[:space:]]slot=\'0x([0-9A-Fa-f]{2})\'[[:space:]]function=\'0x([0-9A-Fa-f])\' ]]; then + B="${BASH_REMATCH[1]}" + S="${BASH_REMATCH[2]}" + F="${BASH_REMATCH[3]}" + BDF="${B}:${S}.${F}" + pci_to_vm["$BDF"]="$VM" + break + fi + done + fi + done <<< "$xml" + + # -- MDEV hostdevs: locate and extract UUID -- + while IFS= read -r line; do + if [[ $line =~ \ line + while IFS= read -r sub; do + if [[ $sub =~ uuid=\'([0-9a-fA-F-]+)\' ]]; then + UUID="${BASH_REMATCH[1]}" + mdev_to_vm["$UUID"]="$VM" + break + fi + done + fi + done <<< "$xml" +done + +# Helper: convert a VM name to JSON value (quoted string or null) +to_json_vm() { + local vm="$1" + if [[ -z "$vm" ]]; then + echo "null" + else + json_escape "$vm" + fi +} + +# === GPU Discovery === + +mapfile -t LINES < <(lspci -nnm) + +echo '{ "gpus": [' + +first_gpu=true +for LINE in "${LINES[@]}"; do + # Parse lspci -nnm fields: SLOT "CLASS [CODE]" "VENDOR [VID]" "DEVICE [DID]" ... + if [[ $LINE =~ ^([^[:space:]]+)[[:space:]]\"([^\"]+)\"[[:space:]]\"([^\"]+)\"[[:space:]]\"([^\"]+)\" ]]; then + PCI_ADDR="${BASH_REMATCH[1]}" + PCI_CLASS="${BASH_REMATCH[2]}" + VENDOR_FIELD="${BASH_REMATCH[3]}" + DEVICE_FIELD="${BASH_REMATCH[4]}" + else + continue + fi + + # Only process GPU classes + if [[ ! "$PCI_CLASS" =~ (VGA\ compatible\ controller|3D\ controller) ]]; then + continue + fi + + # Extract vendor name and ID + VENDOR=$(sed -E 's/ \[[0-9A-Fa-f]{4}\]$//' <<<"$VENDOR_FIELD") + VENDOR_ID=$(sed -E 's/.*\[([0-9A-Fa-f]{4})\]$/\1/' <<<"$VENDOR_FIELD") + # Extract device name and ID + DEVICE=$(sed -E 's/ \[[0-9A-Fa-f]{4}\]$//' <<<"$DEVICE_FIELD") + DEVICE_ID=$(sed -E 's/.*\[([0-9A-Fa-f]{4})\]$/\1/' <<<"$DEVICE_FIELD") + + # Kernel driver + DRV_PATH="/sys/bus/pci/devices/0000:$PCI_ADDR/driver" + if [[ -L $DRV_PATH ]]; then + DRIVER=$(basename "$(readlink "$DRV_PATH")") + else + DRIVER="unknown" + fi + + # IOMMU group + IOMMU=$(get_iommu_group "$PCI_ADDR") + + # SR-IOV counts + read -r TOTALVFS NUMVFS < <(get_sriov_counts "$PCI_ADDR") + + # === full_passthrough usage === + FULL_USED_JSON="null" + if (( TOTALVFS == 0 )); then + raw="${pci_to_vm[$PCI_ADDR]:-}" + FULL_USED_JSON=$(to_json_vm "$raw") + fi + + # === vGPU (MDEV) instances === + VGPU_ARRAY="[]" + MDEV_PATH="/sys/class/mdev_bus/0000:$PCI_ADDR" + if [[ -d $MDEV_PATH ]]; then + declare -a vlist=() + for U in "$MDEV_PATH"/devices/*; do + [[ -d $U ]] || continue + MDEV_UUID=${U##*/} + + # Profile name from mdev_type/name + TYPE_PATH="$(readlink -f "$U/mdev_type")" + PROFILE_NAME="" + if [[ -f "$TYPE_PATH/name" ]]; then + PROFILE_NAME=$(<"$TYPE_PATH/name") + fi + + # Available instances + AVAILABLE_INSTANCES=0 + if [[ -f "$TYPE_PATH/available_instances" ]]; then + AVAILABLE_INSTANCES=$(<"$TYPE_PATH/available_instances") + fi + + # libvirt_address uses PF BDF + DOMAIN="0x0000" + BUS="0x${PCI_ADDR:0:2}" + SLOT="0x${PCI_ADDR:3:2}" + FUNC="0x${PCI_ADDR:6:1}" + + # Determine which VMs use this UUID + raw="${mdev_to_vm[$MDEV_UUID]:-}" + USED_JSON=$(to_json_vm "$raw") + + vlist+=( \ + "{\"mdev_uuid\":\"$MDEV_UUID\",\"profile_name\":$(json_escape "$PROFILE_NAME"),\"available_instances\":$AVAILABLE_INSTANCES,\"libvirt_address\":{"\ + "\"domain\":\"$DOMAIN\",\"bus\":\"$BUS\",\"slot\":\"$SLOT\",\"function\":\"$FUNC\"},\"used_by_vm\":$USED_JSON}" ) + done + VGPU_ARRAY="[${vlist[*]}]" + fi + + # === VF instances (SR-IOV / MIG) === + VF_ARRAY="[]" + if (( TOTALVFS > 0 )); then + declare -a flist=() + for VF_LINK in /sys/bus/pci/devices/0000:"$PCI_ADDR"/virtfn*; do + [[ -L $VF_LINK ]] || continue + VF_PATH=$(readlink -f "$VF_LINK") + VF_ADDR=${VF_PATH##*/} # e.g. "0000:65:00.2" + VF_BDF="${VF_ADDR:5}" # "65:00.2" + + DOMAIN="0x0000" + BUS="0x${VF_BDF:0:2}" + SLOT="0x${VF_BDF:3:2}" + FUNC="0x${VF_BDF:6:1}" + + # Determine vf_type and vf_profile + VF_TYPE="sr-iov" + VF_PROFILE="" + if [[ $VENDOR_ID == "10de" && -f "/sys/bus/pci/devices/$VF_ADDR/device/mig_profile" ]]; then + VF_TYPE="mig" + VF_PROFILE=$(<"/sys/bus/pci/devices/$VF_ADDR/device/mig_profile") + else + if VF_LINE=$(lspci -nnm -s "$VF_BDF" 2>/dev/null); then + if [[ $VF_LINE =~ \"([^\"]+)\"[[:space:]]\"([^\"]+)\"[[:space:]]\"([^\"]+)\"[[:space:]]\"([^\"]+)\" ]]; then + VF_DEVICE_FIELD="${BASH_REMATCH[4]}" + VF_PROFILE=$(sed -E 's/ \[[0-9A-Fa-f]{4}\]$//' <<<"$VF_DEVICE_FIELD") + fi + fi + fi + VF_PROFILE_JSON=$(json_escape "$VF_PROFILE") + + # Determine which VMs use this VF_BDF + raw="${pci_to_vm[$VF_BDF]:-}" + USED_JSON=$(to_json_vm "$raw") + + flist+=( \ + "{\"vf_pci_address\":\"$VF_BDF\",\"vf_profile\":$VF_PROFILE_JSON,\"libvirt_address\":{"\ + "\"domain\":\"$DOMAIN\",\"bus\":\"$BUS\",\"slot\":\"$SLOT\",\"function\":\"$FUNC\"},\"used_by_vm\":$USED_JSON}" ) + done + VF_ARRAY="[${flist[*]}]" + fi + + # === full_passthrough block === + FP_ENABLED=$(( TOTALVFS == 0 )) + DOMAIN="0x0000" + BUS="0x${PCI_ADDR:0:2}" + SLOT="0x${PCI_ADDR:3:2}" + FUNC="0x${PCI_ADDR:6:1}" + + # Emit JSON + if $first_gpu; then + first_gpu=false + else + echo "," + fi + + cat < allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan pla avoid.addHost(host.getId()); } - return allocateTo(plan, offering, template, avoid, clusterHosts, returnUpTo, considerReservedCapacity, account); + return allocateTo(vmProfile, plan, offering, template, avoid, clusterHosts, returnUpTo, considerReservedCapacity, account); } @Override @@ -285,13 +289,13 @@ public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan pla hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering)); if (!hostsCopy.isEmpty()) { - suitableHosts = allocateTo(plan, offering, template, avoid, hostsCopy, returnUpTo, considerReservedCapacity, account); + suitableHosts = allocateTo(vmProfile, plan, offering, template, avoid, hostsCopy, returnUpTo, considerReservedCapacity, account); } return suitableHosts; } - protected List allocateTo(DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, + protected List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity, Account account) { String vmAllocationAlgorithm = DeploymentClusterPlanner.VmAllocationAlgorithm.value(); if (vmAllocationAlgorithm.equals("random") || vmAllocationAlgorithm.equals("userconcentratedpod_random")) { @@ -342,7 +346,23 @@ protected List allocateTo(DeploymentPlan plan, ServiceOffering offering, V } // Check if GPU device is required by offering and host has the availability - if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.vgpuType.toString())) != null) { + if (offering.getGpuOfferingId() != null) { + GpuOfferingVO gpuOffering = gpuOfferingDao.findById(offering.getGpuOfferingId()); + if (gpuOffering == null) { + logger.debug("Adding host [{}] to avoid set, because this host does not have GPU devices available.", host); + avoid.addHost(host.getId()); + continue; + } + Integer gpuCount = offering.getGpuCount(); + if (gpuCount == null) { + gpuCount = 1; + } + if(!_resourceMgr.isGPUDeviceAvailable(host, vmProfile.getId(), gpuOffering, gpuCount)){ + logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", host); + avoid.addHost(host.getId()); + continue; + } + } else if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.vgpuType.toString())) != null) { ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.pciDevice.toString()); if(!_resourceMgr.isGPUDeviceAvailable(host, groupName.getValue(), offeringDetails.getValue())){ logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", host); @@ -537,7 +557,7 @@ protected List prioritizeHosts(VMTemplateVO template, ServiceOff prioritizedHosts.addAll(lowPriorityHosts); // if service offering is not GPU enabled then move all the GPU enabled hosts to the end of priority list. - if (_serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) == null) { + if (_serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) == null && offering.getGpuOfferingId() == null) { List gpuEnabledHosts = new ArrayList<>(); // Check for GPU enabled hosts. diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 15d8c7309d4a..96b6189c9442 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2161,6 +2161,7 @@ public List createCapacityResponse(List re float capacityUsed = 0; long capacityMax = 0; for (VgpuTypesInfo capacity : gpuCapacities) { + // TODO: Fix calculation here and also take into account the gpu count for the VM. if (vgpuVMs.containsKey(capacity.getGroupName().concat(capacity.getModelName()))) { capacityUsed += (float)vgpuVMs.get(capacity.getGroupName().concat(capacity.getModelName())) / capacity.getMaxVpuPerGpu(); } 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 57bc347eefff..ee3976b91e6c 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -4010,6 +4010,7 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic String storageType = cmd.getStorageType(); ServiceOffering.State state = cmd.getState(); final Long templateId = cmd.getTemplateId(); + final Long gpuOfferingId = cmd.getGpuOfferingId(); final Account owner = accountMgr.finalizeOwner(caller, accountName, domainId, projectId); @@ -4049,6 +4050,10 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic serviceOfferingSearch.and("state", serviceOfferingSearch.entity().getState(), Op.EQ); } + if (gpuOfferingId != null) { + serviceOfferingSearch.and("gpuOfferingId", serviceOfferingSearch.entity().getGpuOfferingId(), Op.EQ); + } + if (vmId != null) { currentVmOffering = _srvOfferingDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); diskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentVmOffering.getDiskOfferingId()); @@ -4335,6 +4340,10 @@ private Pair, Integer> searchForServiceOfferingIdsAndCount(ListServic sc.setParameters("state", state); } + if (gpuOfferingId != null) { + sc.setParameters("gpuOfferingId", gpuOfferingId); + } + if (vmId != null) { if (!currentVmOffering.isDynamic()) { sc.setParameters("idNEQ", currentVmOffering.getId()); 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 4ae5074c511c..010a3888e922 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 @@ -137,6 +137,9 @@ public ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offeringResponse.setDomainId(offering.getDomainUuid()); offeringResponse.setZone(offering.getZoneName()); offeringResponse.setZoneId(offering.getZoneUuid()); + offeringResponse.setGpuOfferingId(offering.getGpuOfferingUuid()); + offeringResponse.setGpuOfferingName(offering.getGpuOfferingName()); + offeringResponse.setGpuCount(offering.getGpuCount()); offeringResponse.setNetworkRate(offering.getRateMbps()); offeringResponse.setHostTag(offering.getHostTag()); offeringResponse.setDeploymentPlanner(offering.getDeploymentPlanner()); diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index ba2aff423291..a3265d32276c 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -16,6 +16,42 @@ // under the License. package com.cloud.api.query.dao; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.dao.GpuOfferingDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiConstants.VMDetails; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.api.response.NicSecondaryIpResponse; +import org.apache.cloudstack.api.response.SecurityGroupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VnfNicResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.query.QueryService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiResponseHelper; import com.cloud.api.query.vo.UserVmJoinVO; @@ -52,42 +88,13 @@ import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDetailsDao; -import org.apache.cloudstack.affinity.AffinityGroupResponse; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ApiConstants.VMDetails; -import org.apache.cloudstack.api.ResponseObject.ResponseView; -import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; -import org.apache.cloudstack.api.response.NicResponse; -import org.apache.cloudstack.api.response.NicSecondaryIpResponse; -import org.apache.cloudstack.api.response.SecurityGroupResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.VnfNicResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.vm.lease.VMLeaseManager; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -import javax.inject.Inject; -import java.text.DecimalFormat; import java.time.LocalDate; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; @Component public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation implements UserVmJoinDao { @@ -114,6 +121,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation VmDetailSearch; private final SearchBuilder activeVmByIsoSearch; @@ -249,6 +260,11 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setDiskOfferingName(userVm.getDiskOfferingName()); } } + if (details.contains(VMDetails.all) || details.contains(VMDetails.gpuoff)) { + userVmResponse.setGpuOfferingId(userVm.getGpuOfferingUuid()); + userVmResponse.setGpuOfferingName(userVm.getGpuOfferingName()); + userVmResponse.setGpuCount(userVm.getGpuCount()); + } if (details.contains(VMDetails.all) || details.contains(VMDetails.backoff)) { userVmResponse.setBackupOfferingId(userVm.getBackupOfferingUuid()); userVmResponse.setBackupOfferingName(userVm.getBackupOfferingName()); @@ -257,9 +273,20 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setCpuNumber(userVm.getCpu()); userVmResponse.setCpuSpeed(userVm.getSpeed()); userVmResponse.setMemory(userVm.getRamSize()); - ServiceOfferingDetailsVO serviceOfferingDetail = ApiDBUtils.findServiceOfferingDetail(userVm.getServiceOfferingId(), GPU.Keys.vgpuType.toString()); - if (serviceOfferingDetail != null) { - userVmResponse.setVgpu(serviceOfferingDetail.getValue()); + + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(userVm.getServiceOfferingId()); + + if (serviceOffering.getGpuOfferingId() != null) { + GpuOfferingVO gpuOffering = gpuOfferingDao.findById(serviceOffering.getGpuOfferingId()); + if (gpuOffering != null) { + userVmResponse.setVgpu(gpuOffering.getName()); + } + } else { + ServiceOfferingDetailsVO serviceOfferingDetail = ApiDBUtils.findServiceOfferingDetail(userVm.getServiceOfferingId(), + GPU.Keys.vgpuType.toString()); + if (serviceOfferingDetail != null) { + userVmResponse.setVgpu(serviceOfferingDetail.getValue()); + } } } userVmResponse.setGuestOsId(userVm.getGuestOsUuid()); 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 5b75c5729331..dc3875d3a9d3 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 @@ -227,6 +227,18 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit @Enumerated(value = EnumType.STRING) private VMLeaseManager.ExpiryAction leaseExpiryAction; + @Column(name = "gpu_offering_id") + private Long gpuOfferingId; + + @Column(name = "gpu_offering_uuid") + private String gpuOfferingUuid; + + @Column(name = "gpu_offering_name") + private String gpuOfferingName; + + @Column(name = "gpu_count") + private Integer gpuCount; + public ServiceOfferingJoinVO() { } @@ -473,4 +485,20 @@ public Integer getLeaseDuration() { public VMLeaseManager.ExpiryAction getLeaseExpiryAction() { return leaseExpiryAction; } + + public Long getGpuOfferingId() { + return gpuOfferingId; + } + + public String getGpuOfferingUuid() { + return gpuOfferingUuid; + } + + public String getGpuOfferingName() { + return gpuOfferingName; + } + + public Integer getGpuCount() { + return gpuCount; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index a0680f557531..f57f5b191c82 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -240,6 +240,18 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "service_offering_name") private String serviceOfferingName; + @Column(name = "gpu_offering_id") + private Long gpuOfferingId; + + @Column(name = "gpu_offering_uuid") + private String gpuOfferingUuid; + + @Column(name = "gpu_offering_name") + private String gpuOfferingName; + + @Column(name = "gpu_count") + private Integer gpuCount; + @Column(name = "backup_offering_id") private Long backupOfferingId; @@ -703,6 +715,22 @@ public String getServiceOfferingName() { return serviceOfferingName; } + public Long getGpuOfferingId() { + return gpuOfferingId; + } + + public String getGpuOfferingUuid() { + return gpuOfferingUuid; + } + + public String getGpuOfferingName() { + return gpuOfferingName; + } + + public Integer getGpuCount() { + return gpuCount; + } + public String getBackupOfferingUuid() { return backupOfferingUuid; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 8cee3dba5270..b2ffa5856497 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -51,6 +51,8 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.RoleType; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.dao.GpuOfferingDao; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -355,6 +357,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject DiskOfferingDetailsDao diskOfferingDetailsDao; @Inject + GpuOfferingDao gpuOfferingDao; + @Inject NetworkOfferingDao _networkOfferingDao; @Inject NetworkOfferingJoinDao networkOfferingJoinDao; @@ -3424,6 +3428,14 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) Integer leaseDuration = cmd.getLeaseDuration(); VMLeaseManager.ExpiryAction leaseExpiryAction = validateAndGetLeaseExpiryAction(leaseDuration, cmd.getLeaseExpiryAction()); + final Long gpuOfferingId = cmd.getGpuOfferingId(); + if (gpuOfferingId != null) { + GpuOfferingVO gpuOffering = gpuOfferingDao.findById(gpuOfferingId); + if (gpuOffering == null) { + throw new InvalidParameterValueException("Please specify a valid GPU offering."); + } + } + return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(), cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainIds(), cmd.getZoneIds(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, cmd.getRootDiskSize(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), @@ -3432,7 +3444,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(), cmd.isPurgeResources(), leaseDuration, leaseExpiryAction); + cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), gpuOfferingId, cmd.getGpuCount(), cmd.isPurgeResources(), leaseDuration, leaseExpiryAction); } protected ServiceOfferingVO createServiceOffering(final long userId, final boolean isSystem, final VirtualMachine.Type vmType, @@ -3445,7 +3457,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, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) { + final boolean isCustomized, final boolean encryptRoot, Long gpuOfferingId, final Integer gpuCount, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) { // Filter child domains when both parent and child domains are present List filteredDomainIds = filterChildSubDomains(domainIds); @@ -3527,6 +3539,17 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole } serviceOffering.setDiskOfferingStrictness(diskOfferingStrictness); + if (gpuOfferingId != null) { + serviceOffering.setGpuOfferingId(gpuOfferingId); + if (gpuCount == null || gpuCount < 1) { + serviceOffering.setGpuCount(1); + } else { + serviceOffering.setGpuCount(gpuCount); + } + } else { + serviceOffering.setGpuCount(null); + serviceOffering.setGpuOfferingId(null); + } DiskOfferingVO diskOffering = null; if (diskOfferingId == null) { diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index feb7e66159d9..6435c7bb7576 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -36,6 +36,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.dao.GpuOfferingDao; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -249,6 +251,8 @@ public void setHostAllocators(List hostAllocators) { @Inject protected ResourceManager _resourceMgr; @Inject + protected GpuOfferingDao gpuOfferingDao; + @Inject protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao; protected List _planners; @@ -582,14 +586,33 @@ private boolean canUseLastHost(HostVO host, ExcludeList avoids, DeploymentPlan p return false; } - ServiceOfferingDetailsVO offeringDetails = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()); - ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString()); - if (offeringDetails != null && !_resourceMgr.isGPUDeviceAvailable(host, groupName.getValue(), offeringDetails.getValue())) { - logger.debug("Cannot deploy VM [{}] in the last host [{}] because this host does not have the required GPU devices available. Skipping this and trying other available hosts.", - vm, host); - return false; + if (offering.getGpuOfferingId() != null) { + GpuOfferingVO gpuOffering = gpuOfferingDao.findById(offering.getGpuOfferingId()); + if (gpuOffering == null) { + logger.debug("Cannot deploy VM [{}] in the last host [{}] because the GPU offering is not found. Skipping this and trying other available hosts.", + vm, host); + return false; + } + Integer gpuCount = offering.getGpuCount(); + if (gpuCount == null) { + gpuCount = 0; + } + if (!_resourceMgr.isGPUDeviceAvailable(host, vm.getId(), gpuOffering, gpuCount)) { + logger.debug("Cannot deploy VM [{}] in the last host [{}] because this host does not have GPU devices. Skipping this and trying other available hosts.", + vm, host); + return false; + } + } else { + ServiceOfferingDetailsVO offeringDetails = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()); + ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString()); + if (offeringDetails != null && !_resourceMgr.isGPUDeviceAvailable(host, groupName.getValue(), offeringDetails.getValue())) { + logger.debug("Cannot deploy VM [{}] in the last host [{}] because this host does not have the required GPU devices available. Skipping this and trying other available hosts.", + vm, host); + return false; + } } + if (volumesRequireEncryption && !Boolean.parseBoolean(host.getDetail(Host.HOST_VOLUME_ENCRYPTION))) { logger.warn("The last host of this VM {} does not support volume encryption, which is required by this VM.", host); return false; diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index d01e43622c67..764a619be575 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -203,8 +203,13 @@ public List orderClusters(VirtualMachineProfile vmProfile, DeploymentPlan } } - // In case of non-GPU VMs, protect GPU enabled Hosts and prefer VM deployment on non-GPU Hosts. - if (((serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) == null) && !(hostGpuGroupsDao.listHostIds().isEmpty())) || nonUefiVMDeploy) { + // In case of non-GPU VMs, protect GPU enabled Hosts and prefer VM deployment on + // non-GPU Hosts. + if (((offering.getGpuOfferingId() == null && + serviceOfferingDetailsDao.findDetail(offering.getId(), + GPU.Keys.vgpuType.toString()) == null) + && !(hostGpuGroupsDao.listHostIds().isEmpty())) || nonUefiVMDeploy + ) { int requiredCpu = offering.getCpu() * offering.getSpeed(); long requiredRam = offering.getRamSize() * 1024L * 1024L; reorderClustersBasedOnImplicitTags(clusterList, requiredCpu, requiredRam); diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index 7554c1f97046..1bb7e5bb73fe 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -27,6 +27,8 @@ import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.Domain; import com.cloud.domain.dao.DomainDao; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.dao.GpuOfferingDao; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.user.Account; @@ -110,6 +112,8 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis @Inject protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject + protected GpuOfferingDao gpuOfferingDao; + @Inject protected ServiceOfferingDao serviceOfferingDao; @Inject private NetworkDetailsDao networkDetailsDao; @@ -325,7 +329,16 @@ protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) { // Set GPU details ServiceOfferingDetailsVO offeringDetail = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()); - if (offeringDetail != null) { + if (offering.getGpuOfferingId() != null) { + GpuOfferingVO gpuOffering = gpuOfferingDao.findById(offering.getGpuOfferingId()); + if (gpuOffering != null) { + Integer gpuCount = offering.getGpuCount(); + if (gpuCount == null) { + gpuCount = 1; + } + to.setGpuDevice(_resourceMgr.getGPUDevice(vm, gpuOffering, gpuCount)); + } + } else if (offeringDetail != null) { ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString()); to.setGpuDevice(_resourceMgr.getGPUDevice(vm.getHostId(), groupName.getValue(), offeringDetail.getValue())); } diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 5c8b09e28ab5..a8757961acb4 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -18,6 +18,7 @@ import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; import static com.cloud.configuration.ConfigurationManagerImpl.SET_HOST_DOWN_TO_MAINTENANCE; +import static org.apache.cloudstack.gpu.GpuService.GpuDetachOnStop; import java.net.URI; import java.net.URISyntaxException; @@ -43,6 +44,8 @@ import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.utils.StringUtils; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.dao.GpuOfferingDao; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -64,6 +67,9 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.cloudstack.gpu.GpuService; +import org.apache.cloudstack.gpu.VgpuProfile; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.identity.ManagementServerNode; @@ -257,6 +263,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, @Inject protected VGPUTypesDao _vgpuTypesDao; @Inject + private GpuOfferingDao gpuOfferingDao; + @Inject private PrimaryDataStoreDao _storagePoolDao; @Inject private StoragePoolTagsDao _storagePoolTagsDao; @@ -284,6 +292,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, private ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject private UserVmManager userVmManager; + @Inject + private GpuService gpuService; private List _discoverers; @@ -1409,8 +1419,11 @@ private boolean doMaintain(final long hostId) { } for (final VMInstanceVO vm : vms) { + ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getServiceOfferingId()); if (hosts == null || hosts.isEmpty() || !answer.getMigrate() - || _serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.vgpuType.toString()) != null) { + || offering.getGpuOfferingId() != null + || _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) != null + ) { handleVmForLastHostOrWithVGpu(host, vm); } else if (HypervisorType.LXC.equals(host.getHypervisorType()) && VirtualMachine.Type.User.equals(vm.getType())){ //Migration is not supported for LXC Vms. Schedule restart instead. @@ -2800,7 +2813,7 @@ public boolean configure(final String name, final Map params) th _gpuAvailability.and("groupName", _gpuAvailability.entity().getGroupName(), Op.EQ); final SearchBuilder join1 = _vgpuTypesDao.createSearchBuilder(); join1.and("vgpuType", join1.entity().getVgpuType(), Op.EQ); - join1.and("remainingCapacity", join1.entity().getRemainingCapacity(), Op.GT); + join1.and("remainingCapacity", join1.entity().getRemainingCapacity(), Op.GTEQ); _gpuAvailability.join("groupId", join1, _gpuAvailability.entity().getId(), join1.entity().getGpuGroupId(), JoinBuilder.JoinType.INNER); _gpuAvailability.done(); @@ -3543,10 +3556,16 @@ public HostVO fillRoutingHostVO(final HostVO host, final StartupRoutingCommand s host.setSpeed(ssCmd.getSpeed()); host.setHypervisorType(hyType); host.setHypervisorVersion(ssCmd.getHypervisorVersion()); - host.setGpuGroups(ssCmd.getGpuGroupDetails()); + gpuService.addGpuDevicesToHost(host, ssCmd.getGpuDevices()); + if (CollectionUtils.isNotEmpty(ssCmd.getGpuDevices())) { + host.setGpuGroups(gpuService.getGpuGroupDetailsFromGpuDevices(host)); + } else { + host.setGpuGroups(ssCmd.getGpuGroupDetails()); + } return host; } + @Override public void deleteRoutingHost(final HostVO host, final boolean isForced, final boolean forceDestroyStorage) throws UnableDeleteHostException { if (host.getType() != Host.Type.Routing) { @@ -4167,7 +4186,6 @@ public List listAvailableGPUDevice(final long hostId, final Str sc.setParameters("hostId", hostId); sc.setParameters("groupName", groupName); sc.setJoinParameters("groupId", "vgpuType", vgpuType); - sc.setJoinParameters("groupId", "remainingCapacity", 0); return _hostGpuGroupsDao.customSearch(sc, searchFilter); } @@ -4203,6 +4221,41 @@ public boolean isGPUDeviceAvailable(final Host host, final String groupName, fin } } + @Override + public boolean isGPUDeviceAvailable(Host host, Long vmId, GpuOfferingVO gpuOffering, int gpuCount) { + if (host.getHypervisorType().equals(HypervisorType.XenServer)) { + gpuOfferingDao.loadVgpuProfiles(gpuOffering); + if (CollectionUtils.isEmpty(gpuOffering.getVgpuProfiles())) { + return false; + } + VgpuProfile vgpuProfile = gpuOffering.getVgpuProfiles().get(0); + String groupName = gpuOffering.getName(); + String vgpuType = vgpuProfile.getName(); + return isGPUDeviceAvailable(host, groupName, vgpuType); + } else { + // Use GPU device + return gpuService.isGPUDeviceAvailable(host, vmId, gpuOffering, gpuCount); + } + } + + @Override + public GPUDeviceTO getGPUDevice(VirtualMachine vm, GpuOfferingVO gpuOffering, int gpuCount) { + HostVO host = _hostDao.findById(vm.getHostId()); + if (host.getHypervisorType().equals(HypervisorType.XenServer)) { + gpuOfferingDao.loadVgpuProfiles(gpuOffering); + if (CollectionUtils.isEmpty(gpuOffering.getVgpuProfiles())) { + return null; + } + VgpuProfile vgpuProfile = gpuOffering.getVgpuProfiles().get(0); + String groupName = gpuOffering.getName(); + String vgpuType = vgpuProfile.getName(); + return getGPUDevice(vm.getHostId(), groupName, vgpuType); + } else { + // Use GPU device + return gpuService.getGPUDevice(vm, gpuOffering, gpuCount); + } + } + @Override public GPUDeviceTO getGPUDevice(final long hostId, final String groupName, final String vgpuType) { final List gpuDeviceList = listAvailableGPUDevice(hostId, groupName, vgpuType); @@ -4218,6 +4271,7 @@ public GPUDeviceTO getGPUDevice(final long hostId, final String groupName, final @Override public void updateGPUDetails(final long hostId, final HashMap> groupDetails) { + // TODO: Handle for KVM & Simulator // Update GPU group capacity final TransactionLegacy txn = TransactionLegacy.currentTxn(); txn.start(); @@ -4226,6 +4280,34 @@ public void updateGPUDetails(final long hostId, final HashMap> groupDetails; + if (gpuDevice == null || gpuDevice.getGpuDevices() != null) { + HostVO host = _hostDao.findById(vm.getHostId()); + if (GpuDetachOnStop.valueIn(vm.getDomainId())) { + gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), GpuDevice.State.Free); + } + groupDetails = gpuService.getGpuGroupDetailsFromGpuDevices(host); + } else { + groupDetails = gpuDevice.getGroupDetails(); + } + updateGPUDetails(vm.getHostId(), groupDetails); + } + + @Override + public void updateGPUDetailsForVmStart(long hostId, long vmId, GPUDeviceTO gpuDevice) { + HashMap> groupDetails = gpuDevice.getGroupDetails(); + if (gpuDevice.getGpuDevices() != null) { + HostVO host = _hostDao.findById(hostId); + gpuService.assignGpuDevicesToVmOnHost(vmId, hostId, gpuDevice.getGpuDevices()); + groupDetails = gpuService.getGpuGroupDetailsFromGpuDevices(host); + } + updateGPUDetails(hostId, groupDetails); + } + @Override public HashMap> getGPUStatistics(final HostVO host) { final Answer answer = _agentMgr.easySend(host.getId(), new GetGPUStatsCommand(host.getGuid(), host.getName())); @@ -4239,7 +4321,16 @@ public HashMap> getGPUStatistics(final Ho } else { // now construct the result object if (answer instanceof GetGPUStatsAnswer) { - return ((GetGPUStatsAnswer)answer).getGroupDetails(); + GetGPUStatsAnswer gpuStatsAnswer = (GetGPUStatsAnswer) answer; + HashMap> groupDetails; + gpuService.addGpuDevicesToHost(host, gpuStatsAnswer.getGpuDevices()); + if (CollectionUtils.isNotEmpty(gpuStatsAnswer.getGpuDevices())) { + groupDetails = gpuService.getGpuGroupDetailsFromGpuDevices(host); + } else { + groupDetails = gpuStatsAnswer.getGroupDetails(); + } + + return groupDetails; } } return null; diff --git a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java index a5f7a1b8002a..91baf2a2cebd 100644 --- a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java @@ -183,6 +183,7 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati s_typeMap.put(EntityType.KUBERNETES_CLUSTER, ApiCommandResourceType.None); s_typeMap.put(EntityType.SERVICE_OFFERING, ApiCommandResourceType.ServiceOffering); s_typeMap.put(EntityType.DISK_OFFERING, ApiCommandResourceType.DiskOffering); + s_typeMap.put(EntityType.GPU_OFFERING, ApiCommandResourceType.GpuOffering); s_typeMap.put(EntityType.NETWORK_OFFERING, ApiCommandResourceType.NetworkOffering); s_typeMap.put(EntityType.ZONE, ApiCommandResourceType.Zone); s_typeMap.put(EntityType.POD, ApiCommandResourceType.Pod); diff --git a/server/src/main/java/org/apache/cloudstack/gpu/GpuServiceImpl.java b/server/src/main/java/org/apache/cloudstack/gpu/GpuServiceImpl.java new file mode 100644 index 000000000000..c3800d9f71f8 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gpu/GpuServiceImpl.java @@ -0,0 +1,974 @@ +// 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.gpu; + +import com.cloud.agent.api.VgpuTypesInfo; +import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.gpu.GpuCardVO; +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.gpu.GpuOfferingDetailVO; +import com.cloud.gpu.GpuOfferingVO; +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.gpu.dao.GpuCardDao; +import com.cloud.gpu.dao.GpuDeviceDao; +import com.cloud.gpu.dao.GpuOfferingDao; +import com.cloud.gpu.dao.GpuOfferingDetailsDao; +import com.cloud.gpu.dao.VgpuProfileDao; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.Status; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.component.PluggableService; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuOfferingCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.DisableGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.DiscoverGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.EnableGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.ListGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuOfferingCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateVgpuProfileCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuCardsCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuOfferingsCmd; +import org.apache.cloudstack.api.command.user.gpu.ListVgpuProfilesCmd; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.GpuOfferingResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class GpuServiceImpl extends ManagerBase implements GpuService, PluggableService, Configurable { + private static final Logger s_logger = LogManager.getLogger(GpuServiceImpl.class); + + @Inject + private GpuCardDao gpuCardDao; + + @Inject + private VgpuProfileDao vgpuProfileDao; + + @Inject + private GpuDeviceDao gpuDeviceDao; + + @Inject + private GpuOfferingDao gpuOfferingDao; + + @Inject + private GpuOfferingDetailsDao gpuOfferingDetailsDao; + + @Inject + private HostDao hostDao; + + @Inject + private UserVmManager userVmManager; + + @Inject + private VMInstanceDao vmInstanceDao; + + @Inject + private ResourceManager resourceManager; + + @Override + public boolean configure(String name, java.util.Map params) throws ConfigurationException { + s_logger.info("Configuring GpuServiceImpl: {}", name); + return true; + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList<>(); + // GPU Card Commands + cmdList.add(CreateGpuCardCmd.class); + cmdList.add(UpdateGpuCardCmd.class); + cmdList.add(DeleteGpuCardCmd.class); + cmdList.add(ListGpuCardsCmd.class); + + // vGPU Profile Commands + cmdList.add(CreateVgpuProfileCmd.class); + cmdList.add(UpdateVgpuProfileCmd.class); + cmdList.add(DeleteVgpuProfileCmd.class); + cmdList.add(ListVgpuProfilesCmd.class); + + // GPU Device Commands + cmdList.add(ListGpuDevicesCmd.class); + cmdList.add(DisableGpuDeviceCmd.class); + cmdList.add(EnableGpuDeviceCmd.class); + cmdList.add(DiscoverGpuDevicesCmd.class); + + // GPU Offering Commands + cmdList.add(CreateGpuOfferingCmd.class); + cmdList.add(UpdateGpuOfferingCmd.class); + cmdList.add(ListGpuOfferingsCmd.class); + + return cmdList; + } + + @Override + public String getConfigComponentName() { + return GpuService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{GpuDetachOnStop}; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_GPU_CARD_CREATE, eventDescription = "creating GPU Card") + public GpuCardVO createGpuCard(CreateGpuCardCmd cmd) { + final String deviceId = cmd.getDeviceId(); + final String deviceName = cmd.getDeviceName(); + final String name = cmd.getName(); + final String vendorName = cmd.getVendorName(); + final String vendorId = cmd.getVendorId(); + final Long vramSize = cmd.getVramSize(); + + // Validate inputs + if (StringUtils.isEmpty(deviceId)) { + throw new InvalidParameterValueException("Device ID cannot be empty"); + } + if (StringUtils.isEmpty(deviceName)) { + throw new InvalidParameterValueException("Device name cannot be empty"); + } + if (StringUtils.isEmpty(name)) { + throw new InvalidParameterValueException("Display name cannot be empty"); + } + if (StringUtils.isEmpty(vendorName)) { + throw new InvalidParameterValueException("Vendor name cannot be empty"); + } + if (StringUtils.isEmpty(vendorId)) { + throw new InvalidParameterValueException("Vendor ID cannot be empty"); + } + + // Check if a GPU card with the same vendor ID and device ID already exists + GpuCardVO existingGpuCard = gpuCardDao.findByVendorIdAndDeviceId(vendorId, deviceId); + if (existingGpuCard != null) { + throw new InvalidParameterValueException( + String.format("GPU card with vendor ID %s and device ID %s already exists", vendorId, deviceId)); + } + + GpuCardVO gpuCard = new GpuCardVO(deviceId, deviceName, name, vendorName, vendorId, vramSize); + gpuCard = gpuCardDao.persist(gpuCard); + vgpuProfileDao.persist(new VgpuProfileVO("passthrough", "passthrough", gpuCard.getId(), gpuCard.getVramSize())); + return gpuCard; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_GPU_CARD_EDIT, eventDescription = "Updating GPU Card") + public GpuCardVO updateGpuCard(UpdateGpuCardCmd cmd) { + final Long id = cmd.getId(); + final String deviceName = cmd.getDeviceName(); + final String name = cmd.getName(); + final String vendorName = cmd.getVendorName(); + final Long vramSize = cmd.getVramSize(); + + // Validate inputs + GpuCardVO gpuCard = gpuCardDao.findById(id); + if (gpuCard == null) { + throw new InvalidParameterValueException("GPU card with ID " + id + " not found"); + } + + if (deviceName != null) { + gpuCard.setDeviceName(deviceName); + } + if (name != null) { + gpuCard.setName(name); + } + if (vendorName != null) { + gpuCard.setVendorName(vendorName); + } + if (vramSize != null) { + gpuCard.setVramSize(vramSize); + } + gpuCardDao.update(id, gpuCard); + return gpuCard; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_GPU_CARD_DELETE, eventDescription = "deleting the GPU Card") + public boolean deleteGpuCard(DeleteGpuCardCmd cmd) { + final Long id = cmd.getId(); + + // Validate inputs + GpuCardVO gpuCard = gpuCardDao.findById(id); + if (gpuCard == null) { + throw new InvalidParameterValueException("GPU card with ID " + id + " not found"); + } + + // Check if a GPU card is in use + if (gpuDeviceDao.isGpuCardInUse(id)) { + throw new InvalidParameterValueException( + "Cannot delete GPU card " + gpuCard + " as it is in use by one or more GPU devices"); + } + + return gpuCardDao.remove(id); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VGPU_PROFILE_CREATE, eventDescription = "creating vGPU profile") + public VgpuProfileResponse createVgpuProfile(CreateVgpuProfileCmd cmd) { + final String profileName = cmd.getName(); + final String profileDescription = cmd.getDescription(); + final Long gpuCardId = cmd.getCardId(); + final Long vramSize = cmd.getVramSize(); + + // Validate inputs + if (StringUtils.isBlank(profileName)) { + throw new InvalidParameterValueException("vGPU profile name cannot be empty"); + } + + // Check if the GPU card ID is valid + GpuCardVO gpuCard = gpuCardDao.findById(gpuCardId); + if (gpuCard == null) { + throw new InvalidParameterValueException(String.format("GPU card with ID %d not found", gpuCardId)); + } + + // Check if a vGPU profile with the same name already exists + VgpuProfileVO existingProfile = vgpuProfileDao.findByNameAndCardId(profileName, gpuCardId); + if (existingProfile != null) { + throw new InvalidParameterValueException( + String.format("vGPU profile with name %s already exists", profileName)); + } + + VgpuProfileVO vgpuProfile = new VgpuProfileVO(profileName, profileDescription, gpuCardId, vramSize); + vgpuProfile = vgpuProfileDao.persist(vgpuProfile); + + VgpuProfileResponse response = new VgpuProfileResponse(vgpuProfile, gpuCard); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VGPU_PROFILE_EDIT, eventDescription = "updating vGPU profile") + public VgpuProfileResponse updateVgpuProfile(UpdateVgpuProfileCmd cmd) { + final Long id = cmd.getId(); + final String profileName = cmd.getProfileName(); + final String profileDescription = cmd.getDescription(); + final Long vramSize = cmd.getVramSize(); + + // Validate inputs + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(id); + if (vgpuProfile == null) { + throw new InvalidParameterValueException(String.format("vGPU profile with ID %d not found", id)); + } + + // Check if a vGPU profile with the same name already exists (if the name is being updated) + if (profileName != null && !profileName.equals(vgpuProfile.getName())) { + VgpuProfileVO existingProfile = vgpuProfileDao.findByNameAndCardId(profileName, vgpuProfile.getCardId()); + if (existingProfile != null) { + throw new InvalidParameterValueException( + String.format("vGPU profile with name %s already exists", profileName)); + } + } + + if (profileName != null) { + vgpuProfile.setName(profileName); + } + if (profileDescription != null) { + vgpuProfile.setDescription(profileDescription); + } + if (vramSize != null) { + vgpuProfile.setVramSize(vramSize); + } + vgpuProfileDao.update(id, vgpuProfile); + + VgpuProfileResponse response = + new VgpuProfileResponse(vgpuProfile, gpuCardDao.findById(vgpuProfile.getCardId())); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VGPU_PROFILE_DELETE, eventDescription = "Deleting vGPU profile") + public boolean deleteVgpuProfile(DeleteVgpuProfileCmd cmd) { + final Long id = cmd.getId(); + + // Validate inputs + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(id); + if (vgpuProfile == null) { + throw new InvalidParameterValueException(String.format("vGPU profile with ID %d not found", id)); + } + + // Check if vGPU profile is in use + if (gpuDeviceDao.isVgpuProfileInUse(id)) { + throw new InvalidParameterValueException(String.format( + "Cannot delete vGPU profile with ID %d as it is in use by one or more GPU " + "devices", id)); + } + + return vgpuProfileDao.remove(id); + } + + @Override + public ListResponse listGpuCards(ListGpuCardsCmd cmd) { + Long id = cmd.getId(); + String keyword = cmd.getKeyword(); + String vendorName = cmd.getVendorName(); + String vendorId = cmd.getVendorId(); + String deviceId = cmd.getDeviceId(); + String deviceName = cmd.getDeviceName(); + + Pair, Integer> gpuCardsAndCount = + gpuCardDao.searchAndCountGpuCards(id, keyword, vendorId, vendorName, deviceId, deviceName, + cmd.getStartIndex(), cmd.getPageSizeVal()); + + return getGpuCardResponseListResponse(cmd, gpuCardsAndCount.first(), gpuCardsAndCount.second()); + } + + @NotNull + private static ListResponse getGpuCardResponseListResponse(ListGpuCardsCmd cmd, + List gpuCards, + Integer count) { + ListResponse response = new ListResponse<>(); + List gpuCardResponses = new ArrayList<>(); + + for (GpuCardVO gpuCard : gpuCards) { + GpuCardResponse gpuCardResponse = new GpuCardResponse(gpuCard); + + // Set account info + response.setResponseName(cmd.getCommandName()); + gpuCardResponses.add(gpuCardResponse); + } + + response.setResponses(gpuCardResponses, count); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + public ListResponse listVgpuProfiles(ListVgpuProfilesCmd cmd) { + Long id = cmd.getId(); + String name = cmd.getName(); + String keyword = cmd.getKeyword(); + Long gpuCardId = cmd.getCardId(); + + Pair, Integer> vgpuProfilesAndCount = + vgpuProfileDao.searchAndCountVgpuProfiles(id, name, keyword, gpuCardId, cmd.getStartIndex(), + cmd.getPageSizeVal()); + + return getVgpuProfileResponseListResponse(cmd, vgpuProfilesAndCount.first(), vgpuProfilesAndCount.second()); + } + + @NotNull + private ListResponse getVgpuProfileResponseListResponse(ListVgpuProfilesCmd cmd, + List vgpuProfiles, + Integer count) { + ListResponse response = new ListResponse<>(); + List vgpuProfileResponses = new ArrayList<>(); + + Map cardMap = new HashMap<>(); + for (VgpuProfileVO vgpuProfile : vgpuProfiles) { + GpuCardVO gpuCard = cardMap.get(vgpuProfile.getCardId()); + if (gpuCard == null) { + gpuCard = gpuCardDao.findById(vgpuProfile.getCardId()); + cardMap.put(vgpuProfile.getCardId(), gpuCard); + } + VgpuProfileResponse vgpuProfileResponse = new VgpuProfileResponse(vgpuProfile, gpuCard); + vgpuProfileResponse.setResponseName(cmd.getCommandName()); + vgpuProfileResponses.add(vgpuProfileResponse); + } + + response.setResponses(vgpuProfileResponses, count); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + public ListResponse listGpuDevices(ListGpuDevicesCmd cmd) { + Long id = cmd.getId(); + String keyword = cmd.getKeyword(); + Long hostId = cmd.getHostId(); + Long gpuCardId = cmd.getGpuCardId(); + Long vgpuProfileId = cmd.getVgpuProfileId(); + + Pair, Integer> gpuDevicesAndCount = + gpuDeviceDao.searchAndCountGpuDevices(id, keyword, hostId, gpuCardId, vgpuProfileId, + cmd.getStartIndex(), cmd.getPageSizeVal()); + + return getGpuDeviceResponseListResponse(cmd, gpuDevicesAndCount.first(), gpuDevicesAndCount.second()); + } + + @Override + public boolean disableGpuDevice(DisableGpuDeviceCmd cmd) { + return updateGpuDeviceState(cmd.getId(), GpuDevice.State.Disabled); + } + + @Override + public boolean enableGpuDevice(EnableGpuDeviceCmd cmd) { + return updateGpuDeviceState(cmd.getId(), GpuDevice.State.Free); + } + + @Override + public void deallocateGpuDevicesForVmOnHost(long vmId, GpuDevice.State state) { + List devices = gpuDeviceDao.listByVmId(vmId); + + for (GpuDeviceVO device : devices) { + device.setState(state); + if (state.equals(GpuDevice.State.Free)) { + device.setVmId(null); + } else { + device.setVmId(vmId); + } + gpuDeviceDao.persist(device); + } + } + + @Override + public void assignGpuDevicesToVmOnHost(long vmId, long hostId, List gpuDevices) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + deallocateGpuDevicesForVmOnHost(vmId, GpuDevice.State.Free); + + for (VgpuTypesInfo gpuDevice : gpuDevices) { + GpuDeviceVO device = gpuDeviceDao.findByHostIdAndBusAddress(hostId, gpuDevice.getBusAddress()); + if (device != null) { + device.setState(GpuDevice.State.Allocated); + device.setVmId(vmId); + gpuDeviceDao.persist(device); + } else { + throw new CloudRuntimeException( + String.format("GPU device not found for VM %d on host %d", vmId, hostId)); + } + } + } + }); + } + + @Override + public ListResponse discoverGpuDevices(DiscoverGpuDevicesCmd cmd) { + final Long hostId = cmd.getId(); + HostVO host = hostDao.findById(hostId); + if (host == null) { + throw new InvalidParameterValueException(String.format("Host with ID %d not found", hostId)); + } + if (!Status.Up.equals(host.getStatus())) { + throw new InvalidParameterValueException(String.format("Host [%s] is not in Up status", host)); + } + + // Get GPU stats on the host and update GPU details + // getGPUStatistics() fetches the stats + HashMap> groupDetails = resourceManager.getGPUStatistics(host); + if (!MapUtils.isEmpty(groupDetails)) { + resourceManager.updateGPUDetails(host.getId(), groupDetails); + } + + // Return the list of GPU devices for the host + List gpuDevices = gpuDeviceDao.listByHostId(hostId); + return getGpuDeviceResponseListResponse(cmd, gpuDevices, gpuDevices.size()); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_GPU_OFFERING_CREATE, eventDescription = "creating GPU offering") + public GpuOfferingResponse createGpuOffering(CreateGpuOfferingCmd cmd) { + final String name = cmd.getName(); + final String description = cmd.getDescription(); + final List vgpuProfileIds = cmd.getVgpuProfileIds(); + + // Validate inputs + if (StringUtils.isEmpty(name)) { + throw new InvalidParameterValueException("GPU offering name cannot be empty"); + } + + GpuOfferingVO existingGpuOffering = gpuOfferingDao.findByName(name); + if (existingGpuOffering != null) { + throw new InvalidParameterValueException(String.format("GPU offering with name %s already exists", name)); + } + + List vgpuProfileList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(vgpuProfileIds)) { + for (Long vgpuProfileId : vgpuProfileIds) { + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(vgpuProfileId); + if (vgpuProfile == null) { + throw new InvalidParameterValueException( + String.format("vGpu profile with id %d not found.", vgpuProfileId)); + } + vgpuProfileList.add(vgpuProfile); + } + } + + // Create the GPU offering + final GpuOfferingVO gpuOffering = new GpuOfferingVO(name, description); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + // Persist the GPU offering + GpuOfferingVO newGpuOffering = gpuOfferingDao.persist(gpuOffering); + + // Add vGPU profile IDs if provided + if (vgpuProfileIds != null && !vgpuProfileIds.isEmpty()) { + for (VgpuProfile vgpuProfile : vgpuProfileList) { + gpuOfferingDetailsDao.addDetail(newGpuOffering.getId(), GpuOfferingDetailVO.VgpuProfileId, + String.valueOf(vgpuProfile.getId()), true); + } + newGpuOffering.setVgpuProfiles(vgpuProfileList); + } + } + }); + + GpuOfferingResponse response = createGpuOfferingResponse(gpuOffering); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_GPU_OFFERING_EDIT, eventDescription = "updating GPU offering") + public GpuOfferingResponse updateGpuOffering(UpdateGpuOfferingCmd cmd) { + final Long id = cmd.getId(); + final String name = cmd.getName(); + final String description = cmd.getDescription(); + final Integer sortKey = cmd.getSortKey(); + final List vgpuProfileIds = cmd.getVgpuProfileIds(); + final GpuOffering.State state = cmd.getState(); + + // Validate inputs + final GpuOfferingVO gpuOffering = gpuOfferingDao.findById(id); + if (gpuOffering == null) { + throw new InvalidParameterValueException(String.format("GPU offering with ID %d not found", id)); + } + + // Check for name uniqueness if the name is being updated + if (name != null && !name.equals(gpuOffering.getName())) { + GpuOfferingVO existingGpuOffering = gpuOfferingDao.findByName(name); + if (existingGpuOffering != null) { + throw new InvalidParameterValueException( + String.format("GPU offering with name %s already exists", name)); + } + } + + List vgpuProfileList = new ArrayList<>(); + if (vgpuProfileIds != null) { + for (Long vgpuProfileId : vgpuProfileIds) { + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(vgpuProfileId); + if (vgpuProfile == null) { + throw new InvalidParameterValueException( + String.format("vGPU profile with ID %d not found", vgpuProfileId)); + } + vgpuProfileList.add(vgpuProfile); + } + } + + // Update the GPU offering + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + if (name != null) { + gpuOffering.setName(name); + } + if (description != null) { + gpuOffering.setDescription(description); + } + if (sortKey != null) { + gpuOffering.setSortKey(sortKey); + } + if (state != null) { + gpuOffering.setState(state); + } + + gpuOfferingDao.update(gpuOffering.getId(), gpuOffering); + + // Update vGPU profile IDs if provided + if (vgpuProfileIds != null) { + // First, remove existing associations + gpuOfferingDetailsDao.removeDetails(gpuOffering.getId()); + + // Then add the new ones if not empty + for (VgpuProfile vgpuProfile : vgpuProfileList) { + gpuOfferingDetailsDao.addDetail(gpuOffering.getId(), GpuOfferingDetailVO.VgpuProfileId, + String.valueOf(vgpuProfile.getId()), true); + } + + // Refresh vGPU profiles list + gpuOffering.setVgpuProfiles(vgpuProfileList); + } + } + }); + + GpuOfferingResponse response = createGpuOfferingResponse(gpuOffering); + response.setResponseName(cmd.getCommandName()); + return response; + } + + @Override + public ListResponse listGpuOfferings(ListGpuOfferingsCmd cmd) { + Long id = cmd.getId(); + String keyword = cmd.getKeyword(); + String name = cmd.getName(); + GpuOffering.State state = cmd.getState(); + Long startIndex = cmd.getStartIndex(); + Long pageSize = cmd.getPageSizeVal(); + + Pair, Integer> gpuOfferingAndCount = + gpuOfferingDao.searchAndCountGpuOfferings(id, keyword, name, state, startIndex, pageSize); + return getGpuOfferingResponseListResponse(cmd, gpuOfferingAndCount.first(), gpuOfferingAndCount.second()); + } + + @Override + public boolean isGPUDeviceAvailable(Host host, Long vmId, GpuOffering gpuOffering, int gpuCount) { + gpuOfferingDao.loadVgpuProfiles((GpuOfferingVO) gpuOffering); + List vgpuProfiles = gpuOffering.getVgpuProfiles(); + List vgpuProfileIdList = vgpuProfiles.stream().map(VgpuProfile::getId).collect(Collectors.toList()); + List availableGpuDevices = gpuDeviceDao.listDevicesForAllocation(host.getId(), vgpuProfileIdList); + if (availableGpuDevices.size() >= gpuCount) { + return true; + } else { + // Check if there are already GPU devices assigned to the VM + List existingGpuDevices = gpuDeviceDao.listByHostAndVm(host.getId(), vmId); + return existingGpuDevices.size() + availableGpuDevices.size() >= gpuCount; + } + } + + @Override + public GPUDeviceTO getGPUDevice(VirtualMachine vm, GpuOffering gpuOffering, int gpuCount) { + int requiredNumberOfDevices = gpuCount; + List finalGpuDevices = new ArrayList<>(); + List existingGpuDevices = gpuDeviceDao.listByHostAndVm(vm.getHostId(), vm.getId()); + gpuOfferingDao.loadVgpuProfiles((GpuOfferingVO) gpuOffering); + List vgpuProfiles = gpuOffering.getVgpuProfiles(); + Map vgpuProfileIdMap = + vgpuProfiles.stream().collect(Collectors.toMap(VgpuProfile::getId, vgpuProfile -> vgpuProfile)); + if (existingGpuDevices != null && !existingGpuDevices.isEmpty()) { + logger.debug("VM {} already has GPU devices {} assigned", vm, existingGpuDevices); + for (GpuDeviceVO existingDevice : existingGpuDevices) { + if (finalGpuDevices.size() == gpuCount) { + break; + } + if (vgpuProfileIdMap.containsKey(existingDevice.getVgpuProfileId())) { + finalGpuDevices.add(existingDevice); + --requiredNumberOfDevices; + } else { + logger.debug("VM {} has GPU device {} not in vGPU profile list", vm, existingDevice); + } + } + } + + List availableGpuDevices = + gpuDeviceDao.listDevicesForAllocation(vm.getHostId(), new ArrayList<>(vgpuProfileIdMap.keySet())); + + if (availableGpuDevices.size() < requiredNumberOfDevices) { + throw new CloudRuntimeException( + String.format("Not enough GPU devices available for VM %s on host %d", vm, vm.getHostId())); + } + + for (int i = 0; i < requiredNumberOfDevices; i++) { + finalGpuDevices.add(availableGpuDevices.get(i)); + } + + List vgpuInfoList = new ArrayList<>(); + for (GpuDeviceVO gpuDevice : finalGpuDevices) { + gpuDevice.setState(GpuDevice.State.Allocated); + gpuDevice.setVmId(vm.getId()); + gpuDeviceDao.persist(gpuDevice); + GpuCardVO gpuCard = gpuCardDao.findById(gpuDevice.getCardId()); + VgpuTypesInfo vgpuInfo = + new VgpuTypesInfo(gpuDevice.getType(), gpuCard.getName(), + vgpuProfileIdMap.get(gpuDevice.getVgpuProfileId()).getName(), + gpuDevice.getBusAddress(), gpuCard.getVendorId(), gpuCard.getVendorName(), + gpuCard.getDeviceId(), gpuCard.getDeviceName()); + if (gpuDevice.getParentGpuDeviceId() != null) { + GpuDeviceVO parentGpuDevice = gpuDeviceDao.findById(gpuDevice.getParentGpuDeviceId()); + if (parentGpuDevice != null) { + vgpuInfo.setParentBusAddress(parentGpuDevice.getBusAddress()); + } + } + vgpuInfoList.add(vgpuInfo); + } + + HashMap> groupDetails = + getGpuGroupDetailsFromGpuDevices(hostDao.findById(vm.getHostId())); + return new GPUDeviceTO(gpuOffering.getName(), gpuOffering.getName(), gpuCount, groupDetails, vgpuInfoList); + } + + @Override + public HashMap> getGpuGroupDetailsFromGpuDevices(final Host host) { + HashMap> gpuGroupDetails = new HashMap<>(); + List gpuDevices = gpuDeviceDao.listByHostId(host.getId()); + for (final GpuDeviceVO device : gpuDevices) { + // TODO: Verify this information + // Group name is Card's device name + // Model name is VgpuProfile's name. passthrough for passthrough + + // Calculate GPU capacity and update gpuGroupDetails + GpuCardVO card = gpuCardDao.findById(device.getCardId()); + if (!gpuGroupDetails.containsKey(card.getDeviceName())) { + gpuGroupDetails.put(card.getDeviceName(), new HashMap<>()); + } + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(device.getVgpuProfileId()); + + VgpuTypesInfo gpuDeviceInfo = gpuGroupDetails.get(card.getDeviceName()).get(vgpuProfile.getName()); + if (gpuDeviceInfo == null) { + long maxVgpuPerPgpu = card.getVramSize() / vgpuProfile.getVramSize(); + gpuDeviceInfo = + new VgpuTypesInfo(card.getDeviceName(), vgpuProfile.getName(), card.getVramSize(), null, null, + null, maxVgpuPerPgpu, GpuDevice.State.Free.equals(device.getState()) ? 1L : 0L, + GpuDevice.State.HasVGPUs.equals(device.getState()) ? 0L : 1L); + gpuGroupDetails.get(card.getDeviceName()).put(vgpuProfile.getName(), gpuDeviceInfo); + } else { + // Update the existing VgpuTypesInfo with the new device's information + if (GpuDevice.State.Free.equals(device.getState())) { + gpuDeviceInfo.setRemainingCapacity(gpuDeviceInfo.getRemainingCapacity() + 1); + } + if (!GpuDevice.State.HasVGPUs.equals(device.getState())) { + gpuDeviceInfo.setMaxVmCapacity(gpuDeviceInfo.getMaxCapacity() + 1); + } + } + } + return gpuGroupDetails; + } + + @Override + public void addGpuDevicesToHost(final Host host, final List newGpuDevicesInfo) { + // Check if the host already has a GPU device with the same bus address + // If the device exists for the host but not in ssCmd, remove it + // If the device exists in ssCmd but not in the host, add it to the host + // If the device exists in both, update device's info + List existingGpuDevices = gpuDeviceDao.listByHostId(host.getId()); + Map existingGpuDevicesMap = new HashMap<>(); + Map gpuDevicesToDeleteMap = new HashMap<>(); + for (final GpuDeviceVO device : existingGpuDevices) { + // TODO: Key might change depending on the actual implementation + existingGpuDevicesMap.put(device.getBusAddress(), device); + gpuDevicesToDeleteMap.put(device.getBusAddress(), device); + } + + Map cardMap = new HashMap<>(); + Map vgpuProfileMap = new HashMap<>(); + + for (final VgpuTypesInfo deviceInfo : newGpuDevicesInfo) { + String cardMapKey = deviceInfo.getDeviceId() + " - " + deviceInfo.getVendorId(); + GpuCardVO card = cardMap.get(cardMapKey); + if (card == null) { + card = gpuCardDao.findByVendorIdAndDeviceId(deviceInfo.getVendorId(), deviceInfo.getDeviceId()); + if (card == null) { + continue; + } + cardMap.put(cardMapKey, card); + } + + String vgpuProfileKey = card.getUuid() + " | " + deviceInfo.getModelName(); + VgpuProfileVO vgpuProfile = vgpuProfileMap.get(vgpuProfileKey); + if (vgpuProfile == null) { + vgpuProfile = vgpuProfileDao.findByNameAndCardId(deviceInfo.getModelName(), card.getId()); + vgpuProfileMap.put(vgpuProfileKey, vgpuProfile); + } + + GpuDeviceVO existingDevice = existingGpuDevicesMap.get(deviceInfo.getBusAddress()); + if (existingDevice == null) { + Long parentGpuDeviceId = null; + if (deviceInfo.getParentBusAddress() != null) { + GpuDeviceVO parentGpuDevice = gpuDeviceDao.findByHostIdAndBusAddress( + host.getId(), deviceInfo.getParentBusAddress()); + if (parentGpuDevice != null) { + parentGpuDeviceId = parentGpuDevice.getId(); + } + } + GpuDeviceVO gpuDevice = new GpuDeviceVO(card.getId(), vgpuProfile.getId(), + deviceInfo.getBusAddress(), host.getId(), parentGpuDeviceId); + gpuDevice.setHostId(host.getId()); + gpuDevice.setBusAddress(deviceInfo.getBusAddress()); + gpuDevice.setCardId(card.getId()); + setStateAndVmName(deviceInfo, gpuDevice); + + gpuDeviceDao.persist(gpuDevice); + } else { + // Update the device's info + existingDevice.setCardId(card.getId()); + existingDevice.setVgpuProfileId(vgpuProfile.getId()); + if (existingDevice.getParentGpuDeviceId() == null && deviceInfo.getParentBusAddress() != null) { + GpuDeviceVO parentGpuDevice = gpuDeviceDao.findByHostIdAndBusAddress(host.getId(), + deviceInfo.getParentBusAddress()); + if (parentGpuDevice != null) { + existingDevice.setParentGpuDeviceId(parentGpuDevice.getId()); + } + } + setStateAndVmName(deviceInfo, existingDevice); + gpuDeviceDao.update(existingDevice.getId(), existingDevice); + } + gpuDevicesToDeleteMap.remove(deviceInfo.getBusAddress()); + } + + // Remove the devices that are not in the new list + for (final GpuDeviceVO device : gpuDevicesToDeleteMap.values()) { + gpuDeviceDao.remove(device.getId()); + } + } + + private void setStateAndVmName(VgpuTypesInfo deviceInfo, GpuDeviceVO device) { + if (!deviceInfo.isPassthroughEnabled()) { + device.setState(GpuDevice.State.HasVGPUs); + } + + if (StringUtils.isNotBlank(deviceInfo.getVmName())) { + VMInstanceVO vm = vmInstanceDao.findVMByInstanceNameIncludingRemoved(deviceInfo.getVmName()); + if (vm != null) { + device.setVmId(vm.getId()); + } + } + } + + private ListResponse getGpuOfferingResponseListResponse( + BaseCmd cmd, List gpuOfferings, Integer count + ) { + ListResponse response = new ListResponse<>(); + List gpuOfferingResponses = + gpuOfferings.stream().map(this::createGpuOfferingResponse).collect(Collectors.toList()); + + response.setResponses(gpuOfferingResponses, count); + response.setResponseName(cmd.getCommandName()); + return response; + } + + private GpuOfferingResponse createGpuOfferingResponse(GpuOfferingVO gpuOffering) { + GpuOfferingResponse response = new GpuOfferingResponse(gpuOffering); + List vgpuProfileResponses = new ArrayList<>(); + if (gpuOffering.getVgpuProfiles() == null) { + gpuOfferingDao.loadVgpuProfiles(gpuOffering); + } + for (VgpuProfile vgpuProfile : gpuOffering.getVgpuProfiles()) { + VgpuProfileVO vgpuProfileVO = vgpuProfileDao.findById(vgpuProfile.getId()); + if (vgpuProfileVO != null) { + vgpuProfileResponses.add( + new VgpuProfileResponse(vgpuProfileVO, gpuCardDao.findById(vgpuProfile.getCardId()))); + } + } + response.setVgpuProfiles(vgpuProfileResponses); + return response; + } + + private boolean updateGpuDeviceState(long gpuDeviceId, GpuDevice.State state) { + GpuDeviceVO gpuDevice = gpuDeviceDao.findById(gpuDeviceId); + if (gpuDevice == null) { + throw new InvalidParameterValueException(String.format("GPU device with ID %d not found", gpuDeviceId)); + } + if (!List.of(GpuDevice.State.Free, GpuDevice.State.Disabled).contains(gpuDevice.getState())) { + throw new InvalidParameterValueException( + String.format("GPU device %s cannot be changed from %s to state: %s", gpuDevice, + gpuDevice.getState(), state)); + } + if (gpuDevice.getState() == state) { + throw new InvalidParameterValueException( + String.format("GPU device %s is already in state: %s", gpuDevice, state)); + } + if (gpuDevice.getVmId() != null) { + throw new InvalidParameterValueException( + String.format("Cannot change state of GPU device %s as it is in use by VM %d", gpuDevice, + gpuDevice.getVmId())); + } + gpuDevice.setState(state); + return gpuDeviceDao.update(gpuDeviceId, gpuDevice); + } + + private ListResponse getGpuDeviceResponseListResponse(BaseCmd cmd, List gpuDevices, + Integer count) { + ListResponse response = new ListResponse<>(); + List gpuDeviceResponses = new ArrayList<>(); + + for (GpuDeviceVO gpuDevice : gpuDevices) { + GpuDeviceResponse gpuDeviceResponse = createGpuDeviceResponse(gpuDevice); + gpuDeviceResponses.add(gpuDeviceResponse); + } + + response.setResponses(gpuDeviceResponses, count); + response.setResponseName(cmd.getCommandName()); + return response; + } + + private GpuDeviceResponse createGpuDeviceResponse(GpuDeviceVO gpuDevice) { + GpuDeviceResponse response = new GpuDeviceResponse(); + response.setId(gpuDevice.getUuid()); + response.setBussAddress(gpuDevice.getBusAddress()); + response.setState(gpuDevice.getState()); + + // Host name lookup + HostVO host = hostDao.findById(gpuDevice.getHostId()); + if (host != null) { + response.setHostName(host.getName()); + response.setHostId(host.getUuid()); + } + + // GPU card info + GpuCardVO gpuCard = gpuCardDao.findById(gpuDevice.getCardId()); + if (gpuCard != null) { + response.setGpuCardId(gpuCard.getUuid()); + response.setGpuCardName(gpuCard.getName()); + } + + // vGPU profile info + VgpuProfileVO vgpuProfile = vgpuProfileDao.findById(gpuDevice.getVgpuProfileId()); + if (vgpuProfile != null) { + response.setVgpuProfileId(vgpuProfile.getUuid()); + response.setVgpuProfileName(vgpuProfile.getName()); + } + + if (gpuDevice.getVmId() != null) { + UserVmVO vm = userVmManager.getVirtualMachine(gpuDevice.getVmId()); + if (vm != null) { + response.setVmId(vm.getUuid()); + response.setVmName(vm.getInstanceName()); + } else { + s_logger.debug("VM with ID {} not found for GPU device {}", gpuDevice.getVmId(), gpuDevice.getUuid()); + } + } + + return response; + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index d82e20dc344f..daff50a3b67d 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -391,4 +391,7 @@ + + + diff --git a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java index 587aafa1587c..8afdde53b067 100755 --- a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java +++ b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java @@ -29,6 +29,7 @@ import com.cloud.exception.DiscoveryException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceInUseException; +import com.cloud.gpu.GpuOfferingVO; import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.host.Host; import com.cloud.host.Host.Type; @@ -40,6 +41,7 @@ import com.cloud.resource.ResourceState.Event; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.UpdateClusterCmd; @@ -655,6 +657,18 @@ public boolean isGPUDeviceAvailable(final Host host, final String groupName, fin return false; } + @Override + public boolean isGPUDeviceAvailable(Host host, Long vmId, GpuOfferingVO gpuOffering, int gpuCount) { + // TODO Auto-generated method stub + return false; + } + + @Override + public GPUDeviceTO getGPUDevice(VirtualMachine vm, GpuOfferingVO gpuOffering, int gpuCount) { + // TODO Auto-generated method stub + return null; + } + @Override public GPUDeviceTO getGPUDevice(final long hostId, final String groupName, final String vgpuType) { // TODO Auto-generated method stub @@ -672,6 +686,16 @@ public void updateGPUDetails(final long hostId, final HashMap> getGPUStatistics(final HostVO host) { // TODO Auto-generated method stub diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java index 6a979259cd9a..9cacc8f2beee 100644 --- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java +++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java @@ -612,4 +612,20 @@ public void testPoolNeedsIopsStatsUpdating_NullIops() { Mockito.verify(mockPool, Mockito.never()).setCapacityIops(Mockito.anyLong()); Mockito.verify(mockPool, Mockito.never()).setUsedIops(Mockito.anyLong()); } + + + @Test + public void testgpu() { + List gpuDevices = new ArrayList<>(); + gpuDevices.add("00:01.0 \"3D controller [0302]\" \"Apache Cloudstack Simulator [0000]\" \"Simulated GPU [ACS Simulated GPU] [ffff]\" -ra1 -p00 \"XXXX [0000]\" \"Device [0001]\""); + gpuDevices.add("00:02.0 \"3D controller [0302]\" \"Apache Cloudstack Simulator [0000]\" \"Simulated GPU [ACS Simulated GPU] [ffff]\" -ra1 -p00 \"XXXX [0000]\" \"Device [0001]\""); + for (String gpuDevice : gpuDevices) { + String[] gpuDeviceDetails = gpuDevice.split(" \""); + String busAddress = gpuDeviceDetails[0]; + String vendorId = gpuDeviceDetails[2].split("\\[")[1].split("]")[0]; + String deviceId = gpuDeviceDetails[3].split("\\[")[2].split("]")[0]; + String test = busAddress + " " + vendorId + " " + deviceId; + Assert.assertFalse(false); + } + } } diff --git a/setup/db/create-schema-simulator.sql b/setup/db/create-schema-simulator.sql index 6cb6786311ae..7d7e1f558024 100644 --- a/setup/db/create-schema-simulator.sql +++ b/setup/db/create-schema-simulator.sql @@ -22,6 +22,7 @@ DROP TABLE IF EXISTS `simulator`.`mockstoragepool`; DROP TABLE IF EXISTS `simulator`.`mockvm`; DROP TABLE IF EXISTS `simulator`.`mockvolume`; DROP TABLE IF EXISTS `simulator`.`mocksecurityrules`; +DROP TABLE IF EXISTS `simulator`.`mockgpudevice`; CREATE TABLE `simulator`.`mockhost` ( `id` bigint unsigned NOT NULL auto_increment, @@ -127,3 +128,23 @@ CREATE TABLE `simulator`.`mocksecurityrules` ( INDEX `i_mocksecurityrules__vmid`(`vmid`), INDEX `i_mocksecurityrules__hostid`(`hostid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + +-- Mock GPU Devices for Simulator +CREATE TABLE IF NOT EXISTS `simulator`.`mockgpudevice` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `bus_address` varchar(64) NOT NULL, + `vendor_id` varchar(32) NOT NULL, + `device_id` varchar(32) NOT NULL, + `vendor_name` varchar(128) NOT NULL, + `device_name` varchar(128) NOT NULL, + `host_id` bigint unsigned DEFAULT NULL, + `vm_id` bigint unsigned DEFAULT NULL, + `state` varchar(32) DEFAULT 'Available', + `device_type` varchar(32) DEFAULT 'PCI', + `parent_device_id` bigint unsigned DEFAULT NULL, + `profile_name` varchar(128) DEFAULT NULL, + `passthrough_enabled` tinyint(1) DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_mockgpudevice__bus_address` (`bus_address`, `host_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 8d507670ba82..c0df9fc257e7 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -264,7 +264,9 @@ 'listGuiThemes': 'GUI Theme', 'createGuiTheme': 'GUI Theme', 'updateGuiTheme': 'GUI Theme', - 'removeGuiTheme': 'GUI Theme' + 'removeGuiTheme': 'GUI Theme', + 'Gpu': 'GPU', + 'Vgpu': 'GPU', } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 3fdfa03ecd04..aacd9919849d 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -818,6 +818,7 @@ "label.desttaguuid": "Destination Tag", "label.details": "Details", "label.deviceid": "Device ID", +"label.devicename": "Device Name", "label.devices": "Devices", "label.dhcp": "DHCP", "label.direct.attached.public.ip": "Direct attached public IP", @@ -1065,6 +1066,9 @@ "label.glustervolume": "Volume", "label.go.back": "Go back", "label.gpu": "GPU", +"label.gpucardname": "GPU Card Name", +"label.gpu.card": "GPU Card", +"label.gpu.devices": "GPU Devices", "label.chart.info": "Information about the charts", "label.group": "Group", "label.group.optional": "Group (Optional)", @@ -2529,10 +2533,14 @@ "label.vcenterpassword": "vCenter password", "label.vcenterusername": "vCenter username", "label.vcsdeviceid": "ID", +"label.vendorid": "Vendor ID", +"label.vendorname": "Vendor Name", "label.verify": "Verify", "label.version": "Version", "label.versions": "Versions", "label.vgpu": "VGPU", +"label.vgpuprofilename": "VGPU Profile Name", +"label.vgpu.profile": "VGPU Profile", "label.vgputype": "vGPU type", "label.view": "View", "label.view.all": "View all", @@ -2649,6 +2657,7 @@ "label.vpn.users": "VPN Users", "label.vpncustomergateway": "IP address of the remote gateway", "label.vpncustomergatewayid": "VPN customer gateway", +"label.vramsize": "VRAM Size", "label.vsmipaddress": "Nexus 1000v IP address", "label.vsmpassword": "Nexus 1000v password", "label.vsmusername": "Nexus 1000v username", @@ -2696,6 +2705,7 @@ "label.zones": "Zones", "label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as Instance Templates and Snapshots.", "label.buckets": "Buckets", +"label.busaddress": "Bus Address", "label.objectstorageid": "Object Storage Pool", "label.oobm.address": "Out-of-band management address", "label.oobm.driver": "Out-of-band management driver", diff --git a/ui/src/components/view/GPUTab.vue b/ui/src/components/view/GPUTab.vue new file mode 100644 index 000000000000..bae2c3aca7e1 --- /dev/null +++ b/ui/src/components/view/GPUTab.vue @@ -0,0 +1,147 @@ +// 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. + + + + diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 403c097a67e9..b7eeae29e817 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -29,10 +29,20 @@ :style="{ 'overflow-y': this.$route.name === 'usage' ? 'hidden' : 'auto' }" >