想自动分 VMware 虚拟机,因此抽空研究了下它的 Python api。VMware api 本质上是 restapi,只不过有多种语言的封装,若是有兴趣能够关注下 VMware 其余语言的实现。html
Python api 有 pyvmomi 以及下面要讲到的 vsphere-automation-sdk 这两种,其中 pyvmomi 使用基于 SOAP 的 API 的 vSphere Web 服务 API,是比较老的东西了;而 vsphere-automation-sdk 使用新版基于 REST 的 api,有相似于 Tagging 和 Content Library 这样的新功能。Appliance Management、NSX、VMware Cloud 等 api 只能使用 vsphere-automation-sdk。python
简单来讲,之后新的功能的实现就在 vsphere automation sdk 上。虽然如此,可是 vsphere automation sdk 未必就必定适合你。并且 vsphere automation sdk 无论你用仍是不用,pyvmomi 是必定要使用的。git
为何这么说呢?vsphere-automation-sdk-python 因为使用了新的 api,可使用相似于内容库(关于内容库的内容,接下来会提到)以及 tagging 这样的新功能,可是其余功能就乏善可陈了,好比它对虚拟机自己的管理就没有丝毫办法。github
就说一个很现实的问题,使用 automation sdk 虽然能够从内容库中的 ovf 模板建立虚拟机,可是它只能根据模板的配置建立虚拟机,没法修改 cpu、内存、磁盘、ip 等参数(或许是我不知道?),而这些功能刚好 pyvmomi 都能胜任。json
所以,在存在多个 vcenter 的环境,可使用内容库保持多个 vcenter 之间 ovf 模板的一致,这样无论在哪一个 vcenter 中建立虚拟机操做都同样。而后建立完虚拟机以后,再使用 pyvmomi 对虚拟机配置进行修改。固然,若是你的环境中只有一个 vcenter,那么彻底就用不到 vsphere-automation-sdk,由于使用 pyvmomi 直接克隆机器速度更快,由于它少了从内容库下载模板的时间。vim
这个文章要讲的是,使用 vsphere automation sdk 从内容库中的 ovf 模板建立虚拟机,而后经过 pyvmomi 对虚拟机配置进行修改。须要注意的是,内容库是 VSphere 6.0 才有的功能,这篇文章基于 6.5。后端
咱们能够先进入其 github 主页。api
这个 SDK api 没什么文档,你得看它提供的示例才能对它有所了解。它全部的示例都在 samples 目录,有意思的是,它的示例不是割裂的,而是一个总体,整个示例就是一个 Python 包,你能够经过传递参数给它来实现建立简单的虚拟机等操做。tomcat
说实话,示例写的比较烂,坑不少。有些简单的、几行代码能搞定的事情,它能用更复杂的方式帮你实现。有感于此,我就将我研究的东西写出来,让你们少走弯路吧。安全
本文基于 VSphere 6.5 + Python 3.6。Python 2.7 也能用,差异不大。
首先安装它的 SDK:
git clone https://github.com/vmware/vsphere-automation-sdk-python.git
cd vsphere-automation-sdk-python
pip3 install --upgrade --force-reinstall -r requirements.txt --extra-index-url file:///`pwd`/lib
复制代码
--extra-index-url
:默认 pip 会去 pypi.org/simple 下载 Python 包,你还能够指定其余的 URL 进行下载。
网络的问题会致使中断,不要觉得是依赖出现问题,要仔细看报错信息。若是是网络问题,多安装几回,一直安装总会成功的。并且它会自动安装 pyvmomi。
安装完成以后就可使用了,咱们从最简单的开始,登陆,而后列出 vCenter 全部虚拟机:
import requests
import urllib3
from vmware.vapi.vsphere.client import create_vsphere_client
session = requests.session()
session.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
client = create_vsphere_client(server='IP', username='USER', password='PASSWORD', session=session)
client.vcenter.VM.list()
复制代码
list 列出来的虚拟机最多只能返回 1000 台,超过这个数量它会抛出异常,这时你须要使用过滤器来减小它返回的数量。过滤器可使用 client.vcenter.VM.FilterSpec
这个类来指定,它能够经过下面这些属性来进行过滤。
vms=None, names=None, folders=None, datacenters=None, hosts=None, clusters=None, resource_pools=None, power_states=None
复制代码
好比经过虚拟机的名称来进行过滤:
client.vcenter.VM.list(client.vcenter.VM.FilterSpec(names={'10.20.6.77_xyz.tomcat.xyz-common-main.01.sit'}))
复制代码
直接输入集群名称给 vm.FilterSpec 过滤器并不能得到任何结果,而是应该先经过集群名称得到相似集群 id 这样的字串(我是这样理解的)输入进去才行。其实不光是集群,在这个 SDK 中,VMware 中的全部概念,包括网络、存储、文件夹、资源池、虚拟机等它都只认 id,而不是它们的名称(个人理解是名称随时均可以变,可是 id 不会变)。
虽然你不能直接将集群名称给它,可是你能够根据集群名称来获取它的 id,具体作法以下:
from com.vmware.vcenter_client import Cluster
# 获取名为 POC 集群的 id
cluster_id = client.vcenter.Cluster.list(Cluster.FilterSpec(names={'POC'}))[0].cluster
client.vcenter.VM.list(client.vcenter.VM.FilterSpec(clusters={cluster_id}))
复制代码
其实和使用集群名称同样,只不过文件夹有多种类型:
DATACENTER
= Type(string=u'DATACENTER')DATASTORE
= Type(string=u'DATASTORE')HOST
= Type(string=u'HOST')NETWORK
= Type(string=u'NETWORK')VIRTUAL_MACHINE
= Type(string=u'VIRTUAL_MACHINE')咱们在使用的时候最好精确的指定其类型。
from com.vmware.vcenter_client import Folder
# 指定文件夹类型为虚拟机文件夹,文件夹名称为 35
filter_spec = Folder.FilterSpec(type=Folder.Type.VIRTUAL_MACHINE, names={'35'})
folder_id = client.vcenter.Folder.list(filter_spec)[0].folder
client.vcenter.VM.list(client.vcenter.VM.FilterSpec(folders={folder_id}))
复制代码
还有其余的过滤方式,使用起来都同样,这里就很少提了。
一些基础的用法先介绍到这,直接进入“建立虚拟机”这一正题,更多的用法也会在下面伴随着建立虚拟机一一展现。遗憾的是,我并无在 vsphere-automation-sdk-python 中找到直接从模板中建立虚拟机的方式,因此只能退而求其次使用内容库的 ovf 模板进行部署。
在部署以前,说说这个内容库。
Content Libraries(内容库)在 VSphere 6.0 中是一个容器对象,用来存储虚拟机模板、vAPP 模板、ISO 文件,还有其余你能在你网络中共享的文件。
内容库中的全部文件均可以跨 vCenter,有了内容库,你就不须要在每一个 vCenter 中维护一套模板了。在 Container Libraries 中存在的虚拟机模板、vAPP 模板,还有其余文件都被定义为 library items,library items 能够包含单个或多个文件,例如 VOF、ISO 等等。
若是在多个 vCenter 之间能够进行 http/https 访问,那么这些 library items 就能够在这些 vCenter 之间共享。
在 vSphere 中能够建立两种类型的 Content Library:
内容库中除了能够直接上传 ovf 文件到其中做为模板,也能够直接将 VMware 中的模板直接 copy 进去。从内容库中的模板建立机器会比直接从 VMware 上模板建立机器慢,由于它多了一个下载的过程,若是网速够快,差距会缩短。
内容库的建立方式在最下面的参考中网址中。
由于有了内容库的存在,咱们就能够从内容库的模板中建立虚拟机了。咱们要得到内容库中的内容,要先建立所谓的 stub_config,这个里面包含的是你登陆 vCenter 的信息,而后将它传递给内容库相关的类,就可以获取内容库的内容了。
官方的示例文档是先建立 stub_config,我把步骤先列出来,有兴趣的人能够看看,可是这种方式明显是画蛇添足,不知道写示例的人咋想的。
import requests
from com.vmware.cis_client import Session
from vmware.vapi.lib.connect import get_requests_connector
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from vmware.vapi.security.session import create_session_security_context
from vmware.vapi.stdlib.client.factories import StubConfigurationFactory
from vmware.vapi.security.user_password import \
create_user_password_security_context
server = '10.20.9.100'
username = 'administrator@vsphere.local'
password = '....'
skip_verification = True
host_url = f'https://{server}/api'
session = requests.Session()
session.verify = False
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
connector = get_requests_connector(session=session, url=host_url)
stub_config = StubConfigurationFactory.new_std_configuration(connector)
user_password_security_context = create_user_password_security_context(username,
password)
stub_config.connector.set_security_context(user_password_security_context)
session_svc = Session(stub_config)
session_id = session_svc.create()
session_security_context = create_session_security_context(session_id)
stub_config.connector.set_security_context(session_security_context)
复制代码
为何说他画蛇添足呢?由于一开始咱们使用很简单的方式就登陆了,并列出了 vCenter 中全部的虚拟机,那登录的对象 client 中就包含了 stub_config。若是用它这种方式,你要登陆两次,一次是建立 client,另外一次是建立这个 stub。
简单的获取 stub_config:
import requests
import urllib3
from vmware.vapi.vsphere.client import create_vsphere_client
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
复制代码
得到内容库 id:
from com.vmware.content_client import (Library,
LibraryModel,
LocalLibrary,
SubscribedLibrary)
# 实例化内容库对象
library_service = Library(stub_config)
# 根据内容库的名字以及它的类型来得到内容库 id,你也能够不指定内容
# 你可使用 list 而非 find 来列出全部的内容库 id,可是你得到的 id 并不知道它对应的名称是啥
# 由于它的返回值是字符串而非对象
library_id = library_service.find(Library.FindSpec(name='new', type=LibraryModel.LibraryType(u'LOCAL')))[0]
复制代码
根据内容库 ID 获取内容库中的模板:
from com.vmware.content.library_client import (Item,
ItemModel,
StorageBacking,
SubscribedItem)
template_name = 'CentOS 7.5'
library_item_id = library_item_service.find(Item.FindSpec(name=template_name, library_id=library_id))[0]
library_item_obj = library_item_service.get(library_item_id)
复制代码
上面的 library_item_id 就是模板的 id,下面的 library_item_obj 则是这个模板的对象,对象中包含了该模板的一些简要信息。其实若是只是部署虚拟机的话,只须要模板 id,模板对象不重要。
有了模板 id,咱们还须要建立一个 DeploymentTarget,用于指定虚拟机应该部署到哪儿。它能够指定三个属性:资源池、主机和文件夹。
class LibraryItem.DeploymentTarget(resource_pool_id=None, host_id=None, folder_id=None) 复制代码
为啥没有集群呢?在没有资源池的状况下,资源池就是集群;而有资源池的状况下,也就不用指定集群了,因此资源池必须指定。剩下的主机和文件夹看需求。这三个参数的值一样不是它们的名称,而是其 id。得到资源池 id 的方法在此,主机 id 和文件夹 id 的获取方式也是如此,这里就不赘述了。
from com.vmware.vcenter.ovf_client import LibraryItem
library_item = LibraryItem(stub_config)
deployment_target = library_item.DeploymentTarget(resource_pool_id=resource_pool_id)
复制代码
就如同建立虚拟机会建立一个所谓的 spec 同样,部署虚拟机一样先要建立这么个 spec,经过这个就能部署虚拟机了。这个 spec 是一个类型,支持的参数挺多:
class LibraryItem.ResourcePoolDeploymentSpec(name=None, annotation=None, accept_all_eula=None, network_mappings=None, storage_mappings=None, storage_provisioning=None, storage_profile_id=None, locale=None, flags=None, additional_parameters=None, default_datastore_id=None) 复制代码
它的全部选项针对的都是这个 ovf 模板,而不是针对经过它建立的虚拟机,这点要弄清楚。
参数说明:
name
:str 类型或 None,虚拟机的名称;annotation
:str 类型或 None,虚拟机的注解信息,也是虚拟机页面的 Notes 内容;accept_all_eula
:bool 类型,是否接受全部的 End User License Agreements(最终用户许可协议),不知道设置为 False 能不能部署;network_mappings
:指定网络,这个破玩意有些复杂,它的值一个字典,字典的值是某个网络的 id,可是 key 是模板中网卡使用的网络的名称,它的获取方式在此。若是你的模板中没有网卡,那你建立的虚拟机也不存在网络;若是你模板中有网卡,可是这个参数为 None,那么部署的虚拟机将使用和模板相同的网络;storage_mappings
:指定存储,它的值一样是个字典,它的使用方式和网络的一致,字典的 key 获取方式和上面的网络同样,可是 key 内容就有些多了,获取方式在此。没试过将这个参数指定为 None 会怎样,应该和模板同样吧;storage_provisioning
:DiskProvisioningType 类型或 None,磁盘的置备类型,能够在 LibraryItem.StorageGroupMapping 中单独指定,也能够在这里全局指定;storage_profile_id
:str 类型或 None,看你是否使用 storage profile 了,有用的话就指定吧;locale
:str 类型或 None,说是用来解析 OVF 的描述的,没懂是啥意思;flags
:str 列表或 None,说是用来部署用的,没懂是啥意思;additional_parameters
:它用来指定额外的参数,有虚拟机配置的,可是没找到如何配置;有 ip 地址分配的,可是不涉及到 ip 的指定;固然还有其余一堆看不懂的属性,有须要的人能够看看,目前没有发现它的做用;default_datastore_id
:str 类型或 None,默认存储,没搞懂它的意思。官方说若是为 None,服务器将选择默认的存储。我就搞不懂了,这个选项不是指定默认存储的吗,怎么没指定反而使用默认存储了,难以理解。deployment_spec = library_item.ResourcePoolDeploymentSpec(
name="test",
annotation="",
accept_all_eula=True,
network_mappings={"VM Network": network_id},
storage_mappings={"group1": storage_group_mapping},
)
复制代码
根据网络类型的不一样,network_id 的获取方式也不一样,通常有标准网络和分布式网络之分。
综合下来,从内容库中建立虚拟机的方式以下:
import urllib3
import requests
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)
user = ""
ip = ""
password = ""
network_name = ""
cluster_name = ""
datastore_name = ""
folder_name = ""
content_library_name = ""
template_name = ""
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)
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="",
accept_all_eula=True,
# 这个必定要有,不然不是精简置备
storage_provisioning=DiskProvisioningType('thin'),
network_mappings={你的配置: network_id},
storage_mappings={你的配置: storage_group_mapping},
)
# 执行这个语句就开始部署了,会持续 2 分钟左右,能够在 VMware 上看到具体的进度
result = library_item.deploy(library_item_id, deployment_target, deployment_spec)
复制代码
这样部署结束以后,虚拟机是建立完毕了,可是还有以下操做须要完成:
固然这里只是列出了建立的操做,你可能还有其余需求,可是这些需求 automation sdk 都没法完成,须要使用 pyvmomi。
这也是我以为不爽的地方,建立一个虚拟机还要使用两个 api,带来的问题就是要登陆两次(一个 api 一次)。或许官方也不肯意花时间将它们整合吧。
下面将一些常见的用法集中起来,便于查找。这一章你彻底能够跳过,直接看下面的 pyvmomi。
先找到这个机器,而后查看它的状态,接着关机。
vm = client.vcenter.VM.list(client.vcenter.VM.FilterSpec(names={'10.20.6.77-xyz.tomcat.xyz-common-main.01.sit'}))[0]
client.vcenter.vm.Power.get(vm.vm)
client.vcenter.vm.Power.stop(vm.vm)
复制代码
这就关机了。除此以外,还有下面这些电源控制的方法:
判断机器已经关机:
if status == Power.Info(state=Power.State.POWERED_OFF, clean_power_off=True):
print('VM is powered off')
复制代码
开关机能够经过 automation 或者 pyvmomi 都行,看你的兴趣了。
vm 是获取经过 list 获取到虚拟机对象以后,对象的 vm 属性。
client.vcenter.VM.delete(vm)
复制代码
删除虚拟机一样能够经过 pyvmomi。
列出 vCenter 中全部集群:
from com.vmware.vcenter_client import Cluster
client.vcenter.Cluster.list()
复制代码
得到集群名称对应的集群 id:
cluster_name = "POC"
client_id = client.vcenter.Cluster.list(Cluster.FilterSpec(names={cluster_name}))[0].cluster
复制代码
列出 vCenter 中全部文件夹:
from com.vmware.vcenter_client import Folder
client.vcenter.Folder.list()
复制代码
获取文件夹名称对应的文件夹 id:
folder_name = "35"
folder_id = client.vcenter.Folder.list(Folder.FilterSpec(names={folder_name}))[0].folder
复制代码
client.vcenter.Datastore.list()
复制代码
你可能须要进行过滤:
from com.vmware.vcenter_client import Datastore
datastore_name = 'xxx'
filter_spec = Datastore.FilterSpec(names={datastore_name})
datastore_summaries = client.vcenter.Datastore.list(filter_spec)
datastore_id = datastore_summaries[0].datastore
复制代码
client.vcenter.Network.list()
复制代码
过滤:
from com.vmware.vcenter_client import Network
filter = Network.FilterSpec(datacenters={datacenter},
names={std_porggroup_name},
types={Network.Type.STANDARD_PORTGROUP})
network_summaries = client.vcenter.Network.list(filter=filter)
network_id = network_summaries[0].network
复制代码
它的 type 有三种类型:
DISTRIBUTED_PORTGROUP
:vcenter 建立和管理的网络;OPAQUE_NETWORK
:VSphere 以外的设备所建立,可是 vSphere 却能够知道网络的名称和标识符,因此宿主机和虚拟机的网卡才可以链接到;STANDARD_PORTGROUP
:ESX 建立和管理的网络。client.vcenter.Network.list()
复制代码
过滤:
from com.vmware.vcenter_client import Network
filter = Network.FilterSpec(datacenters=set([datacenter]),
names=set([dv_portgroup_name]),
types=set([Network.Type.DISTRIBUTED_PORTGROUP]))
network_summaries = client.vcenter.Network.list(filter=filter)
if len(network_summaries) > 0:
network_id = network_summaries[0].network
print("Selecting Distributed Portgroup Network '{}' ({})".
format(dv_portgroup_name, network))
else:
print("Distributed Portgroup Network not found in Datacenter '{}'".
format(datacenter_name))
复制代码
首先列出 vCenter 中全部资源池:
from com.vmware.vcenter_client import ResourcePool
client.vcenter.ResourcePool.list()
复制代码
每一个集群下面必然存在一个资源池,所以咱们能够经过集群名来获取其下的资源池:
cluster_name = 'XXX'
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
复制代码
它的 api 说明在此,它实际上是个封装枚举类型的类,也就是你只能选择它指定的这些值,若是新版本 api 中增长了新的值,而你要和支持新 api 的服务端通讯的话,你要实例化这个类。
from com.vmware.vcenter.vm_client import GuestOS
GuestOS('CENTOS_6_64')
复制代码
官方说明在此。它用来描述如何启动一个虚拟机。
它接收的参数有:
type
:若是为 None,GuestOS 的默认值会被使用。你本身建立的话,能够指定为 BIOS 和 EFI;efi_legacy_boot
:是否使用传统的 EFI 启动模式,None 的话为 GuestOS 的默认值;network_protocol
:网络启动所使用,能够为 IPV4 和 IPV6。若是为 None,系统的默认值被使用;delay
:当虚拟器开机后,延迟多少毫秒开始启动;retry
:当虚拟机启动失败后,是否从新启动,默认为 false;retry_delay
:两次重启之间的间隔,单位是毫秒,默认为 10000;enter_setup_mode
:指定下次虚拟机启动时候直接进去 setup 模式。一旦虚拟机进入了 setup 模式,那么它的值会置为 false;CPU 支持如下参数:
count
:CPU 的核心数,它必须是 cores_per_socket 的倍数;cores_per_socket
:每插槽的 CPU 核心数,CPU 核心数必须是它的倍数;hot_add_enabled
:CPU 热添加是否启动,它的值只能在虚拟机关机的状况下修改;hot_remove_enabled
:是否启动 CPU 热减小cpu = com.vmware.vcenter.vm.hardware_client.Cpu.UpdateSpec(count=4, cores_per_socket=2, hot_add_enabled=True, hot_remove_enabled=True)
复制代码
上面的 cpu 变量能够直接传递给 CreateSpec。
内存可以的配置的参数以下:
size_mib
:单位为 M;hot_add_enabled
:启用热添加。GiBMemory = 1024
memory = com.vmware.vcenter.vm.hardware_client.Memory.UpdateSpec(4*GiBMemory, True)
复制代码
上面的 memory 变量能够直接传递给 CreateSpec。
磁盘支持如下参数的配置:
type
:能够为 IDE、SATA 和 SCSI,若是不指定,会使用特定于 guest 的类型;ide
:scsi
:sata
:这三个的选项都同样,均可以指定 bus(磁盘设备链接的总线号)和 unit(磁盘设备的单元号)。若是适配器上没有可用的链接,那么请求将会被拒绝;backing
:它和下面的 newVmdk
必须指定一个,指定它的话,你必须指定已存在的 vmdk 的路径。若是为 None,虚拟磁盘不能链接任何已存在的后端;new_vmdk
:为虚拟磁盘建立一个新的 vmdk backing,若是为 None,不会建立新的 vmdk 文件。若是要建立的话,能够指定容量。from com.vmware.vcenter.vm.hardware_client import (
Cpu, Memory, Disk, Ethernet, Cdrom, Serial, Parallel, Floppy, Boot)
GiB = 1024 * 1024 * 1024
disks=[
Disk.CreateSpec(type=Disk.HostBusAdapterType.SCSI,
scsi=ScsiAddressSpec(bus=0, unit=0),
new_vmdk=Disk.VmdkCreateSpec(name='boot',
capacity=40 * GiB)),
Disk.CreateSpec(new_vmdk=Disk.VmdkCreateSpec(name='data1',
capacity=10 * GiB)),
Disk.CreateSpec(new_vmdk=Disk.VmdkCreateSpec(name='data2',
capacity=10 * GiB))
]
复制代码
网卡支持以下配置:
type
:它的值是一个类实现的枚举类型,若是为 None,guest 特定类型的网卡会被建立,它的值有:
upt_compatibility_enabled
:是否兼容 Universal Pass-Through(UPT),默认是 false,不兼容;mac_type
:Mac 地址类型,若是为 None,使用默认值 Ethernet.MacAddressType.GENERATED;
mac_address
:若是 Mac 地址类型指定为手动的话,在这里指定 Mac 地址;pci_slot_number
:网卡链接到哪一个 PCI 总线上。若是总线地址无效,服务器将在虚拟机启动时或设备热添加时更改。若是为 None,将在虚拟机开机时自动选择一个可用的总线地址;wake_on_lan_enabled
:是否启用 wake-on-LAN,若是为 None 就不启用;backing
:虚拟网卡的物理后端。若是为 None,系统会自动寻找一个合适的后端,若是没有找到,请求失败。物理后端具备下面这些属性:
early-binding
(也称为静态),当虚拟机配置使用这个端口时,端口将自动分配,这个属性的值,将决定端口是自动仍是手动分配。若是端口组类型是 ephemeral
,当虚拟机开机,而且网卡链接,这个端口会自动建立并分配。这个属性不能被指定,由于在使用以前不存在空闲的端口,它也许在网络属性为 static/early-binding 时,用来指定一个端口。若是为 None,取决于端口组类型体现的策略将端口自动分配给虚拟网卡;start_connected
:虚拟机若是开机,网卡是否链接网络,默认为 false;allow_guest_control
:guest 是否可以链接或断开这个网卡;from com.vmware.vcenter.vm.hardware_client import (
Cpu, Memory, Disk, Ethernet, Cdrom, Serial, Parallel, Floppy, Boot)
nics = [
Ethernet.CreateSpec(
start_connected=True,
mac_type=Ethernet.MacAddressType.MANUAL,
mac_address='11:23:58:13:21:34',
backing=Ethernet.BackingSpec(
type=Ethernet.BackingType.STANDARD_PORTGROUP,
network=self.standard_network)),
Ethernet.CreateSpec(
start_connected=True,
mac_type=Ethernet.MacAddressType.GENERATED,
backing=Ethernet.BackingSpec(
type=Ethernet.BackingType.DISTRIBUTED_PORTGROUP,
network=self.distributed_network)),
]
复制代码
经过 ovf 模板部署虚拟机,你得知道这个模板使用的网络,否则你没法指定 LibraryItem.ResourcePoolDeploymentSpec 中的 network_mappings。可能提及来有些难懂,我演示一下你就清楚了。
咱们首先得到 ovf 模板的详细信息:
from com.vmware.vcenter.ovf_client import LibraryItem
# ovf_id 也就是内容库中虚拟机模板的 id,前面已经得到了,这里就不演示获取方式了
ovf_id = 'afb120c4-a173-4588-af9c-641a09a58862'
# deployment_target 前面也获取了
deployment_target = library_item.DeploymentTarget(resource_pool_id=resource_pool_id, folder_id=folder_id)
ovf_lib_item_service = LibraryItem(stub_config)
# 而后就能够得到 ovf 模板的详细信息了,这个命令的执行时间有些长
ovf_lib_item_service.filter(ovf_id, deployment_target)
复制代码
信息有些多,这里只截取部分咱们想要的:
networks=['UAT53'], storage_groups=['group1']
复制代码
这里的 UAT53 就是 network_mappings 中的 key,它的 value 则是你要替换的网络 id。group1 则是 storage_mappings 的 key。
若是列表中的值有多个,相应的 network_mappings 或 storage_mappings 中的 key 就须要指定多个了。通常模板不怎么变,因此这个值第一次查出来以后,后面部署的时候就不必查了,直接放在配置文件中就好。
api 官方说明在此。
主要仍是为了建立 LibraryItem.ResourcePoolDeploymentSpec.storage_mappings 这个字典中的 value,它经过 LibraryItem.StorageGroupMapping 这个类来建立。
这个类接受以下参数:
LibraryItem.StorageGroupMapping(type=None, datastore_id=None, storage_profile_id=None, provisioning=None)
复制代码
参数说明:
type
:官方实现的枚举类,只有两种类型,一种是 DATASTORE,另外一种是 STORAGE_PROFILE。若是是 DATASTORE 类型,写起来是这样的 LibraryItem.StorageGroupMapping.Type('DATASTORE');datastore_id
:若是使用 DATASTORE,那么若是指定它的 id,方法在此;storage_profile_id
:若是使用 STORAGE_PROFILE,那么须要指定它的 id;provisioning
:磁盘的制备类型,有厚置备、精简置备、厚置备延迟置零三种,因此它一样是个枚举类。若是要使用精简置备,写起来是这样的 DiskProvisioningType('thin')。若是为 None,LibraryItem.ResourcePoolDeploymentSpec.storage_provisioning 将被使用。示例:
from com.vmware.vcenter.ovf_client import LibraryItem, DiskProvisioningType
datastore_id = 'datastore-8133'
ovf_lib_item_service = LibraryItem(stub_config)
ovf_lib_item_service.StorageGroupMapping(
type=ovf_lib_item_service.StorageGroupMapping.Type('DATASTORE'),
datastore_id=datastore_id,
provisioning=DiskProvisioningType('thin')
)
复制代码
pyvmomi 用来给建立好的虚拟机修改配置,指定 ip 地址等操做。这样会形成一个问题,那就是建立一个虚拟机须要登陆两次,一次是经过 vsphere automation sdk,还有一次就是使用 pyvmomi。所以若是只有一个 vcenter,那仍是直接使用 pyvmomi 来的直接。
这里 是 pyvmomi 的使用示例,东西挺多,可是我要修改虚拟机配置和分配 ip 的操做都没有在其中找到,估计也是我没有细看的缘由。
先登陆。
from pyVim.connect import SmartConnect, Disconnect
import atexit
import ssl
import sys
context = None
if hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context()
si = SmartConnect(host='..',
user='administrator@vsphere.local',
pwd='..',
sslContext=context)
if not si:
print("Could not connect to the specified host using specified "
"username and password")
sys.exit(1)
复制代码
pyvmomi 列出主机的方式是遍历全部 vCenter 文件夹,也就是遍历 VMs and Templates 这页。先从数据中心开始,而后一级级往下。这就和 Linux 的文件系统同样,都是从根开始。
有些虚拟机没有放在文件夹下面,所以遍历的时候须要判断对象是否具备 childEntity 属性,若是有,表示它是个文件夹,能够遍历它来得到它里面的全部虚拟机。文件夹能够嵌套,所以你可能须要使用递归进行遍历了。
si.content.rootFolder.childEntity[0].vmFolder.childEntity[14].childEntity[1].name
复制代码
第一个 childEntity 表示数据中心,第二个 childEntity 表示文件夹,第三个 childEntity 表示文件夹下面的虚拟机。
虚拟机建立完成以后,咱们首先得使用 pyvmomi 找到它,而后再进行操做。
from pyVim.connect import Disconnect, SmartConnectNoSSL, SmartConnect
from pyVmomi import vim, vmodl
import atexit
service_instance = SmartConnectNoSSL(host=ip, user=user, pwd=password)
atexit.register(Disconnect, service_instance)
content = service_instance.RetrieveContent()
复制代码
官方的示例中查找一个虚拟机都是将全部的虚拟机列出来,而后遍历、判断:
container = content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True)
复制代码
可是对于 content.viewManager.CreateContainerView
的使用咱们却并不清楚。咱们能够在 api 文档中查找 Managed Object Types/ViewManager,能够看到 ViewManager 的类型是 vim.view.ViewManager。
反正它的第一个参数是一个文件夹类型,第二个参数则是要查看的类型,第三个是是否递归的意思。其实,咱们从根目录查虚拟机有些麻烦,一是数量多,二是文件夹存在嵌套的可能,递归起来得花更多时间。反正咱们建立虚拟机的时候就指定了文件夹,所以咱们能够先找到这个文件夹,而后经过这个文件夹来查找虚拟机。
# 建立一个自定义异常
class TypeNotFoundException(Exception):
def __init__(self, msg):
self.msg = msg
def get_obj(content, folder, vimtype: list, name: str):
obj = None
container = content.viewManager.CreateContainerView(folder, vimtype, True)
for c in container.view:
if c.name == name:
obj = c
break
else:
raise TypeNotFoundException("did not find {} in {}".format(vimtype, folder))
container.Destroy()
return obj
folder = get_obj(content, content.rootFolder, [vim.Folder], folder_name)
vm = get_obj(content, folder, [vim.VirtualMachine], vm_name)
复制代码
vm 的 config 里面有全部该虚拟机的配置。
虚拟机找到以后,下一步就是修改它的 ip 地址,这经过 VMware 的自定义规范来完成。可是在操做以前,确保你的虚拟机安装了 vm_tools。
CentOS 6 安装 vm_tools 可能有些麻烦,还得挂盘按照官方的要求一步步进行,可是在 CentOS 7 上你只须要安装 open-vm-tools(最小化安装中已经自带了),而后安装 perl 便可。
guest_map = vim.vm.customization.AdapterMapping()
guest_map.adapter = vim.vm.customization.IPSettings()
guest_map.adapter.ip = vim.vm.customization.FixedIp()
# 配置 ip、掩码和网关
guest_map.adapter.ip.ipAddress = ""
guest_map.adapter.subnetMask = ""
guest_map.adapter.gateway = ""
ident = vim.vm.customization.LinuxPrep()
ident.hostName = vim.vm.customization.FixedName()
# 主机名可包含字母数字字符和连字符 (-)。但不能包含句号 (.) 或空格,而且不能只由数字组成。名称不区分大小写。
ident.hostName.name = "hehe"
globalip = vim.vm.customization.GlobalIPSettings()
customspec = vim.vm.customization.Specification()
customspec.nicSettingMap = [guest_map]
customspec.identity = ident
customspec.globalIPSettings = globalip
task = vm.Customize(spec=customspec)
复制代码
执行完毕以后,经过 task 这个变量来获得是否配置成功。由于接下来不少的操做都会产生这个变量,所以能够经过下面这个函数来判断操做是否成功:
def wait_for_task(task):
while task.info.state == "running" or task.info.state == "queued":
time.sleep(1)
if task.info.state == "success":
return
logging.error('error message')
复制代码
ip 配置完成以后会将主机名改成你指定的,且会将主机名添加到 /etc/hosts
文件中,你是你须要注意的。另外,在 CentOS 6 上,在将虚拟机做为 ovf 模板以前,最好将 /etc/udev/rules.d/70-persistent-net.rules
文件直接删除,否则你从虚拟机建立机器并为它配置上 ip 以后,可能会致使网卡名称变成 eth1。
这里的配置指的是 cpu 和内存:
cspec = vim.vm.ConfigSpec()
# 4 核心
cspec.numCPUs = 4
# 将 4 核心分配在两个插槽,一个插槽两核心
cspec.numCoresPerSocket = int(cspec.numCPUs / 2)
# 8G
cspec.memoryMB = 1024 * 8
# 启动 cpu 热添加
cspec.cpuHotAddEnabled = True
# 启动内存在线扩缩
cspec.memoryHotAddEnabled = True
task = vm.Reconfigure(cspec)
# 使用上面定义的函数来判断是否应用成功
wait_for_task(task)
复制代码
添加磁盘也算是常见的操做了,下面的代码你直接照搬便可,这也是官方的示例。若是你要添加多块就调用屡次下面的函数。
def add_disk(capacity) spec = vim.vm.ConfigSpec() dev_changes = [] # capacity 单位为 G new_disk_kb = capacity * 1024 * 1024 unit_number = 0 # 遍历全部的硬件设备,找合适的位置添加 for dev in vm.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.ReconfigVM_Task(spec=spec)
wait_for_task(task)
复制代码
一些经常使用的操做就弄完了,有其余的配置要求的能够去官方示例中查找。最后,配置完成后,你须要开机。开机可使用前面提到的 automation sdk 的方式,也可使用 pyvmomi。
经过上面一套代码下来,可以完成虚拟机从无到有的过程,可是这只是将它实现了而已,如何更简单地建立才是咱们须要追求的。
从上面的代码中能够看出,建立一个虚拟机须要以下属性:
从个人经验中看,若是标准化作的好了,只要给一个主机名就能够了,其余全部属性彻底能够经过主机名得到。
固然,这种绝对标准化的场景很难遇到,达成起来也不容易。所以能够提供一个 rest api,将全部(或者部分)属性都经过 api 传递过来,而后进行建立。
个人我的建议是模板就是刚最小化安装的系统,不要有任何多余的操做。在建立虚拟机完毕以后,须要作的操做均可以经过初始化脚本完成。个人作法是每建立完一台机器,等待开机时间以后使用 ansible 连上去进行初始化操做。
我会为每台机器启动一个初始化线程,这个线程调用 ansible-playbook 命令(不必使用 ansible api),只为这一台机器进行初始化。这样只要判断 ansible-playbook 命令的返回值就可以判断该虚拟机是否初始化成功。
建立虚拟机和初始化要分为两个步骤,否则你初始化失败了,重试的话难道要从建立机器开始吗?不一样的步骤重试方式天然是不同的。
建立机器只是做为对 automation sdk 的了解,并不实用,由于咱们会从模板生成机器,而并不直接建立。
建立机器须要指定诸如集群、数据存储、文件夹、数据中心等属性,而后根据这些属性来建立一个 placement。从字面意思来看,placement 是一个放置虚拟机的属性,指明虚拟机应该放在哪。
建立 placement 须要上述所说的属性,就拿数据存储来讲,你不能直接提供给它数据存储的名字,它不认这个,想必有过前面的经验你也知道了,它要的是它内部维护的所谓的编号这样的东西,这个东西经过数据存储的名字来得到。
好比我先根据数据存储的名字来得到这个数据存储的对象:
from com.vmware.vcenter_client import Datastore
filter_spec = Datastore.FilterSpec(names={datastore_name})
datastores = client.vcenter.Datastore.list(filter_spec)
复制代码
很显然,最终的结果是一个列表。若是列表长度为 0,那表示没有这个数据存储。假如你给的名称正确的话,它的长度为 1,那数据存储的编号就是这个:
datastores[0].datastore
复制代码
其余属性也同样,只不过集群为 VAR[0].cluster,文件夹为 VAR[0].folder。
前面也提到了,Placement 的做用就是指定虚拟机建立在哪,所以它接收的参数有:
folder
:文件夹resource_pool
:资源池host
:宿主机。若是资源池和宿主机同时指定了,你必须得保证资源池属于这个宿主机;若是宿主机和集群也都同时执行,你也必须保证宿主机属于集群;cluster
:集群。若是集群和资源池同时指定,资源池必须属于集群;datastore
:数据存储经过这些参数就能够建立一个 placement 对象。
接下来要给出全部建立一个虚拟机的属性了,它们共同构成 CreateSpec,最后才根据 CreateSpec 建立虚拟机。咱们以前已经得到 placement 了,接下来还能够得到标准交换机或分布式交换机。有了这些以后,就能够尝试着建立 CreateSpec 了。
它接收的参数特别多:
guest_os
:指定操做系统类型,建立的方式在此;name
:指定虚拟机的名称,若是为空(None),服务器会自动为你生成;placement
:以前已经建立了,就很少提了。官方表示当前版本还必需要指定,将来服务端自动帮你选择一个合适的地方放虚拟机;hardware_version
:为空(None)就好,服务端会帮你选择最新的;boot
:启动选项,若是为 None,guest_os 默认值会被使用。它的使用方式在此;boot_devices
:从什么设备启动系统,通常都为系统硬盘,它的值是个列表。若是为 None,一个应用于服务器的启动列表被使用;cpu
:CPU 相关的配置,它的建立方式在此。若是为 None,使用系统默认值;memory
:内存相关的配置,它是建立方式在此。若是为 None,使用系统默认值;disks
:磁盘配置,它的是值是一个列表,单个磁盘的建立方式在此。若是为 None,单个 guest 特定的大小的空虚拟磁盘被建立在虚拟机配置的相同存储上;nics
:网卡配置,它的值是一个列表,单个网卡的建立方式在此。若是为 None,不会建立任何网卡;cdroms
:floppies
:parallel_ports
:serial_ports
:sata_adapters
:SATA 适配器列表,为 None 让它自动建立吧;scsi_adapters
:同上。贴一个官方示例吧:
guest_os = testbed.config['VM_GUESTOS']
iso_datastore_path = testbed.config['ISO_DATASTORE_PATH']
serial_port_network_location = \
testbed.config['SERIAL_PORT_NETWORK_SERVER_LOCATION']
GiB = 1024 * 1024 * 1024
GiBMemory = 1024
vm_create_spec = VM.CreateSpec(
guest_os=guest_os,
name=self.vm_name,
placement=self.placement_spec,
hardware_version=Hardware.Version.VMX_11,
cpu=Cpu.UpdateSpec(count=2,
cores_per_socket=1,
hot_add_enabled=False,
hot_remove_enabled=False),
memory=Memory.UpdateSpec(size_mib=2 * GiBMemory,
hot_add_enabled=False),
disks=[
Disk.CreateSpec(type=Disk.HostBusAdapterType.SCSI,
scsi=ScsiAddressSpec(bus=0, unit=0),
new_vmdk=Disk.VmdkCreateSpec(name='boot',
capacity=40 * GiB)),
Disk.CreateSpec(new_vmdk=Disk.VmdkCreateSpec(name='data1',
capacity=10 * GiB)),
Disk.CreateSpec(new_vmdk=Disk.VmdkCreateSpec(name='data2',
capacity=10 * GiB))
],
nics=[
Ethernet.CreateSpec(
start_connected=True,
mac_type=Ethernet.MacAddressType.MANUAL,
mac_address='11:23:58:13:21:34',
backing=Ethernet.BackingSpec(
type=Ethernet.BackingType.STANDARD_PORTGROUP,
network=self.standard_network)),
Ethernet.CreateSpec(
start_connected=True,
mac_type=Ethernet.MacAddressType.GENERATED,
backing=Ethernet.BackingSpec(
type=Ethernet.BackingType.DISTRIBUTED_PORTGROUP,
network=self.distributed_network)),
],
cdroms=[
Cdrom.CreateSpec(
start_connected=True,
backing=Cdrom.BackingSpec(type=Cdrom.BackingType.ISO_FILE,
iso_file=iso_datastore_path)
)
],
serial_ports=[
Serial.CreateSpec(
start_connected=False,
backing=Serial.BackingSpec(
type=Serial.BackingType.NETWORK_SERVER,
network_location=serial_port_network_location)
)
],
parallel_ports=[
Parallel.CreateSpec(
start_connected=False,
backing=Parallel.BackingSpec(
type=Parallel.BackingType.HOST_DEVICE)
)
],
floppies=[
Floppy.CreateSpec(
backing=Floppy.BackingSpec(
type=Floppy.BackingType.CLIENT_DEVICE)
)
],
boot=Boot.CreateSpec(type=Boot.Type.BIOS,
delay=0,
enter_setup_mode=False
),
# TODO Should DISK be put before CDROM and ETHERNET? Does the BIOS
# automatically try the next device if the DISK is empty?
boot_devices=[
BootDevice.EntryCreateSpec(BootDevice.Type.CDROM),
BootDevice.EntryCreateSpec(BootDevice.Type.DISK),
BootDevice.EntryCreateSpec(BootDevice.Type.ETHERNET)
]
)
复制代码
有了 createSpec 就能够直接建立虚拟机了:
vm = self.client.vcenter.VM.create(vm_create_spec)
复制代码
使用 vsphere automation sdk 链接 vcenter 的时候会报下面的错:
/usr/local/python3/bin/python3 /root/PycharmProjects/demo/zabbix/tmp.py
Traceback (most recent call last):
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 453, in wrap_socket
cnx.do_handshake()
File "/usr/local/python3/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1907, in do_handshake
self._raise_ssl_error(self._ssl, result)
File "/usr/local/python3/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1632, in _raise_ssl_error
raise SysCallError(-1, "Unexpected EOF")
OpenSSL.SSL.SysCallError: (-1, 'Unexpected EOF')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/connectionpool.py", line 594, in urlopen
self._prepare_proxy(conn)
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/connectionpool.py", line 805, in _prepare_proxy
conn.connect()
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/connection.py", line 344, in connect
ssl_context=context)
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 357, in ssl_wrap_socket
return context.wrap_socket(sock)
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 459, in wrap_socket
raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: SysCallError(-1, 'Unexpected EOF')",)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/python3/lib/python3.6/site-packages/requests/adapters.py", line 449, in send
timeout=timeout
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/connectionpool.py", line 638, in urlopen
_stacktrace=sys.exc_info()[2])
File "/usr/local/python3/lib/python3.6/site-packages/urllib3/util/retry.py", line 398, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='', port=443): Max retries exceeded with url: /api (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')",),))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/root/PycharmProjects/demo/zabbix/tmp.py", line 52, in <module>
client = create_vsphere_client(server=ip, username=user, password=password, session=session)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/vsphere/client.py", line 170, in create_vsphere_client
hok_token=hok_token, private_key=private_key)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/vsphere/client.py", line 111, in __init__
session_id = session_svc.create()
File "/usr/local/python3/lib/python3.6/site-packages/com/vmware/cis_client.py", line 197, in create
return self._invoke('create', None)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 317, in _invoke
return self._api_interface.native_invoke(ctx, _method_name, kwargs)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 243, in native_invoke
method_result = self.invoke(ctx, method_id, data_val)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 179, in invoke
ctx)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/security/client/security_context_filter.py", line 99, in invoke
self, service_id, operation_id, input_value, new_ctx)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/provider/filter.py", line 76, in invoke
service_id, operation_id, input_value, ctx)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/protocol/client/msg/json_connector.py", line 79, in invoke
response = self._do_request(VAPI_INVOKE, params)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/protocol/client/msg/json_connector.py", line 120, in _do_request
headers=request_headers, body=request_body))
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/protocol/client/rpc/requests_provider.py", line 98, in do_request
cookies=http_request.cookies, timeout=timeout)
File "/usr/local/python3/lib/python3.6/site-packages/requests/sessions.py", line 533, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/python3/lib/python3.6/site-packages/requests/sessions.py", line 646, in send
r = adapter.send(request, **kwargs)
File "/usr/local/python3/lib/python3.6/site-packages/requests/adapters.py", line 514, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='', port=443): Max retries exceeded with url: /api (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')",),))
Exception ignored in: <bound method VsphereClient.__del__ of <vmware.vapi.vsphere.client.VsphereClient object at 0x7f17d4c42908>>
Traceback (most recent call last):
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/vsphere/client.py", line 136, in __del__
if hasattr(self, 'session'):
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 415, in __getattr__
return getattr(self._stub_factory, name)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 415, in __getattr__
return getattr(self._stub_factory, name)
File "/usr/local/python3/lib/python3.6/site-packages/vmware/vapi/bindings/stub.py", line 415, in __getattr__
return getattr(self._stub_factory, name)
[Previous line repeated 328 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
复制代码
搜一个下,发现这样一个回答,它的意思是 vcenter 只支持 3DES 做为 cipher,而 requests 却将其从默认的 cipher 列表中将其剔除了,由于它默认并不安全。
可是我使用 openssl 命令查看了一把,发现并非这个缘由,由于 vcenter 使用了 tls1.2,而并不是 SSLv1/v2/v3。
# openssl s_client -connect IP:443
...
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 1418 bytes and written 415 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID:
Session-ID-ctx:
Master-Key: E3C9A906DA95855044E8DC4D512084A98A605C3B538505956568585C3555FF371E35F8FE05043734EC73E5D59346B3FC
Key-Arg : None
Krb5 Principal: None
PSK identity: None
PSK identity hint: None
Start Time: 1552027935
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
read:errno=0
复制代码
最后经过抓包发现,原来是由于我系统使用了代理,https 通过代理貌似就会出问题。不知道这是否是 Python 的问题,当 pip 使用代理来安装的时候,也会报 https 相关的错误。其余语言写的软件就没有这样的问题。