Ранее я написал две статьи, обе про API VMware:
- Автоматически создавать виртуальные машины с помощью vsphere-automation-sdk-python: это версия Python;
- Руководство по использованию vsphere golang sdk govmomi: Это основная операция говмоми, рекомендуется ознакомиться перед чтением этой статьи.
Есть много причин для использования govmomi.Есть причины производительности.Go намного сильнее, чем Python.Есть личные причины, я предпочитаю go.
Конечно, будь то govmomi, pyvmomi или различные другие vmomi, все они являются инкапсуляциями собственного API VMware, и все они похожи в использовании.
Следует отметить, что виртуальная машина, созданная в этой статье, развертывается через библиотеку содержимого после размещения шаблона ovf в библиотеке содержимого, а не создается напрямую через шаблон. К тому же библиотека контента 6.7 поддерживает vm-template помимо шаблонного типа ovf.У меня не было времени изучить.Заинтересованные детские ботиночки могут изучить.
Хорошо, текст начинается.
Библиотека контента
Библиотека контента — это новая функция VMware 6.0. С ее помощью вы можете обмениваться iso-файлами, шаблонами виртуальных машин и т. д. между всферами.Когда у вас несколько всфер, это будет очень удобно в использовании. Чтобы использовать библиотеку контента, сначала нужно создать библиотеку контента (библиотеку), дать ей имя, а затем загрузить в библиотеку файлы, шаблоны и шаблоны.Каждый файл или шаблон называется элементом.
Когда вы создаете библиотеку контента в одной vsphere, другие vsphere подписываются на библиотеку контента, так что файлы в библиотеке контента будут синхронизированы со всеми библиотеками контента, которые на нее подписаны.Таким образом, можно гарантировать несколько библиотек контента vsphere. , Согласованность документа.
Здесь не будет продемонстрировано использование библиотеки контента, есть много онлайн-уроков, которые можно найти случайно. Это предполагает, что у вас уже есть библиотека содержимого с шаблоном ovf. Чтобы использовать библиотеку содержимого, мы должны сначала найти объект библиотеки содержимого, а затем использовать его для поиска объекта шаблона ovf.
Вы уже должны знать, как установить и войти в 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)
}
Библиотеки, которые необходимо импортировать в этой статье:
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"
)
Он не будет опубликован позже, и базовая среда разработки автоматически его завершит.
Клиент здесь может использоваться для управления всей vsphere, но он не может напрямую управлять библиотекой контента, мы должны сначала получить через него остальной клиент:
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 так спроектировала, не плохо ли передать клиент напрямую? Когда у вас есть 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()
Вот и все. Таким образом, вам не нужно искать библиотеку, а затем находить элемент через библиотеку.
Тип ресурса
Теперь, когда у нас есть шаблон, все, что нам нужно сделать, это определить, где развернуть виртуальную машину. Ресурсы в 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 или централизованным хранилищем можно разместить виртуальную машину прямо в пуле ресурсов кластера (не важно, если пул ресурсов не разделен, кластер по умолчанию является пулом ресурсов), если нет, то можно развертывать виртуальную машину только непосредственно на хост-компьютере.
Таким образом, вы либо помещаете виртуальную машину в пул ресурсов, либо ставите ее на хост. И оба этих ресурса принадлежат кластеру, поэтому сначала мы получаем кластер. Конечно, не имеет значения, если вы не получите кластер, вы можете напрямую получить пул ресурсов или хост. Я просто перечисляю здесь способы получения кластера, собственно все ресурсы добываются таким образом:
// 集群的名称为 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)
Этот способ обхода ресурсов на самом деле очень низкий, давайте оставим его в покое, давайте сначала пройдем процесс.
место хранения
Хранилище также относится к дата-центру, который может быть как всаном, так и хостом. Это прямое соответствие с указанными выше вариантами.Если вы строите виртуальную машину на хосте, то вы должны выбрать хост для хранения.
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 также есть тип ресурса datastoreCluster, но я его не изучал.
Интернет
Сеть относится к дата-центру, она немного сложнее, потому что имеет много типов:
- 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)
Это запускает развертывание.
Обратите внимание, что все ресурсы, которые я выбираю здесь, являются первыми, просто для демонстрации, вам нужно выбрать правильный, когда вы их используете. Конечно, предыдущий способ получения всех ресурсов путем обхода очень низкий, и производительность очень низкая. Наш подход заключается в периодическом захвате всех ресурсов vsphere и сохранении их в MySQL, включая имя ресурса, тип (необходим только для сети), путь (это ключ) и идентификатор ресурса (то есть 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, изменение конфигурации процессора и памяти, добавление дисков и т. д. Обратите внимание, что эти операции можно выполнять только при выключенной виртуальной машине. , а виртуальная машина, которую только что развернули В выключенном состоянии мы просто в работе.
Обратите внимание, что настройка ip зависит от vmtools, поэтому вы должны убедиться, что vmtools уже существует в вашем шаблоне. Установка vm_tools в CentOS 6 может быть немного хлопотной, и вам придется установить диск и шаг за шагом следовать официальным требованиям. Но в 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)
}
Настройте процессор и память
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.
Хорошо, это конец этой статьи, спасибо за чтение.