使用 govmomi 建立 VMware 虛擬機

以前我寫了兩篇文章,都是關於 VMware api 的:python

之因此使用 govmomi 緣由有不少,有性能的緣由,go 性能強出 Python 太多;有我的的緣由,本人比較喜歡 go;固然還有公司的緣由,公司的運維平臺是 go 寫的。linux

固然,不管是 govmomi 也好,pyvmomi 也好,仍是其餘各類 vmomi 也好,都是 VMware 自身 api 的封裝,使用起來都是大同小異,適合本身的就是最好的,不必糾結太多。git

須要說明的是,本文建立虛擬機是將 ovf 模板放入內容庫後,經過內容庫部署的,而非直接經過模板建立。另外,6.7 的內容庫除了支持 ovf 這種模板類型以外,還支持 vm-template,我沒有來得及研究,有興趣的童鞋能夠研究看看。github

OK,正文開始。golang

內容庫

內容庫是 VMware 6.0 新增的功能,使用它能夠跨 vsphere 共享 iso 文件、虛擬機模板等,當你有多個 vsphere 時,使用起來會很便利。要使用內容庫,你首先得建立一個內容庫(library),給它一個名稱,而後就能夠上傳文件啊模板啊到這個庫中,每一個文件或者模板稱爲一個 item。數據庫

當你在一個 vsphere 中建立內容庫後,其餘 vsphere 就訂閱該內容庫了,這樣內容庫中的文件就會同步到全部訂閱它的內容庫中,經過這種方式來保證多個 vsphere 內容庫中文件的一致性。api

內容庫的使用這裏就不演示了,網上教程不少,隨便就能找到。這裏假設你已經有了一個內容庫,而且裏面已經有了一個 ovf 模板。要想使用內容庫,咱們就必須先找到這個內容庫對象,再經過它來找到其中的 ovf 模板這個對象。markdown

想必經過以前的文章你已經知道了怎麼安裝和登陸 vsphere 了,那這裏就直接登陸了:網絡

const (
    ip = ""
    user = ""
    password = ""
)

u := &url.URL{
    Scheme: "https",
    Host:   ip,
    Path:   "/sdk",
}

ctx := context.Background()
u.User = url.UserPassword(user, password)
client, err := govmomi.NewClient(ctx, u, true)
if err != nil {
    fmt.Fprintf(os.Stderr, "Login to vsphere failed, %v", err)
    os.Exit(1)
}
複製代碼

本文須要導入的庫有這些:app

import (
	"context"
	"fmt"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vapi/library"
	"github.com/vmware/govmomi/vapi/rest"
	"github.com/vmware/govmomi/vapi/vcenter"
	"net/url"
	"os"
)
複製代碼

後面就不貼出來了,基本 IDE 都會自動補全。

這裏的 client 就能夠用來操做整個 vsphere 了,不過經過它沒法直接操做內容庫,咱們必須先要經過它來得到 rest client:

rc := rest.NewClient(client.Client)
if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
    fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
    os.Exit(1)
}
複製代碼

這就至關於又從新登陸了一次,不知道爲何 VMware 這麼設計,直接經過 client 很差麼?有了 rc 以後,就能夠操做內容庫了:

