vsphere golang sdk govmomi 使用指南

上一篇文章提到了如何使用 python 和 vsphere 进行交互,我只能说我知道 govmomi 太晚了,否则怎么着都不会使用 python 的,毕竟 python 性能太差,并发用起来太蹩脚。在知道 govmomi 以后就有些蠢蠢欲动,因而抽空研究了下,并记录了下它的一些简单用法。html

govmomi 和 pyvmomi 虽然都是基于 VMware 的 api 实现的,可是因为语言的不一样,它们使用起来仍是区别很大的。除了性能上的优点以外,govmomi 不只可使用全部操纵虚拟机的功能,还支持从内容库直接部署虚拟机,这就比我以前文章使用 vsphere-automation-sdk-python + pyvmomi 这种蹩脚的用法要强不少了。不过 govmomi 既然支持内容库,没理由 pyvmomi 不支持,莫非是我孤陋寡闻了?不过这些都不是重点,重点是 govmomi。python

govmomi 其实用起来还好,可是坑必定是存在的,这个接下来会提到。咱们先安装它。git

推荐大家直接访问它的 github,上面有它的一些说明。上面也提到了安装的方式:github

go get -u github.com/vmware/govmomi
复制代码

颇有意思的是,govmomi 不只提供了这个第三方库,同时基于这个库还写了 govcvcsimtoolbox 这三个命令行工具,你彻底可使用它们在命令行管理你的 vsphere。在你安装以后,这三个工具的源码也都被你下载下来了。web

其中 govc 的源码是咱们须要密切关注的,由于 govmomi 自己能够说没有任何的示例告诉你如何使用,可是你须要的功能 govc 都提供了。因此你要实现什么功能就看 govc 对应的源码就好了。最重要的是,govc 源码的文件很是清晰明了,你要什么功能看对应的文件就行,这个接下来会讲到。vim

ok,安装安装后,咱们首先须要登陆 vsphere。api

登陆

govmomi 的登陆颇有特点,是我第一次见到的登陆类型,只能说不够直接,有些遮遮掩掩的感受,不是个人菜。bash

登陆的方式有两种,第一种是经过环境变量,它须要以下环境变量:markdown

  • GOVMOMI_URL:vsphere ip;
  • GOVMOMI_USERNAME:用户名;
  • GOVMOMI_PASSWORD:密码;
  • GOVMOMI_INSECURE:是否进行证书校验。

貌似还有其余环境变量,可是这四个是咱们登陆须要的。这种登陆方式更多的是测试性质的,没法正式使用。由于使用这种方式意味着你须要将密码写入环境变量,而且在你须要登陆多个 vsphere 时就没有办法了。固然,你能够尝试在代码内对环境变量进行修改,试他一把,但我推荐使用第二种方式。并发

第二种方式是经过 url 的方式,将用户名和密码都写入到 url 中。你们知道,http url 的定义是这样的:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
复制代码

所以咱们是彻底能够将用户名和密码写入其中的。你可能担忧密码中包含 :/ 这样的特殊字符,致使登陆失败。其实不要紧的,由于 govmomi 会将 url 解析为 url.URL,因此咱们只要本身构建一个 url.URL 就行。

url 的最终格式以下:

https://user:password@IP/sdk
复制代码

所以,咱们能够这么作:

u := &url.URL{
    Scheme: "https",
    Host:   ip,
    Path:   "/sdk",
}
u.User = url.UserPassword(user, password)
复制代码

ok,url.URL 就构建完毕了,你可使用 fmt.Println(u) 直接将 url 打印出来,只不过密码颇有可能会被编码。而后使用它登陆:

ctx := context.Background()
client, err := govmomi.NewClient(p.ctx, u, true)
if err != nil {
    panic(err)
}
复制代码

基本上全部 govmomi 操做都会接收 context 做为上下文,便于操做取消,所以首先须要建立一个 context 对象。

这里的 true 就是不进行证书验证,对应上面的 GOVMOMI_INSECURE 环境变量。client 就是登陆后的对象,这也是咱们下面全部操做的基础。当你有多个 vsphere 时,建立不一样的 url.URL 就能够登陆不一样的 vsphere。

完整写法:

package main

import (
    "context"
    "fmt"
	"github.com/vmware/govmomi"
	"net/url"
)

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

func main() {
	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 {
		panic(err)
	}
	fmt.Println(client)
}
复制代码

使用 govc

在使用以前,咱们先使用 govc,先要了解它的功能。由于 govc 基本上实现了 govmomi 的全部功能,因此咱们彻底能够将 govc 的源码做为参考,当咱们须要实现相应的功能时,就能够直接看 govc 的源码。

