想自動分 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 相關的錯誤。其餘語言寫的軟件就沒有這樣的問題。