Skip to content

Commit a5915e7

Browse files
committed
Manager Interface
This is part of a long series of changes. It expands on the sentiment of the Command interface, but with the goal of handing everything as a method of the Manager interface. Why? So its easier to test and change the functionality of the package. Currently its nearly impossible for a consumer of the package to test, as everything is operated using functions, and methods on structs such as Machine. The Manager interface will contain every API call. The code is currently 100% compatible with existing version, and simply builds on the existing code. For convenience, Run() function is exposed, which can be used to run any command using the default manager, while the APIs are being migrated. Once the full migration completes, the previous functions will be deprecated and later removed. This most likely will mean v2 of the library, but it is fair distance away.
1 parent 5f2cd96 commit a5915e7

File tree

8 files changed

+310
-92
lines changed

8 files changed

+310
-92
lines changed

gen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package virtualbox
2+
3+
//go:generate mockgen -package=virtualbox -source=vbcmd.go -destination=mockvbcmd_test.go

interface.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package virtualbox
2+
3+
import (
4+
"context"
5+
)
6+
7+
// Manager allows to get and edit every property of Virtualbox.
8+
type Manager interface {
9+
// Machine gets the machine by its name or UUID
10+
Machine(context.Context, string) (*Machine, error)
11+
12+
// ListMachines returns the list of all known machines
13+
ListMachines(context.Context) ([]*Machine, error)
14+
15+
// UpdateMachine takes in the properties of the machine and applies the
16+
// configuration
17+
UpdateMachine(context.Context, *Machine) error
18+
}

machine.go

Lines changed: 85 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package virtualbox
22

33
import (
44
"bufio"
5+
"context"
56
"fmt"
67
"path/filepath"
78
"strconv"
89
"strings"
9-
"sync"
1010
"time"
1111
)
1212

@@ -201,18 +201,16 @@ func (m *Machine) Delete() error {
201201
return Manage().run("unregistervm", m.Name, "--delete")
202202
}
203203