func getLibraryItem(ctx context.Context, rc *rest.Client) (*library.Item, error) {
	const (
		libraryName = ""
		libraryItemName = ""
		libraryItemType = "ovf"
	)

	// 須要經過 rc 來得到 library.Manager 對象
	m := library.NewManager(rc)
	// 經過內容庫的名稱來查找內容庫
	libraries, err := m.FindLibrary(ctx, library.Find{Name: libraryName})
	if err != nil {
		fmt.Printf("Find library by name %s failed, %v", libraryName, err)
		return nil, err
	}

	// 判斷是否找到
	if len(libraries) == 0 {
		fmt.Printf("Library %s was not found", libraryName)
		return nil, fmt.Errorf("library %s was not found", libraryName)
	}

	if len(libraries) > 1 {
		fmt.Printf("There are multiple libraries with the name %s", libraryName)
		return nil, fmt.Errorf("there are multiple libraries with the name %s", libraryName)
	}

	// 在內容庫中經過 ovf 模板的名稱來找到 ovf 模板
	items, err := m.FindLibraryItems(ctx, library.FindItem{Name: libraryItemName,
		Type: libraryItemType, LibraryID: libraries[0]})

	if err != nil {
		fmt.Printf("Find library item by name %s failed", libraryItemName)
		return nil, fmt.Errorf("find library item by name %s failed", libraryItemName)
	}

	if len(items) == 0 {
		fmt.Printf("Library item %s was not found", libraryItemName)
		return nil, fmt.Errorf("library item %s was not found", libraryItemName)
	}

	if len(items) > 1 {
		fmt.Printf("There are multiple library items with the name %s", libraryItemName)
		return nil, fmt.Errorf("there are multiple library items with the name %s", libraryItemName)
	}

	item, err := m.GetLibraryItem(ctx, items[0])
	if err != nil {
		fmt.Printf("Get library item by %s failed, %v", items[0], err)
		return nil, err
	}

	return item, nil
}
複製代碼

能夠看到,找到 ovf 模板仍是挺繞的。若是你確保你的內容庫以及裏面的 ovf 不會被刪除重建的話,你能夠首先找到這個 ovf 模板,記下它的 id。等下次要查找的時候,直接調用 m.GetLibraryItem() 就能夠了。這樣就不用找庫,再經過庫來找 item 了。

資源類型

模板有了,如今要作的就是肯定要將虛擬機部署在哪。vsphere 中資源是有層級的,最外層就是數據中心,全部的資源都得屬於某個數據中心,而一個 vsphere 中能夠存在多個數據中心。

因此首先你得確認你要將虛擬機部署到哪一個數據中心,而後再肯定放在哪一個集羣的哪一個資源池、哪一個存儲、哪一個網絡、哪一個文件夾等,等這些都肯定了就能夠部署了。

所以咱們首先要經過名稱來找到這些資源,從數據中心開始。

數據中心

其實建立虛擬機不須要用到數據中心,可是因爲其餘的資源都在數據中心下面,因此你能夠了解了解,固然你不看也沒啥影響。

// 經過這個 finder 你能夠列出 VMware 中的全部資源
finder := find.NewFinder(client.Client)
dcs, err := finder.DatacenterList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list data center at vc %s, %v\n", ip, err)
    os.Exit(1)
}

for _, dc := range dcs {
    // 這個惟一名稱是 vshpere 中的 id,大概相似於數據庫中的自增 id,同一個 vsphere 中惟一,多個 vsphere 不惟一
    dcUniqName := dc.Reference().Value
    // 類型就是 DataCenter
    dcType := dc.Reference().Type
    // 數據中心的名稱
    dcName := dc.Name()
    // 數據中心的路徑,VMware 中的資源相似於 linux 的文件系統,從根開始,每一個資源都有它惟一的路徑
    // 若是你知道一個資源的 path,那麼你就能夠直接經過這個路徑找到這個資源,後續會提到
    dcPath := dc.InventoryPath

    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", dcUniqName, dcName, dcPath, dcType)
}
複製代碼

這就列出了全部的數據中心了。

集羣

集羣中有不少資源,有資源池和 esxi(也就是宿主機,VMware 中稱爲 HostSystem)。在有 vsan 或者集中存儲的環境,你能夠將虛擬機直接放在其中的集羣資源池上(沒有劃分資源池也沒關係,集羣默認就是一個資源池);若是沒有,那麼就只能將虛擬機直接部署到宿主機上了。

所以你要麼將虛擬機放到資源池中,要麼放在宿主機上。而這種兩種資源都屬於集羣,所以咱們首先獲取集羣。固然其實你不獲取集羣也沒有關係,能夠直接獲取資源池或者 host。我這裏只是將獲取集羣的方式列出來,其實全部資源都是這麼獲取的:

// 集羣的名稱爲 ClusterComputeResource,不要搞錯了
clusters, err := finder.ClusterComputeResourceList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list cluster at vc %s, %v", ip, err)
    os.Exit(1)
}
for _, cluster := range clusters {
    clusterUniqName := cluster.Reference().Value
    clusterType := cluster.Reference().Type
    clusterName := cluster.Name()
    clusterPath := cluster.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", clusterUniqName, clusterName, clusterPath, clusterType)
}
複製代碼

