import pandas as pd 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 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: print(f"登录 {node['host']} 失败,请检查用户名和密码:{e.msg}") logger.info(f"登录 {node['host']} 失败,请检查用户名和密码:{e.msg}") except Exception as e: print(f"无法连接到 {node['host']}:{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): """递归扁平化快照数据用于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_data.append({ '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 }) for child in snapshot['children']: collect_snapshot_data(child, vm, snapshot_data) # 输出数据到 Excel 文件 def create_excel_report(vms): vm_data = [] snapshot_data = [] 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) 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()) print("报告已生成:", EXCEL_OUTPUT_PATH) if __name__ == '__main__': vms = get_all_vms() # 主函数入口,获取虚拟机信息 # print(vms) create_excel_report(vms) # 生成Excel报告