From 199cbab4aa9aad4e9bcc4e2ff051c7b2f72bf157 Mon Sep 17 00:00:00 2001 From: panjunlan Date: Thu, 19 Feb 2026 23:23:51 +0800 Subject: [PATCH] Get Vms Snapshots and Export Excel correct info. --- README.md | 331 +++++++++++++++++++++++++++++++++++++++++++-- config/config.yaml | 17 ++- core/get_vms.py | 266 +++++++++++++++++------------------- 3 files changed, 456 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 4b1a7c5..e5c7736 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,12 @@ - [x] 连接vCenter/Esxi/Hyper-V - [x] 获取所有 vms - [x] 获取所有 snapshots -- [x] 筛选出15天(半个月)前的snapshots -- [x] 以上内容以Excel表格的形式导出,超出15天的快照蓝底标识 -- [ ] 最后删除 15 天前的 snapshot,并同时记录删除的 snapshot 日志信息 -- [ ] 需要控制每台 vCenter 不可以同时删除超过4个快照 +- [x] 筛选出15天(半个月)前的 snapshots +- [x] 以上内容以 Excel 表格的形式导出,超出 15 天的快照填充蓝色底标识 +- [ ] Outlook 邮箱发送超出 15 天的快照 - [ ] 增加排除不能删除的快照 - - +- [ ] 需要控制每台 vCenter 不可以同时删除超过 4 个快照 +- [ ] 最后删除 15 天前的 snapshot,并同时记录删除的 snapshot 日志信息 @@ -29,6 +28,21 @@ | **存储路径** | `vm.config.files.vmPathName` | `"[Datastore1] WebServer-01/WebServer-01.vmx"` | | **快照数量** | `len(vm.snapshot.rootSnapshotList)` | `3` | + + + + +## 输出所有可用的属性和方法 + +``` +vm.config.createDate # 虚拟机的创建时间 +vm.runtime.bootTime # 虚拟机上次启动的时间 +``` + + + +>以下这些方法和属性主要用于操作虚拟机(VM)、快照、存储和其他资源。 +> >vm >├── 基础标识 >│ ├── name VM名称 @@ -61,9 +75,308 @@ >└── summary 【快速汇总】 >├── overallStatus 整体健康状态 >└── quickStats 实时性能数据 +> +>1. **AcquireMksTicket / AcquireTicket**:获取访问虚拟机控制台的票据,允许用户通过 Web 界面访问 VM。 +>2. **Clone / CloneVM_Task**:克隆一个虚拟机,创建其副本。 +>3. **CreateSnapshot / CreateSnapshot_Task**:创建虚拟机快照,以保存当前 VM 的状态。 +>4. **Destroy / Destroy_Task**:删除虚拟机及其所有数据。 +>5. **PowerOn / PowerOnVM_Task**:启动虚拟机。 +>6. **PowerOff / PowerOffVM_Task**:关闭虚拟机。 +>7. **Reset / ResetVM_Task**:重置虚拟机,相当于按下重启按钮。 +>8. **RevertToCurrentSnapshot / RevertToCurrentSnapshot_Task**:将虚拟机恢复到当前快照的状态。 +>9. **Migrate / MigrateVM_Task**:迁移虚拟机到不同的主机或数据存储。 +>10. **SetCustomValue**:设置自定义属性值,以便在虚拟机上存储额外信息。 +> +>### 属性列表 +> +>这些属性通常包含关于虚拟机或其他资源的状态和配置信息。以下是一些关键属性的说明: +> +>1. **name**:虚拟机的名称。 +>2. **guest**:关于虚拟机操作系统的信息,如操作系统类型、版本等。 +>3. **config**:虚拟机的配置详情,例如 CPU、内存、硬盘等。 +>4. **runtime**:虚拟机的运行时状态,包括电源状态(开机、关机、挂起等)。 +>5. **snapshot**:当前虚拟机的快照信息。 +>6. **summary**:虚拟机的概述信息,包括状态、资源使用情况等。 +>7. **resourcePool**:虚拟机所在的资源池。 +>8. **datastore**:虚拟机的存储位置,指向其使用的存储库。 +>9. **overallStatus**:虚拟机的总体状态,可能是正常、警告、错误等。 +>10. **recentTask**:最近执行的任务列表。 +> +>### 其他属性 +> +>- **capability**:虚拟机支持的功能,如支持的虚拟硬件版本。 +>- **declaredAlarmState**:声明的警报状态,用于监控虚拟机的健康状况。 +>- **triggeredAlarmState**:触发的警报状态,显示当前激活的警报。 + +```python + for vm in vm_view.view: + print(dir(vm)) # 输出所有可用的属性和方法 + # print(vm.summary) + # print(vm.snapshot) + # print(vars(vm.summary)) +``` + +``` json +['AcquireMksTicket', 'AcquireTicket', 'Answer', 'AnswerVM', 'ApplyEvcMode', 'ApplyEvcModeVM_Task', 'Array', 'AttachDisk', 'AttachDisk_Task', 'CheckCustomizationSpec', 'Clone', 'CloneVM_Task', 'ConsolidateDisks', 'ConsolidateVMDisks_Task', 'CreateScreenshot', 'CreateScreenshot_Task', 'CreateSecondary', 'CreateSecondaryEx', 'CreateSecondaryVMEx_Task', 'CreateSecondaryVM_Task', 'CreateSnapshot', 'CreateSnapshotEx', 'CreateSnapshotEx_Task', 'CreateSnapshot_Task', 'CryptoUnlock', 'CryptoUnlock_Task', 'Customize', 'CustomizeVM_Task', 'DefragmentAllDisks', 'Destroy', 'Destroy_Task', 'DetachDisk', 'DetachDisk_Task', 'DisableSecondary', 'DisableSecondaryVM_Task', 'DropConnections', 'EnableSecondary', 'EnableSecondaryVM_Task', 'EstimateStorageForConsolidateSnapshots_Task', 'EstimateStorageRequirementForConsolidate', 'ExportVm', 'ExtractOvfEnvironment', 'InstantClone', 'InstantClone_Task', 'MakePrimary', 'MakePrimaryVM_Task', 'MarkAsTemplate', 'MarkAsVirtualMachine', 'Migrate', 'MigrateVM_Task', 'MountToolsInstaller', 'PowerOff', 'PowerOffVM_Task', 'PowerOn', 'PowerOnVM_Task', 'PromoteDisks', 'PromoteDisks_Task', 'PutUsbScanCodes', 'QueryChangedDiskAreas', 'QueryConnections', 'QueryFaultToleranceCompatibility', 'QueryFaultToleranceCompatibilityEx', 'QueryUnownedFiles', 'RebootGuest', 'ReconfigVM_Task', 'Reconfigure', 'RefreshStorageInfo', 'Reload', 'ReloadFromPath', 'Relocate', 'RelocateVM_Task', 'RemoveAllSnapshots', 'RemoveAllSnapshots_Task', 'Rename', 'Rename_Task', 'Reset', 'ResetGuestInformation', 'ResetVM_Task', 'RevertToCurrentSnapshot', 'RevertToCurrentSnapshot_Task', 'SendNMI', 'SetCustomValue', 'SetDisplayTopology', 'SetScreenResolution', 'ShutdownGuest', 'StandbyGuest', 'StartRecording', 'StartRecording_Task', 'StartReplaying', 'StartReplaying_Task', 'StopRecording', 'StopRecording_Task', 'StopReplaying', 'StopReplaying_Task', 'Suspend', 'SuspendVM_Task', 'Terminate', 'TerminateFaultTolerantVM', 'TerminateFaultTolerantVM_Task', 'TerminateVM', 'TurnOffFaultTolerance', 'TurnOffFaultToleranceForVM_Task', 'UnmountToolsInstaller', 'Unregister', 'UnregisterVM', 'UpgradeTools', 'UpgradeTools_Task', 'UpgradeVM_Task', 'UpgradeVirtualHardware', '_GetMethodInfo', '_GetMethodList', '_GetMoId', '_GetPropertyInfo', '_GetPropertyList', '_GetServerGuid', '_GetStub', '_InvokeAccessor', '_InvokeMethod', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_methodInfo', '_moId', '_propInfo', '_propList', '_serverGuid', '_stub', '_version', '_wsdlName', 'alarmActionsEnabled', 'availableField', 'capability', 'config', 'configIssue', 'configStatus', 'customValue', 'datastore', 'declaredAlarmState', 'disabledMethod', 'effectiveRole', 'environmentBrowser', 'guest', 'guestHeartbeatStatus', 'layout', 'layoutEx', 'name', 'network', 'overallStatus', 'parent', 'parentVApp', 'permission', 'recentTask', 'reloadVirtualMachineFromPath_Task', 'resourceConfig', 'resourcePool', 'rootSnapshot', 'runtime', 'setCustomValue', 'snapshot', 'storage', 'summary', 'tag', 'triggeredAlarmState', 'value'] + +``` -[{'name': 'snap-02', 'description': 'test-Ansible snapshot', 'createTime': '2026-02-17 08:57:39', 'state': 'poweredOff', 'id': 2, 'moId': 'snapshot-27', 'sizeMB': None, 'quiesced': False, 'children': []}] -[{'name': 'snap-01', 'description': 'Ansible snapshot', 'createTime': '2026-02-17 03:26:08', 'state': 'poweredOff', 'id': 1, 'moId': 'snapshot-26', 'sizeMB': None, 'quiesced': False, 'children': [{'name': 'snap-02', 'description': 'test-Ansible snapshot', 'createTime': '2026-02-17 08:57:39', 'state': 'poweredOff', 'id': 2, 'moId': 'snapshot-27', 'sizeMB': None, 'quiesced': False, 'children': []}]}] -获取到 2 台VM +### 获取虚拟机属性 + +获取和打印出虚拟机 (VM) 概要信息的所有属性,提供了关于虚拟机的基本信息,`vm.summary` 会返回一个字典,包含常见属性。可以使用 vm.guest.ipAddress 方法获取到虚拟机的IP地址等。 + +```python +print(vm.summary) +``` + +> (vim.vm.Summary) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> vm = 'vim.VirtualMachine:1', +> runtime = (vim.vm.RuntimeInfo) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> device = (vim.vm.DeviceRuntimeInfo) [ +> (vim.vm.DeviceRuntimeInfo) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> runtimeState = (vim.vm.DeviceRuntimeInfo.VirtualEthernetCardRuntimeState) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> vmDirectPathGen2Active = false, +> vmDirectPathGen2InactiveReasonVm = (str) [ +> 'vmNptDisabledOrDisconnectedAdapter' +> ], +> vmDirectPathGen2InactiveReasonOther = (str) [], +> vmDirectPathGen2InactiveReasonExtended = , +> uptv2Active = , +> uptv2InactiveReasonVm = (str) [], +> uptv2InactiveReasonOther = (str) [], +> reservationStatus = , +> attachmentStatus = 'red', +> featureRequirement = (vim.vm.FeatureRequirement) [] +> }, +> key = 4000 +> } +> ], +> host = 'vim.HostSystem:ha-host', +> connectionState = 'connected', +> powerState = 'poweredOff', +> vmFailoverInProgress = , +> faultToleranceState = 'notConfigured', +> dasVmProtection = , +> toolsInstallerMounted = false, +> suspendTime = , +> bootTime = , +> suspendInterval = 0, +> question = , +> memoryOverhead = , +> maxCpuUsage = 5184, +> maxMemoryUsage = 4096, +> numMksConnections = 0, +> recordReplayState = 'inactive', +> cleanPowerOff = false, +> needSecondaryReason = , +> onlineStandby = false, +> minRequiredEVCModeKey = , +> consolidationNeeded = false, +> offlineFeatureRequirement = (vim.vm.FeatureRequirement) [ +> (vim.vm.FeatureRequirement) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> key = 'cpuid.lm', +> featureName = 'cpuid.lm', +> value = 'Bool:Min:1' +> } +> ], +> featureRequirement = (vim.vm.FeatureRequirement) [], +> featureMask = (vim.host.FeatureMask) [], +> vFlashCacheAllocation = , +> paused = false, +> snapshotInBackground = false, +> quiescedForkParent = , +> instantCloneFrozen = false, +> cryptoState = , +> suspendedToMemory = , +> opNotificationTimeout = , +> iommuActive = +> }, +> guest = (vim.vm.Summary.GuestSummary) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> guestId = , +> guestFullName = , +> toolsStatus = 'toolsNotRunning', +> toolsVersionStatus = 'guestToolsUnmanaged', +> toolsVersionStatus2 = 'guestToolsUnmanaged', +> toolsRunningStatus = 'guestToolsNotRunning', +> hostName = , +> ipAddress = , +> hwVersion = +> }, +> config = (vim.vm.Summary.ConfigSummary) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> name = 'VMware vCenter Server Appliance', +> template = false, +> vmPathName = '[datastore1] VMware vCenter Server Appliance/VMware vCenter Server Appliance.vmx', +> memorySizeMB = 4096, +> cpuReservation = 0, +> memoryReservation = 0, +> numCpu = 2, +> numEthernetCards = 1, +> numVirtualDisks = 13, +> uuid = '564d7d77-f37f-64a7-47f2-a131a337c070', +> instanceUuid = '529e8be2-58ca-617c-ad7e-7f66de667471', +> guestId = 'other3xLinux64Guest', +> guestFullName = 'Other 3.x Linux (64-bit)', +> annotation = 'VMware vCenter Server Appliance', +> product = , +> installBootRequired = , +> ftInfo = , +> managedBy = , +> tpmPresent = false, +> numVmiopBackings = 0, +> hwVersion = +> }, +> storage = (vim.vm.Summary.StorageSummary) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> committed = 37476296711, +> uncommitted = 303123661815, +> unshared = 37476296711, +> timestamp = 2026-02-19T09:11:27.701184Z +> }, +> quickStats = (vim.vm.Summary.QuickStats) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> overallCpuUsage = , +> overallCpuDemand = , +> overallCpuReadiness = , +> guestMemoryUsage = , +> hostMemoryUsage = , +> guestHeartbeatStatus = 'gray', +> distributedCpuEntitlement = , +> distributedMemoryEntitlement = , +> staticCpuEntitlement = , +> staticMemoryEntitlement = , +> grantedMemory = , +> privateMemory = , +> sharedMemory = , +> swappedMemory = , +> balloonedMemory = , +> consumedOverheadMemory = , +> ftLogBandwidth = , +> ftSecondaryLatency = , +> ftLatencyStatus = , +> compressedMemory = , +> uptimeSeconds = , +> ssdSwappedMemory = , +> activeMemory = , +> memoryTierStats = (vim.vm.Summary.QuickStats.MemoryTierStats) [] +> }, +> overallStatus = 'green', +> customValue = (vim.CustomFieldsManager.Value) [] +> } + + + +### 获取快照属性 + +输出快照所有信息 + +```python +print(vm.snapshot) +``` + +``` json +(vim.vm.SnapshotInfo) { + dynamicType = , + dynamicProperty = (vmodl.DynamicProperty) [], + currentSnapshot = 'vim.vm.Snapshot:1-snapshot-3', + rootSnapshotList = (vim.vm.SnapshotTree) [ + (vim.vm.SnapshotTree) { + dynamicType = , + dynamicProperty = (vmodl.DynamicProperty) [], + snapshot = 'vim.vm.Snapshot:1-snapshot-1', + vm = 'vim.VirtualMachine:1', + name = 'snap-01', # 第一层快照 + description = 'Ansible snapshot', + id = 1, + createTime = 2026-02-17T03:26:08.834505Z, + state = 'poweredOff', + quiesced = false, + backupManifest = , + childSnapshotList = (vim.vm.SnapshotTree) [ + (vim.vm.SnapshotTree) { + dynamicType = , + dynamicProperty = (vmodl.DynamicProperty) [], + snapshot = 'vim.vm.Snapshot:1-snapshot-2', + vm = 'vim.VirtualMachine:1', + name = 'snap-02', # 第二层快照 + description = 'test-Ansible snapshot', + id = 2, + createTime = 2026-02-17T08:57:39.122511Z, + state = 'poweredOff', + quiesced = false, + backupManifest = , + childSnapshotList = (vim.vm.SnapshotTree) [ + (vim.vm.SnapshotTree) { + dynamicType = , + dynamicProperty = (vmodl.DynamicProperty) [], + snapshot = 'vim.vm.Snapshot:1-snapshot-3', + vm = 'vim.VirtualMachine:1', + name = '虚拟机快照 2026%252f2%252f19 12:28:36', # 第二层快照 + description = '', + id = 3, + createTime = 2026-02-19T04:28:38.007703Z, + state = 'poweredOn', + quiesced = false, + backupManifest = , + childSnapshotList = (vim.vm.SnapshotTree) [], + replaySupported = false + } + ], + replaySupported = false + } + ], + replaySupported = false + } + ] +} +``` + + + +### 打印快照树 + +``` python +def print_snapshot_tree(snapshot_info, level=0): + """递归打印快照树(辅助函数)""" + indent = " " * level + print(f"{indent}├─ {snapshot_info['name']}") + print(f"{indent}│ ├─ 创建时间: {snapshot_info['createTime']}") + print(f"{indent}│ ├─ 描述: {snapshot_info['description']}") + print(f"{indent}│ ├─ 状态: {snapshot_info['state']}") + if snapshot_info['sizeMB']: + print(f"{indent}│ ├─ 大小: {snapshot_info['sizeMB']} MB") + + for child in snapshot_info['children']: + print_snapshot_tree(child, level + 1) + +if __name__ == '__main__': + vms = get_all_vms() + + # 打印示例:显示每个VM的快照信息 + for vm in vms: + print(f"\nVM: {vm['name']}") + if vm['snapshots']: + print(f"快照数量: {len(vm['snapshots'])}") + for snapshot in vm['snapshots']: + print_snapshot_tree(snapshot) + else: + print("无快照") +``` + diff --git a/config/config.yaml b/config/config.yaml index 8f0603a..7096289 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,12 +1,12 @@ # 管理节点配置(包含vCenter和ESXi) management_nodes: # vCenter节点 - - type: vcenter # 标记类型为vcenter - name: vc1 # 节点名称(用于日志) - host: 192.168.40.134 # 地址 - user: administrator@lan.com - password: Root@2025 - max_delete_concurrent: 4 # 该节点最大并发删除数 +# - type: vcenter # 标记类型为vcenter +# name: vc1 # 节点名称(用于日志) +# host: 192.168.40.134 # 地址 +# user: administrator@lan.com +# password: Root@2025 +# max_delete_concurrent: 4 # 该节点最大并发删除数 # ESXi节点(未接入 vCenter 的 Esxi 主机) - type: esxi # 标记类型为esxi @@ -16,12 +16,11 @@ management_nodes: password: Root@2025 max_delete_concurrent: 2 # ESXi性能较弱,并发数可设小些 - # 另一个ESXi节点 - type: esxi name: esxi2 - host: esxi2.example.com + host: 192.168.40.135 user: root - password: esxi2_password + password: Root@2025 max_delete_concurrent: 2 # 全局策略配置 diff --git a/core/get_vms.py b/core/get_vms.py index b91ffb8..c94cbe7 100644 --- a/core/get_vms.py +++ b/core/get_vms.py @@ -1,125 +1,13 @@ from pyVmomi import vim from pyVim.connect import SmartConnect, Disconnect from config.settings import MANAGEMENT_NODES -from datetime import timedelta import pandas as pd -import openpyxl from openpyxl.styles import Border, Side, Font, PatternFill from datetime import datetime, timedelta -"""计算虚拟机的总磁盘大小(仅是虚拟磁盘的分配空间)""" -def get_virtual_disk_size(vm): - total_size = 0 - for device in vm.config.hardware.device: - if isinstance(device, vim.vm.device.VirtualDisk): - # 获取每个虚拟磁盘的容量 - total_size += device.capacityInKB / (1024 * 1024) # 转换为GB - return total_size - -"""获取所有虚拟机""" -def get_all_vms(): - # 取第一个节点 - node = MANAGEMENT_NODES[0] - - # 连接vCenter - si = SmartConnect( - host=node['host'], - user=node['user'], - pwd=node['password'], - disableSslCertValidation=True - ) - - # 获取所有VM - content = si.RetrieveContent() - vm_view = content.viewManager.CreateContainerView( - content.rootFolder, [vim.VirtualMachine], True - ) - - vm_list = [] - for vm in vm_view.view: - # 初始化VM信息字典 - vm_info = { - 'name': vm.name, - 'moId': vm._moId, - 'powerState': str(vm.runtime.powerState), - 'system': vm.config.guestFullName, - 'ipAddress': vm.guest.ipAddress, - 'hostName': vm.guest.hostName, - 'vmPath': vm.config.files.vmPathName, - 'Host': vm.runtime.host.name, # 拿到 Host 主机名 - 'snapshots': [], # 添加快照信息 - 'diskSpaceGB': get_virtual_disk_size(vm) # 添加虚拟机占用的磁盘空间 - } - - # 获取快照信息 - if vm.snapshot is not None: - current_snapshot = vm.snapshot.currentSnapshot - root_snapshots = vm.snapshot.rootSnapshotList - - # 处理根快照列表 - for snapshot in root_snapshots: - snapshot_info = get_snapshot_info(snapshot) - vm_info['snapshots'].extend(snapshot_info) - - # 添加当前快照ID(如果有) - if current_snapshot: - vm_info['currentSnapshotId'] = current_snapshot._moId - else: - vm_info['snapshots'] = None - vm_info['currentSnapshotId'] = None - - vm_list.append(vm_info) - - vm_view.Destroy() - Disconnect(si) - - print(f"获取到 {len(vm_list)} 台VM") - return vm_list - - -"""递归获取快照信息""" -def get_snapshot_info(snapshot): - - snapshot_list = [] - # 当前快照信息 - snapshot_info = { - 'name': snapshot.name, - 'description': snapshot.description, - 'createTime': (snapshot.createTime + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), - 'state': str(snapshot.state), - 'id': snapshot.id, - 'moId': snapshot.snapshot._moId, # 快照的Managed Object ID - 'sizeMB': snapshot.diskSizeMB if hasattr(snapshot, 'diskSizeMB') else None, # 快照大小 - 'quiesced': snapshot.quiesced if hasattr(snapshot, 'quiesced') else None, # 是否静默快照 - 'children': [] # 子快照 - } - - # 递归处理子快照 - if snapshot.childSnapshotList: - for child in snapshot.childSnapshotList: - snapshot_info['children'].extend(get_snapshot_info(child)) - - snapshot_list.append(snapshot_info) - return snapshot_list - - -def print_snapshot_tree(snapshot_info, level=0): - """递归打印快照树(辅助函数)""" - indent = " " * level - print(f"{indent}├─ {snapshot_info['name']}") - print(f"{indent}│ ├─ 创建时间: {snapshot_info['createTime']}") - print(f"{indent}│ ├─ 描述: {snapshot_info['description']}") - print(f"{indent}│ ├─ 状态: {snapshot_info['state']}") - if snapshot_info['sizeMB']: - print(f"{indent}│ ├─ 大小: {snapshot_info['sizeMB']} MB") - - for child in snapshot_info['children']: - print_snapshot_tree(child, level + 1) - - -"""表格样式""" +"""设置表格样式""" def style_sheet(sheet, is_old_data=None): """设置工作表样式:列宽、边框、加粗标题、冻结首行和设置背景颜色""" # 定义边框样式 @@ -156,49 +44,156 @@ def style_sheet(sheet, is_old_data=None): cell.fill = PatternFill(start_color='ADD8E6', end_color='ADD8E6', fill_type='solid') +"""获取虚拟机的总磁盘大小(仅是虚拟磁盘的分配空间)""" +def get_virtual_disk_size(vm): + total_size = 0 + for device in vm.config.hardware.device: + if isinstance(device, vim.vm.device.VirtualDisk): + # 获取每个虚拟磁盘的容量 + total_size += device.capacityInKB / (1024 * 1024) # 转换为GB + return total_size + + +"""获取所有虚拟机""" +def get_all_vms(): + vm_list = [] + + for node in MANAGEMENT_NODES: + # 连接vCenter + si = SmartConnect( + host=node['host'], + user=node['user'], + pwd=node['password'], + disableSslCertValidation=True + ) + + # 获取所有VM + content = si.RetrieveContent() + vm_view = content.viewManager.CreateContainerView( + content.rootFolder, [vim.VirtualMachine], True + ) + + for vm in vm_view.view: + # print(dir(vm)) # 输出所有可用的属性和方法 + # print(vm.runtime) + # print(vm.summary) + # print(vm.snapshot) + # 初始化VM信息字典 + # print(vars(vm.summary)) + # print(vm.summary) + vm_info = { + 'NodeHost': node['host'], + 'name': vm.name, + 'moId': vm._moId, + 'powerState': str(vm.runtime.powerState), + 'system': vm.config.guestFullName, + 'ipAddress': vm.guest.ipAddress, + 'hostName': vm.guest.hostName, + 'vmPath': vm.config.files.vmPathName, + 'Host': vm.runtime.host.name, # 拿到 Host 主机名,如果是从 Esxi 主机直接获取的可能拿不到正确信息 + 'snapshots': [], # 添加快照信息 + 'diskSpaceGB': get_virtual_disk_size(vm) # 添加虚拟机占用的磁盘空间 + } + # print(vm_info) + # 获取快照信息 + if vm.snapshot is not None: + current_snapshot = vm.snapshot.currentSnapshot + root_snapshots = vm.snapshot.rootSnapshotList + + # 处理根快照列表 + for snapshot in root_snapshots: + snapshot_info = get_snapshot_info(snapshot) + vm_info['snapshots'].extend(snapshot_info) + + # 添加当前快照ID(如果有) + if current_snapshot: + vm_info['currentSnapshotId'] = current_snapshot._moId + else: + vm_info['snapshots'] = None + vm_info['currentSnapshotId'] = None + + vm_list.append(vm_info) + # print(vm_info) + vm_view.Destroy() + Disconnect(si) + + + print(f"获取到 {len(vm_list)} 台VM") + return vm_list + + +"""递归获取快照信息""" +def get_snapshot_info(snapshot): + + snapshot_list = [] + # 当前快照信息 + snapshot_info = { + 'name': snapshot.name, + 'description': snapshot.description, + 'createTime': (snapshot.createTime + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), # + timedelta(hours=8) 是时间运算,转换为北京时间 + 'state': str(snapshot.state), + 'id': snapshot.id, + 'moId': snapshot.snapshot._moId, # 快照的Managed Object ID + # 'sizeMB': snapshot.diskSizeMB if hasattr(snapshot, 'diskSizeMB') else None, # 快照大小 + 'quiesced': snapshot.quiesced if hasattr(snapshot, 'quiesced') else None, # 是否静默快照 + 'children': [] # 子快照 + } + + # 递归处理子快照 + if snapshot.childSnapshotList: + for child in snapshot.childSnapshotList: + snapshot_info['children'].extend(get_snapshot_info(child)) + + snapshot_list.append(snapshot_info) + return snapshot_list + + """将虚拟机和快照信息写入Excel文件,并标记创建时间在15天前的快照""" def create_excel_report(vms): vm_data = [] snapshot_data = [] - def add_snapshots_to_report(snapshot, vm_name): - """递归将快照信息加入报告""" + def add_snapshots(snapshot, vm_name): create_time = datetime.strptime(snapshot['createTime'], '%Y-%m-%d %H:%M:%S') is_old = create_time < (datetime.now() - timedelta(days=1)) snapshot_data.append({ - 'VM Name': vm_name, + 'NodeHost': vm['NodeHost'], + 'VMName': vm_name, 'Snapshot Name': snapshot['name'], 'Description': snapshot['description'], - 'Create Time': snapshot['createTime'], + 'CreateTime': snapshot['createTime'], 'State': snapshot['state'], 'ID': snapshot['id'], - 'MO ID': snapshot['moId'], - 'Size (MB)': snapshot['sizeMB'], + 'MOID': snapshot['moId'], + # 'Size(MB)': snapshot['sizeMB'], 'Quiesced': snapshot['quiesced'], 'is_old': is_old }) + """递归将快照信息加入报告""" for child in snapshot['children']: - add_snapshots_to_report(child, vm_name) + add_snapshots(child, vm_name) for vm in vms: + # print(vm['NodeHost']) vm_data.append({ - 'VM Name': vm['name'], - 'MO ID': vm['moId'], - 'Power State': vm['powerState'], + 'NodeHost': vm['NodeHost'], + 'VMName': vm['name'], + 'MOID': vm['moId'], + 'PowerState': vm['powerState'], 'System': vm['system'], - 'IP Address': vm['ipAddress'], - 'Host Name': vm['hostName'], - 'VM Path': vm['vmPath'], + 'IPAddress': vm['ipAddress'], + 'HostName': vm['hostName'], + 'CurrentSnapshotID': vm.get('currentSnapshotId', None), + 'DiskSpace/GB': vm['diskSpaceGB'], 'Host': vm['Host'], - 'Current Snapshot ID': vm.get('currentSnapshotId', None), - 'DiskSpace/GB': vm['diskSpaceGB'] + 'VMPath': vm['vmPath'] }) - if vm['snapshots']: + # print(vm['snapshots']) for snapshot in vm['snapshots']: - add_snapshots_to_report(snapshot, vm['name']) + add_snapshots(snapshot, vm['name']) vm_df = pd.DataFrame(vm_data) snapshot_df = pd.DataFrame(snapshot_data) @@ -222,16 +217,7 @@ def create_excel_report(vms): if __name__ == '__main__': vms = get_all_vms() - - # # 打印示例:显示每个VM的快照信息 - # for vm in vms: - # print(f"\nVM: {vm['name']}") - # if vm['snapshots']: - # print(f"快照数量: {len(vm['snapshots'])}") - # for snapshot in vm['snapshots']: - # print_snapshot_tree(snapshot) - # else: - # print("无快照") + # print(vms) # 生成Excel报告 create_excel_report(vms)