|
23 | 23 | import java.io.InputStream; |
24 | 24 | import java.net.URISyntaxException; |
25 | 25 | import java.nio.charset.StandardCharsets; |
| 26 | +import java.util.ArrayList; |
26 | 27 | import java.util.HashSet; |
27 | 28 | import java.util.List; |
28 | 29 | import java.util.Map; |
|
40 | 41 | import javax.xml.parsers.ParserConfigurationException; |
41 | 42 | import javax.xml.transform.TransformerException; |
42 | 43 |
|
| 44 | +import com.cloud.agent.api.VgpuTypesInfo; |
| 45 | +import com.cloud.agent.api.to.GPUDeviceTO; |
| 46 | +import com.cloud.hypervisor.kvm.resource.LibvirtGpuDef; |
43 | 47 | import com.cloud.hypervisor.kvm.resource.LibvirtXMLParser; |
44 | 48 | import org.apache.cloudstack.utils.security.ParserUtils; |
45 | 49 | import org.apache.commons.collections.MapUtils; |
| 50 | +import org.apache.commons.collections4.CollectionUtils; |
46 | 51 | import org.apache.commons.io.FilenameUtils; |
47 | 52 | import org.apache.commons.io.IOUtils; |
48 | 53 | import org.apache.commons.lang3.StringUtils; |
@@ -226,6 +231,8 @@ Use VIR_DOMAIN_XML_SECURE (value = 1) prior to v1.0.0. |
226 | 231 |
|
227 | 232 | xmlDesc = updateVmSharesIfNeeded(command, xmlDesc, libvirtComputingResource); |
228 | 233 |
|
| 234 | + xmlDesc = updateGpuDevicesIfNeeded(command, xmlDesc, libvirtComputingResource); |
| 235 | + |
229 | 236 | dconn = libvirtUtilitiesHelper.retrieveQemuConnection(destinationUri); |
230 | 237 |
|
231 | 238 | if (to.getType() == VirtualMachine.Type.User) { |
@@ -417,6 +424,116 @@ protected Set<String> getMigrateStorageDeviceLabels(List<DiskDef> diskDefinition |
417 | 424 | return setOfLabels; |
418 | 425 | } |
419 | 426 |
|
| 427 | + String updateGpuDevicesIfNeeded(MigrateCommand migrateCommand, String xmlDesc, LibvirtComputingResource libvirtComputingResource) |
| 428 | + throws ParserConfigurationException, IOException, SAXException, TransformerException { |
| 429 | + GPUDeviceTO gpuDevice = migrateCommand.getVirtualMachine().getGpuDevice(); |
| 430 | + if (gpuDevice == null || CollectionUtils.isEmpty(gpuDevice.getGpuDevices())) { |
| 431 | + logger.debug("No GPU device to update for VM [{}].", migrateCommand.getVmName()); |
| 432 | + return xmlDesc; |
| 433 | + } |
| 434 | + |
| 435 | + List<VgpuTypesInfo> devices = gpuDevice.getGpuDevices(); |
| 436 | + logger.info("Updating GPU devices for VM [{}] during migration. Number of devices: {}", |
| 437 | + migrateCommand.getVmName(), devices.size()); |
| 438 | + |
| 439 | + // Parse XML and find devices element |
| 440 | + DocumentBuilderFactory docFactory = ParserUtils.getSaferDocumentBuilderFactory(); |
| 441 | + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); |
| 442 | + Document document; |
| 443 | + try (InputStream inputStream = IOUtils.toInputStream(xmlDesc, StandardCharsets.UTF_8)) { |
| 444 | + document = docBuilder.parse(inputStream); |
| 445 | + } |
| 446 | + |
| 447 | + NodeList devicesList = document.getElementsByTagName("devices"); |
| 448 | + if (devicesList.getLength() == 0) { |
| 449 | + logger.warn("No devices section found in XML for VM [{}]", migrateCommand.getVmName()); |
| 450 | + return xmlDesc; |
| 451 | + } |
| 452 | + |
| 453 | + Element devicesElement = (Element) devicesList.item(0); |
| 454 | + |
| 455 | + // Remove existing GPU hostdev elements and add new ones |
| 456 | + removeExistingGpuHostdevElements(devicesElement); |
| 457 | + addNewGpuHostdevElements(document, devicesElement, devices); |
| 458 | + |
| 459 | + String newXmlDesc = LibvirtXMLParser.getXml(document); |
| 460 | + logger.debug("Updated XML configuration for VM [{}] with new GPU devices", migrateCommand.getVmName()); |
| 461 | + |
| 462 | + return newXmlDesc; |
| 463 | + } |
| 464 | + |
| 465 | + /** |
| 466 | + * Removes existing GPU hostdev elements from the devices section. |
| 467 | + * GPU devices are identified as hostdev elements with type='pci' or type='mdev'. |
| 468 | + */ |
| 469 | + private void removeExistingGpuHostdevElements(Element devicesElement) { |
| 470 | + NodeList hostdevNodes = devicesElement.getElementsByTagName("hostdev"); |
| 471 | + List<Node> nodesToRemove = new ArrayList<>(); |
| 472 | + |
| 473 | + for (int i = 0; i < hostdevNodes.getLength(); i++) { |
| 474 | + Node hostdevNode = hostdevNodes.item(i); |
| 475 | + if (hostdevNode.getNodeType() == Node.ELEMENT_NODE) { |
| 476 | + Element hostdevElement = (Element) hostdevNode; |
| 477 | + String hostdevType = hostdevElement.getAttribute("type"); |
| 478 | + |
| 479 | + // Remove hostdev elements that represent GPU devices (type='pci' or type='mdev') |
| 480 | + if ("pci".equals(hostdevType) || "mdev".equals(hostdevType)) { |
| 481 | + // Additional check: ensure this is actually a GPU device by checking mode='subsystem' |
| 482 | + String mode = hostdevElement.getAttribute("mode"); |
| 483 | + if ("subsystem".equals(mode)) { |
| 484 | + nodesToRemove.add(hostdevNode); |
| 485 | + } |
| 486 | + } |
| 487 | + } |
| 488 | + } |
| 489 | + |
| 490 | + // Remove the nodes |
| 491 | + for (Node node : nodesToRemove) { |
| 492 | + devicesElement.removeChild(node); |
| 493 | + } |
| 494 | + |
| 495 | + logger.debug("Removed {} existing GPU hostdev elements", nodesToRemove.size()); |
| 496 | + } |
| 497 | + |
| 498 | + /** |
| 499 | + * Adds new GPU hostdev elements to the devices section based on the GPU devices |
| 500 | + * allocated on the destination host. |
| 501 | + */ |
| 502 | + private void addNewGpuHostdevElements(Document document, Element devicesElement, List<VgpuTypesInfo> devices) |
| 503 | + throws ParserConfigurationException, IOException, SAXException { |
| 504 | + if (devices.isEmpty()) { |
| 505 | + return; |
| 506 | + } |
| 507 | + |
| 508 | + // Reuse parser for efficiency |
| 509 | + DocumentBuilderFactory factory = ParserUtils.getSaferDocumentBuilderFactory(); |
| 510 | + DocumentBuilder builder = factory.newDocumentBuilder(); |
| 511 | + |
| 512 | + for (VgpuTypesInfo deviceInfo : devices) { |
| 513 | + Element hostdevElement = createGpuHostdevElement(document, deviceInfo, builder); |
| 514 | + devicesElement.appendChild(hostdevElement); |
| 515 | + logger.debug("Added new GPU hostdev element for device: {} (type: {}, busAddress: {})", |
| 516 | + deviceInfo.getDeviceName(), deviceInfo.getDeviceType(), deviceInfo.getBusAddress()); |
| 517 | + } |
| 518 | + } |
| 519 | + |
| 520 | + /** |
| 521 | + * Creates a hostdev element for a GPU device using LibvirtGpuDef. |
| 522 | + */ |
| 523 | + private Element createGpuHostdevElement(Document document, VgpuTypesInfo deviceInfo, DocumentBuilder builder) |
| 524 | + throws IOException, SAXException { |
| 525 | + // Generate GPU XML using LibvirtGpuDef |
| 526 | + LibvirtGpuDef gpuDef = new LibvirtGpuDef(); |
| 527 | + gpuDef.defGpu(deviceInfo); |
| 528 | + String gpuXml = gpuDef.toString(); |
| 529 | + |
| 530 | + // Parse and import into target document |
| 531 | + try (InputStream xmlStream = IOUtils.toInputStream(gpuXml, StandardCharsets.UTF_8)) { |
| 532 | + Document gpuDocument = builder.parse(xmlStream); |
| 533 | + Element hostdevElement = gpuDocument.getDocumentElement(); |
| 534 | + return (Element) document.importNode(hostdevElement, true); |
| 535 | + } |
| 536 | + } |
420 | 537 |
|
421 | 538 | /** |
422 | 539 | * Checks if the CPU shares are equal in the source host and destination host. |
|
0 commit comments