這裏只是演示如何獲取集羣,可是建立虛擬機用不到,須要用的是資源池或者宿主機。它們兩種的獲取方式和集羣同樣:

resourcePools, err := finder.ResourcePoolList(ctx, "*")
hosts, err := finder.HostSystemList(ctx, "*")
複製代碼

固然你也能夠直接經過集羣來獲取它自身的資源池:

clusters[0].ResourcePool(ctx)
複製代碼

這種遍歷資源的方式其實很 low,這個先無論,先把流程走通再說。

存儲

儲存也屬於數據中心,既能夠是 vsan,也能夠是宿主機。這個是和上面的選擇是一一對應的,若是你將虛擬機建在宿主機上,那麼存儲你就應該選擇宿主機。

datastores, err := finder.DatastoreList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list datastore at vc %s, %v", ip, err)
    os.Exit(1)
}
for _, datastore := range datastores {
    datastoreUniqName := datastore.Reference().Value
    datastoreType := datastore.Reference().Type
    datastoreName := datastore.Name()
    datastorePath := datastore.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", datastoreUniqName, datastoreName, datastorePath, datastoreType)
}
複製代碼

VMware 中還存在 datastoreCluste 這種資源類型,可是我沒有研究。

網絡

網絡屬於數據中心,它會複雜一點,由於它有不少種類型:

  • Network
  • OpaqueNetwork
  • DistributedVirtualPortgroup
  • DistributedVirtualSwitch
  • VmwareDistributedVirtualSwitch

具體有啥區別我不是很清楚,大概是若是你使用分佈式交換機的話,你只須要選擇端口組(DistributedVirtualPortgroup)這種的(交換機就不用選了),不然就選擇 Network

networks, err := finder.NetworkList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list network at vc %s, %v", ip, err)
    os.Exit(1)
}

for _, network := range networks {
    networkUniqName := network.Reference().Value
    // 這就是它的 type,須要注意區分
    networkType := network.Reference().Type
    // 沒有 name,name 能夠經過 path 來獲取
    networkPath := network.GetInventoryPath()
    fmt.Printf("id => %s\npath => %s\ntype => %s\n", networkUniqName, networkPath, networkType)
}
複製代碼

文件夾

文件夾屬於數據中心,獲取起來方式同樣:

folders, err := finder.FolderList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list folder at vc %s, %v", ip, err)
    os.Exit(1)
}

for _, folder := range folders {
    folderUniqName := folder.Reference().Value
    folderType := folder.Reference().Type
    folderName := folder.Name()
    folderPath := folder.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", folderUniqName, folderName, folderPath, folderType)
}
複製代碼

部署虛擬機

資源都已經具有了,可是在部署以前,咱們須要獲取 ovf 模板的網絡和存儲,後面會用到。因此要拿到它們,多是後面經過它部署虛擬機的時候,要把它們替換掉吧。

獲取的方式很簡單:

rc := rest.NewClient(client.Client)
if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
    fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
    os.Exit(1)
}

// 先獲取 ovf 模板,這個函數定義在前面
item, err := getLibraryItem(ctx, rc)
if err != nil {
    panic(err)
}

m := vcenter.NewManager(rc)
// 這裏須要前面獲取到的資源池和文件夾,固然宿主機和文件夾也行,就是要將 ResourcePoolID 換成 HostID
// 至於爲何這麼作我也不清楚,只要能夠得到咱們須要的結果就行
fr := vcenter.FilterRequest{Target: vcenter.Target{
    ResourcePoolID: resources[0].Reference().Value,
    FolderID:       folders[0].Reference().Value,
},
}
r, err := m.FilterLibraryItem(ctx, item.ID, fr)
if err != nil {
    fmt.Fprintf(os.Stderr, "FilterLibraryItem error, %v\n", err)
    os.Exit(1)
}
// 模板中的網卡和磁盤可能有多個,我這裏當作一個來處理,建議模板中只有一個網卡的磁盤,由於建立虛擬機的時候能夠加
// 這兩個 key 後面會用到
networkKey := r.Networks[0]
storageKey := r.StorageGroups[0]
fmt.Println(networkKey, storageKey)
複製代碼