首先编译 govc:

go build github.com/vmware/govmomi/govc/
复制代码

要经过定义环境变量来使用它,注意它的环境变量和上面的 govmomi 的环境变量的名称并不同(意思同样),不要搞混了。咱们首先定义环境变量:

export GOVC_URL=""
export GOVC_USERNAME=""
export GOVC_PASSWORD=""
export GOVC_INSECURE="true"
复制代码

我只列出这四个我会用到的,更多的点击这里查看。

定义完成以后就可使用了,好比查找当前的 vsphere 上有哪些文件夹:

./govc find / -type f
复制代码

至于它有哪些功能呢?可使用 ./govc -h 列出。能够看到它的功能很是多,你想要查看对应功能的源码只须要去 govc 源码目录查找对应的文件就行,好比我要查看 host.shutdown 的源码,只须要查看 $GOPATH/src/github.com/vmware/govmomi/govc/host/shutdown.go 文件就行。

查找虚拟机

登陆成功以后,咱们通常作的就是查找虚拟机了,虚拟机的查找有多种方式,能够经过名称查、ip 查、文件夹路径查、uuid 查等。不一样的查找方式效果不一样,性能也不一样。虚拟机有两种表现形式,不一样方式查找到的虚拟机形式也不一样,不过它们能够相互进行转换。

经过名称查找

首先进行最 low 的查找方式,这种就是遍历全部虚拟机,而后判断主机名是否相同。

// 这里的 c 就是上面登陆后 client 的 Client 属性
func findVMByName(ctx context.Context, c *vim25.Client, vmName string) {
	m := view.NewManager(c)

	v, err := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
	if err != nil {
		panic(err)
	}

	defer v.Destroy(ctx)

	// Retrieve summary property for all machines
	// Reference: http://pubs.vmware.com/vsphere-60/topic/com.vmware.wssdk.apiref.doc/vim.VirtualMachine.html
	var vms []mo.VirtualMachine
	err = v.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary"}, &vms)
	if err != nil {
		panic(err)
	}

	// Print summary per vm (see also: govc/vm/info.go)

	for _, vm := range vms {
		// 判断虚拟机名称是否相同,相同的话,vm 就是查找到主机
		if vm.Summary.Config.Name == vmName {
			fmt.Printf("%s: %s\n", vm.Summary.Config.Name, vm.Summary.Config.GuestFullName)
			break
		}
	}
}
复制代码

这样虚拟机就找到了,能够看到找到的虚拟机是 mo.VirtualMachine 类型,后续会提到如何对虚拟机进行修改。

经过 ip 查找

使用 ip 查找须要虚拟机安装了 vmtools,且能够在 web 界面上可以看到它的 ip,所以虚拟机必须处于开机状态。

func findVMByIP(ctx context.Context, c *vim25.Client, ip string) {
	searchIndex := object.NewSearchIndex(c)
	// nil 是数据中心,你若是要指定的话,须要构造数据中心的结构体,这里就不指定了
	// ip 是你虚拟机的 ip
	// true 的意思我没搞懂
	reference, err := searchIndex.FindByIp(ctx, nil, ip, true)
	// 之因此只对 reference 进行判断而非对 err 是由于没有找到不算是 error
	// 也就是说 err 为 nil 并不表明就找到了,可是没找到 reference 必定为 nil
	if reference == nil {
		panic("vm not found")
	}

	// 这类的查找的对象都是 object.Reference,你须要经过对应的方法将其转换为相应的对象
	// 好比虚拟机、文件夹、模板等~
	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}
复制代码

此次找到的虚拟机类型是 *object.VirtualMachine,而不是上面的 mo.VirtualMachine,不过它们是能够相互转换的。

经过文件夹路径查找

这是根据虚拟机所处的文件夹路径来查找,前提是你知道你的文件夹的路径。这个路径并非你在 web 界面上看到的,你看到的只是路径的一部分。那么如何才能知道具体的文件夹路径是啥呢?你可使用 govc 进行查看。

若是你的虚拟机在数据中心下面,没有放入任何文件夹,那么它的文件夹路径就是 /数据中心/vm

func findVMByPath(ctx context.Context, c *vim25.Client, folderPath, vmName string) {
	searchIndex := object.NewSearchIndex(c)
	// 经过 path.Join 将文件夹路径和虚拟机名称拼接成完整的虚拟机路径
	reference, err := searchIndex.FindByInventoryPath(ctx, path.Join(folderPath, vmName))
	if reference == nil {
		panic("vm not found")
	}

	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}
复制代码

