vSphere通 python API接口创建虚拟机及修改配置

背景:跟OA流程打通,自动化创建虚拟机,减少系统工程师重复工作时间,通过vSphere 提供的接口创建虚拟机,笔者通过 vsphere-automation-sdk-python 创建虚拟机,pyvmomi 给VM添加磁盘,打开VM电源,获取虚拟机IP地址

笔者系统环境:windows

python版本:python3.8

在这里插入图片描述

完整代码如下:
import urllib3
import requests
import time
import calendar
from com.vmware.vcenter.ovf_client import LibraryItem, DiskProvisioningType, ImportFlag
from com.vmware.vcenter_client import Folder, ResourcePool, Cluster, Network, Datastore
from vmware.vapi.vsphere.client import create_vsphere_client
from com.vmware.content_client import Library, LibraryModel, LocalLibrary, SubscribedLibrary
from com.vmware.content.library_client import Item, ItemModel, StorageBacking, SubscribedItem
from pyVim.connect import SmartConnectNoSSL
from pyVmomi import vim


def create_vm(user, ip, password, network_name, cluster_name,datastore_name, folder_name, content_library_name,
              template_name, vm_name, annotation):
    """ 通过 vsphere-automation-sdk-python 从模板库复制虚拟机 """
    session = requests.session()
    session.verify = False
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    client = create_vsphere_client(server=ip, username=user, password=password, session=session)

    stub_config = client._stub_config

    library_item = LibraryItem(stub_config)
    network_id = client.vcenter.Network.list(
        Network.FilterSpec(names={network_name},
                           types={Network.Type.DISTRIBUTED_PORTGROUP}))[0].network

    cluster_id = client.vcenter.Cluster.list(Cluster.FilterSpec(names={cluster_name}))[0].cluster
    resource_pool_id = client.vcenter.ResourcePool.list(ResourcePool.FilterSpec(clusters={cluster_id}))[
        0].resource_pool

    datastore_id = client.vcenter.Datastore.list(Datastore.FilterSpec(names={datastore_name}))[0].datastore

    library_service = Library(stub_config)
    library_id = library_service.find(Library.FindSpec(name=content_library_name))[0]

    library_item_service = Item(stub_config)
    library_item_id = library_item_service.find(Item.FindSpec(name=template_name, library_id=library_id))[0]

    ovf_lib_item_service = LibraryItem(stub_config)
    folder_id = client.vcenter.Folder.list(Folder.FilterSpec(names={folder_name}))[0].folder

    deployment_target = library_item.DeploymentTarget(resource_pool_id=resource_pool_id, folder_id=folder_id)
    # ovf_lib_item_service.filter(library_item_id, deployment_target)

    storage_group_mapping = ovf_lib_item_service.StorageGroupMapping(
        type=ovf_lib_item_service.StorageGroupMapping.Type('DATASTORE'),
        datastore_id=datastore_id,
        provisioning=DiskProvisioningType('thin')
    )

    deployment_spec = library_item.ResourcePoolDeploymentSpec(
        name=vm_name,
        annotation=annotation,
        accept_all_eula=True,
        network_mappings={"Your configuration": network_id},
        storage_mappings={"Your configuration": storage_group_mapping},
    )
    # Execute this statement and start deploying. It will last for about 2 minutes and you can see the specific progress on VMware.
    print("开始从内容库复制虚拟机,请稍等......")
    result = library_item.deploy(library_item_id, deployment_target, deployment_spec)
    return result


def get_vm_state(user, ip, password, vm_name):
    session = requests.session()
    session.verify = False
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    client = create_vsphere_client(server=ip, username=user, password=password, session=session)
    vm = client.vcenter.VM.list(client.vcenter.VM.FilterSpec(names={vm_name}))[0]
    vm_state = client.vcenter.vm.Power.get(vm.vm).state
    if vm_state == 'POWERED_OFF':
        client.vcenter.vm.Power.start(vm.vm)
        print(f'虚拟机 {vm_name} 现在启动')
        time.sleep(30)
        return f'{vm_name} 现在启动'
    return f'{vm_name} 电源状态已开启'


def get_vm_network(network_name):
    """ 获取指定网段, vlan-22-10.0.22.* 为分布式交换机VLAN名称 """
    network_name = str.lower(network_name)
    if  network_name == "10.0.22.0/24":
        return "vlan-22-10.0.22.*"
    if  network_name == "10.0.23.0/24":
        return "VLAN-10"
    return "子网不存在,请先创建子网掩码"


def get_vm_cluster_name(cluster_name):
    """ 获取数据中心位置,目前只有一个方便后期扩容 """
    cluster_name = str.lower(cluster_name)
    if cluster_name == "shenzhen":
        return "shenzhen"
    return "集群名称不存在,请先创建集群"