接下里就是部署了:

deploy := vcenter.Deploy{
    DeploymentSpec: vcenter.DeploymentSpec{
        // 虛擬機名稱
        Name:               "test",
        DefaultDatastoreID: datastores[0].Reference().Value,
        AcceptAllEULA:      true,
        NetworkMappings: []vcenter.NetworkMapping{{
            Key:   networkKey,
            Value: networks[0].Reference().Value,
        }},
        StorageMappings: []vcenter.StorageMapping{{
            Key: storageKey,
            Value: vcenter.StorageGroupMapping{
                Type:         "DATASTORE",
                DatastoreID:  datastores[0].Reference().Value,
                // 精簡置備
                Provisioning: "thin",
            },
        }},
        // 精簡置備
        StorageProvisioning: "thin",
    },
    Target: vcenter.Target{
        ResourcePoolID: resources[0].Reference().Value,
        FolderID:       folders[0].Reference().Value,
    },
}

ref, err := vcenter.NewManager(rc).DeployLibraryItem(ctx, item.ID, deploy)
if err != nil {
    fmt.Printf("Deploy vm from library failed, %v", err)
    return
}

f := find.NewFinder(client.Client)
obj, err := f.ObjectReference(ctx, *ref)
if err != nil {
    fmt.Fprintf(os.Stderr, "Find vm failed, %v\n", err)
    os.Exit(1)
}

// 這是虛擬機本尊,後面會用到
vm = obj.(*object.VirtualMachine)
複製代碼

這就開始部署了。

注意我這裏全部的資源都是選擇的第一個,只是爲了演示,你在使用的時候,須要選擇正確的才行。固然前面經過遍歷的方式獲取全部的資源方式顯得很 low,並且性能不好。咱們的作法是定時去抓取全部 vsphere 的全部資源,將其存到 MySQL 中,包括資源的名稱、類型(網絡才須要)、路徑(這是關鍵)、資源的 ID(也就是 uniqName)。

這樣在建立虛擬機的時候經過選擇這些資源,就能夠拿到這些資源的路徑,而經過這些路徑是能夠直接獲取資源自己的。這裏以存儲舉例:

si := object.NewSearchIndex(client.Client)
inventoryPath, err := si.FindByInventoryPath(ctx, "/beijing/datastore/store1")
if inventoryPath == nil {
    fmt.Fprintf(os.Stderr, "Get datastore object failed, %v", err)
    return
}

// 若是是其餘資源就換成其餘資源就行
ds := object.NewDatastore(client.Client, inventoryPath.Reference())
複製代碼

完整代碼

package main

import (
	"context"
	"fmt"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/vapi/library"
	"github.com/vmware/govmomi/vapi/rest"
	"github.com/vmware/govmomi/vapi/vcenter"
	"net/url"
	"os"
)

func getLibraryItem(ctx context.Context, rc *rest.Client) (*library.Item, error) {
	const (
		libraryName     = "Librarysub_from_49.100"
		libraryItemName = "CentOS 7.5"
		libraryItemType = "ovf"
	)

	m := library.NewManager(rc)
	libraries, err := m.FindLibrary(ctx, library.Find{Name: libraryName})
	if err != nil {
		fmt.Printf("Find library by name %s failed, %v", libraryName, err)
		return nil, err
	}

	if len(libraries) == 0 {
		fmt.Printf("Library %s was not found", libraryName)
		return nil, fmt.Errorf("library %s was not found", libraryName)
	}

	if len(libraries) > 1 {
		fmt.Printf("There are multiple libraries with the name %s", libraryName)
		return nil, fmt.Errorf("there are multiple libraries with the name %s", libraryName)
	}

	items, err := m.FindLibraryItems(ctx, library.FindItem{Name: libraryItemName,
		Type: libraryItemType, LibraryID: libraries[0]})

	if err != nil {
		fmt.Printf("Find library item by name %s failed", libraryItemName)
		return nil, fmt.Errorf("find library item by name %s failed", libraryItemName)
	}

	if len(items) == 0 {
		fmt.Printf("Library item %s was not found", libraryItemName)
		return nil, fmt.Errorf("library item %s was not found", libraryItemName)
	}

	if len(items) > 1 {
		fmt.Printf("There are multiple library items with the name %s", libraryItemName)
		return nil, fmt.Errorf("there are multiple library items with the name %s", libraryItemName)
	}

	item, err := m.GetLibraryItem(ctx, items[0])
	if err != nil {
		fmt.Printf("Get library item by %s failed, %v", items[0], err)
		return nil, err
	}
	return item, nil
}