虚拟机类型和上面相同,由于查找的方式相似。

经过 uuid 查找

uuid 查找是惟一的,查找方式和上面几乎同样,除非你知道虚拟机的 uuid,不然也没法使用。

func findVMByUuid(ctx context.Context, c *vim25.Client, uuid string) {
	searchIndex := object.NewSearchIndex(c)
	// 第一个 nil 指的是数据中心
	// true 和以前的 true 意义同样,只不过我不知道是什么意思
	// 最后一个 nil 我也不知道是啥意思
	reference, err := searchIndex.FindByUuid(ctx, nil, uuid, true, nil)
	if reference == nil {
		panic("vm not found")
	}

	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}
复制代码

虚拟机类型和上面相同。固然虚拟机的查找方式确定不止这四种,这里只列出了经常使用的,有兴趣的能够了解其余的用法。

经常使用的功能

这里只是简单的列出了一些经常使用的功能,有其余需求的能够直接查看 govc 的源码,源码都还挺容易懂的。

两种虚拟机类型转换

第一种查找到的虚拟机类型和后面三种的不同,不一样的虚拟机类型有不一样的属性和方法,所以它们存在转换的需求。

转换的方式也很简单,首先是 mo.VirtualMachine 转换为 *object.VirtualMachine

func vmConv(c *vim25.Client, mvm mo.VirtualMachine) {
	vm := object.NewVirtualMachine(c, mvm.Reference())
	fmt.Println(vm)
}
复制代码

使用 mo.VirtualMachine 大多数是须要得到一些虚拟机相关的属性,以及配置参数。所以你想要将 *object.VirtualMachine 转化为 mo.VirtualMachine 能够这么作:

func x(ctx context.Context, vm *object.VirtualMachine, c *vim25.Client) {
	var mvm mo.VirtualMachine

	pc := property.DefaultCollector(c)
	// 若是想要所有属性,能够传一个空的字串切片
	err := pc.RetrieveOne(ctx, vm.Reference(), []string{"runtime.host", "config.uuid"}, &mvm)
}
复制代码

也能这么作:

func x(ctx context.Context, vm *object.VirtualMachine, c *vim25.Client) {
	var o mo.VirtualMachine
	if err := vm.Properties(ctx, vm.Reference(), []string{"config.uuid"}, &o); err != nil {
		panic(err)
	}
}
复制代码

关机

关机很简单,*object.VirtualMachine 对象自己就提供了关机方法。

func vmShutdown(ctx context.Context, vm *object.VirtualMachine) {
	// 第一个返回值是 task,我认为不必处理,若是你要处理的话能够接收后处理
	_, err := vm.PowerOff(ctx)
	if err != nil {
		panic(err)
	}
}
复制代码

删除

删除也简单,*object.VirtualMachine 对象自己也提供了关机方法。

func vmDelete(ctx context.Context, vm *object.VirtualMachine) {
	// task 能够处理,也能够不处理
	task, err := vm.Destroy(ctx)
	if err != nil {
		panic(err)
	}
	if task.Wait(ctx) != nil {
		panic(err)
	}
}
复制代码

断开虚拟机网卡

这方面比 pyvmomi 更简单。

func disconnectNic(ctx context.Context, vm *object.VirtualMachine) {
	devidelst, err := vm.Device(ctx)
	if err != nil {
		panic("获取虚拟机的设备列表失败," + err.Error())
	}
	for _, device := range devidelst {
		switch device.(type) {
		case *types.VirtualVmxnet3:
			if devidelst.Disconnect(device) != nil {
				panic("断开网卡链接失败," + err.Error())
			}
			if vm.EditDevice(p.ctx, device) != nil {
				panic("断开网卡链接失败," + err.Error())
			}
		}
	}
}
复制代码

更改虚拟机所属文件夹

将虚拟机移动到另外一个文件夹。

// 先经过目标文件夹来找到其文件夹对象,而后将虚拟机移动到这个对象中
func vmMove(ctx context.Context, destDir string, c *vim25.Client, vm *object.VirtualMachine, searchIndex *object.SearchIndex) {
	reference, _ := searchIndex.FindByInventoryPath(ctx, destDir)
	if reference == nil {
		panic("目标目录 " + destDir + " 不存在")
	}

	folder := object.NewFolder(c, reference.Reference())
	task, err := folder.MoveInto(ctx, []types.ManagedObjectReference{vm.Reference()})
	if err != nil {
		panic(err)
	}
	if err := task.Wait(ctx); err != nil {
		panic(err)
	}
}
复制代码

就写这么多了,你们有须要的话,能够直接看 govc 的源码。