|
17 | 17 | import org.zstack.header.AbstractService; |
18 | 18 | import org.zstack.header.core.ReturnValueCompletion; |
19 | 19 | import org.zstack.header.core.WhileDoneCompletion; |
| 20 | +import org.zstack.header.core.workflow.Flow; |
20 | 21 | import org.zstack.header.core.workflow.FlowDoneHandler; |
21 | 22 | import org.zstack.header.core.workflow.FlowErrorHandler; |
22 | 23 | import org.zstack.header.core.workflow.FlowTrigger; |
|
32 | 33 | import org.zstack.header.vm.VmInstanceConstant; |
33 | 34 | import org.zstack.header.vm.VmInstanceVO; |
34 | 35 | import org.zstack.header.vm.VmInstanceVO_; |
| 36 | +import org.zstack.header.vm.additions.RestoreVmHostFileMsg; |
| 37 | +import org.zstack.header.vm.additions.RestoreVmHostFileReply; |
35 | 38 | import org.zstack.header.vm.additions.VmHostBackupFileVO; |
36 | 39 | import org.zstack.header.vm.additions.VmHostBackupFileVO_; |
37 | 40 | import org.zstack.header.vm.additions.VmHostFileContentFormat; |
38 | 41 | import org.zstack.header.vm.additions.VmHostFileContentVO; |
39 | 42 | import org.zstack.header.vm.additions.VmHostFileContentVO_; |
| 43 | +import org.zstack.header.vm.additions.VmHostFileOperation; |
40 | 44 | import org.zstack.header.vm.additions.VmHostFileType; |
41 | 45 | import org.zstack.header.vm.additions.VmHostFileVO; |
42 | 46 | import org.zstack.header.vm.additions.VmHostFileVO_; |
|
56 | 60 | import java.util.Base64; |
57 | 61 | import java.util.Collections; |
58 | 62 | import java.util.HashMap; |
| 63 | +import java.util.HashSet; |
59 | 64 | import java.util.List; |
60 | 65 | import java.util.Map; |
61 | 66 | import java.util.Objects; |
| 67 | +import java.util.Set; |
62 | 68 |
|
63 | 69 | import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; |
64 | 70 | import static org.zstack.compute.vm.VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE; |
65 | 71 | import static org.zstack.core.Platform.operr; |
| 72 | +import static org.zstack.kvm.KVMAgentCommands.*; |
66 | 73 | import static org.zstack.kvm.KVMConstant.READ_VM_HOST_FILE_PATH; |
| 74 | +import static org.zstack.kvm.KVMConstant.WRITE_VM_HOST_FILE_PATH; |
| 75 | +import static org.zstack.kvm.KVMConstant.buildNvramFilePath; |
| 76 | +import static org.zstack.kvm.KVMConstant.buildTpmStateFilePath; |
67 | 77 | import static org.zstack.utils.CollectionDSL.list; |
68 | 78 | import static org.zstack.utils.CollectionUtils.findOneOrNull; |
69 | 79 | import static org.zstack.utils.CollectionUtils.transform; |
@@ -165,6 +175,8 @@ public void handleMessage(Message msg) { |
165 | 175 | handle((CloneVmHostFileMsg) msg); |
166 | 176 | } else if (msg instanceof BackupVmHostFileMsg) { |
167 | 177 | handle((BackupVmHostFileMsg) msg); |
| 178 | + } else if (msg instanceof RestoreVmHostFileMsg) { |
| 179 | + handle((RestoreVmHostFileMsg) msg); |
168 | 180 | } else { |
169 | 181 | bus.dealWithUnknownMessage(msg); |
170 | 182 | } |
@@ -555,4 +567,231 @@ protected void scripts() { |
555 | 567 | }.execute(); |
556 | 568 | return filesNeedPersists; |
557 | 569 | } |
| 570 | + |
| 571 | + private void handle(RestoreVmHostFileMsg msg) { |
| 572 | + RestoreVmHostFileReply reply = new RestoreVmHostFileReply(); |
| 573 | + |
| 574 | + List<VmHostBackupFileVO> backupFiles = Q.New(VmHostBackupFileVO.class) |
| 575 | + .eq(VmHostBackupFileVO_.resourceUuid, msg.getSnapshotGroupUuid()) |
| 576 | + .list(); |
| 577 | + |
| 578 | + Tuple tuple = Q.New(VmInstanceVO.class) |
| 579 | + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) |
| 580 | + .eq(VmInstanceVO_.uuid, msg.getVmInstanceUuid()) |
| 581 | + .findTuple(); |
| 582 | + if (tuple == null) { |
| 583 | + reply.setError(operr("VM instance [uuid:%s] not found", msg.getVmInstanceUuid())); |
| 584 | + bus.reply(msg, reply); |
| 585 | + return; |
| 586 | + } |
| 587 | + |
| 588 | + String hostUuid = tuple.get(0, String.class); |
| 589 | + if (hostUuid == null) { |
| 590 | + hostUuid = tuple.get(1, String.class); |
| 591 | + } |
| 592 | + if (hostUuid == null) { |
| 593 | + reply.setError(operr("VM instance [uuid:%s] has no host", msg.getVmInstanceUuid())); |
| 594 | + bus.reply(msg, reply); |
| 595 | + return; |
| 596 | + } |
| 597 | + |
| 598 | + List<VmHostFileVO> currentHostFiles = Q.New(VmHostFileVO.class) |
| 599 | + .eq(VmHostFileVO_.vmInstanceUuid, msg.getVmInstanceUuid()) |
| 600 | + .eq(VmHostFileVO_.hostUuid, hostUuid) |
| 601 | + .list(); |
| 602 | + |
| 603 | + Map<VmHostFileType, VmHostFileVO> currentFilesByType = new HashMap<>(); |
| 604 | + for (VmHostFileVO file : currentHostFiles) { |
| 605 | + currentFilesByType.put(file.getType(), file); |
| 606 | + } |
| 607 | + |
| 608 | + Map<VmHostFileType, VmHostBackupFileVO> backupFilesByType = new HashMap<>(); |
| 609 | + for (VmHostBackupFileVO file : backupFiles) { |
| 610 | + backupFilesByType.put(file.getType(), file); |
| 611 | + } |
| 612 | + |
| 613 | + Set<VmHostFileType> allTypes = new HashSet<>(); |
| 614 | + allTypes.addAll(currentFilesByType.keySet()); |
| 615 | + allTypes.addAll(backupFilesByType.keySet()); |
| 616 | + |
| 617 | + if (allTypes.isEmpty()) { |
| 618 | + bus.reply(msg, reply); |
| 619 | + return; |
| 620 | + } |
| 621 | + |
| 622 | + List<VmHostFileTO> fileList = new ArrayList<>(); |
| 623 | + for (VmHostFileType type : allTypes) { |
| 624 | + VmHostFileTO to = new VmHostFileTO(); |
| 625 | + to.setType(type.toString()); |
| 626 | + |
| 627 | + boolean hasCurrentFile = currentFilesByType.containsKey(type); |
| 628 | + boolean hasBackupFile = backupFilesByType.containsKey(type); |
| 629 | + |
| 630 | + if (hasBackupFile) { |
| 631 | + // Write operation |
| 632 | + VmHostBackupFileVO backupFile = backupFilesByType.get(type); |
| 633 | + VmHostFileContentVO content = Q.New(VmHostFileContentVO.class) |
| 634 | + .eq(VmHostFileContentVO_.uuid, backupFile.getUuid()) |
| 635 | + .find(); |
| 636 | + if (content == null) { |
| 637 | + logger.warn(String.format("backup file content [uuid:%s] not found for type %s", |
| 638 | + backupFile.getUuid(), type)); |
| 639 | + continue; |
| 640 | + } |
| 641 | + |
| 642 | + if (type == VmHostFileType.NvRam) { |
| 643 | + to.setPath(buildNvramFilePath(msg.getVmInstanceUuid())); |
| 644 | + } else if (type == VmHostFileType.TpmState) { |
| 645 | + to.setPath(buildTpmStateFilePath(msg.getVmInstanceUuid())); |
| 646 | + } |
| 647 | + |
| 648 | + to.setFileFormat(content.getFormat().toString()); |
| 649 | + to.setOperation(VmHostFileOperation.Write.toString()); |
| 650 | + String contentBase64 = Base64.getEncoder().encodeToString(content.getContent()); |
| 651 | + to.setContentBase64(contentBase64); |
| 652 | + |
| 653 | + fileList.add(to); |
| 654 | + } else if (hasCurrentFile) { |
| 655 | + // Delete operation |
| 656 | + VmHostFileVO currentFile = currentFilesByType.get(type); |
| 657 | + to.setPath(currentFile.getPath()); |
| 658 | + to.setOperation(VmHostFileOperation.Delete.toString()); |
| 659 | + |
| 660 | + fileList.add(to); |
| 661 | + } |
| 662 | + } |
| 663 | + |
| 664 | + if (fileList.isEmpty()) { |
| 665 | + bus.reply(msg, reply); |
| 666 | + return; |
| 667 | + } |
| 668 | + |
| 669 | + final String finalHostUuid = hostUuid; |
| 670 | + SimpleFlowChain.of("restore-vm-host-file") |
| 671 | + .then(Flow.of("send-cmd") |
| 672 | + .handle(trigger -> { |
| 673 | + KVMAgentCommands.WriteVmHostFileContentCmd cmd = new KVMAgentCommands.WriteVmHostFileContentCmd(); |
| 674 | + cmd.setHostFiles(fileList); |
| 675 | + |
| 676 | + KvmCommandSender sender = new KvmCommandSender(finalHostUuid); |
| 677 | + sender.send(cmd, WRITE_VM_HOST_FILE_PATH, wrapper -> { |
| 678 | + KVMAgentCommands.WriteVmHostFileContentResponse writeRsp = wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); |
| 679 | + return writeRsp.isSuccess() ? null : |
| 680 | + operr("failed to write/delete host file response").withException(writeRsp.getError()); |
| 681 | + }, new ReturnValueCompletion<KvmResponseWrapper>(msg) { |
| 682 | + @Override |
| 683 | + public void success(KvmResponseWrapper wrapper) { |
| 684 | + KVMAgentCommands.WriteVmHostFileContentResponse writeRsp = wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); |
| 685 | + if (writeRsp.isSuccess()) { |
| 686 | + logger.info(String.format("success to restore host files for VM[uuid:%s] from snapshot group[uuid:%s]", |
| 687 | + msg.getVmInstanceUuid(), msg.getSnapshotGroupUuid())); |
| 688 | + trigger.next(); |
| 689 | + return; |
| 690 | + } |
| 691 | + trigger.fail(operr("failed to write/delete host file") |
| 692 | + .withException(writeRsp.getError())); |
| 693 | + } |
| 694 | + |
| 695 | + @Override |
| 696 | + public void fail(ErrorCode errorCode) { |
| 697 | + trigger.fail(operr("failed to restore host files for VM[uuid:%s]", msg.getVmInstanceUuid()) |
| 698 | + .withCause(errorCode)); |
| 699 | + } |
| 700 | + }); |
| 701 | + }) |
| 702 | + .build()) |
| 703 | + .then(Flow.of("persist-content-in-db") |
| 704 | + .handle(trigger -> { |
| 705 | + Timestamp now = Timestamp.from(Instant.now()); |
| 706 | + |
| 707 | + for (VmHostFileType type : allTypes) { |
| 708 | + boolean hasCurrentFile = currentFilesByType.containsKey(type); |
| 709 | + boolean hasBackupFile = backupFilesByType.containsKey(type); |
| 710 | + |
| 711 | + if (hasBackupFile) { |
| 712 | + VmHostBackupFileVO backupFile = backupFilesByType.get(type); |
| 713 | + VmHostFileContentVO backupContent = Q.New(VmHostFileContentVO.class) |
| 714 | + .eq(VmHostFileContentVO_.uuid, backupFile.getUuid()) |
| 715 | + .find(); |
| 716 | + if (backupContent == null) { |
| 717 | + continue; |
| 718 | + } |
| 719 | + |
| 720 | + if (hasCurrentFile) { |
| 721 | + // update existing VmHostFileVO and VmHostFileContentVO |
| 722 | + VmHostFileVO currentFile = currentFilesByType.get(type); |
| 723 | + SQL.New(VmHostFileVO.class) |
| 724 | + .eq(VmHostFileVO_.uuid, currentFile.getUuid()) |
| 725 | + .set(VmHostFileVO_.lastOpDate, now) |
| 726 | + .update(); |
| 727 | + |
| 728 | + VmHostFileContentVO existingContent = Q.New(VmHostFileContentVO.class) |
| 729 | + .eq(VmHostFileContentVO_.uuid, currentFile.getUuid()) |
| 730 | + .find(); |
| 731 | + if (existingContent != null) { |
| 732 | + SQL.New(VmHostFileContentVO.class) |
| 733 | + .eq(VmHostFileContentVO_.uuid, currentFile.getUuid()) |
| 734 | + .set(VmHostFileContentVO_.content, backupContent.getContent()) |
| 735 | + .set(VmHostFileContentVO_.format, backupContent.getFormat()) |
| 736 | + .set(VmHostFileContentVO_.lastOpDate, now) |
| 737 | + .update(); |
| 738 | + } else { |
| 739 | + VmHostFileContentVO newContent = new VmHostFileContentVO(); |
| 740 | + newContent.setUuid(currentFile.getUuid()); |
| 741 | + newContent.setContent(backupContent.getContent()); |
| 742 | + newContent.setFormat(backupContent.getFormat()); |
| 743 | + newContent.setCreateDate(now); |
| 744 | + newContent.setLastOpDate(now); |
| 745 | + databaseFacade.persist(newContent); |
| 746 | + } |
| 747 | + } else { |
| 748 | + // create new VmHostFileVO and VmHostFileContentVO |
| 749 | + String path; |
| 750 | + if (type == VmHostFileType.NvRam) { |
| 751 | + path = buildNvramFilePath(msg.getVmInstanceUuid()); |
| 752 | + } else { |
| 753 | + path = buildTpmStateFilePath(msg.getVmInstanceUuid()); |
| 754 | + } |
| 755 | + |
| 756 | + VmHostFileVO newFile = new VmHostFileVO(); |
| 757 | + newFile.setUuid(Platform.getUuid()); |
| 758 | + newFile.setVmInstanceUuid(msg.getVmInstanceUuid()); |
| 759 | + newFile.setHostUuid(finalHostUuid); |
| 760 | + newFile.setType(type); |
| 761 | + newFile.setPath(path); |
| 762 | + newFile.setCreateDate(now); |
| 763 | + newFile.setLastOpDate(now); |
| 764 | + databaseFacade.persist(newFile); |
| 765 | + |
| 766 | + VmHostFileContentVO newContent = new VmHostFileContentVO(); |
| 767 | + newContent.setUuid(newFile.getUuid()); |
| 768 | + newContent.setContent(backupContent.getContent()); |
| 769 | + newContent.setFormat(backupContent.getFormat()); |
| 770 | + newContent.setCreateDate(now); |
| 771 | + newContent.setLastOpDate(now); |
| 772 | + databaseFacade.persist(newContent); |
| 773 | + } |
| 774 | + } else if (hasCurrentFile) { |
| 775 | + // delete VmHostFileVO and VmHostFileContentVO |
| 776 | + VmHostFileVO currentFile = currentFilesByType.get(type); |
| 777 | + SQL.New(VmHostFileContentVO.class) |
| 778 | + .eq(VmHostFileContentVO_.uuid, currentFile.getUuid()) |
| 779 | + .delete(); |
| 780 | + SQL.New(VmHostFileVO.class) |
| 781 | + .eq(VmHostFileVO_.uuid, currentFile.getUuid()) |
| 782 | + .delete(); |
| 783 | + } |
| 784 | + } |
| 785 | + |
| 786 | + trigger.next(); |
| 787 | + }) |
| 788 | + .build()) |
| 789 | + .propagateExceptionTo(msg) |
| 790 | + .done(() -> bus.reply(msg, reply)) |
| 791 | + .error(errorCode -> { |
| 792 | + reply.setError(errorCode); |
| 793 | + bus.reply(msg, reply); |
| 794 | + }) |
| 795 | + .start(); |
| 796 | + } |
558 | 797 | } |
0 commit comments