vcsimの小ネタ: ID生成の仕組み

（この記事は[個人ブログ](https://volatile-notepad.netlify.app/post/20220225_vcsim_id/)の再投稿です）

vcsimの小ネタシリーズです。今回はかなりマニアックな実装の話。

- 今までの記事
    - [vcsimの小ネタ: disk作成](https://qiita.com/Syuparn/items/5bafc3d5315a2b53f17f)
    - [vcsimの小ネタ: vCenterをコピーして再現](https://qiita.com/Syuparn/items/ceafefa867551337697e)

# TL; DR

- vcsimの**各リソースのIDは何度起動しても変わらない**
    - ただし、vapi (REST API)で操作するリソースのIDは完全ランダム
- vcsimがバージョンアップした際には
    - MOID: 変わるかも
    - UUID: パスが同じ限り変わらない

# はじめに

vcsimの各リソースには、実際のvCenter同様一意なIDが割り振られます。
基本的にこのIDは何度起動しても同じなので、ツールのテストに使用する際には固定値で試せてありがたいです。

```bash
# DC0_H0_VM0 の moid (vcsim v0.27.4)
$ govc vm.info -json DC0_H0_VM0 | jq .VirtualMachines[].Self
{
  "Type": "VirtualMachine",
  "Value": "vm-54"
}
```

ただし、**vcsimのバージョンが変わるとIDがずれる**こともよくあり、その際はテストを書き直して回る必要があります。
今回は、vcsimがどのように各種IDを生成しているのか内部実装を紹介したいと思います。

# バージョン

- vcsim v0.27.4+ (記事投稿時点のmasterブランチ)[^1]

# MOID

各リソースのMOIDはオートインクリメントで払い出されています。
vcsim全体でロックをとっているため、常に一意性が担保されます。

実装としては、作成されたManaged Objectが親フォルダの子エンティティ一覧に追加される際に設定されます。

例として、vm作成のコードを見ていきます。

https://github.com/vmware/govmomi/blob/81c26b4b6522eca2af3ec9f35df669cb2d51f9fb/simulator/virtual_machine.go#L170

```go
func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) {
	vm := &VirtualMachine{}

	// ...

	folder := ctx.Map.Get(parent)
	f, _ := asFolderMO(folder)
	// ここで親folderに新規作成したvmをセット
	folderPutChild(ctx, f, vm)
}
```

`folderPutChild` はフォルダに子エンティティを追加する関数です。

`ctx.Map.reference(o)` はoのMOIDを参照するメソッドですが、副作用として**MOIDを持っていない場合に新規生成**する役目も担っています。

https://github.com/vmware/govmomi/blob/81c26b4b6522eca2af3ec9f35df669cb2d51f9fb/simulator/folder.go#L89

```go
func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
	ctx.WithLock(f, func() {
		// Need to update ChildEntity before Map.Put for ContainerView updates to work properly
		f.ChildEntity = append(f.ChildEntity, ctx.Map.reference(o)) // ★
		ctx.Map.PutEntity(f, o)

		folderUpdate(ctx, f, o, ctx.Map.AddReference)

		ctx.WithLock(o, func() {
			switch e := o.(type) {
			case *mo.Network:
				e.Summary = networkSummary(e)
			case *mo.OpaqueNetwork:
				e.Summary = networkSummary(&e.Network)
			case *DistributedVirtualPortgroup:
				e.Summary = networkSummary(&e.Network)
			}
		})
	})
}
```

https://github.com/vmware/govmomi/blob/81c26b4b6522eca2af3ec9f35df669cb2d51f9fb/simulator/registry.go#L261

`reference` の実装です。typeかvalueが空文字の場合に新規生成しセットしています。

```go
func (r *Registry) reference(item mo.Reference) types.ManagedObjectReference {
	ref := item.Reference()
	if ref.Type == "" || ref.Value == "" {
		ref = r.newReference(item)
		r.setReference(item, ref)
	}
	return ref
}

func (r *Registry) newReference(item mo.Reference) types.ManagedObjectReference {
	ref := item.Reference()

	if ref.Type == "" {
		ref.Type = typeName(item)
	}

	if ref.Value == "" {
		n := atomic.AddInt64(&r.counter, 1)
		ref.Value = fmt.Sprintf("%s%d", valuePrefix(ref.Type), n)
	}

	return ref
}
```

実装からもわかるように

- Type: エンティティの型名(ここでは `VirtualMachine`)
- Value: 型名に応じたprefix + オートインクリメントの整数（例: `vm-123`）

が生成され、晴れて一意なMOIDとして利用可能になります。

## MOIDのずれ

上記のように、MOIDの整数部分はオートインクリメントなので、デフォルトのManaged Objectが新規追加されるとその分だけずれます。

`DC0_H0_VM0` は現在54番目に作られているのでMOIDが `vm-54` ですが、仮に次のリリースで1つManaged Objectが増え、その生成タイミングが `DC0_H0_VM0` よりも前なら `vm-55` に繰り下げになります。

（もちろん、上記以外にもバグ修正によって変な名前だったMOIDがvCenterに準拠する形式に修正されることもあるでしょう）

# UUID

UUIDはリソースのパス文字列をもとに生成されます。

生成には [`github.com/google/uuid.NewSHA1`](https://pkg.go.dev/github.com/google/uuid#NewSHA1) が使用され、Version 5のUUIDが作られます。

https://github.com/vmware/govmomi/blob/81c26b4b6522eca2af3ec9f35df669cb2d51f9fb/simulator/object.go#L64

```go
func sha1UUID(s string) uuid.UUID {
	return uuid.NewSHA1(uuid.NameSpaceOID, []byte(s))
}
```

文字列として与えられる内容は以下の通りです。

- vmのUUID: `.Config.Files.VmPathName`
- vmのInstanceUUID `.Config.Files.VmPathName` を大文字化した文字列
- hostのUUID: ホスト名
- diskのUUID: `Datacenter:${所属データセンターのMOID}` + `.Backing.FileName`

詳しくは以前書いた[Gist](https://gist.github.com/Syuparn/f85d8ebb9a7d1a9d7a1c816f45ba29c7) をご覧ください（v0.26.0当時の内容ですが、v0.27.4でも実装は同じです）。

名前やパスに依存しているため、基本的にvcsimのバージョンアップでは変化しません（ディスクだけはデータセンターのMOIDが入っているので怪しいかも）。

# vapiのリソースのID

上記のようにvSphere Web Services API (SOAP API)のリソースのIDは基本的に固定値です。

一方、カテゴリやタグ等、vapi (REST API)で操作するリソースのIDは完全ランダムです。残念ながら起動のたびに値が変わってしまいます。

```go:vapi/simulator/simulator.go
func newID(kind string) string {
	return fmt.Sprintf("urn:vmomi:InventoryService%s:%s:GLOBAL", kind, uuid.New().String())
}
```

https://github.com/vmware/govmomi/blob/78830359a3a89beb00e27ae4c0638b22cfddb64b/vapi/simulator/simulator.go#L446

# おわりに

以上、vcsimのIDについてでした。変更リスクを上手く見定めて、上手くテストに組み込んでいきたいですね。

[^1]: と思ったら投稿直前に v0.28.0が出ていました :tada: