VMware Python api

想自動分 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

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:

  • Local:items 會被存放在某個的 vCenter 上,可是能夠發佈出來,讓其餘 vCenter 中的用戶來訂閱;
  • Subscribed:訂閱發佈出來的內容庫會建立一個訂閱庫,訂閱庫能夠建立在和發佈庫相同或不一樣的 vCenter 上。訂閱庫經過手動或者自動地同步發佈庫讓其內容保持最新。管理員能夠改變文件內容,但訂閱者只能使用文件。

內容庫中除了能夠直接上傳 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)
複製代碼

建立 ResourcePoolDeploymentSpec

就如同建立虛擬機會建立一個所謂的 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)
複製代碼

這樣部署結束以後,虛擬機是建立完畢了,可是還有以下操做須要完成:

  • 更改配置
  • 添加磁盤
  • 配置 ip
  • 開機

固然這裏只是列出了建立的操做,你可能還有其餘需求,可是這些需求 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)
複製代碼

這就關機了。除此以外,還有下面這些電源控制的方法:

  • reset:重啓;
  • suspend:掛起;
  • start:啓動。

判斷機器已經關機:

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
複製代碼

建立 GuestOS

它的 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

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 特定類型的網卡會被建立,它的值有:
    • E1000
    • E1000E
    • PCNET32
    • VMXNET
    • VMXNET2
    • VMXNET3
  • upt_compatibility_enabled:是否兼容 Universal Pass-Through(UPT),默認是 false,不兼容;
  • mac_type:Mac 地址類型,若是爲 None,使用默認值 Ethernet.MacAddressType.GENERATED
    • ASSIGNED:Mac 地址被 vCenter 分配;
    • GENERATED:Mac 地址被自動生成;
    • MANUAL:手動配置;
  • mac_address:若是 Mac 地址類型指定爲手動的話,在這裏指定 Mac 地址;
  • pci_slot_number:網卡鏈接到哪一個 PCI 總線上。若是總線地址無效,服務器將在虛擬機啓動時或設備熱添加時更改。若是爲 None,將在虛擬機開機時自動選擇一個可用的總線地址;
  • wake_on_lan_enabled:是否啓用 wake-on-LAN,若是爲 None 就不啓用;
  • backing:虛擬網卡的物理後端。若是爲 None,系統會自動尋找一個合適的後端,若是沒有找到,請求失敗。物理後端具備下面這些屬性:
    • type:後端類型:
      • DISTRIBUTED_PORTGROUP:分佈式虛擬交換機;
      • HOST_DEVICE:傳統的宿主機網卡。導入的虛擬機也許有一個這種類型的網卡,可是這種類型的後端不能用來建立或升級虛擬網卡;
      • OPAQUE_NETWORK:vSphere 以外的組件所建立和管理的網絡;
      • STANDARD_PORTGROUP:vSphere 標準端口組網絡後端;
    • network:網絡名稱,這個網絡名稱一樣是它內部維護的編號,能夠在這裏這裏得到。可是上面四種後端類型中,HOST_DEVICE 不支持此參數;
    • distributed_port:虛擬網卡支持的分佈式虛擬端口的 key。取決於端口組的類型,端口也許使用這個字段指定。若是端口組類型是 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 模板網絡

經過 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 就須要指定多個了。通常模板不怎麼變,因此這個值第一次查出來以後,後面部署的時候就不必查了,直接放在配置文件中就好。

指定 ovf 模板部署虛擬機的存儲

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

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 地址

虛擬機找到以後,下一步就是修改它的 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。

自動化建立虛擬機

經過上面一套代碼下來,可以完成虛擬機從無到有的過程,可是這只是將它實現了而已,如何更簡單地建立纔是咱們須要追求的。

從上面的代碼中能夠看出,建立一個虛擬機須要以下屬性:

  • ip
  • 主機名
  • cpu
  • 內存
  • 磁盤
  • 模板名稱
  • 數據存儲
  • 資源池
  • 網絡
  • 文件夾

從個人經驗中看,若是標準化作的好了,只要給一個主機名就能夠了,其餘全部屬性徹底能夠經過主機名得到。

  • 從主機名中能夠得到這個機器所屬的項目,所以能夠從 cmdb 的地址池中得到 ip,同時能夠得到文件夾的名稱(若是經過網段劃分的話)。
  • 從主機名中能夠得到這個機器跑的應用,所以能夠得到 cpu、內存、磁盤、資源池、數據存儲以及模板名稱等屬性。

固然,這種絕對標準化的場景很難遇到,達成起來也不容易。所以能夠提供一個 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

前面也提到了,Placement 的做用就是指定虛擬機建立在哪,所以它接收的參數有:

  • folder:文件夾
  • resource_pool:資源池
  • host:宿主機。若是資源池和宿主機同時指定了,你必須得保證資源池屬於這個宿主機;若是宿主機和集羣也都同時執行,你也必須保證宿主機屬於集羣;
  • cluster:集羣。若是集羣和資源池同時指定,資源池必須屬於集羣;
  • datastore:數據存儲

經過這些參數就能夠建立一個 placement 對象。

建立 CreateSpec

接下來要給出全部建立一個虛擬機的屬性了,它們共同構成 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 相關的錯誤。其餘語言寫的軟件就沒有這樣的問題。

參考

www.starwindsoftware.com/blog/workin…