vmware的python自动化:批量给esxi主机挂载iscsi动态目标

目录

自动化需求

代码

使用方法


自动化需求

给esxi主机批量添加ISCSI动态目标,支持多VC,支持多个动态目标IP,支持重复动态目标IP跳过

版本:

python3.11

pyvmomi 9.0.0.0

pyvim 3.0.3

代码

复制代码
import ssl
import os
import time
import concurrent.futures
from collections import defaultdict
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
from openpyxl import load_workbook

# ================= 配置区域 =================
VC_CONFIGS = {
    '192.168.1.250': {
        'user': 'administrator@vsphere.local',
        'pwd': 'xiaozhou@666.com'
    },
    # 示例:添加第二个vCenter
    # '192.168.1.100': {
    #     'user': 'administrator@vsphere.local',
    #     'pwd': 'YourPasswordHere'
    # }
}

# 输入文件名称
INPUT_FILE = 'iscsi.xlsx'
# 并发配置(建议 5-10)
MAX_WORKERS = 5


# =======================================================================


def get_obj(content, vimtype, name=None):
    """获取对象函数"""
    container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True)
    return next((item for item in container.view if item.name == name), None) if name else container.view


def read_iscsi_config_from_xlsx():
    """
    从Excel读取iSCSI配置
    Excel格式:VC, ESXI主机, iSCSI动态发现IP(多个IP用逗号分隔)
    """
    configs = []
    xlsx_file_path = os.path.join(os.getcwd(), INPUT_FILE)

    if not os.path.exists(xlsx_file_path):
        print(f"错误: Excel文件不存在 '{xlsx_file_path}'")
        return configs

    try:
        wb = load_workbook(xlsx_file_path, read_only=True, data_only=True)
        ws = wb.active
        rows = list(ws.iter_rows(values_only=True))
        wb.close()

        if not rows:
            print("错误: Excel文件为空")
            return configs

        header = [str(h).strip() if h else '' for h in rows[0]]
        expected_headers = ['VC', 'ESXI主机', 'iSCSI动态发现IP']
        if header != expected_headers:
            print(f"警告: Excel表头格式不正确")
            print(f"期望: {expected_headers}")
            print(f"实际: {header}")
            return configs

        for row in rows[1:]:
            if len(row) >= 3:
                # 【修改】解析多个IP(逗号分隔)
                ip_str = str(row[2]).strip() if row[2] else ''
                target_ips = []
                if ip_str:
                    # 按逗号分割,去除空格
                    target_ips = [ip.strip() for ip in ip_str.split(',') if ip.strip()]

                config = {
                    'vc_ip': str(row[0]).strip() if row[0] else '',
                    'esxi_host': str(row[1]).strip() if row[1] else '',
                    'iscsi_target_ips': target_ips  # 【修改】改为列表
                }
                if config['vc_ip'] not in VC_CONFIGS:
                    print(f"警告: 跳过主机 '{config['esxi_host']}',VC '{config['vc_ip']}' 未配置")
                    continue
                if config['vc_ip'] and config['esxi_host'] and config['iscsi_target_ips']:
                    configs.append(config)
                    print(
                        f"读取配置: {config['esxi_host']} -> {len(config['iscsi_target_ips'])} 个目标: {', '.join(config['iscsi_target_ips'])}")
                else:
                    print(f"警告: 跳过无效行(缺少VC/主机/IP): {row}")
            else:
                print(f"警告: Excel行数据不完整: {row}")

    except Exception as e:
        print(f"读取Excel文件失败: {e}")

    return configs


def process_single_host(content, config):
    """
    处理单个ESXi主机:添加多个iSCSI动态发现目标并扫描
    返回: (成功/失败, 主机名, 消息)
    """
    esxi_host = config['esxi_host']
    target_ips = config['iscsi_target_ips']

    try:
        # 1. 获取ESXi主机对象
        host = get_obj(content, [vim.HostSystem], esxi_host)
        if not host:
            return False, esxi_host, f"找不到ESXi主机 '{esxi_host}'"

        # 2. 获取存储系统
        storage_system = host.configManager.storageSystem
        if not storage_system:
            return False, esxi_host, "无法获取存储系统"

        # 3. 查找iSCSI适配器
        iscsi_hba = None
        device_info = storage_system.storageDeviceInfo

        if not device_info or not device_info.hostBusAdapter:
            return False, esxi_host, "未找到存储适配器"

        for hba in device_info.hostBusAdapter:
            if isinstance(hba, vim.host.InternetScsiHba):
                iscsi_hba = hba
                break

        if not iscsi_hba:
            return False, esxi_host, "未找到iSCSI适配器"

        hba_device = iscsi_hba.device
        print(f"   [{esxi_host}] 找到iSCSI适配器: {hba_device}")

        # 4. 获取现有目标列表
        existing_targets = []
        if hasattr(iscsi_hba, 'configuredSendTarget') and iscsi_hba.configuredSendTarget:
            existing_targets = [t.address for t in iscsi_hba.configuredSendTarget]

        # 5. 【修改】循环添加多个目标
        added_count = 0
        skipped_count = 0
        failed_ips = []

        for target_ip in target_ips:
            if target_ip in existing_targets:
                print(f"   [{esxi_host}] 目标 {target_ip} 已存在,跳过")
                skipped_count += 1
                continue

            try:
                print(f"   [{esxi_host}] 正在添加动态发现目标: {target_ip}")
                send_target = vim.host.InternetScsiHba.SendTarget(
                    address=target_ip,
                    port=3260
                )
                storage_system.AddInternetScsiSendTargets(
                    iScsiHbaDevice=hba_device,
                    targets=[send_target]
                )
                print(f"   [{esxi_host}] 成功添加目标: {target_ip}")
                added_count += 1
            except Exception as e:
                print(f"   [{esxi_host}] 添加目标 {target_ip} 失败: {e}")
                failed_ips.append(f"{target_ip}({e})")
                continue

        # 6. 重新扫描存储适配器(只要有添加或有目标就扫描)
        if added_count > 0 or len(target_ips) > 0:
            print(f"   [{esxi_host}] 正在重新扫描存储适配器...")
            storage_system.RescanHba(hbaDevice=hba_device)
            print(f"   [{esxi_host}] 扫描完成")

        # 构建结果消息
        msg_parts = []
        if added_count > 0:
            msg_parts.append(f"新增{added_count}个")
        if skipped_count > 0:
            msg_parts.append(f"跳过{skipped_count}个")
        if failed_ips:
            msg_parts.append(f"失败{len(failed_ips)}个: {'; '.join(failed_ips)}")

        result_msg = ', '.join(msg_parts) if msg_parts else "无操作"

        return True, esxi_host, result_msg

    except Exception as e:
        return False, esxi_host, str(e)


