-
Notifications
You must be signed in to change notification settings - Fork 12
/
vmm.go
235 lines (190 loc) · 5.78 KB
/
vmm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//go:build windows
// +build windows
package hypervctl
import (
"errors"
"fmt"
"github.com/containers/libhvee/pkg/wmiext"
)
const (
HyperVNamespace = "root\\virtualization\\v2"
VirtualSystemManagementService = "Msvm_VirtualSystemManagementService"
MsvmComputerSystem = "Msvm_ComputerSystem"
)
// https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-computersystem
type VirtualMachineManager struct {
}
func NewVirtualMachineManager() *VirtualMachineManager {
return &VirtualMachineManager{}
}
func NewLocalHyperVService() (*wmiext.Service, error) {
service, err := wmiext.NewLocalService(HyperVNamespace)
if err != nil {
return nil, translateCommonHyperVWmiError(err)
}
return service, nil
}
func (vmm *VirtualMachineManager) GetAll() ([]*VirtualMachine, error) {
// Fetch through settings to avoid locale sensitive properties
const wql = "Select * From Msvm_VirtualSystemSettingData Where VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'"
var service *wmiext.Service
var err error
if service, err = NewLocalHyperVService(); err != nil {
return []*VirtualMachine{}, err
}
defer service.Close()
var enum *wmiext.Enum
if enum, err = service.ExecQuery(wql); err != nil {
return nil, err
}
defer enum.Close()
var vms []*VirtualMachine
for {
settings, err := enum.Next()
if err != nil {
return vms, err
}
// Finished iterating
if settings == nil {
break
}
vm, err := vmm.findVMFromSettings(service, settings)
settings.Close()
if err != nil {
return vms, err
}
vms = append(vms, vm)
}
return vms, nil
}
func (vmm *VirtualMachineManager) Exists(name string) (bool, error) {
vms, err := vmm.GetAll()
if err != nil {
return false, err
}
for _, i := range vms {
// TODO should case be honored or ignored?
if i.Name == name {
return true, nil
}
}
return false, nil
}
// GetMachine is a stub to lookup and get settings for a VM
func (vmm *VirtualMachineManager) GetMachine(name string) (*VirtualMachine, error) {
return vmm.getMachine(name)
}
// GetMachineExists looks for a machine defined in hyperv and returns it if it exists
func (vmm *VirtualMachineManager) GetMachineExists(name string) (bool, *VirtualMachine, error) {
vm, err := vmm.getMachine(name)
if err != nil {
if errors.Is(err, wmiext.ErrNoResults) {
return false, nil, nil
}
return false, nil, err
}
return true, vm, nil
}
// getMachine looks up a single VM by name
func (vmm *VirtualMachineManager) getMachine(name string) (*VirtualMachine, error) {
const wql = "Select * From Msvm_VirtualSystemSettingData Where VirtualSystemType = 'Microsoft:Hyper-V:System:Realized' And ElementName='%s'"
vm := &VirtualMachine{}
var service *wmiext.Service
var err error
if service, err = NewLocalHyperVService(); err != nil {
return vm, err
}
defer service.Close()
var enum *wmiext.Enum
if enum, err = service.ExecQuery(fmt.Sprintf(wql, name)); err != nil {
return nil, err
}
defer enum.Close()
settings, err := service.FindFirstInstance(fmt.Sprintf(wql, name))
if err != nil {
if errors.Is(err, wmiext.ErrNoResults) {
return nil, err
}
return vm, fmt.Errorf("could not find virtual machine %q: %w", name, err)
}
defer settings.Close()
return vmm.findVMFromSettings(service, settings)
}
func (vmm *VirtualMachineManager) findVMFromSettings(service *wmiext.Service, settings *wmiext.Instance) (*VirtualMachine, error) {
path, err := settings.Path()
if err != nil {
return nil, err
}
vm := &VirtualMachine{vmm: vmm}
err = service.FindFirstRelatedObject(path, MsvmComputerSystem, vm)
return vm, err
}
func (*VirtualMachineManager) CreateVhdxFile(path string, maxSize uint64) error {
var service *wmiext.Service
var err error
if service, err = NewLocalHyperVService(); err != nil {
return err
}
defer service.Close()
settings := &VirtualHardDiskSettings{}
settings.Format = 3
settings.MaxInternalSize = maxSize
settings.Type = 3
settings.Path = path
instance, err := service.CreateInstance("Msvm_VirtualHardDiskSettingData", settings)
if err != nil {
return err
}
defer instance.Close()
settingsStr := instance.GetCimText()
imms, err := service.GetSingletonInstance("Msvm_ImageManagementService")
if err != nil {
return err
}
defer imms.Close()
var job *wmiext.Instance
var ret int32
err = imms.BeginInvoke("CreateVirtualHardDisk").
In("VirtualDiskSettingData", settingsStr).
Execute().
Out("Job", &job).
Out("ReturnValue", &ret).
End()
if err != nil {
return fmt.Errorf("failed to create vhdx: %w", err)
}
return waitVMResult(ret, service, job, "failed to create vhdx", nil)
}
// GetSummaryInformation returns the live VM summary information for all virtual machines.
// The requestedFields parameter controls which fields of summary information are populated.
// SummaryRequestCommon and SummaryRequestNearAll provide predefined combinations for this
// parameter.
func (vmm *VirtualMachineManager) GetSummaryInformation(requestedFields SummaryRequestSet) ([]SummaryInformation, error) {
return vmm.getSummaryInformation("", requestedFields)
}
func (vmm *VirtualMachineManager) getSummaryInformation(settingsPath string, requestedFields SummaryRequestSet) ([]SummaryInformation, error) {
var service *wmiext.Service
var err error
if service, err = NewLocalHyperVService(); err != nil {
return nil, err
}
defer service.Close()
vsms, err := service.GetSingletonInstance(VirtualSystemManagementService)
if err != nil {
return nil, err
}
defer vsms.Close()
var summary []SummaryInformation
inv := vsms.BeginInvoke("GetSummaryInformation").
In("RequestedInformation", []uint(requestedFields))
if len(settingsPath) > 0 {
inv.In("SettingData", []string{settingsPath})
}
err = inv.Execute().
Out("SummaryInformation", &summary).
End()
if err != nil {
return nil, err
}
return summary, nil
}