func main() {
	const (
		ip       = ""
		user     = ""
		password = ""
	)

	u := &url.URL{
		Scheme: "https",
		Host:   ip,
		Path:   "/sdk",
	}
	ctx := context.Background()
	u.User = url.UserPassword(user, password)
	client, err := govmomi.NewClient(ctx, u, true)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Login to vsphere failed, %v", err)
		os.Exit(1)
	}

	finder := find.NewFinder(client.Client)
	resourcePools, err := finder.ResourcePoolList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list resource pool at vc %s, %v", ip, err)
		os.Exit(1)
	}
	//hosts, err := finder.HostSystemList(ctx, "*")
	datastores, err := finder.DatastoreList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list datastore at vc %s, %v", ip, err)
		os.Exit(1)
	}

	networks, err := finder.NetworkList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list network at vc %s, %v", ip, err)
		os.Exit(1)
	}

	folders, err := finder.FolderList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list folder at vc %s, %v", ip, err)
		os.Exit(1)
	}

	rc := rest.NewClient(client.Client)
	if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
		fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
		os.Exit(1)
	}

	item, err := getLibraryItem(ctx, rc)
	if err != nil {
		return
	}

	m := vcenter.NewManager(rc)
	fr := vcenter.FilterRequest{Target: vcenter.Target{
		ResourcePoolID: resourcePools[0].Reference().Value,
		FolderID:       folders[0].Reference().Value,
	},
	}
	r, err := m.FilterLibraryItem(ctx, item.ID, fr)
	if err != nil {
		fmt.Fprintf(os.Stderr, "FilterLibraryItem error, %v\n", err)
		os.Exit(1)
	}
	networkKey := r.Networks[0]
	storageKey := r.StorageGroups[0]

	deploy := vcenter.Deploy{
		DeploymentSpec: vcenter.DeploymentSpec{
			Name:               "test",
			DefaultDatastoreID: datastores[0].Reference().Value,
			AcceptAllEULA:      true,
			NetworkMappings: []vcenter.NetworkMapping{{
				Key:   networkKey,
				Value: networks[0].Reference().Value,
			}},
			StorageMappings: []vcenter.StorageMapping{{
				Key: storageKey,
				Value: vcenter.StorageGroupMapping{
					Type:        "DATASTORE",
					DatastoreID: datastores[0].Reference().Value,
					Provisioning: "thin",
				},
			}},
			StorageProvisioning: "thin",
		},
		Target: vcenter.Target{
			ResourcePoolID: resourcePools[0].Reference().Value,
			FolderID:       folders[0].Reference().Value,
		},
	}

	ref, err := vcenter.NewManager(rc).DeployLibraryItem(ctx, item.ID, deploy)
	if err != nil {
		fmt.Printf("Deploy vm from library failed, %v", err)
		return
	}

	f := find.NewFinder(client.Client)
	obj, err := f.ObjectReference(ctx, *ref)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Find vm failed, %v\n", err)
		os.Exit(1)
	}

	vm = obj.(*object.VirtualMachine)
}
複製代碼

設置 ip

部署完成後,咱們還須要對虛擬機作一些配置,包括配置 ip、更改 cpu 和內存的配置、增長磁盤等,注意這些操做必須在虛擬機關機的狀況下才能進行,而剛部署完的虛擬機處於關機狀態,咱們正好進行操做。

