|
59 | 59 | import org.zstack.kvm.KVMAgentCommands; |
60 | 60 | import org.zstack.kvm.KvmCommandSender; |
61 | 61 | import org.zstack.kvm.KvmResponseWrapper; |
| 62 | +import org.zstack.kvm.efi.KvmSecureBootExtensions; |
62 | 63 | import org.zstack.kvm.tpm.message.CloneVmTpmMsg; |
63 | 64 | import org.zstack.kvm.tpm.message.CloneVmTpmReply; |
64 | 65 | import org.zstack.resourceconfig.ResourceConfig; |
@@ -98,6 +99,8 @@ public class KvmTpmManager extends AbstractService { |
98 | 99 | private VmTpmManager vmTpmManager; |
99 | 100 | @Autowired |
100 | 101 | private TpmEncryptedResourceKeyBackend tpmKeyBackend; |
| 102 | + @Autowired |
| 103 | + private KvmSecureBootExtensions secureBootExtensions; |
101 | 104 |
|
102 | 105 | @Override |
103 | 106 | public boolean start() { |
@@ -514,25 +517,193 @@ public void handle(ErrorCode errCode, Map data) { |
514 | 517 | }).start(); |
515 | 518 | } |
516 | 519 |
|
| 520 | + static class ResetVmTpmContext { |
| 521 | + String vmInstanceUuid; |
| 522 | + |
| 523 | + List<VmHostFileVO> hostFiles; |
| 524 | + VmHostFileVO hostFileToDeleteLast; |
| 525 | + List<String> hostFileUuidListDeleteSuccessfully = new ArrayList<>(); |
| 526 | + ErrorCodeList errorsOnSendCmd = new ErrorCodeList(); |
| 527 | + |
| 528 | + static ResetVmTpmContext valueOf(ResetVmTpmMsg msg) { |
| 529 | + ResetVmTpmContext context = new ResetVmTpmContext(); |
| 530 | + context.vmInstanceUuid = msg.getVmInstanceUuid(); |
| 531 | + return context; |
| 532 | + } |
| 533 | + } |
| 534 | + |
517 | 535 | private void handle(ResetVmTpmMsg msg) { |
518 | 536 | ResetVmTpmReply reply = new ResetVmTpmReply(); |
| 537 | + threadFacade.chainSubmit(new ChainTask(msg) { |
| 538 | + @Override |
| 539 | + public void run(SyncTaskChain chain) { |
| 540 | + ResetVmTpmContext context = ResetVmTpmContext.valueOf(msg); |
| 541 | + resetVmTpm(context, new Completion(chain, msg) { |
| 542 | + @Override |
| 543 | + public void success() { |
| 544 | + chain.next(); |
| 545 | + bus.reply(msg, reply); |
| 546 | + } |
| 547 | + |
| 548 | + @Override |
| 549 | + public void fail(ErrorCode errorCode) { |
| 550 | + chain.next(); |
| 551 | + reply.setError(errorCode); |
| 552 | + bus.reply(msg, reply); |
| 553 | + } |
| 554 | + }); |
| 555 | + } |
519 | 556 |
|
520 | | - String vmUuid = msg.getVmInstanceUuid(); |
521 | | - new SQLBatch() { |
522 | 557 | @Override |
523 | | - protected void scripts() { |
524 | | - sql(VmHostFileVO.class) |
525 | | - .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) |
526 | | - .eq(VmHostFileVO_.type, VmHostFileType.TpmState) |
527 | | - .delete(); |
528 | | - sql(VmHostBackupFileVO.class) |
529 | | - .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) |
530 | | - .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) |
531 | | - .delete(); |
| 558 | + public String getSyncSignature() { |
| 559 | + return tpmQueueSyncSignature(msg.getVmInstanceUuid()); |
532 | 560 | } |
533 | | - }.execute(); |
534 | 561 |
|
535 | | - bus.reply(msg, reply); |
| 562 | + @Override |
| 563 | + public String getName() { |
| 564 | + return "queue-of-reset-tpm-from-vm-" + msg.getVmInstanceUuid(); |
| 565 | + } |
| 566 | + }); |
| 567 | + } |
| 568 | + |
| 569 | + private void resetVmTpm(ResetVmTpmContext context, Completion completion) { |
| 570 | + String vmUuid = context.vmInstanceUuid; |
| 571 | + |
| 572 | + SimpleFlowChain.of("reset-vm-tpm-" + vmUuid) |
| 573 | + .then(Flow.of("collect-vm-host-files") |
| 574 | + .handle(trigger -> { |
| 575 | + context.hostFiles = Q.New(VmHostFileVO.class) |
| 576 | + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) |
| 577 | + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) |
| 578 | + .orderByAsc(VmHostFileVO_.lastOpDate) |
| 579 | + .list(); |
| 580 | + if (!context.hostFiles.isEmpty()) { |
| 581 | + // We should delete it in last turn: |
| 582 | + context.hostFileToDeleteLast = context.hostFiles.get(context.hostFiles.size() - 1); |
| 583 | + context.hostFiles.remove(context.hostFiles.size() - 1); |
| 584 | + } |
| 585 | + trigger.next(); |
| 586 | + }) |
| 587 | + .build()) |
| 588 | + .then(Flow.of("send-delete-commands-to-hosts-exclude-last-modified") |
| 589 | + .skipIf(data -> context.hostFiles.isEmpty()) |
| 590 | + .handle(trigger -> { |
| 591 | + Map<String, List<VmHostFileVO>> filesByHost = new HashMap<>(); |
| 592 | + for (VmHostFileVO file : context.hostFiles) { |
| 593 | + filesByHost.computeIfAbsent(file.getHostUuid(), k -> new ArrayList<>()).add(file); |
| 594 | + } |
| 595 | + |
| 596 | + new While<>(filesByHost.entrySet()).each((entry, whileCompletion) -> { |
| 597 | + List<KVMAgentCommands.VmHostFileTO> fileTOs = new ArrayList<>(); |
| 598 | + for (VmHostFileVO file : entry.getValue()) { |
| 599 | + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); |
| 600 | + to.setPath(file.getPath()); |
| 601 | + to.setType(file.getType().toString()); |
| 602 | + to.setOperation(VmHostFileOperation.Delete.toString()); |
| 603 | + fileTOs.add(to); |
| 604 | + } |
| 605 | + |
| 606 | + KvmSecureBootExtensions.RewriteVmHostFilesContext ctx = |
| 607 | + new KvmSecureBootExtensions.RewriteVmHostFilesContext(); |
| 608 | + ctx.hostUuid = entry.getKey(); |
| 609 | + ctx.hostFiles = fileTOs; |
| 610 | + |
| 611 | + secureBootExtensions.rewriteVmHostFiles(ctx, new Completion(whileCompletion) { |
| 612 | + @Override |
| 613 | + public void success() { |
| 614 | + context.hostFileUuidListDeleteSuccessfully.addAll( |
| 615 | + transform(entry.getValue(), VmHostFileVO::getUuid)); |
| 616 | + whileCompletion.done(); |
| 617 | + } |
| 618 | + |
| 619 | + @Override |
| 620 | + public void fail(ErrorCode errorCode) { |
| 621 | + context.errorsOnSendCmd.add(errorCode.withOpaque("host.uuid", entry.getKey())); |
| 622 | + whileCompletion.done(); |
| 623 | + } |
| 624 | + }); |
| 625 | + }).run(new WhileDoneCompletion(trigger) { |
| 626 | + @Override |
| 627 | + public void done(ErrorCodeList errorCodeList) { |
| 628 | + trigger.next(); |
| 629 | + } |
| 630 | + }); |
| 631 | + }) |
| 632 | + .build()) |
| 633 | + .then(Flow.of("remove-db-records") |
| 634 | + .skipIf(data -> context.hostFileUuidListDeleteSuccessfully.isEmpty()) |
| 635 | + .handle(trigger -> { |
| 636 | + SQL.New(VmHostFileVO.class) |
| 637 | + .in(VmHostFileVO_.uuid, context.hostFileUuidListDeleteSuccessfully) |
| 638 | + .delete(); |
| 639 | + trigger.next(); |
| 640 | + }) |
| 641 | + .build()) |
| 642 | + .then(Flow.of("check-if-any-error-in-command-sending") |
| 643 | + .handle(trigger -> { |
| 644 | + // If any host failed to delete, abort the chain to preserve |
| 645 | + // the last-modified TPM record as a recovery point. |
| 646 | + if (context.errorsOnSendCmd.hasError()) { |
| 647 | + if (context.errorsOnSendCmd.size() == 1) { |
| 648 | + trigger.fail(context.errorsOnSendCmd.getCauses().get(0)); |
| 649 | + } else { |
| 650 | + trigger.fail(operr("failed to delete TPM files on multiple hosts") |
| 651 | + .withOpaque("vm.uuid", vmUuid) |
| 652 | + .withCause(context.errorsOnSendCmd.getCauses())); |
| 653 | + } |
| 654 | + return; |
| 655 | + } |
| 656 | + trigger.next(); |
| 657 | + }) |
| 658 | + .build()) |
| 659 | + .then(Flow.of("send-delete-commands-to-hosts-for-last-modified") |
| 660 | + .skipIf(data -> context.hostFileToDeleteLast == null) |
| 661 | + .handle(trigger -> { |
| 662 | + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); |
| 663 | + to.setPath(context.hostFileToDeleteLast.getPath()); |
| 664 | + to.setType(context.hostFileToDeleteLast.getType().toString()); |
| 665 | + to.setOperation(VmHostFileOperation.Delete.toString()); |
| 666 | + |
| 667 | + KvmSecureBootExtensions.RewriteVmHostFilesContext ctx = |
| 668 | + new KvmSecureBootExtensions.RewriteVmHostFilesContext(); |
| 669 | + ctx.hostUuid = context.hostFileToDeleteLast.getHostUuid(); |
| 670 | + ctx.hostFiles = list(to); |
| 671 | + |
| 672 | + secureBootExtensions.rewriteVmHostFiles(ctx, new Completion(trigger) { |
| 673 | + @Override |
| 674 | + public void success() { |
| 675 | + trigger.next(); |
| 676 | + } |
| 677 | + |
| 678 | + @Override |
| 679 | + public void fail(ErrorCode errorCode) { |
| 680 | + trigger.fail(errorCode.withOpaque("host.uuid", ctx.hostUuid)); |
| 681 | + } |
| 682 | + }); |
| 683 | + }) |
| 684 | + .build()) |
| 685 | + .then(Flow.of("remove-db-records-for-remains") |
| 686 | + .skipIf(data -> context.hostFileToDeleteLast == null) |
| 687 | + .handle(trigger -> { |
| 688 | + new SQLBatch() { |
| 689 | + @Override |
| 690 | + protected void scripts() { |
| 691 | + sql(VmHostFileVO.class) |
| 692 | + .eq(VmHostFileVO_.uuid, context.hostFileToDeleteLast.getUuid()) |
| 693 | + .delete(); |
| 694 | + sql(VmHostBackupFileVO.class) |
| 695 | + .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) |
| 696 | + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) |
| 697 | + .delete(); |
| 698 | + } |
| 699 | + }.execute(); |
| 700 | + trigger.next(); |
| 701 | + }) |
| 702 | + .build()) |
| 703 | + .propagateExceptionTo(completion) |
| 704 | + .done(completion::success) |
| 705 | + .error(completion::fail) |
| 706 | + .start(); |
536 | 707 | } |
537 | 708 |
|
538 | 709 | private void handle(APIGetTpmCapabilityMsg msg) { |
|
0 commit comments