govmomiの歩き方

# はじめに

govmomiは、vCenterやESXiを操作するAPI （主に [vSphere Web Services API
](https://developer.vmware.com/apis/1192/vsphere)） のSDKです。
Web Client上の操作の自動化等で重宝します。

https://github.com/vmware/govmomi

VMwareが開発しているOSSで、名前の通りGo言語製です[^1]。

表現力が高く便利な一方、少し使い方にコツがいるライブラリなので、本記事では備忘録として使い方と内部構成をまとめてみました。

# ディレクトリ構成

たくさんありますが、かいつまんで紹介すると以下の役割を持っています。

- `find`: managed object取得用ファインダー
- `govc`: CLIツール [govc](https://github.com/vmware/govmomi/tree/master/govc) の実装
- `object`: managed objectをgovmomi上で扱いやすくしたラッパー
- `property`: プロパティ取得関数
- `simulator`: vSphereのシミュレーター [vcsim](https://github.com/vmware/govmomi/tree/master/vcsim) の実装
- `vapi`: [vSphere Automation API](https://developer.vmware.com/apis/vsphere-automation/latest/) (REST API)のSDK (このパッケージ以外は vSphere Web Services API のSDK)
- `vcsim`: [vcsim](https://github.com/vmware/govmomi/tree/master/vcsim) の実装
- `vim25`: Web Services API のリクエスト、レスポンスのスキーマ定義
    - `mo`: managed object(管理対象オブジェクト)を表す構造体[^2]
    - `methods`: リクエスト（managed objectに対する操作）を表す構造体
    - `types`: ↑のプロパティを表す構造体

オブジェクト構成は以下のようになっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/8af0126b-1065-9cc7-975f-83ec3c7a4815.png" width="640">

`mo`, `methods`, `types` の3パッケージは、[vSphere Web Services API
](https://developer.vmware.com/apis/1192/vsphere)のリクエスト、レスポンスのスキーマ[^3]を表す構造体を定義しています。操作用のメソッドは生えておらず、単純に値の入れ物として使用します。

```go:vim25/mo/mo.go
type VirtualMachine struct {
	ManagedEntity

	Capability           types.VirtualMachineCapability    `mo:"capability"`
	Config               *types.VirtualMachineConfigInfo   `mo:"config"`
	Layout               *types.VirtualMachineFileLayout   `mo:"layout"`
	LayoutEx             *types.VirtualMachineFileLayoutEx `mo:"layoutEx"`
	Storage              *types.VirtualMachineStorageInfo  `mo:"storage"`
	EnvironmentBrowser   types.ManagedObjectReference      `mo:"environmentBrowser"`
	ResourcePool         *types.ManagedObjectReference     `mo:"resourcePool"`
	ParentVApp           *types.ManagedObjectReference     `mo:"parentVApp"`
	ResourceConfig       *types.ResourceConfigSpec         `mo:"resourceConfig"`
	Runtime              types.VirtualMachineRuntimeInfo   `mo:"runtime"`
	Guest                *types.GuestInfo                  `mo:"guest"`
	Summary              types.VirtualMachineSummary       `mo:"summary"`
	Datastore            []types.ManagedObjectReference    `mo:"datastore"`
	Network              []types.ManagedObjectReference    `mo:"network"`
	Snapshot             *types.VirtualMachineSnapshotInfo `mo:"snapshot"`
	RootSnapshot         []types.ManagedObjectReference    `mo:"rootSnapshot"`
	GuestHeartbeatStatus types.ManagedEntityStatus         `mo:"guestHeartbeatStatus"`
}
```

対応するレスポンス

https://vdc-download.vmware.com/vmwb-repository/dcr-public/bf660c0a-f060-46e8-a94d-4b5e6ffc77ad/208bc706-e281-49b6-a0ce-b402ec19ef82/SDK/vsphere-ws/docs/ReferenceGuide/vim.VirtualMachine.html

このままでは少し使いづらいですが、govmomiにはmanaged objectを扱いやすくした構造体が定義されています。これが `object` パッケージです。`object` パッケージには操作用のメソッドが生えているので、裏のAPI構造を意識することなく実装できます。

メソッド一覧

https://pkg.go.dev/github.com/vmware/govmomi/object

# objectとmo,methodsの使い分け

可能な限りobjectを使い、できないことがあった場合のみmo/methodsを使うのが良いと思います。

## objectで事足りる例

- VMのパワーオン

```go
// クライアント生成（今回はデモとしてvcsimを使用）
u, _ := url.Parse("https://user:pass@127.0.0.1:8989/sdk")
c, _ := govmomi.NewClient(ctx, u, true)

// VMを取得
finder := find.NewFinder(c.Client)
vm, err := finder.VirtualMachine(ctx, "DC0_H0_VM0") // vmは *object.VirtualMachine型
if err != nil {
	return fmt.Errorf("vm取得失敗: %w", err)
}

task, err := vm.PowerOn(ctx) // taskは *object.Task型
if err != nil {
	return fmt.Errorf("vmパワーオン失敗: %w", err)
}

err = task.Wait(ctx) // タスクの完了を待機
if err != nil {
	return fmt.Errorf("パワーオンタスク失敗: %w", err)
}
```

- VMのデバイス一覧取得

```go
// VMを取得
finder := find.NewFinder(c.Client)
vm, err := finder.VirtualMachine(ctx, "DC0_H0_VM0") // vmは *object.VirtualMachine型
if err != nil {
	return fmt.Errorf("vm取得失敗: %w", err)
}

devices, err := vm.Device(ctx) // devicesは object.DeviceList ([]*types.BaseVirtualDevice)型
if err != nil {
	return fmt.Errorf("device取得失敗: %w", err)
}
for _, d := range devices {
	fmt.Printf("device summary: %s\n", d.GetVirtualDevice().DeviceInfo.GetDescription().Summary)
}
```

## moが必要な例

レスポンス中の特定の要素を取得する場合にはmoを使う必要があります。

```go
// VMを取得
finder := find.NewFinder(c.Client)
vm, err := finder.VirtualMachine(ctx, "DC0_H0_VM0") // vmは *object.VirtualMachine型
if err != nil {
	return fmt.Errorf("vm取得失敗: %w", err)
}

// VMの特定の属性を取得
pc := property.DefaultCollector(c.Client)
var moVM mo.VirtualMachine
props := []string{"summary.config"}                     // mo中の取得したい属性（フィールド）のパス
err = pc.RetrieveOne(ctx, vm.Reference(), props, &moVM) // propsに対応する属性を取得し、結果をmoVMに書き込み
if err != nil {
	return fmt.Errorf("mo取得失敗: %w", err)
}
fmt.Printf("path: %s\n", moVM.Summary.Config.VmPathName) // path: [LocalDS_0] DC0_H0_VM0/DC0_H0_VM0.vmx
```

`pc.RetrieveXXX` を使い、`props` で指定した属性を取得します。以下利用の際の注意点です。

- moが戻り値で返るわけではなく、**引数にポインタを渡してフィールドに書き込む** (`json.Unmarshal` と同じイメージ)
- moのうち **`props` で指定していないフィールドはゼロ値のまま**

## おわりに

以上、駆け足でしたがgovmomiの内部構成の紹介でした。

今回は紹介しきれませんでしたが、実際に操作系のメソッドを扱う際にはobjectパッケージとtypesパッケージを組み合わせて使うことが多いです。
利用の際には、以下の公式実装が参考になると思います。

- [govcの実装](https://github.com/vmware/govmomi/tree/master/govc)
- [vcsimのテスト](https://github.com/vmware/govmomi/tree/master/simulator)

[^1]: 類似ライブラリとして、Python製の[pyvmomi](https://github.com/vmware/pyvmomi)も提供されています。
[^2]: 以下 `mo` パッケージの構造体は `mo`、概念としての管理対象オブジェクトは `managed object` と記載します。
[^3]: SOAP APIなのでXMLで表現されます。