須要注意的是,配置 ip 依賴於 vmtools,所以你得先確保你的模板中已經存在 vmtools。CentOS 6 安裝 vm_tools 可能有些麻煩,還得掛盤按照官方的要求一步步進行。可是在 CentOS 7 上你只須要安裝 open-vm-tools,而後安裝 perl 便可。

type ipAddr struct {
	ip       string
	netmask  string
	gateway  string
	hostname string
}

func (p *ipAddr) setIP(ctx context.Context, vm *object.VirtualMachine) error {
	cam := types.CustomizationAdapterMapping{
		Adapter: types.CustomizationIPSettings{
			Ip:         &types.CustomizationFixedIp{IpAddress: p.ip},
			SubnetMask: p.netmask,
			Gateway:    []string{p.gateway},
		},
	}

	customSpec := types.CustomizationSpec{
		NicSettingMap: []types.CustomizationAdapterMapping{cam},
		Identity:      &types.CustomizationLinuxPrep{HostName: &types.CustomizationFixedName{Name: p.hostname}},
	}

	task, err := vm.Customize(ctx, customSpec)
	if err != nil {
		return err
	}

	return task.Wait(ctx)
}
複製代碼

設置 CPU 和內存

func setCPUAndMem(ctx context.Context, vm *object.VirtualMachine, cpuNum int32, mem int64) error {
	spec := types.VirtualMachineConfigSpec{
		NumCPUs:             cpuNum,
		NumCoresPerSocket:   cpuNum / 2,
		MemoryMB:            1024 * mem,
		CpuHotAddEnabled:    types.NewBool(true),
		MemoryHotAddEnabled: types.NewBool(true),
	}
	task, err := vm.Reconfigure(ctx, spec)
	if err != nil {
		return err
	}

	return task.Wait(ctx)
}
複製代碼

添加磁盤

你須要給它傳遞一個數據存儲對象:

func addDisk(ctx context.Context, vm *object.VirtualMachine, diskCapacityKB int64, ds *types.ManagedObjectReference) error {
	devices, err := vm.Device(ctx)
	if err != nil {
		log.Errorf("Failed to get device list for vm %s, %v", vm.Name(), err)
		return err
	}

    // 這裏要看你的磁盤類型,若是你有 nvme,就選擇 nvme;不然就選 scsi。固然還有 ide,可是還有人用麼
	controller, err := devices.FindDiskController("scsi")
	if err != nil {
		log.Errorf("Failed to find disk controller by name scsi, %v", err)
		return err
	}

	device := types.VirtualDisk{
		CapacityInKB: diskCapacityKB,
		VirtualDevice: types.VirtualDevice{
			Backing: &types.VirtualDiskFlatVer2BackingInfo{
				DiskMode:        string(types.VirtualDiskModePersistent),
				ThinProvisioned: types.NewBool(true),
				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
					Datastore: ds,
				},
			},
		},
	}

	devices.AssignController(&device, controller)
	DeviceSpec := &types.VirtualDeviceConfigSpec{
		Operation:     types.VirtualDeviceConfigSpecOperationAdd,
		FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
		Device:        &device,
	}

	spec := types.VirtualMachineConfigSpec{}
	spec.DeviceChange = append(spec.DeviceChange, DeviceSpec)
	task, err := vm.Reconfigure(ctx, spec)
	if err != nil {
		log.Errorf("Failed to add disk for vm %s, %v", vm.Name(), err)
		return err
	}

	if err := task.Wait(ctx); err != nil {
		log.Errorf("Failed to add disk for vm %s, %v", vm.Name(), err)
		return err
	}
	return nil
}
複製代碼

開機

在設置了 ip 以後,開機會啓動兩次,就是第一次啓動成功後會重啓一次,兩次加起來時間還有點長。我也不知道爲啥會這樣,反正須要等一下子。

func powerOn(ctx context.Context, vm *object.VirtualMachine) error {
	task, err := vm.PowerOn(ctx)
	if err != nil {
		log.Errorf("Failed to power on %s", vm.Name())
		return err
	}

	return task.Wait(ctx)
}
複製代碼

govmomi 的功能很是多,我這裏用到的這是很是少的一部分,若是沒法知足你的全部需求,你可能須要看看 govc 源碼了😂。

OK,本文到此結束,感謝閱讀。