接近完善的代码,待生产环境测试。

This commit is contained in:
panjunlan
2026-02-21 14:27:57 +08:00
parent 17e9e0c3bc
commit 7c95ccb2af

509
README.md
View File

@@ -9,24 +9,13 @@ Vmware 虚拟机自动化程序:自动化导出虚拟机和快照信息,自
- [x] 获取所有 snapshots - [x] 获取所有 snapshots
- [x] 筛选出15天半个月前的 snapshots - [x] 筛选出15天半个月前的 snapshots
- [x] 以上内容以 Excel 表格的形式导出,超出 15 天的快照蓝色底填充标识 - [x] 以上内容以 Excel 表格的形式导出,超出 15 天的快照蓝色底填充标识
- [x] 最后删除 15 天前的 snapshot并同时记录删除的 snapshot 日志信息
- [x] 设置计划任务,每周六(或 每15 天)执行一次
- [ ] 增加排除不能删除的 snapshots 信息,用红色底填充标识 - [ ] 增加排除不能删除的 snapshots 信息,用红色底填充标识
- [ ] Outlook 邮箱发送统计超过 15 天的 snapshots 信息(即要删除的快照列表) - [ ] Outlook 邮箱发送统计超过 15 天的 snapshots 信息(即要删除的快照列表)
- [ ] 需要控制每台 vCenter 不可以同时删除超过 4 个快照(需要同时获取删除成功的信息) - [ ] 需要控制每台 vCenter 不可以同时删除超过 4 个快照
- [x] 最后删除 15 天前的 snapshot并同时记录删除的 snapshot 日志信息 - [ ] 删除前后发送邮件通知
- [ ] 删除成功后发送邮件通知 - [ ] 多线程删除
- [ ] 设置计划任务,每 2 周(半个月)执行一次
✅ 多线程删除
✅ 任务进度显示
✅ 自动重试机制
✅ 企业级日志
✅ 删除前后发送邮件通知
@@ -138,153 +127,153 @@ print(vm.summary)
``` ```
> (vim.vm.Summary) { > (vim.vm.Summary) {
> dynamicType = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [],
> vm = 'vim.VirtualMachine:1',
> runtime = (vim.vm.RuntimeInfo) {
> dynamicType = <unset>, > dynamicType = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [], > dynamicProperty = (vmodl.DynamicProperty) [],
> vm = 'vim.VirtualMachine:1', > device = (vim.vm.DeviceRuntimeInfo) [
> runtime = (vim.vm.RuntimeInfo) { > (vim.vm.DeviceRuntimeInfo) {
> dynamicType = <unset>, > dynamicType = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [], > dynamicProperty = (vmodl.DynamicProperty) [],
> device = (vim.vm.DeviceRuntimeInfo) [ > runtimeState = (vim.vm.DeviceRuntimeInfo.VirtualEthernetCardRuntimeState) {
> (vim.vm.DeviceRuntimeInfo) {
> dynamicType = <unset>, > dynamicType = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [], > dynamicProperty = (vmodl.DynamicProperty) [],
> runtimeState = (vim.vm.DeviceRuntimeInfo.VirtualEthernetCardRuntimeState) { > vmDirectPathGen2Active = false,
> dynamicType = <unset>, > vmDirectPathGen2InactiveReasonVm = (str) [
> dynamicProperty = (vmodl.DynamicProperty) [], > 'vmNptDisabledOrDisconnectedAdapter'
> vmDirectPathGen2Active = false, > ],
> vmDirectPathGen2InactiveReasonVm = (str) [ > vmDirectPathGen2InactiveReasonOther = (str) [],
> 'vmNptDisabledOrDisconnectedAdapter' > vmDirectPathGen2InactiveReasonExtended = <unset>,
> ], > uptv2Active = <unset>,
> vmDirectPathGen2InactiveReasonOther = (str) [], > uptv2InactiveReasonVm = (str) [],
> vmDirectPathGen2InactiveReasonExtended = <unset>, > uptv2InactiveReasonOther = (str) [],
> uptv2Active = <unset>, > reservationStatus = <unset>,
> uptv2InactiveReasonVm = (str) [], > attachmentStatus = 'red',
> uptv2InactiveReasonOther = (str) [], > featureRequirement = (vim.vm.FeatureRequirement) []
> reservationStatus = <unset>, > },
> attachmentStatus = 'red', > key = 4000
> featureRequirement = (vim.vm.FeatureRequirement) [] > }
> }, > ],
> key = 4000 > host = 'vim.HostSystem:ha-host',
> } > connectionState = 'connected',
> ], > powerState = 'poweredOff',
> host = 'vim.HostSystem:ha-host', > vmFailoverInProgress = <unset>,
> connectionState = 'connected', > faultToleranceState = 'notConfigured',
> powerState = 'poweredOff', > dasVmProtection = <unset>,
> vmFailoverInProgress = <unset>, > toolsInstallerMounted = false,
> faultToleranceState = 'notConfigured', > suspendTime = <unset>,
> dasVmProtection = <unset>, > bootTime = <unset>,
> toolsInstallerMounted = false, > suspendInterval = 0,
> suspendTime = <unset>, > question = <unset>,
> bootTime = <unset>, > memoryOverhead = <unset>,
> suspendInterval = 0, > maxCpuUsage = 5184,
> question = <unset>, > maxMemoryUsage = 4096,
> memoryOverhead = <unset>, > numMksConnections = 0,
> maxCpuUsage = 5184, > recordReplayState = 'inactive',
> maxMemoryUsage = 4096, > cleanPowerOff = false,
> numMksConnections = 0, > needSecondaryReason = <unset>,
> recordReplayState = 'inactive', > onlineStandby = false,
> cleanPowerOff = false, > minRequiredEVCModeKey = <unset>,
> needSecondaryReason = <unset>, > consolidationNeeded = false,
> onlineStandby = false, > offlineFeatureRequirement = (vim.vm.FeatureRequirement) [
> minRequiredEVCModeKey = <unset>, > (vim.vm.FeatureRequirement) {
> consolidationNeeded = false, > dynamicType = <unset>,
> offlineFeatureRequirement = (vim.vm.FeatureRequirement) [ > dynamicProperty = (vmodl.DynamicProperty) [],
> (vim.vm.FeatureRequirement) { > key = 'cpuid.lm',
> dynamicType = <unset>, > featureName = 'cpuid.lm',
> dynamicProperty = (vmodl.DynamicProperty) [], > value = 'Bool:Min:1'
> key = 'cpuid.lm', > }
> featureName = 'cpuid.lm', > ],
> value = 'Bool:Min:1' > featureRequirement = (vim.vm.FeatureRequirement) [],
> } > featureMask = (vim.host.FeatureMask) [],
> ], > vFlashCacheAllocation = <unset>,
> featureRequirement = (vim.vm.FeatureRequirement) [], > paused = false,
> featureMask = (vim.host.FeatureMask) [], > snapshotInBackground = false,
> vFlashCacheAllocation = <unset>, > quiescedForkParent = <unset>,
> paused = false, > instantCloneFrozen = false,
> snapshotInBackground = false, > cryptoState = <unset>,
> quiescedForkParent = <unset>, > suspendedToMemory = <unset>,
> instantCloneFrozen = false, > opNotificationTimeout = <unset>,
> cryptoState = <unset>, > iommuActive = <unset>
> suspendedToMemory = <unset>, > },
> opNotificationTimeout = <unset>, > guest = (vim.vm.Summary.GuestSummary) {
> iommuActive = <unset> > dynamicType = <unset>,
> }, > dynamicProperty = (vmodl.DynamicProperty) [],
> guest = (vim.vm.Summary.GuestSummary) { > guestId = <unset>,
> dynamicType = <unset>, > guestFullName = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [], > toolsStatus = 'toolsNotRunning',
> guestId = <unset>, > toolsVersionStatus = 'guestToolsUnmanaged',
> guestFullName = <unset>, > toolsVersionStatus2 = 'guestToolsUnmanaged',
> toolsStatus = 'toolsNotRunning', > toolsRunningStatus = 'guestToolsNotRunning',
> toolsVersionStatus = 'guestToolsUnmanaged', > hostName = <unset>,
> toolsVersionStatus2 = 'guestToolsUnmanaged', > ipAddress = <unset>,
> toolsRunningStatus = 'guestToolsNotRunning', > hwVersion = <unset>
> hostName = <unset>, > },
> ipAddress = <unset>, > config = (vim.vm.Summary.ConfigSummary) {
> hwVersion = <unset> > dynamicType = <unset>,
> }, > dynamicProperty = (vmodl.DynamicProperty) [],
> config = (vim.vm.Summary.ConfigSummary) { > name = 'VMware vCenter Server Appliance',
> dynamicType = <unset>, > template = false,
> dynamicProperty = (vmodl.DynamicProperty) [], > vmPathName = '[datastore1] VMware vCenter Server Appliance/VMware vCenter Server Appliance.vmx',
> name = 'VMware vCenter Server Appliance', > memorySizeMB = 4096,
> template = false, > cpuReservation = 0,
> vmPathName = '[datastore1] VMware vCenter Server Appliance/VMware vCenter Server Appliance.vmx', > memoryReservation = 0,
> memorySizeMB = 4096, > numCpu = 2,
> cpuReservation = 0, > numEthernetCards = 1,
> memoryReservation = 0, > numVirtualDisks = 13,
> numCpu = 2, > uuid = '564d7d77-f37f-64a7-47f2-a131a337c070',
> numEthernetCards = 1, > instanceUuid = '529e8be2-58ca-617c-ad7e-7f66de667471',
> numVirtualDisks = 13, > guestId = 'other3xLinux64Guest',
> uuid = '564d7d77-f37f-64a7-47f2-a131a337c070', > guestFullName = 'Other 3.x Linux (64-bit)',
> instanceUuid = '529e8be2-58ca-617c-ad7e-7f66de667471', > annotation = 'VMware vCenter Server Appliance',
> guestId = 'other3xLinux64Guest', > product = <unset>,
> guestFullName = 'Other 3.x Linux (64-bit)', > installBootRequired = <unset>,
> annotation = 'VMware vCenter Server Appliance', > ftInfo = <unset>,
> product = <unset>, > managedBy = <unset>,
> installBootRequired = <unset>, > tpmPresent = false,
> ftInfo = <unset>, > numVmiopBackings = 0,
> managedBy = <unset>, > hwVersion = <unset>
> tpmPresent = false, > },
> numVmiopBackings = 0, > storage = (vim.vm.Summary.StorageSummary) {
> hwVersion = <unset> > dynamicType = <unset>,
> }, > dynamicProperty = (vmodl.DynamicProperty) [],
> storage = (vim.vm.Summary.StorageSummary) { > committed = 37476296711,
> dynamicType = <unset>, > uncommitted = 303123661815,
> dynamicProperty = (vmodl.DynamicProperty) [], > unshared = 37476296711,
> committed = 37476296711, > timestamp = 2026-02-19T09:11:27.701184Z
> uncommitted = 303123661815, > },
> unshared = 37476296711, > quickStats = (vim.vm.Summary.QuickStats) {
> timestamp = 2026-02-19T09:11:27.701184Z > dynamicType = <unset>,
> }, > dynamicProperty = (vmodl.DynamicProperty) [],
> quickStats = (vim.vm.Summary.QuickStats) { > overallCpuUsage = <unset>,
> dynamicType = <unset>, > overallCpuDemand = <unset>,
> dynamicProperty = (vmodl.DynamicProperty) [], > overallCpuReadiness = <unset>,
> overallCpuUsage = <unset>, > guestMemoryUsage = <unset>,
> overallCpuDemand = <unset>, > hostMemoryUsage = <unset>,
> overallCpuReadiness = <unset>, > guestHeartbeatStatus = 'gray',
> guestMemoryUsage = <unset>, > distributedCpuEntitlement = <unset>,
> hostMemoryUsage = <unset>, > distributedMemoryEntitlement = <unset>,
> guestHeartbeatStatus = 'gray', > staticCpuEntitlement = <unset>,
> distributedCpuEntitlement = <unset>, > staticMemoryEntitlement = <unset>,
> distributedMemoryEntitlement = <unset>, > grantedMemory = <unset>,
> staticCpuEntitlement = <unset>, > privateMemory = <unset>,
> staticMemoryEntitlement = <unset>, > sharedMemory = <unset>,
> grantedMemory = <unset>, > swappedMemory = <unset>,
> privateMemory = <unset>, > balloonedMemory = <unset>,
> sharedMemory = <unset>, > consumedOverheadMemory = <unset>,
> swappedMemory = <unset>, > ftLogBandwidth = <unset>,
> balloonedMemory = <unset>, > ftSecondaryLatency = <unset>,
> consumedOverheadMemory = <unset>, > ftLatencyStatus = <unset>,
> ftLogBandwidth = <unset>, > compressedMemory = <unset>,
> ftSecondaryLatency = <unset>, > uptimeSeconds = <unset>,
> ftLatencyStatus = <unset>, > ssdSwappedMemory = <unset>,
> compressedMemory = <unset>, > activeMemory = <unset>,
> uptimeSeconds = <unset>, > memoryTierStats = (vim.vm.Summary.QuickStats.MemoryTierStats) []
> ssdSwappedMemory = <unset>, > },
> activeMemory = <unset>, > overallStatus = 'green',
> memoryTierStats = (vim.vm.Summary.QuickStats.MemoryTierStats) [] > customValue = (vim.CustomFieldsManager.Value) []
> },
> overallStatus = 'green',
> customValue = (vim.CustomFieldsManager.Value) []
> } > }
@@ -297,126 +286,95 @@ print(vm.summary)
print(vm.snapshot) print(vm.snapshot)
``` ```
``` json >(vim.vm.SnapshotInfo) {
(vim.vm.SnapshotInfo) { >dynamicType = <unset>,
dynamicType = <unset>, >dynamicProperty = (vmodl.DynamicProperty) [],
dynamicProperty = (vmodl.DynamicProperty) [], >currentSnapshot = 'vim.vm.Snapshot:1-snapshot-3',
currentSnapshot = 'vim.vm.Snapshot:1-snapshot-3', >rootSnapshotList = (vim.vm.SnapshotTree) [
rootSnapshotList = (vim.vm.SnapshotTree) [ >(vim.vm.SnapshotTree) {
(vim.vm.SnapshotTree) { > dynamicType = <unset>,
dynamicType = <unset>, > dynamicProperty = (vmodl.DynamicProperty) [],
dynamicProperty = (vmodl.DynamicProperty) [], > snapshot = 'vim.vm.Snapshot:1-snapshot-1',
snapshot = 'vim.vm.Snapshot:1-snapshot-1', > vm = 'vim.VirtualMachine:1',
vm = 'vim.VirtualMachine:1', > name = 'snap-01', # 第一层快照
name = 'snap-01', # 第一层快照 > description = 'Ansible snapshot',
description = 'Ansible snapshot', > id = 1,
id = 1, > createTime = 2026-02-17T03:26:08.834505Z,
createTime = 2026-02-17T03:26:08.834505Z, > state = 'poweredOff',
state = 'poweredOff', > quiesced = false,
quiesced = false, > backupManifest = <unset>,
backupManifest = <unset>, > childSnapshotList = (vim.vm.SnapshotTree) [
childSnapshotList = (vim.vm.SnapshotTree) [ > (vim.vm.SnapshotTree) {
(vim.vm.SnapshotTree) { > dynamicType = <unset>,
dynamicType = <unset>, > dynamicProperty = (vmodl.DynamicProperty) [],
dynamicProperty = (vmodl.DynamicProperty) [], > snapshot = 'vim.vm.Snapshot:1-snapshot-2',
snapshot = 'vim.vm.Snapshot:1-snapshot-2', > vm = 'vim.VirtualMachine:1',
vm = 'vim.VirtualMachine:1', > name = 'snap-02', # 第二层快照
name = 'snap-02', # 第二层快照 > description = 'test-Ansible snapshot',
description = 'test-Ansible snapshot', > id = 2,
id = 2, > createTime = 2026-02-17T08:57:39.122511Z,
createTime = 2026-02-17T08:57:39.122511Z, > state = 'poweredOff',
state = 'poweredOff', > quiesced = false,
quiesced = false, > backupManifest = <unset>,
backupManifest = <unset>, > childSnapshotList = (vim.vm.SnapshotTree) [
childSnapshotList = (vim.vm.SnapshotTree) [ > (vim.vm.SnapshotTree) {
(vim.vm.SnapshotTree) { > dynamicType = <unset>,
dynamicType = <unset>, > dynamicProperty = (vmodl.DynamicProperty) [],
dynamicProperty = (vmodl.DynamicProperty) [], > snapshot = 'vim.vm.Snapshot:1-snapshot-3',
snapshot = 'vim.vm.Snapshot:1-snapshot-3', > vm = 'vim.VirtualMachine:1',
vm = 'vim.VirtualMachine:1', > name = '虚拟机快照 2026%252f2%252f19 12:28:36', # 第二层快照
name = '虚拟机快照 2026%252f2%252f19 12:28:36', # 第二层快照 > description = '',
description = '', > id = 3,
id = 3, > createTime = 2026-02-19T04:28:38.007703Z,
createTime = 2026-02-19T04:28:38.007703Z, > state = 'poweredOn',
state = 'poweredOn', > quiesced = false,
quiesced = false, > backupManifest = <unset>,
backupManifest = <unset>, > childSnapshotList = (vim.vm.SnapshotTree) [],
childSnapshotList = (vim.vm.SnapshotTree) [], > replaySupported = false
replaySupported = false > }
} > ],
], > replaySupported = false
replaySupported = false > }
} > ],
], > replaySupported = false
replaySupported = false >}
} >]
] >}
}
```
### 打印快照树 ## PY 文件作用
``` 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 文件作用描述
``` powershell ``` powershell
PS D:\PycharmProjects\RemoveWeeklyShapshot> tree /F PS D:\PycharmProjects\RemoveWeeklySnapshot> tree /F
卷 Date 的文件夹 PATH 列表 卷 Date 的文件夹 PATH 列表
卷序列号为 0E45-0F72 卷序列号为 0E45-0F72
D:. D:.
README.md # 项目描述 main.py
README.md
├─config # 项目程序配置文件 │ requirements.txt
config.yaml # 配置文件
│ │ settings.py # 配置加载和全局变量 ├─config
│ config.yaml
├─core # 核心程序 │ │ settings.py
│ deleteSnapshots.py
│ │ getVmsSnapshots.py ├─core
│ data_exporter.py
├─logs # 日志文件 │ │ get_vm_snapshots.py
20260220-RemoveWeeklyShapshot.log │ remove_snapshots.py
│ vm_connector.py
├─output # 数据输出文件
│ 2026-02-20-old_snapshots.yaml ├─logs
│ 2026-02-20_20-36-45-VMsSnapShots_report.xlsx │ 2026-02-21-vm_snapshot_cleanup.log
├─utils # 工具函数 ├─output
│ logger.py # 日志配置 old_snapshots-2026-02-21.yaml
│ vm_snapshots_report-2026-02-21.xlsx
├─utils
│ │ logger.py
``` ```
@@ -425,11 +383,11 @@ D:.
``` powershell ``` powershell
PS D:\PycharmProjects\RemoveWeeklyShapshot> pip freeze > requirements.txt PS D:\PycharmProjects\RemoveWeeklyShapshot> pip freeze > requirements.txt
APScheduler==3.11.2
openpyxl==3.2.0b1 openpyxl==3.2.0b1
pandas==3.0.1 pandas==3.0.1
pyvmomi==9.0.0.0 pyvmomi==9.0.0.0
PyYAML==6.0.3 PyYAML==6.0.3
``` ```
``` shell ``` shell
@@ -438,12 +396,11 @@ pip install -r requirements.txt
## **定时执行**Linux crontab ## 构建 Docker 镜像
```bash
# 每月1号凌晨2点执行
0 2 1 * * /usr/bin/python3 /var/RemoveWeeklyShapshot/main.py 2>&1
```