import pandas as pd import yaml from pyVmomi import vim from datetime import datetime, timedelta from pyVim.connect import SmartConnect, Disconnect from openpyxl.styles import Border, Side, Font, PatternFill from utils.logger import logger from config.settings import MANAGEMENT_NODES, SNAPSHOT_RETENTION_DAYS, EXCEL_OUTPUT_PATH, YAML_OUTPUT_PATH def get_all_vms(): """ 主函数,负责流程控制""" vm_list = [] 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 = { '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 style_sheet(sheet, is_old_data=None): """设置工作表样式:列宽、边框、加粗标题、冻结首行和设置背景颜色""" # 定义边框样式 thin = Side(border_style="thin", color="000000") border = Border(left=thin, right=thin, top=thin, bottom=thin) # 设置列宽和边框 for column in sheet.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) cell.border = border # 为每个单元格添加边框 except Exception as e: pass adjusted_width = (max_length + 2) sheet.column_dimensions[column_letter].width = adjusted_width # 加粗标题并冻结首行 for cell in sheet[1]: # 加粗标题 cell.font = Font(bold=True) sheet.freeze_panes = 'A2' # 冻结首行 # 设置背景颜色 if is_old_data is not None: for row in range(2, len(is_old_data) + 2): if is_old_data[row - 2]: for col in range(1, sheet.max_column + 1): # 使用 sheet.max_column cell = sheet.cell(row=row, column=col) 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 文件 def export_yaml(old_snapshots): print(old_snapshots) # 将旧快照信息存储到 YAML 文件 with open(YAML_OUTPUT_PATH, 'w', encoding='utf-8') as yaml_file: # allow_unicode:输出 Unicode 字符(中文等),allow_unicode:使用块样式(多行缩进),sort_keys:不按键名排序,保留原始插入顺序 yaml.dump(old_snapshots, yaml_file, allow_unicode=True, default_flow_style=False, sort_keys=False) logger.debug(f"YAML 文件已生成: {YAML_OUTPUT_PATH}") if __name__ == '__main__': vms = get_all_vms() # 主函数入口,获取虚拟机信息 # print(vms) old_snapshots = create_excel_report(vms) # 生成Excel报告并获取旧快照 export_yaml(old_snapshots) # print(old_snapshots)