接近完善的代码,待生产环境测试。
This commit is contained in:
14
.env
14
.env
@@ -1,14 +0,0 @@
|
|||||||
# 多个vCenter地址(逗号分隔)
|
|
||||||
VCENTER_HOSTS=192.168.40.134,vc2.example.com,vc3.example.com
|
|
||||||
# 所有vCenter共用的账号密码
|
|
||||||
VCENTER_USER=administrator@lan.com
|
|
||||||
VCENTER_PASSWORD=Root@2025
|
|
||||||
SNAPSHOT_RETENTION_DAYS=15
|
|
||||||
# 每台vCenter同时删除的快照数量限制
|
|
||||||
MAX_DELETE_CONCURRENT=4
|
|
||||||
# Excel导出路径
|
|
||||||
# EXCEL_OUTPUT_PATH=/tmp/vm_snapshots_report.xlsx
|
|
||||||
EXCEL_OUTPUT_PATH=./vm_snapshots_report.xlsx
|
|
||||||
# 日志文件路径
|
|
||||||
# LOG_FILE_PATH=/var/logs/vm_snapshot_cleanup.logs
|
|
||||||
LOG_FILE_PATH=./log/vm_snapshot_cleanup.log
|
|
||||||
26
README.md
26
README.md
@@ -400,8 +400,8 @@ D:.
|
|||||||
│ README.md # 项目描述
|
│ README.md # 项目描述
|
||||||
│
|
│
|
||||||
├─config # 项目程序配置文件
|
├─config # 项目程序配置文件
|
||||||
│ │ config.yaml
|
│ │ config.yaml # 配置文件
|
||||||
│ │ settings.py
|
│ │ settings.py # 配置加载和全局变量
|
||||||
│
|
│
|
||||||
├─core # 核心程序
|
├─core # 核心程序
|
||||||
│ │ deleteSnapshots.py
|
│ │ deleteSnapshots.py
|
||||||
@@ -414,8 +414,8 @@ D:.
|
|||||||
│ 2026-02-20-old_snapshots.yaml
|
│ 2026-02-20-old_snapshots.yaml
|
||||||
│ 2026-02-20_20-36-45-VMsSnapShots_report.xlsx
|
│ 2026-02-20_20-36-45-VMsSnapShots_report.xlsx
|
||||||
│
|
│
|
||||||
├─utils # 日志输出格式设置
|
├─utils # 工具函数
|
||||||
│ │ logger.py
|
│ │ logger.py # 日志配置
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -423,11 +423,27 @@ D:.
|
|||||||
|
|
||||||
## 所用到的 Python 库
|
## 所用到的 Python 库
|
||||||
|
|
||||||
|
``` powershell
|
||||||
|
PS D:\PycharmProjects\RemoveWeeklyShapshot> pip freeze > requirements.txt
|
||||||
|
openpyxl==3.2.0b1
|
||||||
|
pandas==3.0.1
|
||||||
|
pyvmomi==9.0.0.0
|
||||||
|
PyYAML==6.0.3
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
pip install pyVmomi ...
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## **定时执行**(Linux crontab):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 每月1号凌晨2点执行
|
||||||
|
0 2 1 * * /usr/bin/python3 /var/RemoveWeeklyShapshot/main.py 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
# 管理节点配置(包含vCenter和ESXi)
|
# 管理节点配置(包含vCenter和ESXi)
|
||||||
management_nodes:
|
management_nodes:
|
||||||
# vCenter节点
|
# vCenter节点
|
||||||
- type: vcenter # 标记类型为vcenter
|
# - type: vcenter # 标记类型为vcenter
|
||||||
name: vc1 # 节点名称(用于日志)
|
# name: vc1 # 节点名称(用于日志)
|
||||||
host: 192.168.40.134 # 地址
|
# host: 192.168.40.134 # 地址
|
||||||
user: administrator@lan.com
|
# user: administrator@lan.com
|
||||||
password: Root@2025
|
# password: Root@2025
|
||||||
max_delete_concurrent: 4 # 该节点最大并发删除数
|
# max_delete_concurrent: 4 # 该节点最大并发删除数
|
||||||
|
|
||||||
# ESXi节点(未接入 vCenter 的 Esxi 主机)
|
# ESXi节点(未接入 vCenter 的 Esxi 主机)
|
||||||
# - type: esxi # 标记类型为esxi
|
- type: esxi # 标记类型为esxi
|
||||||
# name: esxi1
|
name: esxi1
|
||||||
# host: 192.168.40.133
|
host: 192.168.40.133
|
||||||
# user: root # ESXi默认用root
|
user: root # ESXi默认用root
|
||||||
# password: Root@2025
|
password: Root@2025
|
||||||
# max_delete_concurrent: 2 # ESXi性能较弱,并发数可设小些
|
max_delete_concurrent: 2 # ESXi性能较弱,并发数可设小些
|
||||||
|
|
||||||
# - type: esxi
|
- type: esxi
|
||||||
# name: esxi2
|
name: esxi2
|
||||||
# host: 192.168.40.135
|
host: 192.168.40.135
|
||||||
# user: root
|
user: root
|
||||||
# password: Root@2025
|
password: Root@2025
|
||||||
# max_delete_concurrent: 2
|
max_delete_concurrent: 2
|
||||||
|
|
||||||
# 全局策略配置
|
# 全局策略配置
|
||||||
global:
|
global:
|
||||||
snapshot_retention_days: 0 # 可选,使用默认值 15 天
|
disable_ssl_verify: true
|
||||||
|
snapshot_retention_days: 15 # 可选,默认值 15 天
|
||||||
# excel_output_path: ./vm_snapshots_report.xlsx # 可选,使用默认值,如:/logs/2026-02-20_14-00-21-VMsSnapShots_report.xlsx
|
# excel_output_path: ./vm_snapshots_report.xlsx # 可选,使用默认值,如:/logs/2026-02-20_14-00-21-VMsSnapShots_report.xlsx
|
||||||
# 'excel_output_path',: ./vm_snapshots_report.xlsx # 可选,使用默认值,如:/logs/2026-02-20_14-00-21-VMsSnapShots_report.xlsx
|
# 'excel_output_path',: ./vm_snapshots_report.xlsx # 可选,使用默认值,如:/logs/2026-02-20_14-00-21-VMsSnapShots_report.xlsx
|
||||||
# ESXi连接特殊配置(禁用SSL验证,ESXi默认自签证书)
|
# ESXi连接特殊配置(禁用SSL验证,ESXi默认自签证书)
|
||||||
disable_ssl_verify: true
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import yaml
|
import yaml, os
|
||||||
import os
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
"""加载 YAML 配置文件并解析其内容"""
|
"""加载 YAML 配置文件并解析其内容"""
|
||||||
try:
|
try:
|
||||||
@@ -17,10 +17,9 @@ def load_config():
|
|||||||
"SNAPSHOT_RETENTION_DAYS": int(global_config.get('snapshot_retention_days', 15)),
|
"SNAPSHOT_RETENTION_DAYS": int(global_config.get('snapshot_retention_days', 15)),
|
||||||
# "EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'/logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}-VMsSnapShots_report.xlsx'),
|
# "EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'/logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}-VMsSnapShots_report.xlsx'),
|
||||||
# "LOG_FILE_PATH": global_config.get('log_file_path', f'/logs/{datetime.now().strftime('%Y%m%d_%H%M%S')}-VMsSnapShots_cleanup.logs'), "EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'/logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}-VMsSnapShots_report.xlsx'),
|
# "LOG_FILE_PATH": global_config.get('log_file_path', f'/logs/{datetime.now().strftime('%Y%m%d_%H%M%S')}-VMsSnapShots_cleanup.logs'), "EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'/logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}-VMsSnapShots_report.xlsx'),
|
||||||
"EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'D:\\PycharmProjects\\RemoveWeeklyShapshot\\output\\{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}-VMsSnapShots_report.xlsx'),
|
"EXCEL_OUTPUT_PATH": global_config.get('excel_output_path', f'D:\\PycharmProjects\\RemoveWeeklySnapshot\\output\\vm_snapshots_report-{datetime.now().strftime('%Y-%m-%d')}.xlsx'),
|
||||||
"YAML_OUTPUT_PATH": global_config.get('yaml_output_path', f'D:\\PycharmProjects\\RemoveWeeklyShapshot\\output\\{datetime.now().strftime('%Y-%m-%d')}-old_snapshots.yaml'),
|
"YAML_OUTPUT_PATH": global_config.get('yaml_output_path', f'D:\\PycharmProjects\\RemoveWeeklySnapshot\\output\\old_snapshots-{datetime.now().strftime('%Y-%m-%d')}.yaml'),
|
||||||
"DISABLE_SSL_VERIFY": global_config.get('disable_ssl_verify', True),
|
"DISABLE_SSL_VERIFY": global_config.get('disable_ssl_verify', True),
|
||||||
# 算出的过期时间点
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 验证配置
|
# 验证配置
|
||||||
@@ -54,10 +53,6 @@ YAML_OUTPUT_PATH = config["YAML_OUTPUT_PATH"]
|
|||||||
#LOG_FILE_PATH = config["LOG_FILE_PATH"]
|
#LOG_FILE_PATH = config["LOG_FILE_PATH"]
|
||||||
DISABLE_SSL_VERIFY = config["DISABLE_SSL_VERIFY"]
|
DISABLE_SSL_VERIFY = config["DISABLE_SSL_VERIFY"]
|
||||||
|
|
||||||
# 验证配置函数
|
|
||||||
def validate_config():
|
|
||||||
pass # 加载时已验证,此处留空
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 打印全局配置
|
# 打印全局配置
|
||||||
|
|||||||
@@ -1,101 +1,80 @@
|
|||||||
import pandas as pd
|
|
||||||
import yaml
|
import yaml
|
||||||
from pyVmomi import vim
|
import pandas as pd
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pyVim.connect import SmartConnect, Disconnect
|
|
||||||
from openpyxl.styles import Border, Side, Font, PatternFill
|
from openpyxl.styles import Border, Side, Font, PatternFill
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
from config.settings import MANAGEMENT_NODES, SNAPSHOT_RETENTION_DAYS, EXCEL_OUTPUT_PATH, YAML_OUTPUT_PATH
|
from config.settings import EXCEL_OUTPUT_PATH, YAML_OUTPUT_PATH, SNAPSHOT_RETENTION_DAYS
|
||||||
|
from core.get_vm_snapshots import get_all_vms
|
||||||
|
|
||||||
|
# 输出数据到 Excel 文件
|
||||||
|
def create_excel_report(vms):
|
||||||
|
vm_data, snapshot_data, old_snapshots = [], [], []
|
||||||
|
for vm in vms:
|
||||||
|
vm_data.append({
|
||||||
|
'NodeHost': vm['NodeHost'],
|
||||||
|
'VMName': vm['name'],
|
||||||
|
'MOID': vm['moId'],
|
||||||
|
'PowerState': vm['powerState'],
|
||||||
|
'System': vm['system'],
|
||||||
|
'IPAddress': vm['ipAddress'],
|
||||||
|
'HostName': vm['hostName'],
|
||||||
|
'CurrentSnapshotID': vm.get('currentSnapshotId'),
|
||||||
|
'DiskSpace/GB': vm['diskSpaceGB'],
|
||||||
|
'createDate': vm['createDate'], # 虚拟机的创建时间
|
||||||
|
'bootTime': vm['bootTime'], # 虚拟机上次启动的时间
|
||||||
|
'Host': vm['Host'],
|
||||||
|
'VMPath': vm['vmPath']
|
||||||
|
})
|
||||||
|
|
||||||
|
if vm['snapshots']:
|
||||||
|
for snapshot in vm['snapshots']:
|
||||||
|
collect_snapshot_data(snapshot, vm, snapshot_data, old_snapshots)
|
||||||
|
|
||||||
|
vm_df = pd.DataFrame(vm_data)
|
||||||
|
snapshot_df = pd.DataFrame(snapshot_data)
|
||||||
|
logger.info(f"总共有 {len(snapshot_data)} 个快照")
|
||||||
|
with pd.ExcelWriter(EXCEL_OUTPUT_PATH, engine='openpyxl') as writer:
|
||||||
|
vm_df.to_excel(writer, sheet_name='VMs', index=False)
|
||||||
|
snapshot_df.to_excel(writer, sheet_name='Snapshots', index=False)
|
||||||
|
|
||||||
|
workbook = writer.book
|
||||||
|
style_sheet(workbook['VMs'])
|
||||||
|
# style_sheet(workbook['Snapshots'], snapshot_df['is_old'].tolist())
|
||||||
|
# 判断 DataFrame 是否有数据,没有数据则跳过,有数据则写入 excel 文件
|
||||||
|
if 'is_old' in snapshot_df.columns and snapshot_df['is_old'].tolist():
|
||||||
|
style_sheet(workbook['Snapshots'], snapshot_df['is_old'].tolist())
|
||||||
|
|
||||||
|
logger.debug(f"Excel 文件已生成: {EXCEL_OUTPUT_PATH}")
|
||||||
|
# 返回旧快照的数据,用于生成 Yaml 数据文件
|
||||||
|
return old_snapshots
|
||||||
|
|
||||||
|
|
||||||
def get_all_vms():
|
def collect_snapshot_data(snapshot, vm, snapshot_data, old_snapshots):
|
||||||
""" 主函数,负责流程控制"""
|
""" 递归快照数据用于 Excel,并收集旧快照 """
|
||||||
vm_list = []
|
create_time = datetime.strptime(snapshot['createTime'], '%Y-%m-%d %H:%M:%S')
|
||||||
|
is_old = create_time < (datetime.now() - timedelta(days=SNAPSHOT_RETENTION_DAYS))
|
||||||
|
|
||||||
for node in MANAGEMENT_NODES:
|
|
||||||
try:
|
|
||||||
si = SmartConnect(
|
|
||||||
host=node['host'],
|
|
||||||
user=node['user'],
|
|
||||||
pwd=node['password'],
|
|
||||||
disableSslCertValidation=True
|
|
||||||
)
|
|
||||||
|
|
||||||
content = si.RetrieveContent()
|
|
||||||
vm_view = content.viewManager.CreateContainerView(
|
|
||||||
content.rootFolder, [vim.VirtualMachine], True
|
|
||||||
)
|
|
||||||
|
|
||||||
for vm in vm_view.view:
|
|
||||||
vm_info = build_vm_info(vm, node['host'])
|
|
||||||
vm_list.append(vm_info)
|
|
||||||
|
|
||||||
except vim.fault.InvalidLogin as e:
|
|
||||||
logger.info(f"登录 {node['host']} 失败,请检查用户名和密码:{e.msg}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"无法连接到 {node['host']}:{e}")
|
|
||||||
finally:
|
|
||||||
if 'si' in locals(): # 确保 si 是定义过的
|
|
||||||
Disconnect(si) # 确保连接被断开
|
|
||||||
|
|
||||||
print(f"获取到 {len(vm_list)} 台VM")
|
|
||||||
return vm_list
|
|
||||||
|
|
||||||
|
|
||||||
def build_vm_info(vm, node_host):
|
|
||||||
"""构造虚拟机信息字典"""
|
|
||||||
vm_info = {
|
|
||||||
'NodeHost': node_host,
|
|
||||||
'name': vm.name,
|
|
||||||
'moId': vm._moId,
|
|
||||||
'powerState': vm.runtime.powerState,
|
|
||||||
'system': vm.config.guestFullName,
|
|
||||||
'ipAddress': vm.guest.ipAddress,
|
|
||||||
'hostName': vm.guest.hostName,
|
|
||||||
'diskSpaceGB': get_virtual_disk_size(vm),
|
|
||||||
'createDate': (vm.config.createDate + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if vm.runtime.bootTime else None, # 虚拟机的创建时间
|
|
||||||
'bootTime': (vm.runtime.bootTime + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if vm.runtime.bootTime else None, # 虚拟机上次启动的时间 'createDate': (vm.config.createDate + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), # 虚拟机的创建时间
|
|
||||||
# 'createDate': vm.config.createDate, # 虚拟机的创建时间
|
|
||||||
# 'bootTime': vm.runtime.bootTime, # 虚拟机上次启动的时间
|
|
||||||
'snapshots': [],
|
|
||||||
'Host': vm.runtime.host.name,
|
|
||||||
'vmPath': vm.config.files.vmPathName
|
|
||||||
}
|
|
||||||
# 判断虚拟机是否有快照,如果有就构造快照结构并记录当前快照ID;如果没有就设为 None。
|
|
||||||
if vm.snapshot:
|
|
||||||
current_snapshot = vm.snapshot.currentSnapshot
|
|
||||||
root_snapshots = vm.snapshot.rootSnapshotList
|
|
||||||
|
|
||||||
for snapshot in root_snapshots:
|
|
||||||
vm_info['snapshots'].extend(build_snapshot_dict(snapshot))
|
|
||||||
|
|
||||||
vm_info['currentSnapshotId'] = (
|
|
||||||
current_snapshot._moId if current_snapshot else None
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
vm_info['snapshots'] = None
|
|
||||||
vm_info['currentSnapshotId'] = None
|
|
||||||
|
|
||||||
return vm_info
|
|
||||||
|
|
||||||
def build_snapshot_dict(snapshot):
|
|
||||||
"""递归构造快照结构"""
|
|
||||||
snapshot_info = {
|
snapshot_info = {
|
||||||
'name': snapshot.name,
|
'NodeHost': vm['NodeHost'],
|
||||||
'description': snapshot.description,
|
'VMName': vm['name'],
|
||||||
'createTime': (snapshot.createTime + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'),
|
'Snapshot Name': snapshot['name'],
|
||||||
'state': str(snapshot.state),
|
'Description': snapshot['description'],
|
||||||
'id': snapshot.id,
|
'CreateTime': snapshot['createTime'],
|
||||||
'moId': snapshot.snapshot._moId,
|
'State': snapshot['state'],
|
||||||
'quiesced': getattr(snapshot, 'quiesced', None),
|
'ID': snapshot['id'],
|
||||||
'children': []
|
'MOID': snapshot['moId'],
|
||||||
|
'Quiesced': snapshot['quiesced'],
|
||||||
|
'is_old': is_old
|
||||||
}
|
}
|
||||||
|
|
||||||
if snapshot.childSnapshotList:
|
# 如果是旧快照,添加到旧快照列表
|
||||||
for child in snapshot.childSnapshotList:
|
if is_old:
|
||||||
snapshot_info['children'].extend(build_snapshot_dict(child))
|
old_snapshots.append(snapshot_info)
|
||||||
|
|
||||||
return [snapshot_info]
|
snapshot_data.append(snapshot_info)
|
||||||
|
|
||||||
|
for child in snapshot['children']:
|
||||||
|
collect_snapshot_data(child, vm, snapshot_data, old_snapshots)
|
||||||
|
|
||||||
|
|
||||||
"""设置表格样式"""
|
"""设置表格样式"""
|
||||||
@@ -135,104 +114,17 @@ def style_sheet(sheet, is_old_data=None):
|
|||||||
cell.fill = PatternFill(start_color='ADD8E6', end_color='ADD8E6', fill_type='solid')
|
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 collect_snapshot_data(snapshot, vm, snapshot_data, old_snapshots):
|
|
||||||
"""递归扁平化快照数据用于Excel,并收集旧快照"""
|
|
||||||
create_time = datetime.strptime(snapshot['createTime'], '%Y-%m-%d %H:%M:%S')
|
|
||||||
is_old = create_time < (datetime.now() - timedelta(days=SNAPSHOT_RETENTION_DAYS))
|
|
||||||
|
|
||||||
snapshot_info = {
|
|
||||||
'NodeHost': vm['NodeHost'],
|
|
||||||
'VMName': vm['name'],
|
|
||||||
'Snapshot Name': snapshot['name'],
|
|
||||||
'Description': snapshot['description'],
|
|
||||||
'CreateTime': snapshot['createTime'],
|
|
||||||
'State': snapshot['state'],
|
|
||||||
'ID': snapshot['id'],
|
|
||||||
'MOID': snapshot['moId'],
|
|
||||||
'Quiesced': snapshot['quiesced'],
|
|
||||||
'is_old': is_old
|
|
||||||
}
|
|
||||||
|
|
||||||
# 如果是旧快照,添加到旧快照列表
|
|
||||||
if is_old:
|
|
||||||
old_snapshots.append(snapshot_info)
|
|
||||||
|
|
||||||
snapshot_data.append(snapshot_info)
|
|
||||||
|
|
||||||
for child in snapshot['children']:
|
|
||||||
collect_snapshot_data(child, vm, snapshot_data, old_snapshots)
|
|
||||||
|
|
||||||
|
|
||||||
# 输出数据到 Excel 文件
|
|
||||||
def create_excel_report(vms):
|
|
||||||
vm_data = []
|
|
||||||
snapshot_data = []
|
|
||||||
old_snapshots = [] # 用于存储旧快照的信息
|
|
||||||
|
|
||||||
for vm in vms:
|
|
||||||
vm_data.append({
|
|
||||||
'NodeHost': vm['NodeHost'],
|
|
||||||
'VMName': vm['name'],
|
|
||||||
'MOID': vm['moId'],
|
|
||||||
'PowerState': vm['powerState'],
|
|
||||||
'System': vm['system'],
|
|
||||||
'IPAddress': vm['ipAddress'],
|
|
||||||
'HostName': vm['hostName'],
|
|
||||||
'CurrentSnapshotID': vm.get('currentSnapshotId'),
|
|
||||||
'DiskSpace/GB': vm['diskSpaceGB'],
|
|
||||||
'createDate': vm['createDate'], # 虚拟机的创建时间
|
|
||||||
'bootTime': vm['bootTime'], # 虚拟机上次启动的时间
|
|
||||||
'Host': vm['Host'],
|
|
||||||
'VMPath': vm['vmPath']
|
|
||||||
})
|
|
||||||
|
|
||||||
if vm['snapshots']:
|
|
||||||
for snapshot in vm['snapshots']:
|
|
||||||
collect_snapshot_data(snapshot, vm, snapshot_data, old_snapshots)
|
|
||||||
|
|
||||||
vm_df = pd.DataFrame(vm_data)
|
|
||||||
snapshot_df = pd.DataFrame(snapshot_data)
|
|
||||||
|
|
||||||
with pd.ExcelWriter(EXCEL_OUTPUT_PATH, engine='openpyxl') as writer:
|
|
||||||
vm_df.to_excel(writer, sheet_name='VMs', index=False)
|
|
||||||
snapshot_df.to_excel(writer, sheet_name='Snapshots', index=False)
|
|
||||||
|
|
||||||
workbook = writer.book
|
|
||||||
style_sheet(workbook['VMs'])
|
|
||||||
# style_sheet(workbook['Snapshots'], snapshot_df['is_old'].tolist())
|
|
||||||
# 判断 DataFrame 是否有数据,没有数据则跳过,有数据则写入 excel 文件
|
|
||||||
if 'is_old' in snapshot_df.columns and snapshot_df['is_old'].tolist():
|
|
||||||
style_sheet(workbook['Snapshots'], snapshot_df['is_old'].tolist())
|
|
||||||
|
|
||||||
logger.debug(f"Excel 文件已生成: {EXCEL_OUTPUT_PATH}")
|
|
||||||
# 返回旧快照的数据
|
|
||||||
return old_snapshots
|
|
||||||
|
|
||||||
|
|
||||||
# 输出待删除的旧快照到 YAML 文件
|
# 输出待删除的旧快照到 YAML 文件
|
||||||
def export_yaml(old_snapshots):
|
def export_yaml(old_snapshots):
|
||||||
print(old_snapshots)
|
logger.info(f"可删除的快照有 {len(old_snapshots)} 个")
|
||||||
|
|
||||||
# 将旧快照信息存储到 YAML 文件
|
# 将旧快照信息存储到 YAML 文件
|
||||||
with open(YAML_OUTPUT_PATH, 'w', encoding='utf-8') as yaml_file:
|
with open(YAML_OUTPUT_PATH, 'w', encoding='utf-8') as yaml_file:
|
||||||
# allow_unicode:输出 Unicode 字符(中文等),allow_unicode:使用块样式(多行缩进),sort_keys:不按键名排序,保留原始插入顺序
|
# allow_unicode:输出 Unicode 字符(中文等),allow_unicode:使用块样式(多行缩进),sort_keys:不按键名排序,保留原始插入顺序
|
||||||
yaml.dump(old_snapshots, yaml_file, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
yaml.dump(old_snapshots, yaml_file, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
||||||
logger.debug(f"YAML 文件已生成: {YAML_OUTPUT_PATH}")
|
logger.debug(f"YAML 文件已生成: {YAML_OUTPUT_PATH}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
vms = get_all_vms() # 主函数入口,获取虚拟机信息
|
vms = get_all_vms() # 导出 excel 和 yaml 文件,需要先获取虚拟机信息
|
||||||
# print(vms)
|
old_snapshots = create_excel_report(vms) # 生成 Excel 报告并获取旧快照
|
||||||
old_snapshots = create_excel_report(vms) # 生成Excel报告并获取旧快照
|
|
||||||
export_yaml(old_snapshots)
|
export_yaml(old_snapshots)
|
||||||
# print(old_snapshots)
|
# print(old_snapshots)
|
||||||
100
core/get_vm_snapshots.py
Normal file
100
core/get_vm_snapshots.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
from pyVmomi import vim
|
||||||
|
from datetime import timedelta
|
||||||
|
from pyVim.connect import Disconnect
|
||||||
|
from utils.logger import logger
|
||||||
|
from core.vm_connector import connect_vcenter
|
||||||
|
from config.settings import MANAGEMENT_NODES
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_vms():
|
||||||
|
"""主函数,负责流程控制 """
|
||||||
|
vm_list = []
|
||||||
|
for node in MANAGEMENT_NODES:
|
||||||
|
si, vm_view = None, None # 初始两个变量值
|
||||||
|
try:
|
||||||
|
# 1. 连接节点(核心逻辑不变)
|
||||||
|
si = connect_vcenter(node['host'])
|
||||||
|
content = si.RetrieveContent()
|
||||||
|
vm_view = content.viewManager.CreateContainerView(
|
||||||
|
content.rootFolder, [vim.VirtualMachine], True
|
||||||
|
)
|
||||||
|
# 2. 收集VM信息
|
||||||
|
for vm in vm_view.view:
|
||||||
|
vm_list.append(build_vm_info(vm, node['host']))
|
||||||
|
except Exception as e: # 记录错误,跳过当前节点
|
||||||
|
logger.error(f"处理节点 {node['host']} 失败:{e}")
|
||||||
|
finally:
|
||||||
|
# 无论成败,销毁视图+断开连接
|
||||||
|
if vm_view:
|
||||||
|
vm_view.Destroy()
|
||||||
|
if si:
|
||||||
|
Disconnect(si)
|
||||||
|
logger.info(f"获取到 {len(vm_list)} 台虚拟机")
|
||||||
|
return vm_list
|
||||||
|
|
||||||
|
|
||||||
|
def build_vm_info(vm, node_host):
|
||||||
|
"""构造虚拟机信息字典"""
|
||||||
|
vm_info = {
|
||||||
|
'NodeHost': node_host,
|
||||||
|
'name': vm.name,
|
||||||
|
'moId': vm._moId,
|
||||||
|
'powerState': vm.runtime.powerState,
|
||||||
|
'system': vm.config.guestFullName,
|
||||||
|
'ipAddress': vm.guest.ipAddress,
|
||||||
|
'hostName': vm.guest.hostName,
|
||||||
|
'diskSpaceGB': get_virtual_disk_size(vm),
|
||||||
|
'createDate': (vm.config.createDate + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if vm.runtime.bootTime else None, # 虚拟机的创建时间
|
||||||
|
'bootTime': (vm.runtime.bootTime + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if vm.runtime.bootTime else None, # 虚拟机上次启动的时间
|
||||||
|
'snapshots': [],
|
||||||
|
'Host': vm.runtime.host.name,
|
||||||
|
'vmPath': vm.config.files.vmPathName
|
||||||
|
}
|
||||||
|
# 判断虚拟机是否有快照,如果有就构造快照结构并记录当前快照ID;如果没有就设为 None。
|
||||||
|
if vm.snapshot:
|
||||||
|
current_snapshot = vm.snapshot.currentSnapshot
|
||||||
|
root_snapshots = vm.snapshot.rootSnapshotList
|
||||||
|
|
||||||
|
for snapshot in root_snapshots:
|
||||||
|
vm_info['snapshots'].extend(build_snapshot_dict(snapshot))
|
||||||
|
|
||||||
|
vm_info['currentSnapshotId'] = (
|
||||||
|
current_snapshot._moId if current_snapshot else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vm_info['snapshots'] = None
|
||||||
|
vm_info['currentSnapshotId'] = None
|
||||||
|
return vm_info
|
||||||
|
|
||||||
|
|
||||||
|
def build_snapshot_dict(snapshot):
|
||||||
|
"""递归构造快照结构"""
|
||||||
|
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,
|
||||||
|
'quiesced': getattr(snapshot, 'quiesced', None),
|
||||||
|
'children': []
|
||||||
|
}
|
||||||
|
if snapshot.childSnapshotList:
|
||||||
|
for child in snapshot.childSnapshotList:
|
||||||
|
snapshot_info['children'].extend(build_snapshot_dict(child))
|
||||||
|
return [snapshot_info]
|
||||||
|
|
||||||
|
|
||||||
|
"""获取虚拟机的总磁盘大小(仅是虚拟磁盘的分配空间)"""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
vms = get_all_vms() # 主函数入口,获取虚拟机信息
|
||||||
|
# print(vms)
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
import yaml
|
import os, time, yaml
|
||||||
import os, time
|
|
||||||
from pyVmomi import vim
|
from pyVmomi import vim
|
||||||
from pyVim.connect import SmartConnect, Disconnect
|
from pyVim.connect import Disconnect
|
||||||
from config.settings import MANAGEMENT_NODES,YAML_OUTPUT_PATH
|
from core.vm_connector import connect_vcenter
|
||||||
|
from config.settings import YAML_OUTPUT_PATH
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def connect_vcenter(host):
|
def load_old_snapshots(file_path):
|
||||||
"""根据 NodeHost 连接对应 vCenter"""
|
"""从 YAML 文件中加载旧快照"""
|
||||||
for node in MANAGEMENT_NODES:
|
if os.path.exists(file_path):
|
||||||
if node['host'] == host:
|
with open(file_path, 'r', encoding='utf-8') as yaml_file:
|
||||||
return SmartConnect(
|
return yaml.safe_load(yaml_file)
|
||||||
host=node['host'],
|
else:
|
||||||
user=node['user'],
|
logger.error(f"{file_path} 文件不存在.")
|
||||||
pwd=node['password'],
|
return []
|
||||||
disableSslCertValidation=True
|
|
||||||
)
|
|
||||||
raise Exception(f"未找到 {host} 的连接信息")
|
|
||||||
|
|
||||||
|
|
||||||
def main(dele_snapshots):
|
def remove_snapshot(dele_snapshots):
|
||||||
"""根据 YAML 信息删除旧快照"""
|
"""根据 YAML 信息删除旧快照"""
|
||||||
# print(dele_snapshots)
|
# print(dele_snapshots)
|
||||||
if not dele_snapshots:
|
if not dele_snapshots:
|
||||||
@@ -31,52 +28,55 @@ def main(dele_snapshots):
|
|||||||
for snapshot in dele_snapshots:
|
for snapshot in dele_snapshots:
|
||||||
if snapshot['is_old']:
|
if snapshot['is_old']:
|
||||||
grouped.setdefault(snapshot['NodeHost'], []).append(snapshot)
|
grouped.setdefault(snapshot['NodeHost'], []).append(snapshot)
|
||||||
|
# print(grouped)
|
||||||
for host, snapshots in grouped.items():
|
for host, snapshots in grouped.items():
|
||||||
logger.info(f"连接到 {host} 删除快照...")
|
logger.info(f"连接 {host} 进行删除快照...")
|
||||||
|
try:
|
||||||
si = connect_vcenter(host)
|
si = connect_vcenter(host)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for snapshot in snapshots:
|
for snapshot in snapshots:
|
||||||
|
# print(snapshot)
|
||||||
delete_snapshot(si, snapshot) # 调用删除快照函数
|
delete_snapshot(si, snapshot) # 调用删除快照函数
|
||||||
finally:
|
finally:
|
||||||
Disconnect(si)
|
Disconnect(si)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"快照删除失败,因为连接 {host} 失败:{e}" )
|
||||||
|
|
||||||
|
|
||||||
# 执行快照删除的(核心)函数
|
# 执行快照删除的(核心)函数
|
||||||
def delete_snapshot(si, snapshot_info):
|
def delete_snapshot(si, snapshot_info):
|
||||||
"""执行快照删除"""
|
""" 执行快照删除 """
|
||||||
content = si.RetrieveContent()
|
content = si.RetrieveContent()
|
||||||
snap_name = f"{snapshot_info['VMName']}-{snapshot_info['Snapshot Name']}-({snapshot_info['MOID']})"
|
snap_name = f"{snapshot_info['VMName']}-{snapshot_info['Snapshot Name']}-({snapshot_info['MOID']})"
|
||||||
|
vm = find_vm_by_name(content, snapshot_info['VMName']) # 根据快照名,查找出相应的虚拟机
|
||||||
vm = find_vm_by_name(content, snapshot_info['VMName']) # VMName即根据获取到的虚拟机名称查找虚拟机是否存放
|
# print(snap_name,vm)
|
||||||
|
|
||||||
if not vm:
|
if not vm:
|
||||||
logger.info(f"未找到 VM: {snapshot_info['VMName']}")
|
logger.info(f"未找到 VM: {snapshot_info['VMName']}")
|
||||||
return
|
return
|
||||||
# 检查该虚拟机是否有快照
|
# 检查该虚拟机是否有快照
|
||||||
if not vm.snapshot:
|
if not vm.snapshot:
|
||||||
logger.warning(snap_name,":快照不存在")
|
logger.warning(f"{snap_name}:快照不存在")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 虚拟机的快照列表中找到具有指定 MOID 的快照对象
|
# 调用函数获取虚拟机快照的 MOID 信息
|
||||||
snapshot_obj = find_snapshot_by_moid(
|
snapshot_obj = find_snapshot_by_moid(
|
||||||
vm.snapshot.rootSnapshotList,
|
vm.snapshot.rootSnapshotList,
|
||||||
snapshot_info['MOID']
|
snapshot_info['MOID']
|
||||||
)
|
)
|
||||||
|
# print(snapshot_obj)
|
||||||
if not snapshot_obj:
|
if not snapshot_obj:
|
||||||
logger.warning(snap_name,":未找到")
|
logger.warning(snap_name,":未找到")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"正在删除 Snapshot: {snap_name}")
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
"""删除快照核心代码,调用快照对象的 RemoveSnapshot_Task 方法执行。removeChildren = False:表示删除该快照时不删除其子快照。"""
|
"""删除快照核心代码,调用快照对象的 RemoveSnapshot_Task 方法执行。removeChildren = False:表示删除该快照时不删除其子快照。"""
|
||||||
task = snapshot_obj.RemoveSnapshot_Task(removeChildren=False)
|
logger.info(f"正在删除 Snapshot: {snap_name}")
|
||||||
|
|
||||||
|
task = snapshot_obj.RemoveSnapshot_Task(removeChildren=False)
|
||||||
# 改进的等待逻辑
|
# 改进的等待逻辑
|
||||||
while task.info.state in [vim.TaskInfo.State.running, vim.TaskInfo.State.queued]:
|
while task.info.state in [vim.TaskInfo.State.running, vim.TaskInfo.State.queued]:
|
||||||
time.sleep(1) # 避免 CPU 空转,每秒检查一次
|
time.sleep(1) # 每秒检查一次
|
||||||
|
|
||||||
# 更完整的状态判断
|
# 更完整的状态判断
|
||||||
if task.info.state == vim.TaskInfo.State.success:
|
if task.info.state == vim.TaskInfo.State.success:
|
||||||
@@ -94,8 +94,8 @@ def delete_snapshot(si, snapshot_info):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
"""根据 VM 名称查找虚拟机"""
|
||||||
def find_vm_by_name(content, vm_name):
|
def find_vm_by_name(content, vm_name):
|
||||||
"""根据 VM 名称查找虚拟机对象"""
|
|
||||||
container = content.viewManager.CreateContainerView(
|
container = content.viewManager.CreateContainerView(
|
||||||
content.rootFolder, [vim.VirtualMachine], True
|
content.rootFolder, [vim.VirtualMachine], True
|
||||||
)
|
)
|
||||||
@@ -109,8 +109,8 @@ def find_vm_by_name(content, vm_name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
""" 递归查找 snapshot 对象 """
|
||||||
def find_snapshot_by_moid(snapshot_tree, moid):
|
def find_snapshot_by_moid(snapshot_tree, moid):
|
||||||
"""递归查找 snapshot 对象"""
|
|
||||||
for snapshot in snapshot_tree:
|
for snapshot in snapshot_tree:
|
||||||
if snapshot.snapshot._moId == moid:
|
if snapshot.snapshot._moId == moid:
|
||||||
return snapshot.snapshot
|
return snapshot.snapshot
|
||||||
@@ -122,16 +122,6 @@ def find_snapshot_by_moid(snapshot_tree, moid):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_old_snapshots(file_path):
|
|
||||||
"""从 YAML 文件中加载旧快照"""
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as yaml_file:
|
|
||||||
return yaml.safe_load(yaml_file)
|
|
||||||
else:
|
|
||||||
logger.error(f"{file_path} 文件不存在.")
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
old_snapshots = load_old_snapshots(YAML_OUTPUT_PATH)
|
old_snapshots = load_old_snapshots(YAML_OUTPUT_PATH) # 获取待删除的快照信息
|
||||||
main(old_snapshots)
|
remove_snapshot(old_snapshots)
|
||||||
36
core/vm_connector.py
Normal file
36
core/vm_connector.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import ssl
|
||||||
|
from pyVim.connect import SmartConnect, Disconnect
|
||||||
|
from config.settings import MANAGEMENT_NODES
|
||||||
|
from utils.logger import logger # 复用你之前的日志配置
|
||||||
|
|
||||||
|
|
||||||
|
def connect_vcenter(host):
|
||||||
|
"""
|
||||||
|
根据NodeHost连接对应vCenter/ESXi
|
||||||
|
:param host: 节点IP/主机名
|
||||||
|
:return: ServiceInstance对象
|
||||||
|
:raise: Exception(连接失败/未找到配置)
|
||||||
|
"""
|
||||||
|
# 遍历全局配置的MANAGEMENT_NODES
|
||||||
|
for node in MANAGEMENT_NODES:
|
||||||
|
if node['host'] == host:
|
||||||
|
context = ssl._create_unverified_context() # 禁用SSL验证
|
||||||
|
si = SmartConnect(
|
||||||
|
host=node['host'],
|
||||||
|
user=node['user'],
|
||||||
|
pwd=node['password'],
|
||||||
|
sslContext=context # 规范的SSL配置
|
||||||
|
)
|
||||||
|
if not si:
|
||||||
|
raise Exception(f"{host}连接成功但未返回ServiceInstance")
|
||||||
|
logger.info(f"成功连接到节点: {host}")
|
||||||
|
return si
|
||||||
|
|
||||||
|
# 匹配不到 host 主动抛异常
|
||||||
|
logger.error(f"未找到节点 {host} 的连接信息")
|
||||||
|
raise Exception(f"未找到 {host} 的连接信息")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
si = connect_vcenter(MANAGEMENT_NODES[0]['host'])
|
||||||
|
print(si)
|
||||||
63
main.py
63
main.py
@@ -1,20 +1,59 @@
|
|||||||
# 这是一个示例 Python 脚本。
|
import time
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
# 按 Shift+F10 执行或将其替换为您的代码。
|
from utils.logger import logger
|
||||||
# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。
|
from config.settings import YAML_OUTPUT_PATH
|
||||||
|
from core.get_vm_snapshots import get_all_vms
|
||||||
|
from core.data_exporter import create_excel_report, export_yaml
|
||||||
|
from core.remove_snapshots import load_old_snapshots, remove_snapshot
|
||||||
|
|
||||||
|
|
||||||
def print_hi(name):
|
def export_files():
|
||||||
# 在下面的代码行中使用断点来调试脚本。
|
"""导出Excel和Yaml文件的函数"""
|
||||||
print(f'Hi, {name}') # 按 Ctrl+F8 切换断点。
|
logger.info("🔍 开始收集VM和快照信息...")
|
||||||
|
vms = get_all_vms() # 主函数入口,获取虚拟机信息
|
||||||
|
|
||||||
|
# 导出Excel报表
|
||||||
|
logger.info("📝 开始导出Excel报表...")
|
||||||
|
old_snapshots = create_excel_report(vms) # 生成Excel报告并获取旧快照
|
||||||
|
|
||||||
|
# 导出Yaml文件
|
||||||
|
logger.info("📝 开始导出 Yaml 文件...")
|
||||||
|
export_yaml(old_snapshots)
|
||||||
|
logger.info("========== Excel和Yaml文件导出完成 ==========")
|
||||||
|
|
||||||
|
|
||||||
# 按装订区域中的绿色按钮以运行脚本。
|
def delete_old_snapshots():
|
||||||
if __name__ == '__main__':
|
"""删除旧快照的函数"""
|
||||||
print_hi('PyCharm')
|
logger.info("🗑️ 开始删除过旧快照...")
|
||||||
|
old_snapshots = load_old_snapshots(YAML_OUTPUT_PATH)
|
||||||
# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
|
remove_snapshot(old_snapshots)
|
||||||
|
logger.info("========== VM快照清理任务执行完成 ==========")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主执行函数"""
|
||||||
|
|
||||||
|
# 设置定时任务
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
|
||||||
|
# 每周六凌晨4点导出Excel和Yaml文件
|
||||||
|
scheduler.add_job(export_files, 'cron', day_of_week='sat', hour=4, minute=0)
|
||||||
|
|
||||||
|
# 每周六晚上7点执行删除快照任务
|
||||||
|
scheduler.add_job(delete_old_snapshots, 'cron', day_of_week='sat', hour=19, minute=0)
|
||||||
|
|
||||||
|
scheduler.start()
|
||||||
|
|
||||||
|
logger.info("定时任务已设置:每周六凌晨4点导出文件,晚上7点删除快照")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 保持主程序运行,以便调度器能正常工作
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
scheduler.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import logging
|
import os, logging
|
||||||
# from config.settings import LOG_FILE_PATH
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 获取项目根目录(假设 logger.py 在 utils/ 目录下)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||||
|
|
||||||
|
|
||||||
def get_logger():
|
def get_logger():
|
||||||
"""配置日志系统,返回logger实例"""
|
"""配置日志系统,返回logger实例"""
|
||||||
# 创建logger
|
# 创建logger
|
||||||
@@ -15,8 +19,16 @@ def get_logger():
|
|||||||
# 定义日志格式
|
# 定义日志格式
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
# 自动创建日志目录
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
# 日志文件路径
|
||||||
|
log_file = os.path.join(
|
||||||
|
LOG_DIR,
|
||||||
|
f'{datetime.now().strftime("%Y-%m-%d")}-vm_snapshot_cleanup.log'
|
||||||
|
)
|
||||||
|
|
||||||
# 文件处理器(写入日志文件)
|
# 文件处理器(写入日志文件)
|
||||||
file_handler = logging.FileHandler(f'D:\\PycharmProjects\\RemoveWeeklyShapshot\\logs\\{datetime.now().strftime('%Y%m%d')}-RemoveWeeklyShapshot.log', encoding='utf-8')
|
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
||||||
file_handler.setFormatter(formatter) # 应用格式化器
|
file_handler.setFormatter(formatter) # 应用格式化器
|
||||||
|
|
||||||
# 控制台处理器(输出到终端)
|
# 控制台处理器(输出到终端)
|
||||||
|
|||||||
Reference in New Issue
Block a user