def get_vm_datastore_name(datastore_name):
    """ 获取存储位置,目前只有一个方便后期扩容 """
    datastore_name = str.lower(datastore_name)
    if datastore_name == "datastore1":
        return "datastore1"
    return "存储池不存在,请先创建存储池"


def get_vm_folder_name(folder_name):
    """ 获取虚拟机存放位置,目前只有一个方便后期扩容 """
    folder_name = str(folder_name)
    if folder_name == "EID":
        return "EID"
    return "虚拟机文件不存在,请先创建虚拟机文件"


def get_vm_template_name(template_name):
    """ 获取数据库模板,返回模板名称及模板账户密码 """
    template_name = str.lower(template_name)
    if template_name == "centos":
        return {'system': "contos7-01-temp",
                'system_user': 'root',
                'system_pwd': '123.com',
                }
    if template_name == "ubuntu":
        return {'system': "Ubuntu-temp",
                'system_user': 'root',
                'system_pwd': '123@com',
                }
    if template_name == "windows":
        return {'system': "windows-temp",
                'system_user': 'administrator',
                'system_pwd': '123#com',
                }

    return "系统模板不存在,请先创建模板"


class VmManage(object):

    def __init__(self, host, user, password, port, ssl):
        self.config = None
        self.host = host
        self.user = user
        self.pwd = password
        self.port = port
        self.sslContext = ssl
        try:
            self.client = SmartConnectNoSSL(host=host,
                                            user=user,
                                            pwd=password,
                                            port=443
                                            )
            self.content = self.client.RetrieveContent()
            self.result = True
        except Exception as e:
            self.result = e

    def wait_for_task(self, task):
        """ wait for a vCenter task to finish """
        task_done = False

        while not task_done:
            print("task.....%s " % task.info.state)
            time.sleep(2)
            if task.info.state == 'success':
                return {'message': u'执行成功', 'status': True}

            if task.info.state == 'error':
                print("there was an error", f'message:{task.info.error.msg}')

                return {'message': task.info.error.msg, 'status': True}

    def _get_all_objs(self, obj_type, folder=None):
        """
        根据对象类型获取这一类型的所有对象
        """
        if folder is None:
            container = self.content.viewManager.CreateContainerView(self.content.rootFolder, obj_type, True)
        else:
            container = self.content.viewManager.CreateContainerView(folder, obj_type, True)
        return container.view

    def _get_obj(self, obj_type, name):
        """
        根据对象类型和名称来获取具体对象
        """
        obj = None
        content = self.client.RetrieveContent()
        container = content.viewManager.CreateContainerView(content.rootFolder, obj_type, True)
        for c in container.view:
            if c.name == name:
                obj = c
                break
        return obj

    def add_disk(self, vm_obj, capacity):
        spec = vim.vm.ConfigSpec()
        dev_changes = []
        # capacity 为 存储盘容量将单位改为 G
        new_disk_kb = capacity * 1024 * 1024
        unit_number = 0
        # 遍历所有的硬件设备,找合适的位置添加
        for dev in vm_obj.config.hardware.device:
            if hasattr(dev.backing, 'fileName'):
                unit_number = int(dev.unitNumber) + 1
                # unit_number 7 reserved for scsi controller
                if unit_number == 7:
                    unit_number += 1
                if unit_number >= 16:
                    logging.error('we don\'t support this many disks')
            if isinstance(dev, vim.vm.device.VirtualSCSIController):
                controller = dev

        disk_spec = vim.vm.device.VirtualDeviceSpec()
        disk_spec.fileOperation = "create"
        disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
        disk_spec.device = vim.vm.device.VirtualDisk()
        disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
        disk_spec.device.backing.thinProvisioned = True
        disk_spec.device.backing.diskMode = 'persistent'

        disk_spec.device.unitNumber = unit_number
        disk_spec.device.capacityInKB = new_disk_kb
        disk_spec.device.controllerKey = controller.key
        dev_changes.append(disk_spec)
        spec.deviceChange = dev_changes
        task = vm_obj.ReconfigVM_Task(spec=spec)
        result1 = self.wait_for_task(task)
        if result1['status']:
            data = {'message': u'硬盘添加成功', 'result': True}
        else:
            data = {'message': u'硬盘添加失败: %s' % result1['message'], 'result': False}
        return data

    def change_vm_cpu_and_memory(self, vm_obj, cpu, memory ):
        cspec = vim.vm.ConfigSpec()
        cspec.numCPUs = cpu
        # 将 4 核心分配在两个插槽,一个插槽两核心
        # cspec.numCoresPerSocket = int(cspec.numCPUs / 2)
        # 8G
        cspec.memoryMB = 1024 * memory
        # 启动 cpu 热添加
        cspec.cpuHotAddEnabled = True
        # 启动内存在线扩缩
        cspec.memoryHotAddEnabled = True
        task = vm_obj.Reconfigure(cspec)
        result1 = self.wait_for_task(task)
        if result1['status']:
            data = {'message': u'cpu 和 内存变更成功', 'result': True}
        else:
            data = {'message': u'cpu 和 内存变更添加失败: %s' % result1['message'], 'result': False}
        return data

    def get_vm_ip(self, vm_obj ):
        """ 获取 VM 所有 IP 地址 """
        ips = []
        for i in vm_obj.guest.net:
            if i.network:
                for b in range(6):
                    if i.ipAddress:
                        ips.extend(i.ipAddress)
                        return ips
                    else:
                        print("VM 没有获取到IP地址等待10秒")
                        time.sleep(10)
            else:
                print('VM 未配置网卡')
        return 'VM 未获取到IP地址,请检测网络状态或者 DHCP 服务器'


