Skip to content

Commit 58cb165

Browse files
committed
Fix capacity & UI enhancements
1 parent 0a1f69a commit 58cb165

12 files changed

Lines changed: 410 additions & 100 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ public class ApiConstants {
402402
public static final String PARENT = "parent";
403403
public static final String PARENT_ID = "parentid";
404404
public static final String PARENT_DOMAIN_ID = "parentdomainid";
405+
public static final String PARENT_GPU_DEVICE_ID = "parentgpudeviceid";
405406
public static final String PARENT_SUBNET = "parentsubnet";
406407
public static final String PARENT_TEMPLATE_ID = "parenttemplateid";
407408
public static final String PASSWORD = "password";

api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public class GpuDeviceResponse extends BaseResponse {
7070
@Param(description = "the vGPU profile name assigned to this GPU device")
7171
private GpuDevice.State state;
7272

73+
@SerializedName(ApiConstants.PARENT_GPU_DEVICE_ID)
74+
@Param(description = "the ID of the parent GPU device, if this is a vGPU")
75+
private String parentGpuDeviceId;
76+
7377

7478
public GpuDeviceResponse() {
7579
// Empty constructor for serialization
@@ -163,4 +167,12 @@ public GpuDevice.State getState() {
163167
public void setState(GpuDevice.State state) {
164168
this.state = state;
165169
}
170+
171+
public String getParentGpuDeviceId() {
172+
return parentGpuDeviceId;
173+
}
174+
175+
public void setParentGpuDeviceId(String parentGpuDeviceId) {
176+
this.parentGpuDeviceId = parentGpuDeviceId;
177+
}
166178
}

engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class VGPUTypesDaoImpl extends GenericDaoBase<VGPUTypesVO, Long> implemen
4242
private static final String LIST_ZONE_POD_CLUSTER_WIDE_GPU_CAPACITIES =
4343
"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" +
4444
" `cloud`.`vgpu_types` INNER JOIN `cloud`.`host_gpu_groups` ON vgpu_types.gpu_group_id = host_gpu_groups.id INNER JOIN `cloud`.`host`" +
45-
" ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND host.data_center_id = ?";
45+
" ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND vgpu_types.max_capacity > 0 AND host.data_center_id = ?";
4646
private final SearchBuilder<VGPUTypesVO> _searchByGroupId;
4747
private final SearchBuilder<VGPUTypesVO> _searchByGroupIdVGPUType;
4848
@Inject
@@ -80,7 +80,7 @@ public List<VgpuTypesInfo> listGPUCapacities(Long dcId, Long podId, Long cluster
8080
finalQuery.append(" AND host.cluster_id = ?");
8181
resourceIdList.add(clusterId);
8282
}
83-
finalQuery.append(" GROUP BY host_gpu_groups.group_name, vgpu_type");
83+
finalQuery.append(" GROUP BY host_gpu_groups.group_name, vgpu_type, max_vgpu_per_pgpu");
8484

8585
try {
8686
pstmt = txn.prepareAutoCloseStatement(finalQuery.toString());

engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,22 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
131131

132132
private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC ";
133133

134-
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1 =
134+
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1_LEGACY =
135135
"SELECT pci, type, SUM(vmcount) FROM (SELECT MAX(IF(offering.name = 'pciDevice',value,'')) AS pci, MAX(IF(offering.name = 'vgpuType', value,'')) " +
136136
"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 " +
137137
"INNER JOIN `cloud`.`host` ON vm.host_id = host.id WHERE vm.state = 'Running' AND host.data_center_id = ? ";
138+
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2_LEGACY =
139+
"GROUP BY vm.service_offering_id) results GROUP BY pci, type";
140+
141+
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1 =
142+
"SELECT gpu_card.device_name, vgpu_profile.name, COUNT(gpu_device.vm_id) "
143+
+ "FROM `cloud`.`gpu_device` "
144+
+ "INNER JOIN `cloud`.`host` ON gpu_device.host_id = host.id "
145+
+ "INNER JOIN `cloud`.`gpu_card` ON gpu_device.card_id = gpu_card.id "
146+
+ "INNER JOIN `cloud`.`vgpu_profile` ON vgpu_profile.id = gpu_device.vgpu_profile_id "
147+
+ "WHERE vm_id IS NOT NULL AND host.data_center_id = ? ";
138148
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2 =
139-
"GROUP BY pci, type) results GROUP BY pci, type";
149+
"GROUP BY gpu_card.name, vgpu_profile.name";
140150

141151
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";
142152

@@ -794,40 +804,52 @@ public List<Long> listHostIdsByVmCount(long dcId, Long podId, Long clusterId, lo
794804

795805
@Override
796806
public HashMap<String, Long> countVgpuVMs(Long dcId, Long podId, Long clusterId) {
807+
StringBuilder finalQueryLegacy = new StringBuilder();
797808
StringBuilder finalQuery = new StringBuilder();
798809
TransactionLegacy txn = TransactionLegacy.currentTxn();
810+
PreparedStatement pstmtLegacy = null;
799811
PreparedStatement pstmt = null;
800812
List<Long> resourceIdList = new ArrayList<Long>();
801813
HashMap<String, Long> result = new HashMap<String, Long>();
802814

803815
resourceIdList.add(dcId);
816+
finalQueryLegacy.append(COUNT_VMS_BASED_ON_VGPU_TYPES1_LEGACY);
804817
finalQuery.append(COUNT_VMS_BASED_ON_VGPU_TYPES1);
805818

806819
if (podId != null) {
820+
finalQueryLegacy.append("AND host.pod_id = ? ");
807821
finalQuery.append("AND host.pod_id = ? ");
808822
resourceIdList.add(podId);
809823
}
810824

811825
if (clusterId != null) {
826+
finalQueryLegacy.append("AND host.cluster_id = ? ");
812827
finalQuery.append("AND host.cluster_id = ? ");
813828
resourceIdList.add(clusterId);
814829
}
830+
finalQueryLegacy.append(COUNT_VMS_BASED_ON_VGPU_TYPES2_LEGACY);
815831
finalQuery.append(COUNT_VMS_BASED_ON_VGPU_TYPES2);
816832

817833
try {
834+
pstmtLegacy = txn.prepareAutoCloseStatement(finalQueryLegacy.toString());
818835
pstmt = txn.prepareAutoCloseStatement(finalQuery.toString());
819836
for (int i = 0; i < resourceIdList.size(); i++) {
837+
pstmtLegacy.setLong(1 + i, resourceIdList.get(i));
820838
pstmt.setLong(1 + i, resourceIdList.get(i));
821839
}
822-
ResultSet rs = pstmt.executeQuery();
840+
ResultSet rs = pstmtLegacy.executeQuery();
841+
while (rs.next()) {
842+
result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3));
843+
}
844+
rs = pstmt.executeQuery();
823845
while (rs.next()) {
824846
result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3));
825847
}
826848
return result;
827849
} catch (SQLException e) {
828-
throw new CloudRuntimeException("DB Exception on: " + finalQuery, e);
850+
throw new CloudRuntimeException("DB Exception on: " + finalQueryLegacy, e);
829851
} catch (Throwable e) {
830-
throw new CloudRuntimeException("Caught: " + finalQuery, e);
852+
throw new CloudRuntimeException("Caught: " + finalQueryLegacy, e);
831853
}
832854
}
833855

server/src/main/java/org/apache/cloudstack/gpu/GpuServiceImpl.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -555,10 +555,13 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
555555

556556
// Add vGPU profile IDs if provided
557557
if (vgpuProfileIds != null && !vgpuProfileIds.isEmpty()) {
558+
List<GpuOfferingDetailVO> detailList = new ArrayList<>();
559+
558560
for (VgpuProfile vgpuProfile : vgpuProfileList) {
559-
gpuOfferingDetailsDao.addDetail(newGpuOffering.getId(), GpuOfferingDetailVO.VgpuProfileId,
560-
String.valueOf(vgpuProfile.getId()), true);
561+
detailList.add(new GpuOfferingDetailVO(gpuOffering.getId(),
562+
GpuOfferingDetailVO.VgpuProfileId, String.valueOf(vgpuProfile.getId()), true));
561563
}
564+
gpuOfferingDetailsDao.saveDetails(detailList);
562565
newGpuOffering.setVgpuProfiles(vgpuProfileList);
563566
}
564567
}
@@ -632,10 +635,13 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
632635
gpuOfferingDetailsDao.removeDetails(gpuOffering.getId());
633636

