|
23 | 23 | import com.cloud.agent.api.Answer; |
24 | 24 | import com.cloud.agent.api.Command; |
25 | 25 | import com.cloud.agent.api.CreateVhbaDeviceCommand; |
| 26 | +import com.cloud.agent.api.DeleteVhbaDeviceAnswer; |
| 27 | +import com.cloud.agent.api.DeleteVhbaDeviceCommand; |
26 | 28 | import com.cloud.agent.api.ListHostDeviceAnswer; |
27 | 29 | import com.cloud.agent.api.ListHostHbaDeviceAnswer; |
28 | 30 | import com.cloud.agent.api.ListHostLunDeviceAnswer; |
|
34 | 36 | import com.cloud.agent.api.UpdateHostScsiDeviceAnswer; |
35 | 37 | import com.cloud.agent.api.UpdateHostScsiDeviceCommand; |
36 | 38 | import com.cloud.agent.api.UpdateHostUsbDeviceAnswer; |
37 | | -import com.cloud.agent.api.UpdateHostVhbaDeviceCommand; |
| 39 | +import com.cloud.agent.api.UpdateHostVhbaDeviceAnswer; |
38 | 40 | import com.cloud.utils.net.NetUtils; |
39 | 41 | import com.cloud.utils.script.OutputInterpreter; |
40 | 42 | import com.cloud.utils.script.Script; |
|
51 | 53 | import java.util.Arrays; |
52 | 54 | import java.util.Collections; |
53 | 55 | import java.util.Date; |
| 56 | +import java.util.HashMap; |
54 | 57 | import java.util.HashSet; |
55 | 58 | import java.util.LinkedList; |
56 | 59 | import java.util.List; |
|
64 | 67 | import org.apache.logging.log4j.Logger; |
65 | 68 | import org.json.JSONArray; |
66 | 69 | import org.json.JSONObject; |
| 70 | +import javax.naming.ConfigurationException; |
67 | 71 |
|
68 | 72 | public abstract class ServerResourceBase implements ServerResource { |
69 | 73 | protected Logger logger = LogManager.getLogger(getClass()); |
@@ -214,6 +218,7 @@ public Answer listHostLunDevices(Command command) { |
214 | 218 | List<String> hostDevicesNames = new ArrayList<>(); |
215 | 219 | List<String> hostDevicesText = new ArrayList<>(); |
216 | 220 | List<Boolean> hasPartitions = new ArrayList<>(); |
| 221 | + Map<String, String> deviceMappings = new HashMap<>(); // LUN -> SCSI 매핑 |
217 | 222 |
|
218 | 223 | Script cmd = new Script("/usr/bin/lsblk"); |
219 | 224 | cmd.add("--json", "--paths", "--output", "NAME,TYPE,SIZE,MOUNTPOINT"); |
@@ -302,6 +307,7 @@ public Answer listHostScsiDevices(Command command) { |
302 | 307 | List<String> hostDevicesNames = new ArrayList<>(); |
303 | 308 | List<String> hostDevicesText = new ArrayList<>(); |
304 | 309 | List<Boolean> hasPartitions = new ArrayList<>(); |
| 310 | + Map<String, String> deviceMappings = new HashMap<>(); // SCSI -> LUN 매핑 |
305 | 311 | try { |
306 | 312 | Script cmd = new Script("/usr/bin/lsscsi"); |
307 | 313 | cmd.add("-g"); |
@@ -339,6 +345,11 @@ public Answer listHostScsiDevices(Command command) { |
339 | 345 | hostDevicesNames.add(name); |
340 | 346 | hostDevicesText.add(text.toString()); |
341 | 347 | hasPartitions.add(false); |
| 348 | + |
| 349 | + // SCSI 디바이스와 LUN 디바이스 매핑 저장 |
| 350 | + if (dev != null && !dev.isEmpty()) { |
| 351 | + deviceMappings.put(name, dev); |
| 352 | + } |
342 | 353 | } |
343 | 354 | return new com.cloud.agent.api.ListHostScsiDeviceAnswer(true, hostDevicesNames, hostDevicesText, hasPartitions); |
344 | 355 | } catch (Exception e) { |
@@ -581,6 +592,98 @@ public Answer createHostVHbaDevice(CreateVhbaDeviceCommand command, String paren |
581 | 592 | } |
582 | 593 | } |
583 | 594 |
|
| 595 | + public Answer deleteHostVHbaDevice(DeleteVhbaDeviceCommand command) { |
| 596 | + String vhbaName = command.getVhbaName(); |
| 597 | + logger.info("vHBA 삭제 시작 - vHBA 이름: " + vhbaName); |
| 598 | + |
| 599 | + try { |
| 600 | + // 1. 입력 파라미터 검증 |
| 601 | + if (vhbaName == null || vhbaName.trim().isEmpty()) { |
| 602 | + logger.error("vHBA 이름이 제공되지 않았습니다"); |
| 603 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA 이름이 필요합니다"); |
| 604 | + } |
| 605 | + |
| 606 | + // 2. vHBA 디바이스 존재 여부 확인 |
| 607 | + if (!validateVhbaDeviceExists(vhbaName)) { |
| 608 | + logger.error("vHBA 디바이스가 존재하지 않습니다: " + vhbaName); |
| 609 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA 디바이스가 존재하지 않습니다: " + vhbaName); |
| 610 | + } |
| 611 | + |
| 612 | + // 3. vHBA가 VM에 할당되어 있는지 확인 |
| 613 | + if (isVhbaAllocatedToVm(vhbaName)) { |
| 614 | + logger.error("vHBA가 VM에 할당되어 있어 삭제할 수 없습니다: " + vhbaName); |
| 615 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA가 VM에 할당되어 있어 삭제할 수 없습니다. 먼저 할당을 해제해주세요."); |
| 616 | + } |
| 617 | + |
| 618 | + // 4. virsh nodedev-destroy 명령 실행 |
| 619 | + Script destroyCommand = new Script("/bin/bash"); |
| 620 | + destroyCommand.add("-c"); |
| 621 | + destroyCommand.add("virsh nodedev-destroy " + vhbaName); |
| 622 | + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); |
| 623 | + String result = destroyCommand.execute(parser); |
| 624 | + |
| 625 | + if (result != null) { |
| 626 | + logger.error("vHBA 삭제 실패: " + result); |
| 627 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA 삭제 실패: " + result); |
| 628 | + } |
| 629 | + |
| 630 | + // 5. 삭제 확인 |
| 631 | + if (validateVhbaDeviceExists(vhbaName)) { |
| 632 | + logger.error("vHBA 삭제 후에도 여전히 존재합니다: " + vhbaName); |
| 633 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA 삭제 확인 실패"); |
| 634 | + } |
| 635 | + |
| 636 | + logger.info("vHBA 디바이스 삭제 성공: " + vhbaName); |
| 637 | + return new DeleteVhbaDeviceAnswer(command, true, "vHBA 디바이스가 성공적으로 삭제되었습니다: " + vhbaName); |
| 638 | + |
| 639 | + } catch (Exception e) { |
| 640 | + logger.error("vHBA 디바이스 삭제 중 오류: " + e.getMessage(), e); |
| 641 | + return new DeleteVhbaDeviceAnswer(command, false, "vHBA 삭제 중 오류: " + e.getMessage()); |
| 642 | + } |
| 643 | + } |
| 644 | + |
| 645 | + // vHBA 디바이스 존재 여부 확인 |
| 646 | + private boolean validateVhbaDeviceExists(String vhbaName) { |
| 647 | + try { |
| 648 | + Script checkCommand = new Script("/bin/bash"); |
| 649 | + checkCommand.add("-c"); |
| 650 | + checkCommand.add("virsh nodedev-info " + vhbaName + " >/dev/null 2>&1"); |
| 651 | + String result = checkCommand.execute(null); |
| 652 | + return result == null; // 결과가 null이면 성공 (디바이스 존재) |
| 653 | + } catch (Exception e) { |
| 654 | + logger.debug("vHBA 디바이스 존재 확인 중 오류: " + e.getMessage()); |
| 655 | + return false; |
| 656 | + } |
| 657 | + } |
| 658 | + |
| 659 | + // vHBA가 VM에 할당되어 있는지 확인 |
| 660 | + private boolean isVhbaAllocatedToVm(String vhbaName) { |
| 661 | + try { |
| 662 | + Script checkCommand = new Script("/bin/bash"); |
| 663 | + checkCommand.add("-c"); |
| 664 | + checkCommand.add("virsh list --all | grep -v 'Id' | grep -v '^-' | while read line; do " + |
| 665 | + "vm_id=$(echo $line | awk '{print $1}'); " + |
| 666 | + "if [ ! -z \"$vm_id\" ]; then " + |
| 667 | + "virsh dumpxml $vm_id | grep -q '" + vhbaName + "'; " + |
| 668 | + "if [ $? -eq 0 ]; then " + |
| 669 | + "echo 'allocated'; " + |
| 670 | + "break; " + |
| 671 | + "fi; " + |
| 672 | + "fi; " + |
| 673 | + "done"); |
| 674 | + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); |
| 675 | + String result = checkCommand.execute(parser); |
| 676 | + |
| 677 | + if (result == null && parser.getLines() != null) { |
| 678 | + return parser.getLines().trim().equals("allocated"); |
| 679 | + } |
| 680 | + return false; |
| 681 | + } catch (Exception e) { |
| 682 | + logger.debug("vHBA 할당 상태 확인 중 오류: " + e.getMessage()); |
| 683 | + return false; |
| 684 | + } |
| 685 | + } |
| 686 | + |
584 | 687 | // virsh nodedev-list --cap vports에서 나온 부모 HBA 유효성 검증 |
585 | 688 | private boolean validateParentHbaFromVports(String parentHbaName) { |
586 | 689 | try { |
@@ -1407,48 +1510,6 @@ protected Answer updateHostLunDevices(Command command, String vmName, String xml |
1407 | 1510 | } |
1408 | 1511 | } |
1409 | 1512 |
|
1410 | | - protected Answer updateHostScsiDevices(UpdateHostScsiDeviceCommand command, String vmName, String xmlConfig, boolean isAttach) { |
1411 | | - String scsiXmlPath = String.format("/tmp/scsi_device_%s.xml", vmName); |
1412 | | - try { |
1413 | | - // XML 파일이 없을 경우에만 생성 |
1414 | | - File xmlFile = new File(scsiXmlPath); |
1415 | | - if (!xmlFile.exists()) { |
1416 | | - try (PrintWriter writer = new PrintWriter(scsiXmlPath)) { |
1417 | | - writer.write(xmlConfig); |
1418 | | - } |
1419 | | - logger.info("Generated XML file: {} for VM: {}", scsiXmlPath, vmName); |
1420 | | - } |
1421 | | - |
1422 | | - Script virshCmd = new Script("virsh"); |
1423 | | - if (isAttach) { |
1424 | | - virshCmd.add("attach-device", vmName, scsiXmlPath); |
1425 | | - } else { |
1426 | | - virshCmd.add("detach-device", vmName, scsiXmlPath); |
1427 | | - logger.info("Executing detach command for VM: {} with XML: {}", vmName, xmlConfig); |
1428 | | - } |
1429 | | - |
1430 | | - logger.info("isAttach value: {}", isAttach); |
1431 | | - |
1432 | | - String result = virshCmd.execute(); |
1433 | | - |
1434 | | - if (result != null) { |
1435 | | - String action = isAttach ? "attach" : "detach"; |
1436 | | - logger.error("Failed to {} SCSI device: {}", action, result); |
1437 | | - return new UpdateHostScsiDeviceAnswer(false, vmName, xmlConfig, isAttach); |
1438 | | - } |
1439 | | - |
1440 | | - String action = isAttach ? "attached to" : "detached from"; |
1441 | | - logger.info("Successfully {} SCSI device for VM {}", action, vmName); |
1442 | | - return new UpdateHostScsiDeviceAnswer(true, vmName, xmlConfig, isAttach); |
1443 | | - |
1444 | | - } catch (Exception e) { |
1445 | | - String action = isAttach ? "attaching" : "detaching"; |
1446 | | - logger.error("Error {} SCSI device: {}", action, e.getMessage(), e); |
1447 | | - return new UpdateHostScsiDeviceAnswer(false, vmName, xmlConfig, isAttach); |
1448 | | - } |
1449 | | - } |
1450 | | - |
1451 | | - |
1452 | 1513 | protected Answer updateHostHbaDevices(Command command, String vmName, String xmlConfig, boolean isAttach) { |
1453 | 1514 | String hbaXmlPath = String.format("/tmp/hba_device_%s.xml", vmName); |
1454 | 1515 | try { |
@@ -1492,7 +1553,7 @@ protected Answer updateHostHbaDevices(Command command, String vmName, String xml |
1492 | 1553 |
|
1493 | 1554 | protected Answer updateHostVHbaDevices(Command command, String vmName, String xmlConfig, boolean isAttach) { |
1494 | 1555 | try { |
1495 | | - UpdateHostVhbaDeviceCommand cmd = (UpdateHostVhbaDeviceCommand) command; |
| 1556 | + UpdateHostVhbaDeviceAnswer cmd = (UpdateHostVhbaDeviceAnswer) command; |
1496 | 1557 | String vhbaName = cmd.getVhbaName(); |
1497 | 1558 |
|
1498 | 1559 | String vhbaXmlPath = String.format("/tmp/vhba_device_%s.xml", vmName); |
@@ -1538,4 +1599,118 @@ protected Answer updateHostVHbaDevices(Command command, String vmName, String xm |
1538 | 1599 | return new com.cloud.agent.api.UpdateHostVhbaDeviceAnswer(false, null, null, null, false); |
1539 | 1600 | } |
1540 | 1601 | } |
| 1602 | + |
| 1603 | + protected Answer updateHostScsiDevices(UpdateHostScsiDeviceCommand command, String vmName, String xmlConfig, boolean isAttach) { |
| 1604 | + String scsiXmlPath = String.format("/tmp/scsi_device_%s.xml", vmName); |
| 1605 | + try { |
| 1606 | + // XML 파일이 없을 경우에만 생성 |
| 1607 | + File xmlFile = new File(scsiXmlPath); |
| 1608 | + if (!xmlFile.exists()) { |
| 1609 | + try (PrintWriter writer = new PrintWriter(scsiXmlPath)) { |
| 1610 | + writer.write(xmlConfig); |
| 1611 | + } |
| 1612 | + logger.info("Generated XML file: {} for VM: {}", scsiXmlPath, vmName); |
| 1613 | + } |
| 1614 | + |
| 1615 | + Script virshCmd = new Script("virsh"); |
| 1616 | + if (isAttach) { |
| 1617 | + virshCmd.add("attach-device", vmName, scsiXmlPath); |
| 1618 | + } else { |
| 1619 | + virshCmd.add("detach-device", vmName, scsiXmlPath); |
| 1620 | + logger.info("Executing detach command for VM: {} with XML: {}", vmName, xmlConfig); |
| 1621 | + } |
| 1622 | + |
| 1623 | + logger.info("isAttach value: {}", isAttach); |
| 1624 | + |
| 1625 | + String result = virshCmd.execute(); |
| 1626 | + |
| 1627 | + if (result != null) { |
| 1628 | + String action = isAttach ? "attach" : "detach"; |
| 1629 | + logger.error("Failed to {} SCSI device: {}", action, result); |
| 1630 | + return new UpdateHostScsiDeviceAnswer(false, vmName, xmlConfig, isAttach); |
| 1631 | + } |
| 1632 | + |
| 1633 | + String action = isAttach ? "attached to" : "detached from"; |
| 1634 | + logger.info("Successfully {} SCSI device for VM {}", action, vmName); |
| 1635 | + return new UpdateHostScsiDeviceAnswer(true, vmName, xmlConfig, isAttach); |
| 1636 | + |
| 1637 | + } catch (Exception e) { |
| 1638 | + String action = isAttach ? "attaching" : "detaching"; |
| 1639 | + logger.error("Error {} SCSI device: {}", action, e.getMessage(), e); |
| 1640 | + return new UpdateHostScsiDeviceAnswer(false, vmName, xmlConfig, isAttach); |
| 1641 | + } |
| 1642 | + } |
| 1643 | + |
| 1644 | + public Answer createVhbaScsiStoragePool(String poolName, String parentHba, String wwnn, String wwpn, String fabricWwn) { |
| 1645 | + try { |
| 1646 | + // 1. XML 생성 |
| 1647 | + StringBuilder xml = new StringBuilder(); |
| 1648 | + xml.append("<pool type='scsi'>\n"); |
| 1649 | + xml.append(" <name>").append(poolName).append("</name>\n"); |
| 1650 | + xml.append(" <source>\n"); |
| 1651 | + xml.append(" <adapter type='fc_host'"); |
| 1652 | + if (parentHba != null && !parentHba.isEmpty()) { |
| 1653 | + xml.append(" parent='").append(parentHba).append("'"); |
| 1654 | + } |
| 1655 | + if (wwnn != null && !wwnn.isEmpty()) { |
| 1656 | + xml.append(" wwnn='").append(wwnn).append("'"); |
| 1657 | + } |
| 1658 | + if (wwpn != null && !wwpn.isEmpty()) { |
| 1659 | + xml.append(" wwpn='").append(wwpn).append("'"); |
| 1660 | + } |
| 1661 | + if (fabricWwn != null && !fabricWwn.isEmpty()) { |
| 1662 | + xml.append(" fabric_wwn='").append(fabricWwn).append("'"); |
| 1663 | + } |
| 1664 | + xml.append("/>\n"); |
| 1665 | + xml.append(" </source>\n"); |
| 1666 | + xml.append(" <target>\n"); |
| 1667 | + xml.append(" <path>/dev/disk/by-path</path>\n"); |
| 1668 | + xml.append(" <permissions>\n"); |
| 1669 | + xml.append(" <mode>0700</mode>\n"); |
| 1670 | + xml.append(" <owner>0</owner>\n"); |
| 1671 | + xml.append(" <group>0</group>\n"); |
| 1672 | + xml.append(" </permissions>\n"); |
| 1673 | + xml.append(" </target>\n"); |
| 1674 | + xml.append("</pool>\n"); |
| 1675 | + |
| 1676 | + // 2. XML 파일로 저장 |
| 1677 | + String xmlFilePath = "/tmp/" + poolName + ".xml"; |
| 1678 | + try (FileWriter writer = new FileWriter(xmlFilePath)) { |
| 1679 | + writer.write(xml.toString()); |
| 1680 | + } |
| 1681 | + |
| 1682 | + // 3. virsh pool-define |
| 1683 | + Script defineCmd = new Script("virsh"); |
| 1684 | + defineCmd.add("pool-define", xmlFilePath); |
| 1685 | + String defineResult = defineCmd.execute(); |
| 1686 | + if (defineResult != null) { |
| 1687 | + logger.error("Failed to define vHBA SCSI pool: " + defineResult); |
| 1688 | + return new Answer(null, false, "Failed to define vHBA SCSI pool: " + defineResult); |
| 1689 | + } |
| 1690 | + |
| 1691 | + // 4. virsh pool-start |
| 1692 | + Script startCmd = new Script("virsh"); |
| 1693 | + startCmd.add("pool-start", poolName); |
| 1694 | + String startResult = startCmd.execute(); |
| 1695 | + if (startResult != null) { |
| 1696 | + logger.error("Failed to start vHBA SCSI pool: " + startResult); |
| 1697 | + return new Answer(null, false, "Failed to start vHBA SCSI pool: " + startResult); |
| 1698 | + } |
| 1699 | + |
| 1700 | + // 5. virsh pool-autostart |
| 1701 | + Script autoCmd = new Script("virsh"); |
| 1702 | + autoCmd.add("pool-autostart", poolName); |
| 1703 | + String autoResult = autoCmd.execute(); |
| 1704 | + if (autoResult != null) { |
| 1705 | + logger.warn("Failed to autostart vHBA SCSI pool: " + autoResult); |
| 1706 | + } |
| 1707 | + |
| 1708 | + logger.info("vHBA SCSI storage pool 생성 및 시작 성공: " + poolName); |
| 1709 | + return new Answer(null, true, "vHBA SCSI storage pool created and started: " + poolName); |
| 1710 | + |
| 1711 | + } catch (Exception e) { |
| 1712 | + logger.error("vHBA SCSI storage pool 생성 중 오류: " + e.getMessage(), e); |
| 1713 | + return new Answer(null, false, "vHBA SCSI storage pool 생성 중 오류: " + e.getMessage()); |
| 1714 | + } |
| 1715 | + } |
1541 | 1716 | } |
0 commit comments