def main(cluster_name, network_name, template_name, capacity, cpu,memory, annotation, datastore_name, folder_name,
         content_library_name,):
	""" user为vSphere账户,password为密码, ip为登录地址, port为端口号 """
    user = 'Administrator@TEST-VSPHERE.COM'
    ip = '192.168.3.100'
    password = '123.com'
    port = 443
    # 虚拟机名称为 vm- 加上 时间戳
    vm_name = f"vm-{calendar.timegm(time.gmtime())}"

    network_name = get_vm_network(network_name)
    cluster_name = get_vm_cluster_name(cluster_name)
    datastore_name = get_vm_datastore_name(datastore_name)
    folder_name = get_vm_folder_name(folder_name)
    template_name = get_vm_template_name(template_name)

    vm_info = {
                "子网段": network_name,
                "集群名称": cluster_name,
                "datastore_name": datastore_name,
                "folder_name": folder_name,
                "template_name": template_name['system'],
                "template_user": template_name['system_user'],
                "template_pwd": template_name['system_pwd'],
                "annotation": annotation,
                }

    print(vm_info)


    # 记录当前时间
    now = time.time()

    try:
        # 通过 vsphere-automation-sdk 开始从内容库复制虚拟机
        create_vm_01 = create_vm(user, ip, password, network_name, cluster_name, datastore_name, folder_name,
                                content_library_name, template_name['system'], vm_name, annotation)
        print(create_vm_01)

        # pyvmomi 登录设备
        vm = VmManage(host=ip,
                      user=user,
                      password=password,
                      port=port, ssl=None)

        vm_obj = vm._get_obj([vim.VirtualMachine], vm_name)

        # 虚拟机添加硬盘
        vm_obj_add_disk = vm.add_disk(vm_obj, capacity)
        print(vm_obj_add_disk)

        # 改变虚拟机 cpu 和 内存
        vm_obj_cpu_memory = vm.change_vm_cpu_and_memory(vm_obj, cpu, memory)
        print(vm_obj_cpu_memory)

        # 虚拟机开机,等待60秒,待虚拟机开机启动获取IP地址
        task = vm_obj.PowerOn()
        print("启动 VM 电源")
        print(vm.wait_for_task(task))
        time.sleep(60)

        # 获取IP地址
        print("开始获取 VM IP地址")
        vm_obj_ip = vm.get_vm_ip(vm_obj)
        # print(vm_obj_ip)

        vm_uid = str(vm_obj).replace('\'', '').split(':')[-1]

        results = {
            "errcode": 0,
            "errmsg": "ok",
            'vm_id': vm_uid,
            'vm_name': vm_name,
            'ip_add': vm_obj_ip[0],
            'vm_user': template_name['system_user'],
            'vm_password': template_name['system_pwd'],
        }

        # 计算虚拟机创建消耗时间
        print(time.time() - now)

    except Exception as err:
        raise err

    else:
        # save_log(results)
        return results



安装依赖, 需要先安装git

""" 安装依赖 """
pip install requests
pip install urllib3
pip install chinesecalendar
pip install pyvmomi

# 安装SDK
pip install --upgrade setuptools==60.10.0
pip install --upgrade git+https://github.com/vmware/vsphere-automation-sdk-python.git

笔者发现一个问题,通过pyvmomi修改虚拟机的IP地址后脚本没有问题,但是虚拟机IP地址未修改且网卡切换至未连接状态,研究了几天没有解决相关问题,只能妥协使用DHCP服务下发地址,如有大佬有相关解决方案可以给笔者一些建议跟思路。

笔者脚本能力有限写的不好望大佬勿喷。

参考:
https://github.com/vmware/pyvmomi-community-samples
https://github.com/vmware/vsphere-automation-sdk-python
https://juejin.cn/post/6844903870561271822