634637
// Then add the new ones if not empty
638+
List<GpuOfferingDetailVO> detailList = new ArrayList<>();
639+
635640
for (VgpuProfile vgpuProfile : vgpuProfileList) {
636-
gpuOfferingDetailsDao.addDetail(gpuOffering.getId(), GpuOfferingDetailVO.VgpuProfileId,
637-
String.valueOf(vgpuProfile.getId()), true);
641+
detailList.add(new GpuOfferingDetailVO(gpuOffering.getId(),
642+
GpuOfferingDetailVO.VgpuProfileId, String.valueOf(vgpuProfile.getId()), true));
638643
}
644+
gpuOfferingDetailsDao.saveDetails(detailList);
639645

640646
// Refresh vGPU profiles list
641647
gpuOffering.setVgpuProfiles(vgpuProfileList);
@@ -760,16 +766,14 @@ public HashMap<String, HashMap<String, VgpuTypesInfo>> getGpuGroupDetailsFromGpu
760766
gpuDeviceInfo =
761767
new VgpuTypesInfo(card.getDeviceName(), vgpuProfile.getName(), card.getVramSize(), null, null,
762768
null, maxVgpuPerPgpu, GpuDevice.State.Free.equals(device.getState()) ? 1L : 0L,
763-
GpuDevice.State.HasVGPUs.equals(device.getState()) ? 0L : 1L);
769+
1L);
764770
gpuGroupDetails.get(card.getDeviceName()).put(vgpuProfile.getName(), gpuDeviceInfo);
765771
} else {
766772
// Update the existing VgpuTypesInfo with the new device's information
767773
if (GpuDevice.State.Free.equals(device.getState())) {
768774
gpuDeviceInfo.setRemainingCapacity(gpuDeviceInfo.getRemainingCapacity() + 1);
769775
}
770-
if (!GpuDevice.State.HasVGPUs.equals(device.getState())) {
771-
gpuDeviceInfo.setMaxVmCapacity(gpuDeviceInfo.getMaxCapacity() + 1);
772-
}
776+
gpuDeviceInfo.setMaxVmCapacity(gpuDeviceInfo.getMaxCapacity() + 1);
773777
}
774778
}
775779
return gpuGroupDetails;
@@ -969,6 +973,15 @@ private GpuDeviceResponse createGpuDeviceResponse(GpuDeviceVO gpuDevice) {
969973
}
970974
}
971975

