-
Notifications
You must be signed in to change notification settings - Fork 0
/
machine.go
318 lines (272 loc) · 8.35 KB
/
machine.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
package drivervbox
import (
"fmt"
"github.com/kuttiproject/drivercore"
"github.com/kuttiproject/workspace"
)
// Machine implements the drivercore.Machine interface for VirtualBox
type Machine struct {
driver *Driver
name string
// netname string
clustername string
savedipaddress string
status drivercore.MachineStatus
errormessage string
}
// Name is the name of the machine.
func (vh *Machine) Name() string {
return vh.name
}
func (vh *Machine) qname() string {
return vh.driver.QualifiedMachineName(vh.name, vh.clustername)
}
func (vh *Machine) netname() string {
return vh.driver.QualifiedNetworkName(vh.clustername)
}
// Status can be drivercore.MachineStatusRunning, drivercore.MachineStatusStopped
// drivercore.MachineStatusUnknown or drivercore.MachineStatusError.
func (vh *Machine) Status() drivercore.MachineStatus {
return vh.status
}
// Error returns the last error caused when manipulating this machine.
// A valid value can be expected only when Status() returns
// drivercore.MachineStatusError.
func (vh *Machine) Error() string {
return vh.errormessage
}
// IPAddress returns the current IP Address of this Machine.
// The Machine status has to be Running.
func (vh *Machine) IPAddress() string {
// This guestproperty is only available if the VM is
// running, and has the Virtual Machine additions enabled
result, _ := vh.getproperty(propIPAddress)
return trimpropend(result)
}
// SSHAddress returns the address and port number to SSH into this Machine.
func (vh *Machine) SSHAddress() string {
// This guestproperty is set when the SSH port is forwarded
result, _ := vh.getproperty(propSSHAddress)
return trimpropend(result)
}
// Start starts a Machine.
// It does this by running the command:
// VBoxManage startvm <machinename> --type headless
// Note that a Machine may not be ready for further operations at the end of this,
// and therefore its status will not change.
// See WaitForStateChange().
func (vh *Machine) Start() error {
output, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"startvm",
vh.qname(),
"--type",
"headless",
)
if err != nil {
return fmt.Errorf("could not start the host '%s': %v. Output was %s", vh.name, err, output)
}
return nil
}
// Stop stops a Machine.
// It does this by running the command:
// VBoxManage controlvm <machinename> acpipowerbutton
// Note that a Machine may not be ready for further operations at the end of this,
// and therefore its status will not change.
// See WaitForStateChange().
func (vh *Machine) Stop() error {
_, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"controlvm",
vh.qname(),
"acpipowerbutton",
)
if err != nil {
return fmt.Errorf("could not stop the host '%s': %v", vh.name, err)
}
// Big risk. Deleteing the LoggedInUser property that is used
// to check running status. Should be ok, because starting a
// VirtualBox VM is supposed to recreate that property.
vh.unsetproperty(propLoggedInUsers)
return nil
}
// ForceStop stops a Machine forcibly.
// It does this by running the command:
// VBoxManage controlvm <machinename> poweroff
// This operation will set the status to drivercore.MachineStatusStopped.
func (vh *Machine) ForceStop() error {
_, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"controlvm",
vh.qname(),
"poweroff",
)
if err != nil {
return fmt.Errorf("could not force stop the host '%s': %v", vh.name, err)
}
// Big risk. Deleteing the LoggedInUser property that is used
// to check running status. Should be ok, because starting a
// VM host is supposed to recreate that property.
vh.unsetproperty(propLoggedInUsers)
vh.status = drivercore.MachineStatusStopped
return nil
}
// WaitForStateChange waits the specified number of seconds,
// or until the Machine status changes.
// It does this by running the command:
// VBoxManage guestproperty wait <machinename> /VirtualBox/GuestInfo/OS/LoggedInUsers --timeout <milliseconds> --fail-on-timeout
// WaitForStateChange should be called after a call to Start, before
// any other operation. From observation, it should not be called _before_ Stop.
func (vh *Machine) WaitForStateChange(timeoutinseconds int) {
workspace.RunWithResults(
vh.driver.vboxmanagepath,
"guestproperty",
"wait",
vh.qname(),
propLoggedInUsers,
"--timeout",
fmt.Sprintf("%v", timeoutinseconds*1000),
"--fail-on-timeout",
)
vh.get()
}
func (vh *Machine) forwardingrulename(machineport int) string {
return fmt.Sprintf("Node %s Port %d", vh.qname(), machineport)
}
// ForwardPort creates a rule to forward the specified Machine port to the
// specified physical host port. It does this by running the command:
// VBoxManage natnetwork modify --netname <networkname> --port-forward-4 <rule>
// Port forwarding rule format is:
// <rule name>:<protocol>:[<host ip>]:<host port>:[<guest ip>]:<guest port>
// The brackets [] are to be taken literally.
// This driver writes the rule name as "Node <machinename> Port <machineport>".
//
// So a sample rule would look like this:
//
// Node node1 Port 80:TCP:[]:18080:[192.168.125.11]:80
func (vh *Machine) ForwardPort(hostport int, machineport int) error {
forwardingrule := fmt.Sprintf(
"%s:tcp:[]:%d:[%s]:%d",
vh.forwardingrulename(machineport),
hostport,
vh.savedipAddress(),
machineport,
)
_, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"natnetwork",
"modify",
"--netname",
vh.netname(),
"--port-forward-4",
forwardingrule,
)
if err != nil {
return fmt.Errorf(
"could not create port forwarding rule %s for node %s on network %s: %v",
forwardingrule,
vh.name,
vh.netname(),
err,
)
}
return nil
}
// UnforwardPort removes the rule which forwarded the specified VM host port.
// It does this by running the command:
// VBoxManage natnetwork modify --netname <networkname> --port-forward-4 delete <rulename>
// This driver writes the rule name as "Node <machinename> Port <machineport>".
func (vh *Machine) UnforwardPort(machineport int) error {
rulename := vh.forwardingrulename(machineport)
_, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"natnetwork",
"modify",
"--netname",
vh.netname(),
"--port-forward-4",
"delete",
rulename,
)
if err != nil {
return fmt.Errorf(
"driver returned error while removing port forwarding rule %s for VM %s on network %s: %v",
rulename,
vh.name,
vh.netname(),
err,
)
}
return nil
}
// ForwardSSHPort forwards the SSH port of this Machine to the specified
// physical host port. See ForwardPort() for details.
func (vh *Machine) ForwardSSHPort(hostport int) error {
err := vh.ForwardPort(hostport, 22)
if err != nil {
return fmt.Errorf(
"could not create SSH port forwarding rule for node %s on network %s: %v",
vh.name,
vh.netname(),
err,
)
}
sshaddress := fmt.Sprintf("localhost:%d", hostport)
err = vh.setproperty(
propSSHAddress,
sshaddress,
)
if err != nil {
return fmt.Errorf(
"could not save SSH address for node %s : %v",
vh.name,
err,
)
}
return nil
}
// ImplementsCommand returns true if the driver implements the specified predefined command.
// The vbox driver implements drivercore.RenameMachine
func (vh *Machine) ImplementsCommand(command drivercore.PredefinedCommand) bool {
_, ok := vboxCommands[command]
return ok
}
// ExecuteCommand executes the specified predefined command.
func (vh *Machine) ExecuteCommand(command drivercore.PredefinedCommand, params ...string) error {
commandfunc, ok := vboxCommands[command]
if !ok {
return fmt.Errorf(
"command '%v' not implemented",
command,
)
}
return commandfunc(vh, params...)
}
func (vh *Machine) get() error {
output, err := workspace.RunWithResults(
vh.driver.vboxmanagepath,
"guestproperty",
"enumerate",
vh.qname(),
"--patterns",
"/VirtualBox/GuestInfo/Net/0/*|/kutti/*|/VirtualBox/GuestInfo/OS/LoggedInUsers",
)
if err != nil {
return fmt.Errorf("machine %s not found", vh.name)
}
// If machine properties could be retrieved, assume the machine is in
// Stopped state. Parsing the properties may change this.
vh.status = drivercore.MachineStatusStopped
if output != "" {
vh.parseProps(output)
}
return nil
}
func (vh *Machine) savedipAddress() string {
// This guestproperty is set when the VM is created
if vh.savedipaddress != "" {
return vh.savedipaddress
}
result, _ := vh.getproperty(propSavedIPAddress)
return trimpropend(result)
}