204-
var mutex sync.Mutex
205-
206-
// GetMachine finds a machine by its name or UUID.
207-
func GetMachine(id string) (*Machine, error) {
204+
// Machine returns the current machine state based on the current state.
205+
func (m *manager) Machine(ctx context.Context, id string) (*Machine, error) {
208206
/* There is a strage behavior where running multiple instances of
209207
'VBoxManage showvminfo' on same VM simultaneously can return an error of
210208
'object is not ready (E_ACCESSDENIED)', so we sequential the operation with a mutex.
211209
Note if you are running multiple process of go-virtualbox or 'showvminfo'
212210
in the command line side by side, this not gonna work. */
213-
mutex.Lock()
214-
stdout, stderr, err := Manage().runOutErr("showvminfo", id, "--machinereadable")
215-
mutex.Unlock()
211+
m.lock.Lock()
212+
stdout, stderr, err := m.run(ctx, "showvminfo", id, "--machinereadable")
213+
m.lock.Unlock()
216214
if err != nil {
217215
if reMachineNotFound.FindString(stderr) != "" {
218216
return nil, ErrMachineNotExist
@@ -240,27 +238,27 @@ func GetMachine(id string) (*Machine, error) {
240238
}
241239

242240
/* Extract basic info */
243-
m := New()
244-
m.Name = propMap["name"]
245-
m.UUID = propMap["UUID"]
246-
m.State = MachineState(propMap["VMState"])
241+
vm := New()
242+
vm.Name = propMap["name"]
243+
vm.UUID = propMap["UUID"]
244+
vm.State = MachineState(propMap["VMState"])
247245
n, err := strconv.ParseUint(propMap["memory"], 10, 32)
248246
if err != nil {
249247
return nil, err
250248
}
251-
m.Memory = uint(n)
249+
vm.Memory = uint(n)
252250
n, err = strconv.ParseUint(propMap["cpus"], 10, 32)
253251
if err != nil {
254252
return nil, err
255253
}
256-
m.CPUs = uint(n)
254+
vm.CPUs = uint(n)
257255
n, err = strconv.ParseUint(propMap["vram"], 10, 32)
258256
if err != nil {
259257
return nil, err
260258
}
261-
m.VRAM = uint(n)
262-
m.CfgFile = propMap["CfgFile"]
263-
m.BaseFolder = filepath.Dir(m.CfgFile)
259+
vm.VRAM = uint(n)
260+
vm.CfgFile = propMap["CfgFile"]
261+
vm.BaseFolder = filepath.Dir(vm.CfgFile)
264262

265263
/* Extract NIC info */
266264
for i := 1; i <= 4; i++ {
@@ -283,29 +281,46 @@ func GetMachine(id string) (*Machine, error) {
283281
} else if nic.Network == NICNetBridged {
284282
nic.HostInterface = propMap[fmt.Sprintf("bridgeadapter%d", i)]
285283
}
286-
m.NICs = append(m.NICs, nic)
284+
vm.NICs = append(vm.NICs, nic)
287285
}
288286

289287
if err := s.Err(); err != nil {
290288
return nil, err
291289
}
292-
return m, nil
290+
return vm, nil
291+
}
292+
293+
// GetMachine finds a machine by its name or UUID.
294+
//
295+
// Deprecated: Use Manager.Machine()
296+
func GetMachine(id string) (*Machine, error) {
297+
return defaultManager.Machine(context.Background(), id)
293298
}
294299

295300
// ListMachines lists all registered machines.
301+
//
302+
// Deprecated: Use Manager.ListMachines()
296303
func ListMachines() ([]*Machine, error) {
297-
out, err := Manage().runOut("list", "vms")
304+
return defaultManager.ListMachines(context.Background())
305+
}
306+
307+
// ListMachines lists all registered machines.
308+
func (m *manager) ListMachines(ctx context.Context) ([]*Machine, error) {
309+
m.lock.Lock()
310+
out, _, err := m.run(ctx, "list", "vms")
311+
m.lock.Unlock()
298312
if err != nil {
299313
return nil, err
300314
}
315+
301316
ms := []*Machine{}
302317
s := bufio.NewScanner(strings.NewReader(out))
303318
for s.Scan() {
304319
res := reVMNameUUID.FindStringSubmatch(s.Text())
305320
if res == nil {
306321
continue
307322
}
308-
m, err := GetMachine(res[1])
323+
m, err := m.Machine(ctx, res[1])
309324
if err != nil {
310325
// Sometimes a VM is listed but not available, so we need to handle this.
311326
if err == ErrMachineNotExist {
@@ -356,44 +371,44 @@ func CreateMachine(name, basefolder string) (*Machine, error) {
356371
return m, nil
357372
}
358373

359-
// Modify changes the settings of the machine.
360-
func (m *Machine) Modify() error {
361-
args := []string{"modifyvm", m.Name,
374+
// UpdateMachine updates the machine details based on the struct fields.
375+
func (m *manager) UpdateMachine(ctx context.Context, vm *Machine) error {
376+
args := []string{"modifyvm", vm.Name,
362377
"--firmware", "bios",
363378
"--bioslogofadein", "off",
364379
"--bioslogofadeout", "off",
365380
"--bioslogodisplaytime", "0",
366381
"--biosbootmenu", "disabled",
367382

368-
"--ostype", m.OSType,
369-
"--cpus", fmt.Sprintf("%d", m.CPUs),
370-
"--memory", fmt.Sprintf("%d", m.Memory),
371-
"--vram", fmt.Sprintf("%d", m.VRAM),
372-
373-
"--acpi", m.Flag.Get(ACPI),
374-
"--ioapic", m.Flag.Get(IOAPIC),
375-
"--rtcuseutc", m.Flag.Get(RTCUSEUTC),
376-
"--cpuhotplug", m.Flag.Get(CPUHOTPLUG),
377-
"--pae", m.Flag.Get(PAE),
378-
"--longmode", m.Flag.Get(LONGMODE),
379-
"--hpet", m.Flag.Get(HPET),
380-
"--hwvirtex", m.Flag.Get(HWVIRTEX),
381-
"--triplefaultreset", m.Flag.Get(TRIPLEFAULTRESET),
382-
"--nestedpaging", m.Flag.Get(NESTEDPAGING),
383-
"--largepages", m.Flag.Get(LARGEPAGES),
384-
"--vtxvpid", m.Flag.Get(VTXVPID),
385-
"--vtxux", m.Flag.Get(VTXUX),
386-
"--accelerate3d", m.Flag.Get(ACCELERATE3D),
387-
}
388-
389-
for i, dev := range m.BootOrder {
383+
"--ostype", vm.OSType,
384+
"--cpus", fmt.Sprintf("%d", vm.CPUs),
385+
"--memory", fmt.Sprintf("%d", vm.Memory),
386+
"--vram", fmt.Sprintf("%d", vm.VRAM),
387+
388+
"--acpi", vm.Flag.Get(ACPI),
389+
"--ioapic", vm.Flag.Get(IOAPIC),
390+
"--rtcuseutc", vm.Flag.Get(RTCUSEUTC),
391+
"--cpuhotplug", vm.Flag.Get(CPUHOTPLUG),
392+
"--pae", vm.Flag.Get(PAE),
393+
"--longmode", vm.Flag.Get(LONGMODE),
394+
"--hpet", vm.Flag.Get(HPET),
395+
"--hwvirtex", vm.Flag.Get(HWVIRTEX),
396+
"--triplefaultreset", vm.Flag.Get(TRIPLEFAULTRESET),
397+
"--nestedpaging", vm.Flag.Get(NESTEDPAGING),
398+
"--largepages", vm.Flag.Get(LARGEPAGES),
399+
"--vtxvpid", vm.Flag.Get(VTXVPID),
400+
"--vtxux", vm.Flag.Get(VTXUX),
401+
"--accelerate3d", vm.Flag.Get(ACCELERATE3D),
402+
}
403+
404+
for i, dev := range vm.BootOrder {
390405
if i > 3 {
391406
break // Only four slots `--boot{1,2,3,4}`. Ignore the rest.
392407
}
393408
args = append(args, fmt.Sprintf("--boot%d", i+1), dev)
394409
}
395410

396-
for i, nic := range m.NICs {
411+
for i, nic := range vm.NICs {
397412
n := i + 1
398413
args = append(args,
399414
fmt.Sprintf("--nic%d", n), string(nic.Network),
@@ -406,10 +421,14 @@ func (m *Machine) Modify() error {
406421
}
407422
}
408423

409-
if err := Manage().run(args...); err != nil {
424+
if _, _, err := m.run(ctx, args...); err != nil {
410425
return err
411426
}
412-
return m.Refresh()
427+
return vm.Refresh()
428+
}
429+
430+
func (m *Machine) Modify() error {
431+
return defaultManager.UpdateMachine(context.Background(), m)
413432
}
414433

415434
// AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name.
@@ -463,22 +482,27 @@ func (m *Machine) DelStorageCtl(name string) error {
463482

464483
// AttachStorage attaches a storage medium to the named storage controller.
465484
func (m *Machine) AttachStorage(ctlName string, medium StorageMedium) error {
466-
return Manage().run("storageattach", m.Name, "--storagectl", ctlName,
485+
_, _, err := defaultManager.run(context.Background(),
486+
"storageattach", m.Name, "--storagectl", ctlName,
467487
"--port", fmt.Sprintf("%d", medium.Port),
468488
"--device", fmt.Sprintf("%d", medium.Device),
469489
"--type", string(medium.DriveType),
470490
"--medium", medium.Medium,
471491
)
492+
return err
472493
}
473494

474495
// SetExtraData attaches custom string to the VM.
475496
func (m *Machine) SetExtraData(key, val string) error {
476-
return Manage().run("setextradata", m.Name, key, val)
497+
_, _, err := defaultManager.run(context.Background(),
498+
"setextradata", m.Name, key, val)
499+
return err
477500
}
478501

479502
// GetExtraData retrieves custom string from the VM.
480503
func (m *Machine) GetExtraData(key string) (*string, error) {
481-
value, err := Manage().runOut("getextradata", m.Name, key)
504+
value, _, err := defaultManager.run(context.Background(),
505+
"getextradata", m.Name, key)
482506
if err != nil {
483507
return nil, err
484508
}
@@ -494,13 +518,19 @@ func (m *Machine) GetExtraData(key string) (*string, error) {
494518

495519
// DeleteExtraData removes custom string from the VM.
496520
func (m *Machine) DeleteExtraData(key string) error {
497-
return Manage().run("setextradata", m.Name, key)
521+
_, _, err := defaultManager.run(context.Background(),
522+
"setextradata", m.Name, key)
523+
return err
498524
}
499525

500526
// CloneMachine clones the given machine name into a new one.
501527
func CloneMachine(baseImageName string, newImageName string, register bool) error {
502528
if register {
503-
return Manage().run("clonevm", baseImageName, "--name", newImageName, "--register")
529+
_, _, err := defaultManager.run(context.Background(),
530+
"clonevm", baseImageName, "--name", newImageName, "--register")
531+
return err
504532
}
505-
return Manage().run("clonevm", baseImageName, "--name", newImageName)
533+
_, _, err := defaultManager.run(context.Background(),
534+
"clonevm", baseImageName, "--name", newImageName)
535+
return err
506536
}

manager.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package virtualbox
2+
3+
import (
4+
"context"
5+
"sync"
6+
)
7+
8+
var defaultManager = NewManager()
9+
10+
// manager implements all the functionality of the Manager, and is the default
11+
// one used.
12+
type manager struct {
13+
// Wrap around the existing code until its migrated
14+
cmd Command
15+
// lock the whole manager to only allow one action at a time
16+
// TODO: Decide is this a good idea, or should we have one mutex per
17+
// type of operation
18+
lock sync.Mutex
19+
}
20+
21+
// NewManager returns the real instance of the manager
22+
func NewManager() *manager {
23+
return &manager{
24+
cmd: Manage(),
25+
}
26+
}
27+
28+
// run is the internal function used by other commands.
29+
func (m *manager) run(ctx context.Context, args ...string) (string, string, error) {
30+
return m.cmd.runOutErrContext(ctx, args...)
31+
}
32+
33+
// Run is a helper function using the defaultManager and can be used to directly
34+
// run commands which are not exposed as part of the Manager API. It returns the
35+
// stdout, stderr and any errors which happened while executing the command.
36+
// The `VBoxManage` argument should not be specified at the beginning as it is
37+
// deducted from the environment.
38+
//
39+
// Notice: Its possible that if we ever cover the API 1:1, this function might
40+
// be deprecated and later removed.
41+
func Run(ctx context.Context, args ...string) (string, string, error) {
42+
return defaultManager.run(ctx, args...)
43+
}

mock/gen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package mock
2+
3+
//go:generate mockgen -package=mock -source=../interface.go -destination=interface_mock.go

0 commit comments

Comments
 (0)