976+
if (gpuDevice.getParentGpuDeviceId() != null) {
977+
GpuDeviceVO parentGpuDevice = gpuDeviceDao.findById(gpuDevice.getParentGpuDeviceId());
978+
if (parentGpuDevice != null) {
979+
response.setParentGpuDeviceId(parentGpuDevice.getUuid());
980+
} else {
981+
s_logger.debug("Parent GPU device with ID {} not found for GPU device {}", gpuDevice.getParentGpuDeviceId(), gpuDevice.getUuid());
982+
}
983+
}
984+
972985
return response;
973986
}
974987
}

ui/public/locales/en.json

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@
265265
"label.add.f5.device": "Add F5 device",
266266
"label.add.firewall": "Add firewall rule",
267267
"label.add.firewallrule": "Add Firewall Rule",
268+
"label.add.gpu.card": "Add GPU card",
269+
"label.add.gpu.offering": "Add GPU offering",
268270
"label.add.guest.network": "Add guest Network",
269271
"label.add.guest.os": "Add guest OS",
270272
"label.add.guest.os.category": "Add guest OS category",
@@ -336,6 +338,7 @@
336338
"label.add.user": "Add User",
337339
"label.add.upstream.ipv4.routes": "Add upstream IPv4 routes",
338340
"label.add.upstream.ipv6.routes": "Add upstream IPv6 routes",
341+
"label.add.vgpu.profile": "Add vGPU profile",
339342
"label.add.vm": "Add Instance",
340343
"label.add.vms": "Add Instances",
341344
"label.add.vmware.datacenter": "Add VMware datacenter",
@@ -835,6 +838,7 @@
835838
"label.disable.webhook": "Disable Webhook",
836839
"label.disabled": "Disabled",
837840
"label.disconnected": "Last disconnected",
841+
"label.discover.gpu.devices": "Discover GPU devices",
838842
"label.disk": "Disk",
839843
"label.disk.offerings": "Disk offerings",
840844
"label.disk.path": "Disk Path",
@@ -1065,9 +1069,16 @@
10651069
"label.glustervolume": "Volume",
10661070
"label.go.back": "Go back",
10671071
"label.gpu": "GPU",
1072+
"label.gpucardid": "GPU Card",
10681073
"label.gpucardname": "GPU Card Name",
10691074
"label.gpu.card": "GPU Card",
1075+
"label.gpu.count": "GPU Count",
1076+
"label.gpucount": "GPU Count",
1077+
"label.gpu.device": "GPU Device",
10701078
"label.gpu.devices": "GPU Devices",
1079+
"label.gpuofferingname": "GPU offering",
1080+
"label.gpu.offering": "GPU Offering",
1081+
"label.gpu.offerings": "GPU Offerings",
10711082
"label.chart.info": "Information about the charts",
10721083
"label.group": "Group",
10731084
"label.group.optional": "Group (Optional)",
@@ -2534,9 +2545,10 @@
25342545
"label.verify": "Verify",
25352546
"label.version": "Version",
25362547
"label.versions": "Versions",
2537-
"label.vgpu": "VGPU",
2538-
"label.vgpuprofilename": "VGPU Profile Name",
2539-
"label.vgpu.profile": "VGPU Profile",
2548+
"label.vgpu": "vGPU",
2549+
"label.vgpuprofileids": "vGPU Profile",
2550+
"label.vgpuprofilename": "vGPU Profile Name",
2551+
"label.vgpu.profile": "vGPU Profile",
25402552
"label.vgputype": "vGPU type",
25412553
"label.view": "View",
25422554
"label.view.all": "View all",
@@ -2781,6 +2793,7 @@
27812793
"message.action.disable.pod": "Please confirm that you want to disable this pod.",
27822794
"message.action.disable.static.nat": "Please confirm that you want to disable static NAT.",
27832795
"message.action.disable.zone": "Please confirm that you want to disable this zone.",
2796+
"message.action.discover.gpu.devices": "Please confirm that you want to discover GPU devices. Only devices on the host which match GPU Card & vGPU Profile will be discovered and will be available for use.",
27842797
"message.action.download.iso": "Please confirm that you want to download this ISO.",
27852798
"message.action.download.snapshot": "Please confirm that you want to download this Snapshot.",
27862799
"message.action.download.template": "Please confirm that you want to download this Template.",

