|
1 | | -import re |
2 | 1 | import pynetbox |
| 2 | +import re |
3 | 3 | import requests |
| 4 | +import time |
4 | 5 | import urllib |
5 | 6 |
|
6 | 7 | from proxmoxer import ProxmoxAPI, ResourceException |
@@ -591,3 +592,150 @@ def proxmox_delete_lxc(self, json_in): |
591 | 592 | return 500, {'result': e.content} |
592 | 593 |
|
593 | 594 |
|
| 595 | +class NetBoxProxmoxHelperMigrate(NetBoxProxmoxHelper): |
| 596 | + def __init__(self): |
| 597 | + self.proxmox_cluster_name = 'default-proxmox-cluster-name' |
| 598 | + self.proxmox_nodes = {} |
| 599 | + self.proxmox_vms = {} |
| 600 | + self.proxmox_lxc = {} |
| 601 | + |
| 602 | + self.__get_cluster_name_and_nodes() |
| 603 | + self.__get_proxmox_vms() |
| 604 | + self.__get_proxmox_lxcs() |
| 605 | + |
| 606 | + |
| 607 | + def __get_cluster_name_and_nodes(self): |
| 608 | + try: |
| 609 | + cluster_status = self.proxmox_api.cluster.status.get() |
| 610 | + |
| 611 | + for resource in cluster_status: |
| 612 | + if not 'type' in resource: |
| 613 | + raise ValueError(f"Missing 'type' in Proxmox cluster resource {resource}") |
| 614 | + |
| 615 | + if resource['type'] == 'cluster': |
| 616 | + self.proxmox_cluster_name = resource['name'] |
| 617 | + elif resource['type'] == 'node': |
| 618 | + if not resource['name'] in self.proxmox_nodes: |
| 619 | + self.proxmox_nodes[resource['name']] = {} |
| 620 | + |
| 621 | + self.proxmox_nodes[resource['name']]['ip'] = resource['ip'] |
| 622 | + self.proxmox_nodes[resource['name']]['online'] = resource['online'] |
| 623 | + except ResourceException as e: |
| 624 | + raise RuntimeError(f"Proxmox API error: {e}") from e |
| 625 | + except requests.exceptions.ConnectionError: |
| 626 | + raise RuntimeError("Failed to connect to Proxmox API") |
| 627 | + except requests.exceptions.HTTPError as e: |
| 628 | + status = e.response.status_code |
| 629 | + raise RuntimeError(f"HTTP {status}: {e.response.text}") from e |
| 630 | + |
| 631 | + |
| 632 | + def __get_proxmox_vms(self): |
| 633 | + try: |
| 634 | + for proxmox_node in self.proxmox_nodes: |
| 635 | + all_vm_settings = self.proxmox_api.nodes(proxmox_node).get('qemu') |
| 636 | + for vm_setting in all_vm_settings: |
| 637 | + if 'template' in vm_setting and vm_setting['template'] == 1: |
| 638 | + continue |
| 639 | + |
| 640 | + if not vm_setting['name'] in self.proxmox_vms: |
| 641 | + self.proxmox_vms[vm_setting['name']] = {} |
| 642 | + |
| 643 | + self.proxmox_vms[vm_setting['name']]['vmid'] = vm_setting['vmid'] |
| 644 | + self.proxmox_vms[vm_setting['name']]['node'] = proxmox_node |
| 645 | + except ResourceException as e: |
| 646 | + raise RuntimeError(f"Proxmox API error: {e}") from e |
| 647 | + except requests.exceptions.ConnectionError: |
| 648 | + raise RuntimeError("Failed to connect to Proxmox API") |
| 649 | + except requests.exceptions.HTTPError as e: |
| 650 | + status = e.response.status_code |
| 651 | + raise RuntimeError(f"HTTP {status}: {e.response.text}") from e |
| 652 | + |
| 653 | + |
| 654 | + def __get_proxmox_lxcs(self): |
| 655 | + try: |
| 656 | + for proxmox_node in self.proxmox_nodes: |
| 657 | + all_lxc_settings = self.proxmox_api.nodes(proxmox_node).get('lxc') |
| 658 | + for lxc_setting in all_lxc_settings: |
| 659 | + if 'template' in lxc_setting and lxc_setting['template'] == 1: |
| 660 | + continue |
| 661 | + |
| 662 | + if not lxc_setting['name'] in self.proxmox_lxc: |
| 663 | + self.proxmox_lxc[lxc_setting['name']] = {} |
| 664 | + |
| 665 | + self.proxmox_lxc[lxc_setting['name']]['vmid'] = lxc_setting['vmid'] |
| 666 | + self.proxmox_lxc[lxc_setting['name']]['node'] = proxmox_node |
| 667 | + except ResourceException as e: |
| 668 | + raise RuntimeError(f"Proxmox API error: {e}") from e |
| 669 | + except requests.exceptions.ConnectionError: |
| 670 | + raise RuntimeError("Failed to connect to Proxmox API") |
| 671 | + except requests.exceptions.HTTPError as e: |
| 672 | + status = e.response.status_code |
| 673 | + raise RuntimeError(f"HTTP {status}: {e.response.text}") from e |
| 674 | + |
| 675 | + |
| 676 | + def __wait_for_migration_task(self, proxmox_node: str, proxmox_task_id: int): |
| 677 | + try: |
| 678 | + start_time = int(time.time()) |
| 679 | + |
| 680 | + while True: |
| 681 | + current_time = int(time.time()) |
| 682 | + elapsed_seconds = current_time - start_time |
| 683 | + |
| 684 | + if elapsed_seconds >= 600: # 10 minutes |
| 685 | + raise ValueError(f"Unable to complete task {proxmox_task_id} in defined time") |
| 686 | + |
| 687 | + task_status = self.proxmox_api.nodes(proxmox_node).tasks(proxmox_task_id).status.get() |
| 688 | + |
| 689 | + if task_status['status'] == 'stopped': |
| 690 | + if 'exitstatus' in task_status and task_status['exitstatus'] == 'OK': |
| 691 | + break |
| 692 | + else: |
| 693 | + return 500, {'result': f"Task {proxmox_task_id} is stopped but exit status does not appear to be successful: {task_status['exit_status']}"} |
| 694 | + except ResourceException as e: |
| 695 | + return 500, {'content': f"Proxmox API error: {e}"} |
| 696 | + except requests.exceptions.ConnectionError as e: |
| 697 | + return 500, {'content': f"Failed to connect to Proxmox API: {e}"} |
| 698 | + except requests.exceptions.HTTPError as e: |
| 699 | + status = e.response.status_code |
| 700 | + return 500, {'content': f"HTTP {status}: {e.response.text}"} |
| 701 | + |
| 702 | + |
| 703 | + def migrate_vm(self, proxmox_vmid: int, proxmox_node: str, proxmox_target_node: str): |
| 704 | + migrate_vm_data = { |
| 705 | + 'target': proxmox_target_node, |
| 706 | + 'online': 1 |
| 707 | + } |
| 708 | + |
| 709 | + try: |
| 710 | + migrate_vm_task_id = self.proxmox_api.nodes(proxmox_node).qemu(proxmox_vmid).migrate.post(**migrate_vm_data) |
| 711 | + self.__wait_for_migration_task(proxmox_node, migrate_vm_task_id) |
| 712 | + |
| 713 | + return 200, {'result': f"VM (vmid: {proxmox_vmid}) has been migrated to node {proxmox_target_node}"} |
| 714 | + except ResourceException as e: |
| 715 | + return 500, {'result': f"Proxmox API error: {e}"} |
| 716 | + except requests.exceptions.ConnectionError: |
| 717 | + return 500, {'result': f"Failed to connect to Proxmox API: {e}"} |
| 718 | + except requests.exceptions.HTTPError as e: |
| 719 | + status = e.response.status_code |
| 720 | + return 500, {'result': f"HTTP {status}: {e.response.text}"} |
| 721 | + |
| 722 | + |
| 723 | + def migrate_lxc(self, proxmox_vmid: int, proxmox_node: str, proxmox_target_node: str): |
| 724 | + migrate_lxc_data = { |
| 725 | + 'target': proxmox_target_node, |
| 726 | + 'online': 1 |
| 727 | + } |
| 728 | + |
| 729 | + try: |
| 730 | + migrate_lxc_task_id = self.proxmox_api.nodes(proxmox_node).lxc(proxmox_vmid).migrate.post(**migrate_lxc_data) |
| 731 | + self.__wait_for_migration_task(proxmox_node, migrate_lxc_task_id) |
| 732 | + return 200, {'result': f"LXC (vmid: {proxmox_vmid}) has been migrated to node {proxmox_target_node}"} |
| 733 | + except ResourceException as e: |
| 734 | + return 500, {'result': f"Proxmox API error: {e}"} |
| 735 | + except requests.exceptions.ConnectionError: |
| 736 | + return 500, {'result': f"Failed to connect to Proxmox API: {e}"} |
| 737 | + except requests.exceptions.HTTPError as e: |
| 738 | + status = e.response.status_code |
| 739 | + return 500, {'result': f"HTTP {status}: {e.response.text}"} |
| 740 | + |
| 741 | + |
0 commit comments