def process_vcenter_tasks(vc_ip, vc_cred, host_list):
    """处理单个vCenter下的所有主机"""
    print(f"\n========== 开始处理 vCenter: {vc_ip} ==========")
    si = None
    context = ssl._create_unverified_context()

    try:
        # 连接vCenter
        si = SmartConnect(
            host=vc_ip,
            user=vc_cred['user'],
            pwd=vc_cred['pwd'],
            port=443,
            sslContext=context
        )
        content = si.content
        print(f"✅ 成功连接到 vCenter: {vc_ip}")
        print(f"🚀 开始并发处理 (并发数: {MAX_WORKERS})...")

        # 并发处理
        success_count = 0
        failed_count = 0
        failed_details = []

        with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
            future_to_host = {
                executor.submit(process_single_host, content, config): config
                for config in host_list
            }

            for i, future in enumerate(concurrent.futures.as_completed(future_to_host), 1):
                config = future_to_host[future]
                try:
                    success, host_name, message = future.result()
                    if success:
                        success_count += 1
                        print(f"✅ [{i}/{len(host_list)}] {host_name}: {message}")
                    else:
                        failed_count += 1
                        failed_details.append((host_name, message))
                        print(f"❌ [{i}/{len(host_list)}] {host_name}: {message}")
                except Exception as e:
                    failed_count += 1
                    failed_details.append((config['esxi_host'], str(e)))
                    print(f"❌ [{i}/{len(host_list)}] {config['esxi_host']}: {str(e)}")

        # 打印总结
        print(f"\n📊 {vc_ip} 处理完成总结:")
        print(f"   成功: {success_count} 台")
        print(f"   失败: {failed_count} 台")
        if failed_details:
            print(f"   失败详情:")
            for name, msg in failed_details:
                print(f"     - {name}: {msg}")

    except Exception as e:
        print(f"vCenter {vc_ip} 执行错误: {e}")
        import traceback
        traceback.print_exc()
    finally:
        if si:
            Disconnect(si)
            print(f"已断开 vCenter {vc_ip} 连接")


def main():
    print("=" * 60)
    print("🚀 ESXi主机批量添加iSCSI动态发现目标工具 (多IP版)")
    print("=" * 60)

    # 读取所有配置
    all_configs = read_iscsi_config_from_xlsx()
    if not all_configs:
        print("没有有效的配置数据")
        return

    # 按VC IP分组
    vc_groups = defaultdict(list)
    for config in all_configs:
        vc_groups[config['vc_ip']].append(config)

    print(f"\n共读取到 {len(all_configs)} 个主机配置")

    # 逐个处理每个vCenter
    for vc_ip, host_list in vc_groups.items():
        if vc_ip in VC_CONFIGS:
            process_vcenter_tasks(vc_ip, VC_CONFIGS[vc_ip], host_list)
        else:
            print(f"警告: 跳过未配置的vCenter: {vc_ip}")

    print("\n所有任务处理完毕")


if __name__ == "__main__":
    main()

使用方法

在代码内填入vc的有关信息,然后创建iscsi.xlsx(包含VC,ESXI主机,iSCSI动态发现IP

三列)在代码文件同目录,运行代码即可

iscsi.xlsx示意图如下:

脚本为ai编写,使用前请使用测试环境进行测试

相关推荐
sbjdhjd3 分钟前
Tomcat(下) 集群高可用实战:反向代理・负载均衡・分布式 Session
运维·前端·云原生·开源·tomcat·负载均衡·memcached
xjxijd9 分钟前
行为感知算法赋能运维,提前预判硬件故障与异常访问
运维·算法
ai_coder_ai11 分钟前
如何使用adb实现自动化脚本?
运维·adb·自动化
Splashtop高性能远程控制软件11 分钟前
微软2026年6月补丁星期二技术分析:206个漏洞、3个已公开零日的分级修复方案
运维·安全·自动化·远程桌面·splashtop
陈猪的杰咪13 分钟前
【2026最新指南】AI大模型API中转站选型参考:国内稳定接入ChatGPT、Claude、Gemini等主流模型实践分享
运维·网络·人工智能·chatgpt·架构
羿悦科技20 分钟前
自动推箱设备中的接近开关:让重载动作更有节奏
人工智能·自动化·边缘计算·制造·接近开关
keyipatience21 分钟前
命名管道:跨进程通信的终极指南
linux·运维·服务器
AOwhisky9 小时前
Redis 学习笔记(第三期):持久化与主从复制
运维·数据库·redis·笔记·学习·云计算
c238569 小时前
Linux C++ 进度条进阶美化与工程化封装
linux·运维·服务器
李小白669 小时前
第四天-WEB服务器基本原理,IIS服务
运维·服务器·前端