ui/src/components/view/GPUTab.vue

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,54 @@
1717

1818
<template>
1919
<div>
20-
<list-view
21-
:tabLoading="tabLoading"
20+
<a-table
21+
:loading="tabLoading"
2222
:columns="columns"
23-
:items="items"
24-
:actions="actions"
25-
:columnKeys="columnKeys"
26-
:selectedColumns="selectedColumnKeys"
27-
ref="listview"
28-
@update-selected-columns="updateSelectedColumns"
29-
@refresh="this.fetchData"
30-
@enable-gpu-device="enableGpuDevice"
31-
@disable-gpu-device="disableGpuDevice" />
32-
</div>
23+
:dataSource="items"
24+
:pagination="false"
25+
:rowKey="record => record.id"
26+
:childrenColumnName="'children'"
27+
:defaultExpandAllRows="true"
28+
size="small"
29+
>
30+
<template #bodyCell="{ column, record }">
31+
<template v-if="column.key === 'gpuDeviceActions'">
32+
<a-space>
33+
<a-button
34+
v-if="record.state === 'Disabled'"
35+
type="primary"
36+
size="small"
37+
@click="enableGpuDevice(record)"
38+
>
39+
{{ $t('label.enable') }}
40+
</a-button>
41+
<a-button
42+
v-if="record.state === 'Free'"
43+
type="primary"
44+
danger
45+
size="small"
46+
@click="disableGpuDevice(record)"
47+
>
48+
{{ $t('label.disable') }}
49+
</a-button>
50+
</a-space>
51+
</template>
52+
<template v-else-if="column.key === 'busaddress'">
53+
<span :style="{ paddingLeft: record.parentgpudeviceid ? '20px' : '0px' }">
54+
{{ record.busaddress }}
55+
</span>
56+
</template>
57+
</template>
58+
</a-table>
59+
</div>
3360
</template>
3461

3562
<script>
3663
import { api } from '@/api'
3764
import { genericCompare } from '@/utils/sort.js'
38-
import ListView from '@/components/view/ListView'
65+
3966
export default {
4067
name: 'GPUTab',
41-
components: {
42-
ListView
43-
},
4468
props: {
4569
resource: {
4670
type: Object,
@@ -88,14 +112,47 @@ export default {
88112
}
89113
this.tabLoading = true
90114
api('listGpuDevices', params).then(json => {
91-
this.items = []
92-
this.items = json?.listgpudevicesresponse?.gpudevice || []
115+
const devices = json?.listgpudevicesresponse?.gpudevice || []
116+
this.items = this.buildGpuTree(devices)
93117
}).catch(error => {
94118
this.$notifyError(error)
95119
}).finally(() => {
96120
this.tabLoading = false
97121
})
98122
},
123+
buildGpuTree (devices) {
124+
// Separate parent devices and vGPUs
125+
const parentDevices = []
126+
const vgpuDevices = []
127+
for (const device of devices) {
128+
if (device.parentgpudeviceid) {
129+
vgpuDevices.push(device)
130+
} else {
131+
parentDevices.push(device)
132+
}
133+
}
134+
135+
// Group vGPUs by their parent ID
136+
const vgpusByParent = {}
137+
vgpuDevices.forEach(vgpu => {
138+
const parentId = vgpu.parentgpudeviceid
139+
if (!vgpusByParent[parentId]) {
140+
vgpusByParent[parentId] = []
141+
}
142+
vgpusByParent[parentId].push(vgpu)
143+
})
144+
145+
// Build tree structure
146+
const treeData = parentDevices.map(parent => {
147+
const children = vgpusByParent[parent.id] || []
148+
return {
149+
...parent,
150+
children: children.length > 0 ? children : undefined
151+
}
152+
})
153+
154+
return treeData
155+
},
99156
updateSelectedColumns (key) {
100157
if (this.selectedColumnKeys.includes(key)) {
101158
this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)

0 commit comments

Comments
 (0)