diff --git a/README.md b/README.md index 4c09dca..22e1adf 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,13 @@ Vmware 虚拟机自动化程序:自动化导出虚拟机和快照信息,自 - [x] 获取所有 snapshots - [x] 筛选出15天(半个月)前的 snapshots - [x] 以上内容以 Excel 表格的形式导出,超出 15 天的快照蓝色底填充标识 +- [x] 最后删除 15 天前的 snapshot,并同时记录删除的 snapshot 日志信息 +- [x] 设置计划任务,每周六(或 每15 天)执行一次 - [ ] 增加排除不能删除的 snapshots 信息,用红色底填充标识 - [ ] Outlook 邮箱发送统计超过 15 天的 snapshots 信息(即要删除的快照列表) -- [ ] 需要控制每台 vCenter 不可以同时删除超过 4 个快照(需要同时获取删除成功的信息) -- [x] 最后删除 15 天前的 snapshot,并同时记录删除的 snapshot 日志信息 -- [ ] 删除成功后发送邮件通知 -- [ ] 设置计划任务,每 2 周(半个月)执行一次 - - - -✅ 多线程删除 - -✅ 任务进度显示 - -✅ 自动重试机制 - -✅ 企业级日志 - -✅ 删除前后发送邮件通知 +- [ ] 需要控制每台 vCenter 不可以同时删除超过 4 个快照 +- [ ] 删除前后发送邮件通知 +- [ ] 多线程删除 @@ -138,153 +127,153 @@ print(vm.summary) ``` > (vim.vm.Summary) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> vm = 'vim.VirtualMachine:1', +> runtime = (vim.vm.RuntimeInfo) { > dynamicType = , > dynamicProperty = (vmodl.DynamicProperty) [], -> vm = 'vim.VirtualMachine:1', -> runtime = (vim.vm.RuntimeInfo) { -> dynamicType = , -> dynamicProperty = (vmodl.DynamicProperty) [], -> device = (vim.vm.DeviceRuntimeInfo) [ -> (vim.vm.DeviceRuntimeInfo) { +> device = (vim.vm.DeviceRuntimeInfo) [ +> (vim.vm.DeviceRuntimeInfo) { +> dynamicType = , +> dynamicProperty = (vmodl.DynamicProperty) [], +> runtimeState = (vim.vm.DeviceRuntimeInfo.VirtualEthernetCardRuntimeState) { > 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) [] +> 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) [] > } @@ -297,126 +286,95 @@ print(vm.summary) 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 - } - ] -} -``` +>(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("无快照") -``` - - - -## PY 文件作用描述 +## PY 文件作用 ``` powershell -PS D:\PycharmProjects\RemoveWeeklyShapshot> tree /F +PS D:\PycharmProjects\RemoveWeeklySnapshot> tree /F 卷 Date 的文件夹 PATH 列表 卷序列号为 0E45-0F72 D:. -│ README.md # 项目描述 -│ -├─config # 项目程序配置文件 -│ │ config.yaml # 配置文件 -│ │ settings.py # 配置加载和全局变量 -│ -├─core # 核心程序 -│ │ deleteSnapshots.py -│ │ getVmsSnapshots.py -│ -├─logs # 日志文件 -│ 20260220-RemoveWeeklyShapshot.log -│ -├─output # 数据输出文件 -│ 2026-02-20-old_snapshots.yaml -│ 2026-02-20_20-36-45-VMsSnapShots_report.xlsx -│ -├─utils # 工具函数 -│ │ logger.py # 日志配置 - +│ main.py +│ README.md +│ requirements.txt +│ +├─config +│ │ config.yaml +│ │ settings.py +│ +├─core +│ │ data_exporter.py +│ │ get_vm_snapshots.py +│ │ remove_snapshots.py +│ │ vm_connector.py +│ +├─logs +│ 2026-02-21-vm_snapshot_cleanup.log +│ +├─output +│ old_snapshots-2026-02-21.yaml +│ vm_snapshots_report-2026-02-21.xlsx +│ +├─utils +│ │ logger.py ``` @@ -425,11 +383,11 @@ D:. ``` powershell PS D:\PycharmProjects\RemoveWeeklyShapshot> pip freeze > requirements.txt +APScheduler==3.11.2 openpyxl==3.2.0b1 pandas==3.0.1 pyvmomi==9.0.0.0 PyYAML==6.0.3 - ``` ``` shell @@ -438,12 +396,11 @@ pip install -r requirements.txt -## **定时执行**(Linux crontab): - -```bash -# 每月1号凌晨2点执行 -0 2 1 * * /usr/bin/python3 /var/RemoveWeeklyShapshot/main.py 2>&1 -``` +## 构建 Docker 镜像 + + + +