├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── Readme.md ├── constants.go ├── dhcp.go ├── disk.go ├── disk_test.go ├── errors.go ├── hack └── verify-gofmt.sh ├── machine.go ├── machine_test.go ├── net.go ├── net_test.go ├── types.go ├── utils.go ├── vbox.go ├── vbox_unix.go ├── vbox_windows.go └── vendor ├── github.com └── golang │ └── glog │ ├── LICENSE │ ├── README │ ├── glog.go │ └── glog_file.go └── gopkg.in └── d4l3k └── messagediff.v1 ├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bypass.go ├── bypasssafe.go └── messagediff.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | _output 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | .idea/* -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" 7 | name = "github.com/golang/glog" 8 | packages = ["."] 9 | pruneopts = "UT" 10 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" 11 | 12 | [[projects]] 13 | digest = "1:653e04c4515b37169ef5874d752f4254e63b4aedb5c8b3a4118e9e3a4e3503e1" 14 | name = "gopkg.in/d4l3k/messagediff.v1" 15 | packages = ["."] 16 | pruneopts = "UT" 17 | revision = "29f32d820d112dbd66e58492a6ffb7cc3106312b" 18 | version = "v1.2.1" 19 | 20 | [solve-meta] 21 | analyzer-name = "dep" 22 | analyzer-version = 1 23 | input-imports = [ 24 | "github.com/golang/glog", 25 | "gopkg.in/d4l3k/messagediff.v1", 26 | ] 27 | solver-name = "gps-cdcl" 28 | solver-version = 1 29 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/golang/glog" 31 | 32 | [[constraint]] 33 | name = "gopkg.in/d4l3k/messagediff.v1" 34 | version = "1.2.1" 35 | 36 | [prune] 37 | go-tests = true 38 | unused-packages = true 39 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # virtualbox-go 3 | The most complete Golang wrapper for [virtualbox](https://www.virtualbox.org/) for macOS (Not tested on other platforms). This library includes a wide range of support for different virtualbox operations that include dhcp, disk, network, virtualmachines, power, etc. You can use this library to stitch your own network and compute topology using virtualbox. Refer to examples for more details. 4 | 5 | ## What is Virtualbox ? 6 | From [here](https://www.virtualbox.org/manual/ch01.html), Oracle VM VirtualBox is a cross-platform virtualization application. What does that mean? For one thing, it installs on your existing Intel or AMD-based computers, whether they are running Windows, Mac OS X, Linux, or Oracle Solaris operating systems (OSes). Secondly, it extends the capabilities of your existing computer so that it can run multiple OSes, inside multiple virtual machines, at the same time. As an example, you can run Windows and Linux on your Mac, run Windows Server 2016 on your Linux server, run Linux on your Windows PC, and so on, all alongside your existing applications. You can install and run as many virtual machines as you like. The only practical limits are disk space and memory. 7 | 8 | Use cases include: 9 | 10 | - Testing new network topology 11 | - Testing new routing methodologies 12 | - Testing new container networking 13 | - Testing new Kubernetes network plugins 14 | - Testing new software defined firewalls and gateways 15 | - Local simulation of a datacenter L2/L3/L4 usecases. 16 | 17 | ## Prerequisites 18 | 19 | - MacOS > High Sierra 10.13.1 (Not tested on other OS) 20 | - Virtualbox > 5.1.28r117968 21 | - Golang > 1.5 22 | 23 | ## Installation 24 | You can add virtualbox-go to your GOPATH by running: 25 | ```bash 26 | go get -u github.com/uruddarraju/virtualbox-go 27 | ``` 28 | You can then add the following imports to your golang code for you to start using it and having fun: 29 | ```go 30 | import ( 31 | vbg "github.com/uruddarraju/virtualbox-go" 32 | ) 33 | ``` 34 | 35 | ## Examples 36 | 37 | ### Create a Virtual Machine 38 | ```go 39 | func CreateVM() { 40 | // setup temp directory, that will be used to cache different VM related files during the creation of the VM. 41 | dirName, err := ioutil.TempDir("", "vbm") 42 | if err != nil { 43 | t.Errorf("Tempdir creation failed %v", err) 44 | } 45 | defer os.RemoveAll(dirName) 46 | 47 | vb := vbg.NewVBox(vbg.Config{ 48 | BasePath: dirName, 49 | }) 50 | 51 | disk1 := vbg.Disk{ 52 | Path: filepath.Join(dirName, "disk1.vdi"), 53 | Format: VDI, 54 | SizeMB: 10, 55 | } 56 | 57 | err = vb.CreateDisk(&disk1) 58 | if err != nil { 59 | t.Errorf("CreateDisk failed %v", err) 60 | } 61 | 62 | vm := &vbg.VirtualMachine{} 63 | vm.Spec.Name = "testvm1" 64 | vm.Spec.OSType = Linux64 65 | vm.Spec.CPU.Count = 2 66 | vm.Spec.Memory.SizeMB = 1000 67 | vm.Spec.Disks = []vbg.Disk{disk1} 68 | 69 | err = vb.CreateVM(vm) 70 | if err != nil { 71 | t.Fatalf("Failed creating vm %v", err) 72 | } 73 | 74 | err = vb.RegisterVM(vm) 75 | if err != nil { 76 | t.Fatalf("Failed registering vm") 77 | } 78 | } 79 | ``` 80 | 81 | ### Get VM Info 82 | ```go 83 | func GetVMInfo(name string) (machine *vbm.VirtualMachine, err error) { 84 | vb := vbg.NewVBox(vbg.Config{}) 85 | return vb.VMInfo(name) 86 | } 87 | ``` 88 | 89 | ### Managing states of a Virtual Machine 90 | ```go 91 | func ManageStates(vm *vbg.VirtualMachine) { 92 | vb := vbg.NewVBox(vbg.Config{}) 93 | ctx := context.Background() 94 | context.WithTimeout(ctx, 1*time.Minute) 95 | // Start a VM, this call is idempotent. 96 | _, err = vb.Start(vm) 97 | if err != nil { 98 | t.Fatalf("Failed to start vm %s, error %v", vm.Spec.Name, err) 99 | } 100 | 101 | // Reset a VM 102 | _, err = vb.Reset(vm) 103 | if err != nil { 104 | t.Fatalf("Failed to reset vm %s, error %v", vm.Spec.Name, err) 105 | } 106 | 107 | // Pause and Resume VMs 108 | _, err = vb.Pause(vm) 109 | if err != nil { 110 | t.Fatalf("Failed to pause vm %s, error %v", vm.Spec.Name, err) 111 | } 112 | _, err = vb.Resume(vm) 113 | if err != nil { 114 | t.Fatalf("Failed to resume vm %s, error %v", vm.Spec.Name, err) 115 | } 116 | 117 | // Stop a VM, this call is also idempotent. 118 | _, err = vb.Stop(vm) 119 | if err != nil { 120 | t.Fatalf("Failed to stop vm %s, error %v", vm.Spec.Name, err) 121 | } 122 | } 123 | ``` 124 | 125 | ### Attach New Disk to existing VM 126 | ```go 127 | func AttachDisk(vm *vbg.VirtualMachine) error { 128 | disk2 := &vbg.Disk{ 129 | Path: filepath.Join(dirName, "disk2.vdi"), 130 | Format: VDI, 131 | SizeMB: 100, 132 | } 133 | vb := vbg.NewVBox(vbg.Config{}) 134 | ctx := context.Background() 135 | context.WithTimeout(ctx, 1*time.Minute) 136 | vb.AttachStorage(vm, disk2) 137 | } 138 | ``` 139 | 140 | ### More Documentation 141 | Coming soon.... 142 | 143 | ## Why did I build it and not use something else ? 144 | I looked around a lot for clean implementations of virtualbox golang wrappers, but could not find anything with high quality and test coverage, and also most libraries out there only have support for a few operations in virtualbox but not everything. The closest match to my requirements was [libmachine from docker](https://github.com/docker/machine/tree/master/libmachine), but it had some tight coupling with the rest of docker packages which I definitely did not want to use. Also, it was very difficult finding something that had good documentation and examples. You might not find these to be good enough reasons to start a new project, but I found them compelling enough to start my own. -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | var Linux32 OSType = OSType{ID: "Linux", FamilyID: "Linux", Bit64: false} 4 | var Linux64 OSType = OSType{ID: "Linux_64", FamilyID: "Linux", Bit64: true} 5 | 6 | var Ubuntu32 OSType = OSType{ID: "Ubuntu", FamilyID: "Linux", Bit64: false} 7 | var Ubuntu64 OSType = OSType{ID: "Ubuntu_64", FamilyID: "Linux", Bit64: true} 8 | -------------------------------------------------------------------------------- /dhcp.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func (vb *VBox) DisableDHCPServer(netName string) (string, error) { 9 | _, err := vb.manage("dhcpserver", "remove", "--netname", netName) 10 | if err != nil && !strings.Contains(err.Error(), "does not exist") { 11 | return "", err 12 | } 13 | return "Disabled dhcp server", nil 14 | } 15 | 16 | func (vb *VBox) EnableDHCPServer(netName string, ip string, netmask string, lowerIP string, upperIP string) (string, error) { 17 | return vb.manage("dhcpserver", "add", "--netname", netName, fmt.Sprintf("--ip=%s", ip), fmt.Sprintf("--netmask=%s", netmask), fmt.Sprintf("--lowerip=%s", lowerIP), fmt.Sprintf("--upperip=%s", upperIP), "--enable") 18 | } 19 | -------------------------------------------------------------------------------- /disk.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type DiskFormat string 10 | 11 | const ( 12 | VDI = DiskFormat("VDI") 13 | VMDK = DiskFormat("VMDK") 14 | VHD = DiskFormat("VHD") 15 | ) 16 | 17 | type DiskNotFoundError string 18 | 19 | func (d DiskNotFoundError) Error() string { 20 | return string(d) 21 | } 22 | 23 | func IsDiskNotFound(err error) bool { 24 | _, ok := err.(DiskNotFoundError) 25 | return ok 26 | } 27 | 28 | func (vb *VBox) EnsureDisk(context context.Context, disk *Disk) (*Disk, error) { 29 | d, err := vb.DiskInfo(disk) 30 | if IsDiskNotFound(err) { 31 | err = vb.CreateDisk(disk) 32 | if err != nil { 33 | return nil, err 34 | } else { 35 | d, err = vb.DiskInfo(disk) 36 | } 37 | } 38 | 39 | return d, err 40 | } 41 | 42 | func (disk *Disk) UUIDorPath() string { 43 | var uuidOrPath string 44 | if disk.UUID != "" { 45 | uuidOrPath = disk.UUID 46 | } else { 47 | uuidOrPath = disk.Path 48 | } 49 | return uuidOrPath 50 | } 51 | 52 | func (vb *VBox) DiskInfo(disk *Disk) (*Disk, error) { 53 | args := []string{"showmediuminfo"} 54 | if disk.Type != "" { 55 | args = append(args, disk.Type.ForShowMedium()) 56 | } 57 | args = append(args, disk.UUIDorPath()) 58 | out, err := vb.manage(args...) 59 | if err != nil { 60 | if IsVBoxError(err) && isFileNotFoundMessage(err.Error()) { 61 | return nil, DiskNotFoundError(out) 62 | } 63 | return nil, err 64 | } 65 | 66 | var ndisk Disk 67 | _ = parseKeyValues(out, reColonLine, func(key, val string) error { 68 | switch key { 69 | case "UUID": 70 | ndisk.UUID = val 71 | case "Location": 72 | ndisk.Path = val 73 | case "Storage format": 74 | ndisk.Format = DiskFormat(val) 75 | } 76 | 77 | return nil 78 | }) 79 | 80 | if ndisk.UUID == "" { 81 | return &ndisk, DiskNotFoundError(disk.UUIDorPath()) 82 | } 83 | 84 | return &ndisk, nil 85 | } 86 | 87 | func (vb *VBox) CreateDisk(disk *Disk) error { 88 | if disk.Format == "" { 89 | disk.Format = VDI 90 | } 91 | 92 | _, err := vb.manage("createmedium", "disk", "--filename", disk.Path, "--size", fmt.Sprintf("%d", disk.SizeMB), 93 | "--format", string(disk.Format)) 94 | 95 | return err 96 | } 97 | 98 | func (vb *VBox) DeleteDisk(uuidOfFile string) error { 99 | out, err := vb.manage("closemedium", uuidOfFile, "--delete") 100 | if err != nil { 101 | if isFileNotFoundMessage(out) { 102 | return DiskNotFoundError(out) 103 | } 104 | } 105 | return err 106 | } 107 | 108 | func isFileNotFoundMessage(out string) bool { 109 | return strings.Contains(out, "VERR_FILE_NOT_FOUND") 110 | } 111 | -------------------------------------------------------------------------------- /disk_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/user" 7 | "path/filepath" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestVbox_CreateDelete(t *testing.T) { 13 | 14 | dirName, err := ioutil.TempDir("", "vbm") 15 | if err != nil { 16 | t.Errorf("CreateDisk failed %v", err) 17 | } 18 | defer os.RemoveAll(dirName) 19 | 20 | expected := Disk{ 21 | Path: filepath.Join(dirName, "disk1.vdi"), 22 | Format: VDI, 23 | SizeMB: 10, 24 | } 25 | 26 | var vb VBox 27 | 28 | err = vb.CreateDisk(&expected) 29 | if err != nil { 30 | t.Errorf("CreateDisk failed %v", err) 31 | } 32 | 33 | actual, err := vb.DiskInfo(&expected) 34 | if err != nil { 35 | t.Fatalf("DiksInfo failed with %v", err) 36 | } 37 | 38 | if actual.UUID == "" { 39 | t.Fatalf("Disk was not created?") 40 | } 41 | 42 | err = vb.DeleteDisk(actual.UUID) 43 | if err != nil { 44 | t.Fatalf("error deleting disk %v", err) 45 | } 46 | 47 | actual, err = vb.DiskInfo(&expected) 48 | if err == nil { 49 | t.Fatalf("Expected error, but gone none") 50 | } 51 | 52 | if !IsDiskNotFound(err) { 53 | t.Fatalf("Expected DiskNotFoundError error, but gone %v", err) 54 | } 55 | 56 | } 57 | 58 | func TestShowMediumOutputRegex(t *testing.T) { 59 | var sampleDiskOut = ` 60 | UUID: 0e3f0c1b-f523-4a50-b1a8-d1e8c9a508b4 61 | Parent UUID: base 62 | State: created 63 | Type: normal (base) 64 | Storage format: VDI 65 | Format variant: dynamic default 66 | Capacity: 1000 MBytes 67 | Size on disk: 2 MBytes 68 | Encryption: disabled 69 | ` 70 | 71 | user, _ := user.Current() 72 | expected := Disk{ 73 | Path: user.HomeDir + "/.vbm/Vbox/myvm1/disk1.vdi", 74 | Format: VDI, 75 | UUID: "0e3f0c1b-f523-4a50-b1a8-d1e8c9a508b4", 76 | } 77 | 78 | //Vbox.CreateDisk(disk1.Path, ) 79 | var disk = Disk{} 80 | _ = parseKeyValues(sampleDiskOut, reColonLine, func(key, val string) error { 81 | switch key { 82 | case "UUID": 83 | disk.UUID = val 84 | case "Location": 85 | disk.Path = val 86 | case "Storage format": 87 | disk.Format = DiskFormat(val) 88 | } 89 | 90 | return nil 91 | }) 92 | 93 | if !reflect.DeepEqual(expected, disk) { 94 | t.Errorf("Did not parse showmediuminfo out to disk as expected. Got %+v", disk) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type OperationErrorType string 9 | 10 | type OperationError struct { 11 | Path string 12 | // for e.g GET, PUT, WRITE, READ 13 | Op string 14 | Type OperationErrorType 15 | Err error 16 | } 17 | 18 | func (o OperationError) Error() string { 19 | return strings.Join([]string{o.Path, o.Op, string(o.Type), o.Err.Error()}, ", \n") 20 | } 21 | 22 | type ValidationError struct { 23 | Path string 24 | Err error 25 | } 26 | 27 | func (v ValidationError) Error() string { 28 | return v.Err.Error() 29 | } 30 | 31 | type ValidationErrors struct { 32 | errors []ValidationError 33 | } 34 | 35 | func (v ValidationErrors) Error() string { 36 | var messages []string 37 | for _, v := range v.errors { 38 | messages = append(messages, v.Error()) 39 | } 40 | 41 | return strings.Join(messages, "\n") 42 | } 43 | func (v ValidationErrors) Add(path string, err error) { 44 | v.errors = append(v.errors, ValidationError{path, err}) 45 | } 46 | 47 | type AlreadyExists string 48 | 49 | func (v AlreadyExists) Error() string { 50 | return string(v) 51 | } 52 | 53 | func (v AlreadyExists) New(item string, hints ...string) AlreadyExists { 54 | hint := "" 55 | if len(hints) > 0 { 56 | hint = strings.Join(append([]string{"Try the following: \n"}, hints...), "\n") 57 | } 58 | return AlreadyExists(fmt.Sprintf("%s already exists. %s", item, hint)) 59 | } 60 | 61 | var AlreadyExistsErrorr AlreadyExists = "already exists" 62 | 63 | func IsAlreadyExistsError(err error) bool { 64 | _, ok := err.(AlreadyExists) 65 | return ok 66 | } 67 | 68 | type AlreadyAttachedError string 69 | 70 | func (v AlreadyAttachedError) Error() string { 71 | return string(v) 72 | } 73 | func IsAlreadyAttachedError(err error) bool { 74 | _, ok := err.(AlreadyAttachedError) 75 | return ok 76 | } 77 | 78 | type NotFoundError string 79 | 80 | func (n NotFoundError) Error() string { 81 | return string(n) 82 | } 83 | -------------------------------------------------------------------------------- /hack/verify-gofmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | ROOT=$(dirname "${BASH_SOURCE}")/.. 8 | 9 | GO_VERSION=($(go version)) 10 | 11 | if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.9|go1.10|go1.11') ]]; then 12 | echo "Unknown go version '${GO_VERSION}', skipping gofmt." 13 | exit 0 14 | fi 15 | 16 | cd "${ROOT}" 17 | 18 | find_files() { 19 | find . -not \( \ 20 | \( \ 21 | -wholename './output' \ 22 | -o -wholename './_output' \ 23 | -o -wholename './release' \ 24 | -o -wholename './target' \ 25 | -o -wholename '*/third_party/*' \ 26 | -o -wholename '*/Godeps/*' \ 27 | -o -wholename '*/vendor/*' \ 28 | \) -prune \ 29 | \) -name '*.go' 30 | } 31 | 32 | GOFMT="gofmt -s -w" 33 | bad_files=$(find_files | xargs $GOFMT -l) 34 | if [[ -n "${bad_files}" ]]; then 35 | echo "!!! '$GOFMT' needs to be run on the following files: " 36 | echo "${bad_files}" 37 | exit 1 38 | fi 39 | 40 | GOFMT="gofmt -s" 41 | bad_files=$(find_files | xargs $GOFMT -l) 42 | if [[ -n "${bad_files}" ]]; then 43 | echo "!!! '$GOFMT' needs to be run on the following files: " 44 | echo "${bad_files}" 45 | exit 1 46 | fi 47 | -------------------------------------------------------------------------------- /machine.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/golang/glog" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | func (vb *VBox) CreateVM(vm *VirtualMachine) error { 14 | 15 | args := []string{"createvm", "--name", vm.Spec.Name, "--ostype", vm.Spec.OSType.ID} 16 | 17 | //args = append(args, "--basefolder", strconv.Quote(vm.Path)) 18 | args = append(args, "--basefolder", vb.Config.BasePath) 19 | 20 | if vm.Spec.Group != "" { 21 | args = append(args, "--groups", vm.Spec.Group) 22 | } 23 | 24 | _, err := vb.manage(args...) 25 | 26 | if err != nil && isAlreadyExistErrorMessage(err.Error()) { 27 | return AlreadyExistsErrorr.New(vb.getVMSettingsFile(vm)) 28 | } 29 | 30 | // TODO: Get the UUID populated in the vm.UUID 31 | return err 32 | } 33 | 34 | // DeleteVM removes the setting file and must be used with caution. The VM must be unregistered before calling this 35 | func (vb *VBox) DeleteVM(vm *VirtualMachine) error { 36 | return os.RemoveAll(vb.getVMSettingsFile(vm)) 37 | } 38 | 39 | // TODO: Ensure this is idempotent 40 | func (vb *VBox) RegisterVM(vm *VirtualMachine) error { 41 | _, err := vb.manage("registervm", vb.getVMSettingsFile(vm)) 42 | return err 43 | } 44 | 45 | func (vb *VBox) UnRegisterVM(vm *VirtualMachine) error { 46 | _, err := vb.manage("unregistervm", vb.getVMSettingsFile(vm)) 47 | return err 48 | } 49 | 50 | func (vb *VBox) AddStorageController(vm *VirtualMachine, ctr StorageController) error { 51 | 52 | _, err := vb.manage("storagectl", vm.UUIDOrName(), "--name", ctr.Name, "--add", string(ctr.Type)) 53 | if err != nil && isAlreadyExistErrorMessage(err.Error()) { 54 | return AlreadyExists(vm.Spec.Name) 55 | } 56 | return nil 57 | } 58 | 59 | func (vb *VBox) AttachStorage(vm *VirtualMachine, disk *Disk) error { 60 | nonRotational := "off" 61 | if disk.NonRotational { 62 | nonRotational = "on" 63 | } 64 | autoDiscard := "off" 65 | if disk.AutoDiscard { 66 | if disk.Format != VDI { 67 | glog.Warning( 68 | "Disk format ", disk.Format, " is not VDI. ", 69 | "Ignoring AutoDiscard.") 70 | } else { 71 | autoDiscard = "on" 72 | } 73 | } 74 | _, err := vb.manage( 75 | "storageattach", vm.Spec.Name, 76 | "--storagectl", disk.Controller.Name, 77 | "--port", strconv.Itoa(disk.Controller.Port), 78 | "--device", strconv.Itoa(disk.Controller.Device), 79 | "--type", string(disk.Type), 80 | "--medium", disk.Path, 81 | "--nonrotational", nonRotational, 82 | "--discard", autoDiscard) 83 | 84 | return err 85 | } 86 | 87 | func (vb *VBox) SetMemory(vm *VirtualMachine, sizeMB int) error { 88 | _, err := vb.modify(vm, "--memory", strconv.Itoa(sizeMB)) 89 | return err 90 | } 91 | 92 | func (vb *VBox) SetCPUCount(vm *VirtualMachine, cpus int) error { 93 | _, err := vb.modify(vm, "--cpus", strconv.Itoa(cpus)) 94 | return err 95 | } 96 | 97 | func (vb *VBox) SetBootOrder(vm *VirtualMachine, bootOrder []BootDevice) error { 98 | args := []string{} 99 | for i, b := range bootOrder { 100 | args = append(args, fmt.Sprintf("--boot%d", i+1), string(b)) 101 | } 102 | _, err := vb.modify(vm, args...) 103 | return err 104 | } 105 | 106 | func (vb *VBox) Start(vm *VirtualMachine) (string, error) { 107 | return vb.manage("startvm", vm.UUIDOrName(), "--type", "headless") 108 | } 109 | 110 | func (vb *VBox) Stop(vm *VirtualMachine) (string, error) { 111 | return vb.control(vm, "poweroff") 112 | } 113 | 114 | func (vb *VBox) Restart(vm *VirtualMachine) (string, error) { 115 | vb.Stop(vm) 116 | return vb.Start(vm) 117 | } 118 | 119 | func (vb *VBox) Save(vm *VirtualMachine) (string, error) { 120 | return vb.control(vm, "save") 121 | } 122 | 123 | func (vb *VBox) Pause(vm *VirtualMachine) (string, error) { 124 | return vb.control(vm, "pause") 125 | } 126 | 127 | func (vb *VBox) Resume(vm *VirtualMachine) (string, error) { 128 | return vb.control(vm, "resume") 129 | } 130 | 131 | func (vb *VBox) Reset(vm *VirtualMachine) (string, error) { 132 | return vb.control(vm, "reset") 133 | } 134 | 135 | func (vb *VBox) EnableIOAPIC(vm *VirtualMachine) (string, error) { 136 | return vb.modify(vm, "--ioapic", "on") 137 | } 138 | func (vb *VBox) VMInfo(uuidOrVmName string) (machine *VirtualMachine, err error) { 139 | out, err := vb.manage("showvminfo", uuidOrVmName, "--machinereadable") 140 | 141 | // lets populate the map from output strings 142 | m := map[string]interface{}{} 143 | _ = parseKeyValues(out, reKeyEqVal, func(key, val string) error { 144 | if strings.HasPrefix(key, "\"") { 145 | if k, err := strconv.Unquote(key); err == nil { 146 | key = k 147 | } //else ignore; might need to warn in log 148 | } 149 | if strings.HasPrefix(val, "\"") { 150 | if val, err := strconv.Unquote(val); err == nil { 151 | m[key] = val 152 | } 153 | } else if i, err := strconv.Atoi(val); err == nil { 154 | m[key] = i 155 | } else { // we dont expect any actually 156 | glog.V(6).Infof("ignoring parsing val %s for key %s", val, key) 157 | } 158 | return nil 159 | }) 160 | 161 | vm := &VirtualMachine{} 162 | 163 | vm.UUID = m["UUID"].(string) 164 | vm.Spec.Name = m["name"].(string) 165 | path := m["CfgFile"].(string) 166 | if vpath, err := filepath.Rel(vb.Config.BasePath, path); err == nil { 167 | elems := strings.Split(vpath, string(filepath.Separator)) 168 | if len(elems) >= 3 { //we assume the first one to be group 169 | vm.Spec.Group = "/" + elems[0] 170 | } 171 | } 172 | if path != vb.getVMSettingsFile(vm) { 173 | return nil, fmt.Errorf("path %s does not match expected structure", path) 174 | } 175 | 176 | vm.Spec.CPU.Count = m["cpus"].(int) 177 | vm.Spec.Memory.SizeMB = m["memory"].(int) 178 | 179 | // fill in storage details 180 | vm.Spec.StorageControllers = make([]StorageController, 0, 2) 181 | 182 | for i := 0; i < 20; i++ { // upto a 20 storage controller? :) 183 | sk := fmt.Sprintf("storagecontrollername%d", i) 184 | if v, ok := m[sk]; ok { // e.g of v is SATA1 185 | 186 | sc := StorageController{Name: v.(string)} 187 | 188 | switch fmt.Sprintf("storagecontrollertype%d", i) { 189 | case string(SATA): 190 | sc.Type = SATA 191 | case string(IDE): 192 | sc.Type = IDE 193 | case string(SCSCI): 194 | sc.Type = SCSCI 195 | case string(NVME): 196 | sc.Type = NVME 197 | } 198 | 199 | var err error 200 | 201 | if si, ok := m[fmt.Sprintf("storagecontrollerinstance%d", i)]; ok { 202 | if sc.Instance, err = strconv.Atoi(si.(string)); err != nil { 203 | return nil, fmt.Errorf("wrong val") 204 | } 205 | } 206 | 207 | if sb, ok := m[fmt.Sprintf("storagecontrollerbootable%d", i)]; ok { 208 | if sc.Bootable, ok = sb.(string); !ok { 209 | return nil, fmt.Errorf("wrong val for storagecontrollerbootable") 210 | } 211 | } 212 | 213 | if sp, ok := m[fmt.Sprintf("storagecontrollerportcount%d", i)]; ok { 214 | if sc.PortCount, err = strconv.Atoi(sp.(string)); err != nil { 215 | return nil, fmt.Errorf("wrong val for storageportcount") 216 | } 217 | } 218 | 219 | vm.Spec.Disks = make([]Disk, 0, 2) 220 | 221 | for j := 0; j < sc.PortCount; j++ { 222 | dp := fmt.Sprintf("%s-%d-%d", v, j, 0) // key to path of disk, e.g SATA1-0-0 223 | if dpv, ok := m[dp]; ok && dpv != "none" { 224 | d := Disk{ 225 | Path: dpv.(string), 226 | Controller: StorageControllerAttachment{ 227 | Type: sc.Type, 228 | Port: j, 229 | Name: sc.Name, 230 | }, 231 | } 232 | if duv, ok := m[fmt.Sprintf("%s-ImageUUID-%d-%d", v, j, 0)]; ok { // e.g SATA1-ImageUUID-0-0 233 | d.UUID = duv.(string) 234 | } 235 | vm.Spec.Disks = append(vm.Spec.Disks, d) 236 | } 237 | } 238 | vm.Spec.StorageControllers = append(vm.Spec.StorageControllers, sc) 239 | 240 | } else { //storage controllers index not found, dont loop anymore 241 | break 242 | } 243 | } 244 | 245 | // now populate network 246 | 247 | for i := 1; i < 20; i++ { // upto a 20 nics 248 | n := fmt.Sprintf("nic%d", i) 249 | 250 | nic := NIC{} 251 | if v, ok := m[n]; ok { 252 | if v == "none" { 253 | continue 254 | } 255 | nic.Mode = NetworkMode(v.(string)) 256 | } else { 257 | continue 258 | } 259 | 260 | n = fmt.Sprintf("nictype%d", i) 261 | if v, ok := m[n]; ok { 262 | nic.Type = NICType(v.(string)) 263 | } 264 | 265 | n = fmt.Sprintf("nicspeed%d", i) 266 | if v, ok := m[n]; ok { 267 | nic.Speedkbps, err = strconv.Atoi(v.(string)) 268 | } 269 | 270 | n = fmt.Sprintf("macaddress%d", i) 271 | if v, ok := m[n]; ok { 272 | nic.MAC = v.(string) 273 | } 274 | 275 | n = fmt.Sprintf("cableconnected%d", i) 276 | if v, ok := m[n]; ok { 277 | nic.CableConnected = v.(string) == "on" 278 | } 279 | 280 | switch nic.Mode { 281 | case NWMode_hostonly: 282 | n = fmt.Sprintf("hostonlyadapter%d", i) 283 | if v, ok := m[n]; ok { 284 | nic.NetworkName = v.(string) 285 | } 286 | case NWMode_natnetwork: 287 | n = fmt.Sprintf("natnet%d", i) 288 | if v, ok := m[n]; ok { 289 | nic.NetworkName = v.(string) 290 | } 291 | } 292 | 293 | vm.Spec.NICs = append(vm.Spec.NICs, nic) 294 | } 295 | 296 | return vm, nil 297 | } 298 | 299 | func (vb *VBox) Define(context context.Context, vm *VirtualMachine) (*VirtualMachine, error) { 300 | 301 | if err := vb.EnsureVMHostPath(vm); err != nil { 302 | return nil, err 303 | } 304 | 305 | for i := range vm.Spec.Disks { 306 | disk, err := vb.EnsureDisk(context, &vm.Spec.Disks[i]) 307 | if err != nil { 308 | return nil, err 309 | } else { 310 | vm.Spec.Disks[i].UUID = disk.UUID 311 | } 312 | 313 | if err != nil { 314 | return nil, OperationError{Path: fmt.Sprintf("disk/%d", i), Op: "ensure", Err: err} 315 | } 316 | } 317 | 318 | if err := vb.CreateVM(vm); err != nil && !IsAlreadyExistsError(err) { 319 | return nil, OperationError{Path: "vm", Op: "ensure", Err: err} 320 | } 321 | 322 | if err := vb.RegisterVM(vm); err != nil { 323 | return nil, OperationError{Path: "vm", Op: "ensure", Err: err} 324 | } 325 | 326 | if err := vb.SetCPUCount(vm, vm.Spec.CPU.Count); err != nil { 327 | return nil, OperationError{Path: "vm/cpu", Op: "set", Err: err} 328 | } 329 | 330 | if err := vb.SetMemory(vm, vm.Spec.Memory.SizeMB); err != nil { 331 | return nil, OperationError{Path: "vm/memory", Op: "set", Err: err} 332 | } 333 | 334 | for i, ctr := range vm.Spec.StorageControllers { 335 | if err := vb.AddStorageController(vm, ctr); err != nil && !IsAlreadyExistsError(err) { 336 | return nil, OperationError{Path: fmt.Sprintf("storagecontroller/%d", i), Op: "add", Err: err} 337 | } 338 | } 339 | 340 | disks := vm.Spec.Disks 341 | for i := range disks { 342 | if err := vb.AttachStorage(vm, &disks[i]); err != nil && !IsAlreadyExistsError(err) { 343 | return nil, OperationError{Path: fmt.Sprintf("storagecontroller/%d", i), Op: "attach", Err: err} 344 | } 345 | } 346 | 347 | if _, err := vb.EnableIOAPIC(vm); err != nil { 348 | return nil, OperationError{Path: "ioapic", Op: "enable", Err: err} 349 | } 350 | 351 | var nics = vm.Spec.NICs 352 | for i := range nics { 353 | if err := vb.AddNic(vm, &nics[i]); err != nil { 354 | return nil, fmt.Errorf("cannot add nic %#v", nics) 355 | } 356 | } 357 | 358 | if len(vm.Spec.Boot) > 0 { 359 | vb.SetBootOrder(vm, vm.Spec.Boot) 360 | } 361 | 362 | dvm, err := vb.VMInfo(vm.UUIDOrName()) 363 | if err != nil || dvm.UUID == "" { 364 | return nil, err // to retry? 365 | } 366 | 367 | if vm.UUID == "" { 368 | vm.UUID = dvm.UUID 369 | } 370 | 371 | return dvm, nil 372 | } 373 | 374 | func (vb *VBox) EnsureVMHostPath(vm *VirtualMachine) error { 375 | path := vb.getVMBaseDir(vm) 376 | return os.MkdirAll(path, os.ModePerm) 377 | } 378 | 379 | // EnsureDefaults expands the vm structure to fill in details needed based on well defined conventions 380 | // The returned instance has all the modifications and may be the same as the passed in instance 381 | func (vb *VBox) EnsureDefaults(vm *VirtualMachine) (machine *VirtualMachine, err error) { 382 | 383 | verr := ValidationErrors{} 384 | tsctl := map[string]*StorageController{} 385 | 386 | for i, c := range vm.Spec.StorageControllers { 387 | if c.Name == "" { 388 | c.Name = fmt.Sprintf("%s%d", string(c.Type), i+1) // for e.g ide1 389 | } 390 | if _, ok := tsctl[c.Name]; !ok { 391 | tsctl[c.Name] = &c 392 | } else { 393 | verr.Add(fmt.Sprintf("storagecontroller/[%d]/", i), fmt.Errorf("duplicate name")) 394 | } 395 | } 396 | 397 | disks := vm.Spec.Disks 398 | 399 | // First ensure that we set default Buses for the disks and the ref names 400 | for i := range disks { 401 | disk := &vm.Spec.Disks[i] 402 | 403 | if !filepath.IsAbs(disks[i].Path) { 404 | disks[i].Path = fmt.Sprintf("%s/%s", vb.getVMBaseDir(vm), disks[i].Path) 405 | } 406 | 407 | if disks[i].Type == "" { 408 | disks[i].Type = HDDrive 409 | } 410 | 411 | if disk.Controller.Type == "" { 412 | disk.Controller.Type = SATA 413 | } 414 | 415 | if disk.Controller.Name == "" { 416 | sctlName := fmt.Sprintf("%s1", string(disk.Controller.Type)) 417 | disk.Controller.Name = sctlName // default to 1, for e.g ide1 418 | // auto create a storage controller if one does not already exist 419 | if _, ok := tsctl[sctlName]; !ok { 420 | tsctl[sctlName] = &StorageController{Name: sctlName, Type: disk.Controller.Type} 421 | } 422 | } 423 | 424 | if disks[i].Format == "" { 425 | disks[i].Format = VDI 426 | } 427 | } 428 | 429 | // counts track the number of disks using a storage controller 430 | counts := map[string]int{} 431 | 432 | // now set back the storage controllers to VM 433 | vm.Spec.StorageControllers = []StorageController{} 434 | for k, v := range tsctl { 435 | vm.Spec.StorageControllers = append(vm.Spec.StorageControllers, *v) 436 | counts[k] = 0 437 | } 438 | 439 | // now ensure that we account for all user set and auto assigned (defaulted) value and attach them to ports 440 | for i := range disks { 441 | 442 | if disks[i].Path == "" { 443 | verr.Add(fmt.Sprintf("disk/%d", i), fmt.Errorf("disk path is empty, needs an absolute file path")) 444 | } 445 | 446 | if count, ok := counts[disks[i].Controller.Name]; ok { 447 | counts[disks[i].Controller.Name] = count + 1 448 | switch disks[i].Controller.Type { 449 | case IDE: 450 | disks[i].Controller.Port = count / 2 451 | disks[i].Controller.Device = count % 2 452 | case SATA: 453 | disks[i].Controller.Port = count 454 | default: 455 | disks[i].Controller.Port = count 456 | glog.Warning("trying to default the port for controller type %s, this might not work", disks[i].Controller.Type) 457 | } 458 | 459 | } else { 460 | verr.Add(fmt.Sprintf("disk/%d", i), fmt.Errorf("storagecontroller ref %s did not resolve", disks[i].Controller.Name)) 461 | } 462 | } 463 | 464 | if err := vb.SetNICDefaults(vm); err != nil { 465 | return nil, err 466 | } 467 | 468 | if len(verr.errors) > 0 { 469 | return nil, verr 470 | } else { // return the same instance 471 | return vm, nil 472 | } 473 | } 474 | 475 | func isAlreadyExistErrorMessage(out string) bool { 476 | return strings.Contains(out, "already exists") 477 | } 478 | -------------------------------------------------------------------------------- /machine_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "sort" 11 | "testing" 12 | "time" 13 | 14 | "github.com/golang/glog" 15 | diff "gopkg.in/d4l3k/messagediff.v1" 16 | ) 17 | 18 | func init() { 19 | flag.Set("logtostderr", "true") 20 | flag.Set("v", "10") 21 | } 22 | 23 | func TestVBox_Define(t *testing.T) { 24 | 25 | // Object under test 26 | vb := NewVBox(Config{}) 27 | 28 | disk1 := Disk{ 29 | Path: "disk1.vdi", 30 | SizeMB: 10, 31 | } 32 | 33 | vm := &VirtualMachine{} 34 | vm.Spec.Name = "vm01" 35 | vm.Spec.Group = "/example" 36 | vm.Spec.OSType = Linux64 37 | vm.Spec.CPU.Count = 2 38 | vm.Spec.Memory.SizeMB = 1000 39 | vm.Spec.Disks = []Disk{disk1} 40 | 41 | vb.EnsureDefaults(vm) 42 | 43 | ctx := context.Background() 44 | context.WithTimeout(ctx, 1*time.Minute) 45 | 46 | vb.UnRegisterVM(vm) 47 | vb.DeleteVM(vm) 48 | 49 | defer vb.DeleteVM(vm) 50 | defer vb.UnRegisterVM(vm) 51 | 52 | nvm, err := vb.Define(ctx, vm) 53 | if err != nil { 54 | t.Errorf("Error %+v", err) 55 | } 56 | 57 | fmt.Printf("Created %#v\n", vm) 58 | fmt.Printf("Created %#v\n", nvm) 59 | 60 | if diff, equal := diff.PrettyDiff(vm, nvm); !equal { 61 | t.Logf("%s", diff) // we need to fix these diffs 62 | } 63 | 64 | } 65 | 66 | func TestVBox_SetStates(t *testing.T) { 67 | 68 | // Object under test 69 | vb := NewVBox(Config{}) 70 | 71 | disk1 := Disk{ 72 | Path: "disk1.vdi", 73 | SizeMB: 10, 74 | } 75 | 76 | vm := &VirtualMachine{} 77 | vm.Spec.Name = "vm01" 78 | vm.Spec.Group = "/example" 79 | vm.Spec.OSType = Linux64 80 | vm.Spec.CPU.Count = 2 81 | vm.Spec.Memory.SizeMB = 1000 82 | vm.Spec.Disks = []Disk{disk1} 83 | 84 | // Method under test 85 | vb.EnsureDefaults(vm) 86 | 87 | ctx := context.Background() 88 | context.WithTimeout(ctx, 1*time.Minute) 89 | 90 | vb.UnRegisterVM(vm) 91 | vb.DeleteVM(vm) 92 | 93 | //defer vb.DeleteVM(vm) 94 | //defer vb.UnRegisterVM(vm) 95 | 96 | nvm, err := vb.Define(ctx, vm) 97 | 98 | if err != nil { 99 | t.Fatalf("%v", err) 100 | } else if nvm.UUID == "" { 101 | t.Fatalf("VM not discvoerable after creation %s", vm.Spec.Name) 102 | } 103 | 104 | _, err = vb.Start(vm) 105 | if err != nil { 106 | t.Fatalf("Failed to start vm %s, error %v", vm.Spec.Name, err) 107 | } 108 | 109 | _, err = vb.Stop(vm) 110 | if err != nil { 111 | t.Fatalf("Failed to stop vm %s, error %v", vm.Spec.Name, err) 112 | } 113 | 114 | _ = nvm 115 | _ = err 116 | } 117 | 118 | func TestVBox_EnsureDefaults(t *testing.T) { 119 | 120 | // Object under test 121 | vb := NewVBox(Config{}) 122 | 123 | disk1 := Disk{ 124 | Path: "disk1.vdi", 125 | } 126 | 127 | disk2 := Disk{ 128 | Path: "disk2.vdi", 129 | SizeMB: 10, 130 | Controller: StorageControllerAttachment{ 131 | Type: NVME, 132 | }, 133 | } 134 | 135 | disk3 := Disk{ 136 | Path: "disk3.vdi", 137 | SizeMB: 10, 138 | Controller: StorageControllerAttachment{ 139 | Type: IDE, 140 | }, 141 | } 142 | 143 | disk4 := Disk{ 144 | Path: "disk4.vdi", 145 | SizeMB: 10, 146 | Controller: StorageControllerAttachment{ 147 | Type: SCSCI, 148 | }, 149 | } 150 | 151 | vm := &VirtualMachine{} 152 | vm.Spec.Name = "testvm1" 153 | vm.Spec.OSType = Linux64 154 | vm.Spec.CPU.Count = 2 155 | vm.Spec.Memory.SizeMB = 1000 156 | vm.Spec.Disks = []Disk{disk1, disk2, disk3, disk4} 157 | 158 | // Method under test 159 | vb.EnsureDefaults(vm) 160 | 161 | if len(vm.Spec.StorageControllers) != 4 { 162 | t.Errorf("Expected stroage cotnroller to be auto created") 163 | } 164 | 165 | sort.Slice(vm.Spec.StorageControllers, func(i, j int) bool { 166 | return vm.Spec.StorageControllers[i].Name < vm.Spec.StorageControllers[j].Name 167 | }) 168 | 169 | if vm.Spec.StorageControllers[0].Type != IDE || 170 | vm.Spec.StorageControllers[1].Type != NVME || 171 | vm.Spec.StorageControllers[2].Type != SATA || 172 | vm.Spec.StorageControllers[3].Type != SCSCI { 173 | t.Errorf("Expected 4 storage controller to be auto created, got %+v", vm.Spec.StorageControllers) 174 | } 175 | 176 | if vm.Spec.Disks[0].Controller.Type != SATA { 177 | t.Errorf("Expected disks type to be SATA, got %v", vm.Spec.Disks[0].Type) 178 | } 179 | 180 | for i := range vm.Spec.Disks { 181 | if !filepath.IsAbs(vm.Spec.Disks[i].Path) { 182 | t.Errorf("Expected disks path to be absolute, relative to vm path, got %v", vm.Spec.Disks[i].Path) 183 | } 184 | 185 | if vm.Spec.Disks[i].Type != HDDrive { 186 | t.Errorf("Expected disks type to be default set to HDD got %v", vm.Spec.Disks[i].Type) 187 | } 188 | } 189 | 190 | } 191 | 192 | func TestVBox_CreateVM(t *testing.T) { 193 | glog.V(10).Info("setup") 194 | 195 | dirName, err := ioutil.TempDir("", "vbm") 196 | if err != nil { 197 | t.Errorf("Tempdir creation failed %v", err) 198 | } 199 | defer os.RemoveAll(dirName) 200 | 201 | vb := NewVBox(Config{ 202 | BasePath: dirName, 203 | }) 204 | 205 | disk1 := Disk{ 206 | Path: filepath.Join(dirName, "disk1.vdi"), 207 | Format: VDI, 208 | SizeMB: 10, 209 | } 210 | 211 | err = vb.CreateDisk(&disk1) 212 | if err != nil { 213 | t.Errorf("CreateDisk failed %v", err) 214 | } 215 | 216 | vm := &VirtualMachine{} 217 | vm.Spec.Name = "testvm1" 218 | vm.Spec.OSType = Linux64 219 | vm.Spec.CPU.Count = 2 220 | vm.Spec.Memory.SizeMB = 1000 221 | vm.Spec.Disks = []Disk{disk1} 222 | 223 | err = vb.CreateVM(vm) 224 | if err != nil { 225 | t.Fatalf("Failed creating vm %v", err) 226 | } 227 | 228 | err = vb.RegisterVM(vm) 229 | if err != nil { 230 | t.Fatalf("Failed registering vm") 231 | } 232 | } 233 | 234 | func TestVBox_CreateVMDefaultPath(t *testing.T) { 235 | glog.V(10).Info("setup") 236 | 237 | // No BasePath specified 238 | vb := NewVBox(Config{}) 239 | 240 | vm := &VirtualMachine{} 241 | vm.Spec.Name = "testvm1" 242 | vm.Spec.OSType = Linux64 243 | vm.Spec.CPU.Count = 2 244 | vm.Spec.Memory.SizeMB = 1000 245 | 246 | err := vb.CreateVM(vm) 247 | if err != nil { 248 | t.Fatalf("Failed creating vm %v", err) 249 | } 250 | 251 | err = vb.RegisterVM(vm) 252 | if err != nil { 253 | t.Fatalf("Failed registering vm") 254 | } 255 | 256 | err = vb.UnRegisterVM(vm) 257 | if err != nil { 258 | t.Fatalf("Failed registering vm") 259 | } 260 | 261 | vb.DeleteVM(vm) 262 | if err != nil { 263 | t.Fatalf("Failed registering vm") 264 | } 265 | } 266 | 267 | var showVmInfoOutput = ` 268 | name="testvm1" 269 | groups="/tess,/tess2" 270 | ostype="Other Linux (32-bit)" 271 | UUID="6aa44e71-71c6-4e68-a61f-f69e133ecffa" 272 | CfgFile="/Users/araveendrann/VirtualBox VMs/tess/testvm1/testvm1.vbox" 273 | SnapFldr="/Users/araveendrann/VirtualBox VMs/tess/testvm1/Snapshots" 274 | LogFldr="/Users/araveendrann/VirtualBox VMs/tess/testvm1/Logs" 275 | hardwareuuid="6aa44e71-71c6-4e68-a61f-f69e133ecffa" 276 | memory=128 277 | pagefusion="off" 278 | vram=8 279 | cpuexecutioncap=100 280 | hpet="off" 281 | chipset="piix3" 282 | firmware="BIOS" 283 | cpus=1 284 | pae="on" 285 | longmode="off" 286 | triplefaultreset="off" 287 | apic="on" 288 | x2apic="on" 289 | cpuid-portability-level=0 290 | bootmenu="messageandmenu" 291 | boot1="floppy" 292 | boot2="dvd" 293 | boot3="disk" 294 | boot4="none" 295 | acpi="on" 296 | ioapic="off" 297 | biosapic="apic" 298 | biossystemtimeoffset=0 299 | rtcuseutc="off" 300 | hwvirtex="on" 301 | nestedpaging="on" 302 | largepages="on" 303 | vtxvpid="on" 304 | vtxux="on" 305 | paravirtprovider="default" 306 | effparavirtprovider="kvm" 307 | VMState="poweroff" 308 | VMStateChangeTime="2017-12-10T01:18:02.000000000" 309 | monitorcount=1 310 | accelerate3d="off" 311 | accelerate2dvideo="off" 312 | teleporterenabled="off" 313 | teleporterport=0 314 | teleporteraddress="" 315 | teleporterpassword="" 316 | tracing-enabled="off" 317 | tracing-allow-vm-access="off" 318 | tracing-config="" 319 | autostart-enabled="off" 320 | autostart-delay=0 321 | defaultfrontend="" 322 | storagecontrollername0="SATA1" 323 | storagecontrollertype0="IntelAhci" 324 | storagecontrollerinstance0="0" 325 | storagecontrollermaxportcount0="30" 326 | storagecontrollerportcount0="30" 327 | storagecontrollerbootable0="on" 328 | "SATA1-0-0"="/Users/araveendrann/VirtualBox VMs/tess/testvm1/disk1.vdi" 329 | "SATA1-ImageUUID-0-0"="38f0cf9d-6c60-4f59-ba0b-cd1dfb5329d6" 330 | "SATA1-1-0"="none" 331 | "SATA1-2-0"="none" 332 | "SATA1-3-0"="none" 333 | "SATA1-4-0"="none" 334 | "SATA1-5-0"="none" 335 | "SATA1-6-0"="none" 336 | "SATA1-7-0"="none" 337 | "SATA1-8-0"="none" 338 | "SATA1-9-0"="none" 339 | "SATA1-10-0"="none" 340 | "SATA1-11-0"="none" 341 | "SATA1-12-0"="none" 342 | "SATA1-13-0"="none" 343 | "SATA1-14-0"="none" 344 | "SATA1-15-0"="none" 345 | "SATA1-16-0"="none" 346 | "SATA1-17-0"="none" 347 | "SATA1-18-0"="none" 348 | "SATA1-19-0"="none" 349 | "SATA1-20-0"="none" 350 | "SATA1-21-0"="none" 351 | "SATA1-22-0"="none" 352 | "SATA1-23-0"="none" 353 | "SATA1-24-0"="none" 354 | "SATA1-25-0"="none" 355 | "SATA1-26-0"="none" 356 | "SATA1-27-0"="none" 357 | "SATA1-28-0"="none" 358 | "SATA1-29-0"="none" 359 | natnet1="nat" 360 | macaddress1="080027220665" 361 | cableconnected1="on" 362 | nic1="nat" 363 | nictype1="Am79C973" 364 | nicspeed1="0" 365 | mtu="0" 366 | sockSnd="64" 367 | sockRcv="64" 368 | tcpWndSnd="64" 369 | tcpWndRcv="64" 370 | nic2="none" 371 | nic3="none" 372 | nic4="none" 373 | nic5="none" 374 | nic6="none" 375 | nic7="none" 376 | nic8="none" 377 | hidpointing="ps2mouse" 378 | hidkeyboard="ps2kbd" 379 | uart1="off" 380 | uart2="off" 381 | uart3="off" 382 | uart4="off" 383 | lpt1="off" 384 | lpt2="off" 385 | audio="coreaudio" 386 | clipboard="disabled" 387 | draganddrop="disabled" 388 | vrde="off" 389 | usb="off" 390 | ehci="off" 391 | xhci="off" 392 | vcpenabled="off" 393 | vcpscreens=0 394 | vcpfile="/Users/araveendrann/VirtualBox VMs/tess/testvm1/testvm1.webm" 395 | vcpwidth=1024 396 | vcpheight=768 397 | vcprate=512 398 | vcpfps=25 399 | GuestMemoryBalloon=0 400 | ` 401 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | func (vb *VBox) HostOnlyNetInfo() ([]Network, error) { 11 | out, err := vb.manage("list", "hostonlyifs") 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | var nws []Network 17 | 18 | var nw Network 19 | _ = tryParseKeyValues(out, reColonLine, func(key, val string, ok bool) error { 20 | switch key { 21 | case "Name": 22 | nw.Name = val 23 | case "GUID": 24 | nw.GUID = val 25 | case "HardwareAddress": 26 | nw.HWAddress = val 27 | case "VBoxNetworkName": 28 | nw.DeviceName = val[len("HostInterfaceNetworking-"):] 29 | default: 30 | if !ok && strings.TrimSpace(val) == "" { 31 | nw.Mode = NWMode_hostonly 32 | nws = append(nws, nw) 33 | nw = Network{} 34 | } 35 | } 36 | return nil 37 | }) 38 | return nws, nil 39 | } 40 | 41 | func (vb *VBox) NatNetInfo() ([]Network, error) { 42 | out, err := vb.manage("list", "natnets") 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | var nws []Network 48 | 49 | var nw Network 50 | _ = tryParseKeyValues(out, reColonLine, func(key, val string, ok bool) error { 51 | switch key { 52 | case "NetworkName": 53 | nw.Name = val 54 | default: 55 | if !ok && strings.TrimSpace(val) == "" { 56 | nw.Mode = NWMode_natnetwork 57 | nws = append(nws, nw) 58 | nw = Network{} 59 | } 60 | } 61 | return nil 62 | }) 63 | return nws, nil 64 | } 65 | 66 | func (vb *VBox) InternalNetInfo() ([]Network, error) { 67 | out, err := vb.manage("list", "intnets") 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | var nws []Network 73 | 74 | var nw Network 75 | _ = tryParseKeyValues(out, reColonLine, func(key, val string, ok bool) error { 76 | switch key { 77 | case "Name": 78 | nw.Name = val 79 | default: 80 | if !ok && strings.TrimSpace(val) == "" { 81 | nw.Mode = NWMode_intnet 82 | nws = append(nws, nw) 83 | nw = Network{} 84 | } 85 | } 86 | return nil 87 | }) 88 | return nws, nil 89 | } 90 | 91 | func (vb *VBox) BridgeNetInfo() ([]Network, error) { 92 | out, err := vb.manage("list", "bridgedifs") 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | var nws []Network 98 | 99 | var nw Network 100 | _ = tryParseKeyValues(out, reColonLine, func(key, val string, ok bool) error { 101 | switch key { 102 | case "Name": 103 | nw.Name = val 104 | case "GUID": 105 | nw.GUID = val 106 | case "HardwareAddress": 107 | nw.HWAddress = val 108 | case "VBoxNetworkName": 109 | nw.DeviceName = val[len("HostInterfaceNetworking-"):] 110 | default: 111 | if !ok && strings.TrimSpace(val) == "" { 112 | nw.Mode = NWMode_bridged 113 | nws = append(nws, nw) 114 | nw = Network{} 115 | } 116 | } 117 | return nil 118 | }) 119 | return nws, nil 120 | } 121 | 122 | func (vb *VBox) SyncNICs() (err error) { 123 | 124 | if hostOnlyNws, err := vb.HostOnlyNetInfo(); err != nil { 125 | return err 126 | } else { 127 | for i := range hostOnlyNws { 128 | vb.HostOnlyNws[hostOnlyNws[i].Name] = &hostOnlyNws[i] 129 | } 130 | } 131 | 132 | if internalNws, err := vb.InternalNetInfo(); err != nil { 133 | return err 134 | } else { 135 | for i := range internalNws { 136 | vb.InternalNws[internalNws[i].Name] = &internalNws[i] 137 | } 138 | } 139 | 140 | if natNws, err := vb.NatNetInfo(); err != nil { 141 | return err 142 | } else { 143 | for i := range natNws { 144 | vb.NatNws[natNws[i].Name] = &natNws[i] 145 | } 146 | } 147 | 148 | if bridgedNws, err := vb.BridgeNetInfo(); err != nil { 149 | return err 150 | } else { 151 | for i := range bridgedNws { 152 | vb.BridgedNws[bridgedNws[i].Name] = &bridgedNws[i] 153 | } 154 | } 155 | 156 | return nil 157 | } 158 | 159 | func (vb *VBox) CreateNet(net *Network) error { 160 | 161 | out, err := vb.manage("hostonlyif", "create") 162 | if err != nil { 163 | return err 164 | } 165 | 166 | re := regexp.MustCompile(`Interface '([^']+)' was successfully created`) 167 | matches := re.FindStringSubmatch(out) 168 | if len(matches) >= 2 { 169 | net.Name = matches[1] 170 | } else { 171 | return fmt.Errorf("could not determine the interface name from vbox output: %s", out) 172 | } 173 | 174 | return err 175 | } 176 | 177 | func (vb *VBox) DeleteNet(net *Network) error { 178 | 179 | switch net.Mode { 180 | case NWMode_hostonly: 181 | _, err := vb.manage("hostonlyif", "remove", net.Name) 182 | if err != nil && isHostDeviceNotFound(err.Error()) { 183 | return NotFoundError(err.Error()) 184 | } 185 | case NWMode_natnetwork: 186 | _, err := vb.manage("natnetwork", "remove", "--netname", net.Name) 187 | if err != nil && isHostDeviceNotFound(err.Error()) { 188 | return NotFoundError(err.Error()) 189 | } 190 | } //others are no op 191 | 192 | return nil 193 | } 194 | 195 | func isHostDeviceNotFound(text string) bool { 196 | return strings.Contains(text, "could not be found") 197 | } 198 | 199 | func (vb *VBox) AddNic(vm *VirtualMachine, nic *NIC) error { 200 | args := []string{} 201 | switch nic.Mode { 202 | case NWMode_bridged: 203 | args = append(args, fmt.Sprintf("--nic %d", nic.Index), string(NWMode_bridged), fmt.Sprintf("--bridgeadapter%d", nic.Index), nic.NetworkName) 204 | case NWMode_hostonly: 205 | args = append(args, fmt.Sprintf("--nic%d", nic.Index), string(NWMode_hostonly), fmt.Sprintf("--hostonlyadapter%d", nic.Index), nic.NetworkName) 206 | case NWMode_intnet: 207 | args = append(args, fmt.Sprintf("--nic%d", nic.Index), string(NWMode_intnet), fmt.Sprintf("--intnet%d", nic.Index), nic.NetworkName) 208 | case NWMode_natnetwork: 209 | args = append(args, fmt.Sprintf("--nic%d", nic.Index), string(NWMode_natnetwork), fmt.Sprintf("--nat-network%d", nic.NetworkName)) 210 | } 211 | 212 | args = append(args, fmt.Sprintf("--nictype%d", nic.Index), string(nic.Type)) 213 | 214 | _, err := vb.modify(vm, args...) 215 | return err 216 | } 217 | 218 | func (vb *VBox) SetNICDefaults(vm *VirtualMachine) error { 219 | if err := vb.SyncNICs(); err != nil { 220 | return err 221 | } 222 | 223 | verrs := ValidationErrors{} 224 | 225 | nics := vm.Spec.NICs 226 | for i := range nics { 227 | //set defaults 228 | nics[i].Index = i + 1 // will override the set index value 229 | 230 | if nics[i].Mode == "" { 231 | nics[i].Mode = NWMode_hostonly 232 | } 233 | 234 | if nics[i].Type == "" { 235 | nics[i].Type = NIC_82540EM 236 | } 237 | 238 | if nics[i].NetworkName == "" { 239 | if nics[i].Mode == NWMode_intnet { 240 | verrs.Add(fmt.Sprintf("nic/%d", i), fmt.Errorf("networkname missing for internal net")) 241 | continue 242 | } 243 | 244 | if nw, err := vb.getDefaultNetwork(nics[i].Mode); err == nil { 245 | nics[i].NetworkName = nw.Name 246 | } else { 247 | verrs.Add(fmt.Sprintf("nic/%d", i), err) 248 | continue 249 | } 250 | } 251 | } 252 | 253 | if len(verrs.errors) > 0 { 254 | return verrs 255 | } 256 | return nil 257 | } 258 | 259 | func (vb *VBox) getNetwork(nw string, mode NetworkMode) (*Network, error) { 260 | switch mode { 261 | case NWMode_bridged: 262 | return vb.BridgedNws[nw], nil 263 | case NWMode_hostonly: 264 | return vb.HostOnlyNws[nw], nil 265 | case NWMode_intnet: 266 | return vb.InternalNws[nw], nil 267 | case NWMode_natnetwork: 268 | return vb.NatNws[nw], nil 269 | default: 270 | return nil, nil 271 | } 272 | } 273 | 274 | func (vb *VBox) getDefaultNetwork(mode NetworkMode) (*Network, error) { 275 | var nws []*Network 276 | switch mode { 277 | case NWMode_bridged: 278 | nws = make([]*Network, 0, len(vb.BridgedNws)) 279 | for _, v := range vb.BridgedNws { 280 | nws = append(nws, v) 281 | } 282 | case NWMode_hostonly: 283 | nws = make([]*Network, 0, len(vb.HostOnlyNws)) 284 | for _, v := range vb.HostOnlyNws { 285 | nws = append(nws, v) 286 | } 287 | case NWMode_intnet: 288 | nws = make([]*Network, 0, len(vb.InternalNws)) 289 | for _, v := range vb.InternalNws { 290 | nws = append(nws, v) 291 | } 292 | case NWMode_natnetwork: 293 | nws = make([]*Network, 0, len(vb.NatNws)) 294 | for _, v := range vb.NatNws { 295 | nws = append(nws, v) 296 | } 297 | } 298 | 299 | sort.Slice(nws, func(i, j int) bool { 300 | return nws[i].Name < nws[j].Name 301 | }) 302 | 303 | if len(nws) > 0 { 304 | return nws[0], nil 305 | } 306 | 307 | return nil, nil 308 | } 309 | 310 | func (vb *VBox) EnsureNets() error { 311 | return nil 312 | } 313 | -------------------------------------------------------------------------------- /net_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | diff "gopkg.in/d4l3k/messagediff.v1" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestVBox_Netinfo(t *testing.T) { 12 | verifyNetwork := func(mode string, nws []Network) { 13 | 14 | for i := range nws { 15 | nw := nws[i] 16 | 17 | if nw.Name == "" { 18 | t.Errorf("does not have name set for modetype %s, got %#v", mode, nw) 19 | } 20 | if nw.Mode == "" { 21 | t.Errorf("does not have mode set for modetype %s, got %#v", mode, nw) 22 | } 23 | if nw.Mode == NWMode_hostonly || nw.Mode == NWMode_bridged { 24 | if nw.DeviceName == "" { 25 | t.Errorf("does not have devicename") 26 | } 27 | } 28 | } 29 | } 30 | 31 | vb := NewVBox(Config{}) 32 | if nws, err := vb.HostOnlyNetInfo(); err != nil { 33 | t.Errorf("error %#v", err) 34 | } else { 35 | verifyNetwork("hostonly", nws) 36 | } 37 | 38 | if nws, err := vb.NatNetInfo(); err != nil { 39 | t.Errorf("error %#v", err) 40 | } else { 41 | verifyNetwork("nat", nws) 42 | } 43 | 44 | if nws, err := vb.BridgeNetInfo(); err != nil { 45 | t.Errorf("error %#v", err) 46 | } else { 47 | verifyNetwork("bridge", nws) 48 | } 49 | 50 | if nws, err := vb.InternalNetInfo(); err != nil { 51 | t.Errorf("error %#v", err) 52 | } else { 53 | verifyNetwork("internal", nws) 54 | } 55 | } 56 | 57 | func TestSetNetDefaults(t *testing.T) { 58 | // Object under test 59 | vb := NewVBox(Config{}) 60 | 61 | nic1 := NIC{} 62 | nic2 := NIC{} 63 | 64 | vm := &VirtualMachine{} 65 | vm.Spec.Name = "testvm1" 66 | vm.Spec.Group = "/tess" 67 | vm.Spec.OSType = Linux64 68 | vm.Spec.CPU.Count = 2 69 | vm.Spec.Memory.SizeMB = 1000 70 | vm.Spec.NICs = []NIC{nic1, nic2} 71 | 72 | // Method under test 73 | vb.SetNICDefaults(vm) 74 | 75 | if len(vm.Spec.NICs) == 0 { 76 | fmt.Errorf("expected nics, got none") 77 | } 78 | 79 | for i := range vm.Spec.NICs { 80 | nic := &vm.Spec.NICs[i] 81 | if nic.Mode != NWMode_hostonly { 82 | t.Errorf("expected %s, got %s", NWMode_hostonly, nic.Mode) 83 | } 84 | 85 | if nic.NetworkName == "" { 86 | t.Errorf("expected a non nil networkname") 87 | } 88 | } 89 | 90 | fmt.Printf("%#v", vm.Spec.NICs) 91 | } 92 | 93 | func TestSyncetwork(t *testing.T) { 94 | vb := NewVBox(Config{}) 95 | 96 | network := &Network{Mode: NWMode_hostonly} 97 | err := vb.CreateNet(network) 98 | if err != nil { 99 | t.Fatalf("%#v", err) 100 | } 101 | defer vb.DeleteNet(network) 102 | 103 | if network.Name == "" { 104 | t.Errorf("expected name") 105 | } 106 | 107 | err = vb.SyncNICs() 108 | if err != nil { 109 | t.Fatalf("error syncing %#v", err) 110 | } 111 | 112 | //alteast we should find the network we created 113 | if nw1, ok := vb.HostOnlyNws[network.Name]; !ok { 114 | t.Fatalf("error syncing %#v", err) 115 | } else { 116 | if network.Name != nw1.Name { 117 | t.Errorf("Not the same name, got %s", nw1.Name) 118 | } 119 | } 120 | } 121 | 122 | func TestVBox_Ensure(t *testing.T) { 123 | // Object under test 124 | vb := NewVBox(Config{}) 125 | 126 | nic1 := NIC{ 127 | Mode: NWMode_hostonly, 128 | NetworkName: "vboxnet0", 129 | BootPrio: 0, 130 | } 131 | nic2 := NIC{ 132 | Mode: NWMode_intnet, 133 | NetworkName: "intnet0", 134 | BootPrio: 0, 135 | } 136 | 137 | vm := &VirtualMachine{} 138 | vm.Spec.Name = "testvm1" 139 | vm.Spec.Group = "/tess" 140 | vm.Spec.OSType = Linux64 141 | vm.Spec.CPU.Count = 2 142 | vm.Spec.Memory.SizeMB = 1000 143 | vm.Spec.NICs = []NIC{nic1, nic2} 144 | 145 | // Method under test 146 | vb.EnsureDefaults(vm) 147 | 148 | ctx := context.Background() 149 | context.WithTimeout(ctx, 1*time.Minute) 150 | 151 | vb.UnRegisterVM(vm) 152 | vb.DeleteVM(vm) 153 | 154 | //defer vb.DeleteVM(vm) 155 | //defer vb.UnRegisterVM(vm) 156 | 157 | nvm, err := vb.Define(ctx, vm) 158 | if err != nil { 159 | t.Errorf("Error defining %#v", err) 160 | } else { 161 | t.Logf("Defined vm %#v", nvm) 162 | } 163 | 164 | fmt.Printf("Created %#v\n", vm) 165 | fmt.Printf("Created %#v\n", nvm) 166 | 167 | if diff, equal := diff.PrettyDiff(vm, nvm); !equal { 168 | t.Logf("%s", diff) // we need to fix these diffs 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "net" 4 | 5 | type StorageControllerType string 6 | 7 | const ( 8 | IDE = StorageControllerType("IDE") 9 | SATA = StorageControllerType("SATA") 10 | SCSCI = StorageControllerType("SCSCI") 11 | NVME = StorageControllerType("NVME") 12 | ) 13 | 14 | type DiskType string 15 | 16 | const DVDDrive = DiskType("dvddrive") 17 | const HDDrive = DiskType("hdd") 18 | const FDDrive = DiskType("fdd") 19 | 20 | func (d DiskType) ForShowMedium() string { 21 | switch d { 22 | case DVDDrive: 23 | return "dvd" 24 | case HDDrive: 25 | return "disk" 26 | case FDDrive: 27 | return "floppy" 28 | } 29 | return "" 30 | } 31 | 32 | type Disk struct { 33 | // Path represents the absolute path in the system where the disk is stored, normally is under the vm folder 34 | Path string 35 | SizeMB int64 36 | Format DiskFormat 37 | UUID string 38 | Controller StorageControllerAttachment 39 | Type DiskType 40 | NonRotational bool 41 | AutoDiscard bool 42 | } 43 | 44 | type StorageControllerAttachment struct { 45 | // Type represents the storage controller, rest of the fields needs to interpreted based on this 46 | Type StorageControllerType 47 | Port int 48 | Device int 49 | // Name of the storage controller target for this attachment 50 | Name string 51 | } 52 | 53 | type StorageController struct { 54 | Name string 55 | Type StorageControllerType 56 | Instance int 57 | PortCount int 58 | Bootable string //on, off 59 | } 60 | 61 | type CPU struct { 62 | Count int 63 | } 64 | 65 | type Memory struct { 66 | SizeMB int 67 | } 68 | 69 | type NetworkMode string 70 | 71 | const ( 72 | NWMode_none = NetworkMode("none") 73 | NWMode_null = NetworkMode("null") 74 | NWMode_nat = NetworkMode("nat") 75 | NWMode_natnetwork = NetworkMode("natnetwork") 76 | NWMode_bridged = NetworkMode("bridged") 77 | NWMode_intnet = NetworkMode("intnet") 78 | NWMode_hostonly = NetworkMode("hostonly") 79 | NWMode_generic = NetworkMode("generic") 80 | ) 81 | 82 | type NICType string 83 | 84 | const ( 85 | NIC_Am79C970A = NICType("Am79C970A") 86 | NIC_Am79C973 = NICType("Am79C973") 87 | NIC_82540EM = NICType("82540EM") 88 | NIC_82543GC = NICType("82543GC") 89 | NIC_82545EM = NICType("82545EM") 90 | NIC_virtio = NICType("virtio") 91 | ) 92 | 93 | type NIC struct { 94 | Index int 95 | // Name string // device name of this nic, used for correlation with Adapters 96 | Mode NetworkMode // nat, hostonly etc 97 | NetworkName string //optional name of the Network to connect this nic to. For hostnetwork and int this is the same as the host device 98 | Type NICType 99 | CableConnected bool 100 | Speedkbps int 101 | BootPrio int 102 | PromiscuousMode string 103 | MAC string //auto assigns mac automatically 104 | } 105 | 106 | type Network struct { 107 | GUID string 108 | Name string 109 | IPNet net.IPNet 110 | Mode NetworkMode 111 | DeviceName string 112 | HWAddress string 113 | } 114 | 115 | type BootDevice string 116 | 117 | const BOOT_net, BOOT_disk, BOOT_none BootDevice = "net", "disk", "none" 118 | 119 | type VirtualMachineSpec struct { 120 | // Name identifies the vm and is also used in forming full path, see VBox.BasePath 121 | Name string 122 | Group string 123 | Disks []Disk 124 | CPU CPU 125 | Memory Memory 126 | NICs []NIC 127 | OSType OSType 128 | StorageControllers []StorageController 129 | Boot []BootDevice 130 | } 131 | 132 | type VirtualMachine struct { 133 | UUID string 134 | Spec VirtualMachineSpec 135 | } 136 | 137 | func (vm *VirtualMachine) UUIDOrName() string { 138 | if vm.UUID == "" { 139 | return vm.Spec.Name 140 | } else { 141 | return vm.UUID 142 | } 143 | } 144 | 145 | type DHCPServer struct { 146 | IPAddress string 147 | NetworkName string 148 | NetworkMask string 149 | LowerIPAddress string 150 | UpperIPAddress string 151 | Enabled bool 152 | } 153 | 154 | type OSType struct { 155 | ID string 156 | Description string 157 | FamilyID string 158 | FamilyDescription string 159 | Bit64 bool 160 | } 161 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bufio" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | func parseKeyValues(text string, regexp *regexp.Regexp, callback func(key, val string) error) error { 10 | return tryParseKeyValues(text, regexp, func(key, val string, ok bool) error { 11 | if ok { 12 | return callback(key, val) 13 | } 14 | return nil 15 | }) 16 | } 17 | 18 | func tryParseKeyValues(stdOut string, regexp *regexp.Regexp, callback func(key, val string, ok bool) error) error { 19 | r := strings.NewReader(stdOut) 20 | s := bufio.NewScanner(r) 21 | 22 | for s.Scan() { 23 | line := s.Text() 24 | 25 | if strings.TrimSpace(line) == "" { 26 | callback("", "", false) 27 | continue 28 | } 29 | 30 | res := regexp.FindStringSubmatch(line) 31 | if res == nil { 32 | callback("", line, false) 33 | continue 34 | } 35 | 36 | key, val := res[1], res[2] 37 | if err := callback(key, val, true); err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return s.Err() 43 | } 44 | -------------------------------------------------------------------------------- /vbox.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os/exec" 8 | "os/user" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/golang/glog" 14 | ) 15 | 16 | const ( 17 | VBoxManage = "VBoxManage" 18 | NatPortBase = 10900 19 | ) 20 | 21 | var DefaultVBBasePath = GetDefaultVBBasePath() 22 | 23 | var reColonLine = regexp.MustCompile(`([^:]+):\s+(.*)`) 24 | 25 | // parses lines like the following 26 | // foo="bar" 27 | var reKeyEqVal = regexp.MustCompile(`([^=]+)=\s*(.*)`) 28 | 29 | // Config for the Manager 30 | type Config struct { 31 | // BasePath is the base filesystem location for managing this provider's configuration 32 | // Defaults to $HOME/.vbm/VBox BasePath string 33 | BasePath string 34 | // VirtualBoxPath is where the VirtualBox cmd is available on the local machine 35 | VirtualBoxPath string 36 | 37 | Groups []string 38 | 39 | // expected to be managed by this tool 40 | Networks []Network 41 | } 42 | 43 | // VBox uses the VBoxManage command for its functionality 44 | type VBox struct { 45 | Config Config 46 | Verbose bool 47 | // as discovered and includes networks created out of band (not through this api) 48 | // TODO: Merge them to a single map and provide accessors for specific filtering 49 | HostOnlyNws map[string]*Network 50 | BridgedNws map[string]*Network 51 | InternalNws map[string]*Network 52 | NatNws map[string]*Network 53 | } 54 | 55 | func NewVBox(config Config) *VBox { 56 | if config.BasePath == "" { 57 | config.BasePath = DefaultVBBasePath 58 | } 59 | return &VBox{ 60 | Config: config, 61 | HostOnlyNws: make(map[string]*Network), 62 | BridgedNws: make(map[string]*Network), 63 | InternalNws: make(map[string]*Network), 64 | NatNws: make(map[string]*Network), 65 | } 66 | } 67 | 68 | func GetDefaultVBBasePath() string { 69 | user, err := user.Current() 70 | if err != nil { 71 | panic(fmt.Errorf("basepath not supplied and default location cannot be determined %v", err)) 72 | } 73 | return fmt.Sprintf("%s/VirtualBox VMs", user.HomeDir) 74 | } 75 | 76 | func IsVBoxError(err error) bool { 77 | _, ok := err.(VBoxError) 78 | return ok 79 | } 80 | 81 | //VBoxError are errors that are returned as error by Virtualbox cli on stderr 82 | type VBoxError string 83 | 84 | func (ve VBoxError) Error() string { 85 | return string(ve) 86 | } 87 | 88 | func (vb *VBox) getVMBaseDir(vm *VirtualMachine) string { 89 | var group string 90 | if vm.Spec.Group != "" { 91 | group = vm.Spec.Group 92 | } 93 | 94 | return filepath.Join(vb.Config.BasePath, group, vm.Spec.Name) 95 | } 96 | 97 | func (vb *VBox) getVMSettingsFile(vm *VirtualMachine) string { 98 | return filepath.Join(vb.getVMBaseDir(vm), vm.Spec.Name+".vbox") 99 | } 100 | 101 | func (vb *VBox) manage(args ...string) (string, error) { 102 | vboxManage := vboxManagePath() 103 | cmd := exec.Command(vboxManage, args...) 104 | glog.V(4).Infof("COMMAND: %v %v", vboxManage, strings.Join(args, " ")) 105 | 106 | var stdout bytes.Buffer 107 | var stderr bytes.Buffer 108 | cmd.Stdout = &stdout 109 | cmd.Stderr = &stderr 110 | 111 | err := cmd.Run() 112 | stderrStr := stderr.String() 113 | 114 | if err != nil { 115 | if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 116 | return "", errors.New("unable to find VBoxManage command in path") 117 | } 118 | return "", VBoxError(stderrStr) 119 | } 120 | 121 | glog.V(10).Infof("STDOUT:\n{\n%v}", stdout.String()) 122 | glog.V(10).Infof("STDERR:\n{\n%v}", stderrStr) 123 | 124 | return string(stdout.Bytes()), err 125 | } 126 | 127 | func (vb *VBox) modify(vm *VirtualMachine, args ...string) (string, error) { 128 | return vb.manage(append([]string{"modifyvm", vm.UUIDOrName()}, args...)...) 129 | } 130 | 131 | func (vb *VBox) control(vm *VirtualMachine, args ...string) (string, error) { 132 | return vb.manage(append([]string{"controlvm", vm.UUIDOrName()}, args...)...) 133 | } 134 | 135 | func (vb *VBox) ListDHCPServers() (map[string]*DHCPServer, error) { 136 | listOutput, err := vb.manage("list", "dhcpservers") 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | m := make(map[string]*DHCPServer) 142 | 143 | var dhcpServer *DHCPServer 144 | 145 | err = parseKeyValues(listOutput, reColonLine, func(key, val string) error { 146 | switch key { 147 | case "NetworkName": 148 | dhcpServer = &DHCPServer{} 149 | m[val] = dhcpServer 150 | dhcpServer.NetworkName = val 151 | case "IP": 152 | dhcpServer.IPAddress = val 153 | case "upperIPAddress": 154 | dhcpServer.UpperIPAddress = val 155 | case "lowerIPAddress": 156 | dhcpServer.LowerIPAddress = val 157 | case "NetworkMask": 158 | dhcpServer.NetworkMask = val 159 | case "Enabled": 160 | dhcpServer.Enabled = val == "Yes" 161 | } 162 | return nil 163 | }) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | return m, nil 169 | } 170 | 171 | func (vb *VBox) ListOSTypes() (map[string]*OSType, error) { 172 | listOutput, err := vb.manage("list", "ostypes") 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | m := make(map[string]*OSType) 178 | 179 | var osType *OSType 180 | 181 | err = parseKeyValues(listOutput, reColonLine, func(key, val string) error { 182 | switch key { 183 | case "ID": 184 | osType = &OSType{} 185 | m[val] = osType 186 | osType.ID = val 187 | case "Description": 188 | osType.Description = val 189 | case "Family ID": 190 | osType.FamilyID = val 191 | case "Family Desc": 192 | osType.FamilyDescription = val 193 | case "64 bit": 194 | osType.Bit64 = val == "true" 195 | } 196 | return nil 197 | }) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | return m, nil 203 | } 204 | 205 | func (vb *VBox) MarkHDImmutable(hdPath string) error { 206 | vb.manage("modifyhd", hdPath, "--type", "immutable") 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /vbox_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package virtualbox 4 | 5 | func vboxManagePath() string { 6 | return VBoxManage 7 | } 8 | -------------------------------------------------------------------------------- /vbox_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package virtualbox 4 | 5 | import ( 6 | "path/filepath" 7 | 8 | "golang.org/x/sys/windows/registry" 9 | ) 10 | 11 | func vboxManagePath() string { 12 | k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Oracle\VirtualBox`, registry.QUERY_VALUE) 13 | if err != nil { 14 | return VBoxManage 15 | } 16 | defer k.Close() 17 | 18 | s, _, err := k.GetStringValue("InstallDir") 19 | if err != nil { 20 | return VBoxManage 21 | } 22 | return filepath.Join(s, VBoxManage) 23 | } 24 | -------------------------------------------------------------------------------- /vendor/github.com/golang/glog/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /vendor/github.com/golang/glog/README: -------------------------------------------------------------------------------- 1 | glog 2 | ==== 3 | 4 | Leveled execution logs for Go. 5 | 6 | This is an efficient pure Go implementation of leveled logs in the 7 | manner of the open source C++ package 8 | https://github.com/google/glog 9 | 10 | By binding methods to booleans it is possible to use the log package 11 | without paying the expense of evaluating the arguments to the log. 12 | Through the -vmodule flag, the package also provides fine-grained 13 | control over logging at the file level. 14 | 15 | The comment from glog.go introduces the ideas: 16 | 17 | Package glog implements logging analogous to the Google-internal 18 | C++ INFO/ERROR/V setup. It provides functions Info, Warning, 19 | Error, Fatal, plus formatting variants such as Infof. It 20 | also provides V-style logging controlled by the -v and 21 | -vmodule=file=2 flags. 22 | 23 | Basic examples: 24 | 25 | glog.Info("Prepare to repel boarders") 26 | 27 | glog.Fatalf("Initialization failed: %s", err) 28 | 29 | See the documentation for the V function for an explanation 30 | of these examples: 31 | 32 | if glog.V(2) { 33 | glog.Info("Starting transaction...") 34 | } 35 | 36 | glog.V(2).Infoln("Processed", nItems, "elements") 37 | 38 | 39 | The repository contains an open source version of the log package 40 | used inside Google. The master copy of the source lives inside 41 | Google, not here. The code in this repo is for export only and is not itself 42 | under development. Feature requests will be ignored. 43 | 44 | Send bug reports to golang-nuts@googlegroups.com. 45 | -------------------------------------------------------------------------------- /vendor/github.com/golang/glog/glog.go: -------------------------------------------------------------------------------- 1 | // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ 2 | // 3 | // Copyright 2013 Google Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | // Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. 18 | // It provides functions Info, Warning, Error, Fatal, plus formatting variants such as 19 | // Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. 20 | // 21 | // Basic examples: 22 | // 23 | // glog.Info("Prepare to repel boarders") 24 | // 25 | // glog.Fatalf("Initialization failed: %s", err) 26 | // 27 | // See the documentation for the V function for an explanation of these examples: 28 | // 29 | // if glog.V(2) { 30 | // glog.Info("Starting transaction...") 31 | // } 32 | // 33 | // glog.V(2).Infoln("Processed", nItems, "elements") 34 | // 35 | // Log output is buffered and written periodically using Flush. Programs 36 | // should call Flush before exiting to guarantee all log output is written. 37 | // 38 | // By default, all log statements write to files in a temporary directory. 39 | // This package provides several flags that modify this behavior. 40 | // As a result, flag.Parse must be called before any logging is done. 41 | // 42 | // -logtostderr=false 43 | // Logs are written to standard error instead of to files. 44 | // -alsologtostderr=false 45 | // Logs are written to standard error as well as to files. 46 | // -stderrthreshold=ERROR 47 | // Log events at or above this severity are logged to standard 48 | // error as well as to files. 49 | // -log_dir="" 50 | // Log files will be written to this directory instead of the 51 | // default temporary directory. 52 | // 53 | // Other flags provide aids to debugging. 54 | // 55 | // -log_backtrace_at="" 56 | // When set to a file and line number holding a logging statement, 57 | // such as 58 | // -log_backtrace_at=gopherflakes.go:234 59 | // a stack trace will be written to the Info log whenever execution 60 | // hits that statement. (Unlike with -vmodule, the ".go" must be 61 | // present.) 62 | // -v=0 63 | // Enable V-leveled logging at the specified level. 64 | // -vmodule="" 65 | // The syntax of the argument is a comma-separated list of pattern=N, 66 | // where pattern is a literal file name (minus the ".go" suffix) or 67 | // "glob" pattern and N is a V level. For instance, 68 | // -vmodule=gopher*=3 69 | // sets the V level to 3 in all Go files whose names begin "gopher". 70 | // 71 | package glog 72 | 73 | import ( 74 | "bufio" 75 | "bytes" 76 | "errors" 77 | "flag" 78 | "fmt" 79 | "io" 80 | stdLog "log" 81 | "os" 82 | "path/filepath" 83 | "runtime" 84 | "strconv" 85 | "strings" 86 | "sync" 87 | "sync/atomic" 88 | "time" 89 | ) 90 | 91 | // severity identifies the sort of log: info, warning etc. It also implements 92 | // the flag.Value interface. The -stderrthreshold flag is of type severity and 93 | // should be modified only through the flag.Value interface. The values match 94 | // the corresponding constants in C++. 95 | type severity int32 // sync/atomic int32 96 | 97 | // These constants identify the log levels in order of increasing severity. 98 | // A message written to a high-severity log file is also written to each 99 | // lower-severity log file. 100 | const ( 101 | infoLog severity = iota 102 | warningLog 103 | errorLog 104 | fatalLog 105 | numSeverity = 4 106 | ) 107 | 108 | const severityChar = "IWEF" 109 | 110 | var severityName = []string{ 111 | infoLog: "INFO", 112 | warningLog: "WARNING", 113 | errorLog: "ERROR", 114 | fatalLog: "FATAL", 115 | } 116 | 117 | // get returns the value of the severity. 118 | func (s *severity) get() severity { 119 | return severity(atomic.LoadInt32((*int32)(s))) 120 | } 121 | 122 | // set sets the value of the severity. 123 | func (s *severity) set(val severity) { 124 | atomic.StoreInt32((*int32)(s), int32(val)) 125 | } 126 | 127 | // String is part of the flag.Value interface. 128 | func (s *severity) String() string { 129 | return strconv.FormatInt(int64(*s), 10) 130 | } 131 | 132 | // Get is part of the flag.Value interface. 133 | func (s *severity) Get() interface{} { 134 | return *s 135 | } 136 | 137 | // Set is part of the flag.Value interface. 138 | func (s *severity) Set(value string) error { 139 | var threshold severity 140 | // Is it a known name? 141 | if v, ok := severityByName(value); ok { 142 | threshold = v 143 | } else { 144 | v, err := strconv.Atoi(value) 145 | if err != nil { 146 | return err 147 | } 148 | threshold = severity(v) 149 | } 150 | logging.stderrThreshold.set(threshold) 151 | return nil 152 | } 153 | 154 | func severityByName(s string) (severity, bool) { 155 | s = strings.ToUpper(s) 156 | for i, name := range severityName { 157 | if name == s { 158 | return severity(i), true 159 | } 160 | } 161 | return 0, false 162 | } 163 | 164 | // OutputStats tracks the number of output lines and bytes written. 165 | type OutputStats struct { 166 | lines int64 167 | bytes int64 168 | } 169 | 170 | // Lines returns the number of lines written. 171 | func (s *OutputStats) Lines() int64 { 172 | return atomic.LoadInt64(&s.lines) 173 | } 174 | 175 | // Bytes returns the number of bytes written. 176 | func (s *OutputStats) Bytes() int64 { 177 | return atomic.LoadInt64(&s.bytes) 178 | } 179 | 180 | // Stats tracks the number of lines of output and number of bytes 181 | // per severity level. Values must be read with atomic.LoadInt64. 182 | var Stats struct { 183 | Info, Warning, Error OutputStats 184 | } 185 | 186 | var severityStats = [numSeverity]*OutputStats{ 187 | infoLog: &Stats.Info, 188 | warningLog: &Stats.Warning, 189 | errorLog: &Stats.Error, 190 | } 191 | 192 | // Level is exported because it appears in the arguments to V and is 193 | // the type of the v flag, which can be set programmatically. 194 | // It's a distinct type because we want to discriminate it from logType. 195 | // Variables of type level are only changed under logging.mu. 196 | // The -v flag is read only with atomic ops, so the state of the logging 197 | // module is consistent. 198 | 199 | // Level is treated as a sync/atomic int32. 200 | 201 | // Level specifies a level of verbosity for V logs. *Level implements 202 | // flag.Value; the -v flag is of type Level and should be modified 203 | // only through the flag.Value interface. 204 | type Level int32 205 | 206 | // get returns the value of the Level. 207 | func (l *Level) get() Level { 208 | return Level(atomic.LoadInt32((*int32)(l))) 209 | } 210 | 211 | // set sets the value of the Level. 212 | func (l *Level) set(val Level) { 213 | atomic.StoreInt32((*int32)(l), int32(val)) 214 | } 215 | 216 | // String is part of the flag.Value interface. 217 | func (l *Level) String() string { 218 | return strconv.FormatInt(int64(*l), 10) 219 | } 220 | 221 | // Get is part of the flag.Value interface. 222 | func (l *Level) Get() interface{} { 223 | return *l 224 | } 225 | 226 | // Set is part of the flag.Value interface. 227 | func (l *Level) Set(value string) error { 228 | v, err := strconv.Atoi(value) 229 | if err != nil { 230 | return err 231 | } 232 | logging.mu.Lock() 233 | defer logging.mu.Unlock() 234 | logging.setVState(Level(v), logging.vmodule.filter, false) 235 | return nil 236 | } 237 | 238 | // moduleSpec represents the setting of the -vmodule flag. 239 | type moduleSpec struct { 240 | filter []modulePat 241 | } 242 | 243 | // modulePat contains a filter for the -vmodule flag. 244 | // It holds a verbosity level and a file pattern to match. 245 | type modulePat struct { 246 | pattern string 247 | literal bool // The pattern is a literal string 248 | level Level 249 | } 250 | 251 | // match reports whether the file matches the pattern. It uses a string 252 | // comparison if the pattern contains no metacharacters. 253 | func (m *modulePat) match(file string) bool { 254 | if m.literal { 255 | return file == m.pattern 256 | } 257 | match, _ := filepath.Match(m.pattern, file) 258 | return match 259 | } 260 | 261 | func (m *moduleSpec) String() string { 262 | // Lock because the type is not atomic. TODO: clean this up. 263 | logging.mu.Lock() 264 | defer logging.mu.Unlock() 265 | var b bytes.Buffer 266 | for i, f := range m.filter { 267 | if i > 0 { 268 | b.WriteRune(',') 269 | } 270 | fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) 271 | } 272 | return b.String() 273 | } 274 | 275 | // Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the 276 | // struct is not exported. 277 | func (m *moduleSpec) Get() interface{} { 278 | return nil 279 | } 280 | 281 | var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") 282 | 283 | // Syntax: -vmodule=recordio=2,file=1,gfs*=3 284 | func (m *moduleSpec) Set(value string) error { 285 | var filter []modulePat 286 | for _, pat := range strings.Split(value, ",") { 287 | if len(pat) == 0 { 288 | // Empty strings such as from a trailing comma can be ignored. 289 | continue 290 | } 291 | patLev := strings.Split(pat, "=") 292 | if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { 293 | return errVmoduleSyntax 294 | } 295 | pattern := patLev[0] 296 | v, err := strconv.Atoi(patLev[1]) 297 | if err != nil { 298 | return errors.New("syntax error: expect comma-separated list of filename=N") 299 | } 300 | if v < 0 { 301 | return errors.New("negative value for vmodule level") 302 | } 303 | if v == 0 { 304 | continue // Ignore. It's harmless but no point in paying the overhead. 305 | } 306 | // TODO: check syntax of filter? 307 | filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) 308 | } 309 | logging.mu.Lock() 310 | defer logging.mu.Unlock() 311 | logging.setVState(logging.verbosity, filter, true) 312 | return nil 313 | } 314 | 315 | // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters 316 | // that require filepath.Match to be called to match the pattern. 317 | func isLiteral(pattern string) bool { 318 | return !strings.ContainsAny(pattern, `\*?[]`) 319 | } 320 | 321 | // traceLocation represents the setting of the -log_backtrace_at flag. 322 | type traceLocation struct { 323 | file string 324 | line int 325 | } 326 | 327 | // isSet reports whether the trace location has been specified. 328 | // logging.mu is held. 329 | func (t *traceLocation) isSet() bool { 330 | return t.line > 0 331 | } 332 | 333 | // match reports whether the specified file and line matches the trace location. 334 | // The argument file name is the full path, not the basename specified in the flag. 335 | // logging.mu is held. 336 | func (t *traceLocation) match(file string, line int) bool { 337 | if t.line != line { 338 | return false 339 | } 340 | if i := strings.LastIndex(file, "/"); i >= 0 { 341 | file = file[i+1:] 342 | } 343 | return t.file == file 344 | } 345 | 346 | func (t *traceLocation) String() string { 347 | // Lock because the type is not atomic. TODO: clean this up. 348 | logging.mu.Lock() 349 | defer logging.mu.Unlock() 350 | return fmt.Sprintf("%s:%d", t.file, t.line) 351 | } 352 | 353 | // Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the 354 | // struct is not exported 355 | func (t *traceLocation) Get() interface{} { 356 | return nil 357 | } 358 | 359 | var errTraceSyntax = errors.New("syntax error: expect file.go:234") 360 | 361 | // Syntax: -log_backtrace_at=gopherflakes.go:234 362 | // Note that unlike vmodule the file extension is included here. 363 | func (t *traceLocation) Set(value string) error { 364 | if value == "" { 365 | // Unset. 366 | t.line = 0 367 | t.file = "" 368 | } 369 | fields := strings.Split(value, ":") 370 | if len(fields) != 2 { 371 | return errTraceSyntax 372 | } 373 | file, line := fields[0], fields[1] 374 | if !strings.Contains(file, ".") { 375 | return errTraceSyntax 376 | } 377 | v, err := strconv.Atoi(line) 378 | if err != nil { 379 | return errTraceSyntax 380 | } 381 | if v <= 0 { 382 | return errors.New("negative or zero value for level") 383 | } 384 | logging.mu.Lock() 385 | defer logging.mu.Unlock() 386 | t.line = v 387 | t.file = file 388 | return nil 389 | } 390 | 391 | // flushSyncWriter is the interface satisfied by logging destinations. 392 | type flushSyncWriter interface { 393 | Flush() error 394 | Sync() error 395 | io.Writer 396 | } 397 | 398 | func init() { 399 | flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") 400 | flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") 401 | flag.Var(&logging.verbosity, "v", "log level for V logs") 402 | flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") 403 | flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") 404 | flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") 405 | 406 | // Default stderrThreshold is ERROR. 407 | logging.stderrThreshold = errorLog 408 | 409 | logging.setVState(0, nil, false) 410 | go logging.flushDaemon() 411 | } 412 | 413 | // Flush flushes all pending log I/O. 414 | func Flush() { 415 | logging.lockAndFlushAll() 416 | } 417 | 418 | // loggingT collects all the global state of the logging setup. 419 | type loggingT struct { 420 | // Boolean flags. Not handled atomically because the flag.Value interface 421 | // does not let us avoid the =true, and that shorthand is necessary for 422 | // compatibility. TODO: does this matter enough to fix? Seems unlikely. 423 | toStderr bool // The -logtostderr flag. 424 | alsoToStderr bool // The -alsologtostderr flag. 425 | 426 | // Level flag. Handled atomically. 427 | stderrThreshold severity // The -stderrthreshold flag. 428 | 429 | // freeList is a list of byte buffers, maintained under freeListMu. 430 | freeList *buffer 431 | // freeListMu maintains the free list. It is separate from the main mutex 432 | // so buffers can be grabbed and printed to without holding the main lock, 433 | // for better parallelization. 434 | freeListMu sync.Mutex 435 | 436 | // mu protects the remaining elements of this structure and is 437 | // used to synchronize logging. 438 | mu sync.Mutex 439 | // file holds writer for each of the log types. 440 | file [numSeverity]flushSyncWriter 441 | // pcs is used in V to avoid an allocation when computing the caller's PC. 442 | pcs [1]uintptr 443 | // vmap is a cache of the V Level for each V() call site, identified by PC. 444 | // It is wiped whenever the vmodule flag changes state. 445 | vmap map[uintptr]Level 446 | // filterLength stores the length of the vmodule filter chain. If greater 447 | // than zero, it means vmodule is enabled. It may be read safely 448 | // using sync.LoadInt32, but is only modified under mu. 449 | filterLength int32 450 | // traceLocation is the state of the -log_backtrace_at flag. 451 | traceLocation traceLocation 452 | // These flags are modified only under lock, although verbosity may be fetched 453 | // safely using atomic.LoadInt32. 454 | vmodule moduleSpec // The state of the -vmodule flag. 455 | verbosity Level // V logging level, the value of the -v flag/ 456 | } 457 | 458 | // buffer holds a byte Buffer for reuse. The zero value is ready for use. 459 | type buffer struct { 460 | bytes.Buffer 461 | tmp [64]byte // temporary byte array for creating headers. 462 | next *buffer 463 | } 464 | 465 | var logging loggingT 466 | 467 | // setVState sets a consistent state for V logging. 468 | // l.mu is held. 469 | func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { 470 | // Turn verbosity off so V will not fire while we are in transition. 471 | logging.verbosity.set(0) 472 | // Ditto for filter length. 473 | atomic.StoreInt32(&logging.filterLength, 0) 474 | 475 | // Set the new filters and wipe the pc->Level map if the filter has changed. 476 | if setFilter { 477 | logging.vmodule.filter = filter 478 | logging.vmap = make(map[uintptr]Level) 479 | } 480 | 481 | // Things are consistent now, so enable filtering and verbosity. 482 | // They are enabled in order opposite to that in V. 483 | atomic.StoreInt32(&logging.filterLength, int32(len(filter))) 484 | logging.verbosity.set(verbosity) 485 | } 486 | 487 | // getBuffer returns a new, ready-to-use buffer. 488 | func (l *loggingT) getBuffer() *buffer { 489 | l.freeListMu.Lock() 490 | b := l.freeList 491 | if b != nil { 492 | l.freeList = b.next 493 | } 494 | l.freeListMu.Unlock() 495 | if b == nil { 496 | b = new(buffer) 497 | } else { 498 | b.next = nil 499 | b.Reset() 500 | } 501 | return b 502 | } 503 | 504 | // putBuffer returns a buffer to the free list. 505 | func (l *loggingT) putBuffer(b *buffer) { 506 | if b.Len() >= 256 { 507 | // Let big buffers die a natural death. 508 | return 509 | } 510 | l.freeListMu.Lock() 511 | b.next = l.freeList 512 | l.freeList = b 513 | l.freeListMu.Unlock() 514 | } 515 | 516 | var timeNow = time.Now // Stubbed out for testing. 517 | 518 | /* 519 | header formats a log header as defined by the C++ implementation. 520 | It returns a buffer containing the formatted header and the user's file and line number. 521 | The depth specifies how many stack frames above lives the source line to be identified in the log message. 522 | 523 | Log lines have this form: 524 | Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... 525 | where the fields are defined as follows: 526 | L A single character, representing the log level (eg 'I' for INFO) 527 | mm The month (zero padded; ie May is '05') 528 | dd The day (zero padded) 529 | hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds 530 | threadid The space-padded thread ID as returned by GetTID() 531 | file The file name 532 | line The line number 533 | msg The user-supplied message 534 | */ 535 | func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { 536 | _, file, line, ok := runtime.Caller(3 + depth) 537 | if !ok { 538 | file = "???" 539 | line = 1 540 | } else { 541 | slash := strings.LastIndex(file, "/") 542 | if slash >= 0 { 543 | file = file[slash+1:] 544 | } 545 | } 546 | return l.formatHeader(s, file, line), file, line 547 | } 548 | 549 | // formatHeader formats a log header using the provided file name and line number. 550 | func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { 551 | now := timeNow() 552 | if line < 0 { 553 | line = 0 // not a real line number, but acceptable to someDigits 554 | } 555 | if s > fatalLog { 556 | s = infoLog // for safety. 557 | } 558 | buf := l.getBuffer() 559 | 560 | // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. 561 | // It's worth about 3X. Fprintf is hard. 562 | _, month, day := now.Date() 563 | hour, minute, second := now.Clock() 564 | // Lmmdd hh:mm:ss.uuuuuu threadid file:line] 565 | buf.tmp[0] = severityChar[s] 566 | buf.twoDigits(1, int(month)) 567 | buf.twoDigits(3, day) 568 | buf.tmp[5] = ' ' 569 | buf.twoDigits(6, hour) 570 | buf.tmp[8] = ':' 571 | buf.twoDigits(9, minute) 572 | buf.tmp[11] = ':' 573 | buf.twoDigits(12, second) 574 | buf.tmp[14] = '.' 575 | buf.nDigits(6, 15, now.Nanosecond()/1000, '0') 576 | buf.tmp[21] = ' ' 577 | buf.nDigits(7, 22, pid, ' ') // TODO: should be TID 578 | buf.tmp[29] = ' ' 579 | buf.Write(buf.tmp[:30]) 580 | buf.WriteString(file) 581 | buf.tmp[0] = ':' 582 | n := buf.someDigits(1, line) 583 | buf.tmp[n+1] = ']' 584 | buf.tmp[n+2] = ' ' 585 | buf.Write(buf.tmp[:n+3]) 586 | return buf 587 | } 588 | 589 | // Some custom tiny helper functions to print the log header efficiently. 590 | 591 | const digits = "0123456789" 592 | 593 | // twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. 594 | func (buf *buffer) twoDigits(i, d int) { 595 | buf.tmp[i+1] = digits[d%10] 596 | d /= 10 597 | buf.tmp[i] = digits[d%10] 598 | } 599 | 600 | // nDigits formats an n-digit integer at buf.tmp[i], 601 | // padding with pad on the left. 602 | // It assumes d >= 0. 603 | func (buf *buffer) nDigits(n, i, d int, pad byte) { 604 | j := n - 1 605 | for ; j >= 0 && d > 0; j-- { 606 | buf.tmp[i+j] = digits[d%10] 607 | d /= 10 608 | } 609 | for ; j >= 0; j-- { 610 | buf.tmp[i+j] = pad 611 | } 612 | } 613 | 614 | // someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. 615 | func (buf *buffer) someDigits(i, d int) int { 616 | // Print into the top, then copy down. We know there's space for at least 617 | // a 10-digit number. 618 | j := len(buf.tmp) 619 | for { 620 | j-- 621 | buf.tmp[j] = digits[d%10] 622 | d /= 10 623 | if d == 0 { 624 | break 625 | } 626 | } 627 | return copy(buf.tmp[i:], buf.tmp[j:]) 628 | } 629 | 630 | func (l *loggingT) println(s severity, args ...interface{}) { 631 | buf, file, line := l.header(s, 0) 632 | fmt.Fprintln(buf, args...) 633 | l.output(s, buf, file, line, false) 634 | } 635 | 636 | func (l *loggingT) print(s severity, args ...interface{}) { 637 | l.printDepth(s, 1, args...) 638 | } 639 | 640 | func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { 641 | buf, file, line := l.header(s, depth) 642 | fmt.Fprint(buf, args...) 643 | if buf.Bytes()[buf.Len()-1] != '\n' { 644 | buf.WriteByte('\n') 645 | } 646 | l.output(s, buf, file, line, false) 647 | } 648 | 649 | func (l *loggingT) printf(s severity, format string, args ...interface{}) { 650 | buf, file, line := l.header(s, 0) 651 | fmt.Fprintf(buf, format, args...) 652 | if buf.Bytes()[buf.Len()-1] != '\n' { 653 | buf.WriteByte('\n') 654 | } 655 | l.output(s, buf, file, line, false) 656 | } 657 | 658 | // printWithFileLine behaves like print but uses the provided file and line number. If 659 | // alsoLogToStderr is true, the log message always appears on standard error; it 660 | // will also appear in the log file unless --logtostderr is set. 661 | func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { 662 | buf := l.formatHeader(s, file, line) 663 | fmt.Fprint(buf, args...) 664 | if buf.Bytes()[buf.Len()-1] != '\n' { 665 | buf.WriteByte('\n') 666 | } 667 | l.output(s, buf, file, line, alsoToStderr) 668 | } 669 | 670 | // output writes the data to the log files and releases the buffer. 671 | func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { 672 | l.mu.Lock() 673 | if l.traceLocation.isSet() { 674 | if l.traceLocation.match(file, line) { 675 | buf.Write(stacks(false)) 676 | } 677 | } 678 | data := buf.Bytes() 679 | if !flag.Parsed() { 680 | os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) 681 | os.Stderr.Write(data) 682 | } else if l.toStderr { 683 | os.Stderr.Write(data) 684 | } else { 685 | if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { 686 | os.Stderr.Write(data) 687 | } 688 | if l.file[s] == nil { 689 | if err := l.createFiles(s); err != nil { 690 | os.Stderr.Write(data) // Make sure the message appears somewhere. 691 | l.exit(err) 692 | } 693 | } 694 | switch s { 695 | case fatalLog: 696 | l.file[fatalLog].Write(data) 697 | fallthrough 698 | case errorLog: 699 | l.file[errorLog].Write(data) 700 | fallthrough 701 | case warningLog: 702 | l.file[warningLog].Write(data) 703 | fallthrough 704 | case infoLog: 705 | l.file[infoLog].Write(data) 706 | } 707 | } 708 | if s == fatalLog { 709 | // If we got here via Exit rather than Fatal, print no stacks. 710 | if atomic.LoadUint32(&fatalNoStacks) > 0 { 711 | l.mu.Unlock() 712 | timeoutFlush(10 * time.Second) 713 | os.Exit(1) 714 | } 715 | // Dump all goroutine stacks before exiting. 716 | // First, make sure we see the trace for the current goroutine on standard error. 717 | // If -logtostderr has been specified, the loop below will do that anyway 718 | // as the first stack in the full dump. 719 | if !l.toStderr { 720 | os.Stderr.Write(stacks(false)) 721 | } 722 | // Write the stack trace for all goroutines to the files. 723 | trace := stacks(true) 724 | logExitFunc = func(error) {} // If we get a write error, we'll still exit below. 725 | for log := fatalLog; log >= infoLog; log-- { 726 | if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. 727 | f.Write(trace) 728 | } 729 | } 730 | l.mu.Unlock() 731 | timeoutFlush(10 * time.Second) 732 | os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. 733 | } 734 | l.putBuffer(buf) 735 | l.mu.Unlock() 736 | if stats := severityStats[s]; stats != nil { 737 | atomic.AddInt64(&stats.lines, 1) 738 | atomic.AddInt64(&stats.bytes, int64(len(data))) 739 | } 740 | } 741 | 742 | // timeoutFlush calls Flush and returns when it completes or after timeout 743 | // elapses, whichever happens first. This is needed because the hooks invoked 744 | // by Flush may deadlock when glog.Fatal is called from a hook that holds 745 | // a lock. 746 | func timeoutFlush(timeout time.Duration) { 747 | done := make(chan bool, 1) 748 | go func() { 749 | Flush() // calls logging.lockAndFlushAll() 750 | done <- true 751 | }() 752 | select { 753 | case <-done: 754 | case <-time.After(timeout): 755 | fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) 756 | } 757 | } 758 | 759 | // stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. 760 | func stacks(all bool) []byte { 761 | // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. 762 | n := 10000 763 | if all { 764 | n = 100000 765 | } 766 | var trace []byte 767 | for i := 0; i < 5; i++ { 768 | trace = make([]byte, n) 769 | nbytes := runtime.Stack(trace, all) 770 | if nbytes < len(trace) { 771 | return trace[:nbytes] 772 | } 773 | n *= 2 774 | } 775 | return trace 776 | } 777 | 778 | // logExitFunc provides a simple mechanism to override the default behavior 779 | // of exiting on error. Used in testing and to guarantee we reach a required exit 780 | // for fatal logs. Instead, exit could be a function rather than a method but that 781 | // would make its use clumsier. 782 | var logExitFunc func(error) 783 | 784 | // exit is called if there is trouble creating or writing log files. 785 | // It flushes the logs and exits the program; there's no point in hanging around. 786 | // l.mu is held. 787 | func (l *loggingT) exit(err error) { 788 | fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) 789 | // If logExitFunc is set, we do that instead of exiting. 790 | if logExitFunc != nil { 791 | logExitFunc(err) 792 | return 793 | } 794 | l.flushAll() 795 | os.Exit(2) 796 | } 797 | 798 | // syncBuffer joins a bufio.Writer to its underlying file, providing access to the 799 | // file's Sync method and providing a wrapper for the Write method that provides log 800 | // file rotation. There are conflicting methods, so the file cannot be embedded. 801 | // l.mu is held for all its methods. 802 | type syncBuffer struct { 803 | logger *loggingT 804 | *bufio.Writer 805 | file *os.File 806 | sev severity 807 | nbytes uint64 // The number of bytes written to this file 808 | } 809 | 810 | func (sb *syncBuffer) Sync() error { 811 | return sb.file.Sync() 812 | } 813 | 814 | func (sb *syncBuffer) Write(p []byte) (n int, err error) { 815 | if sb.nbytes+uint64(len(p)) >= MaxSize { 816 | if err := sb.rotateFile(time.Now()); err != nil { 817 | sb.logger.exit(err) 818 | } 819 | } 820 | n, err = sb.Writer.Write(p) 821 | sb.nbytes += uint64(n) 822 | if err != nil { 823 | sb.logger.exit(err) 824 | } 825 | return 826 | } 827 | 828 | // rotateFile closes the syncBuffer's file and starts a new one. 829 | func (sb *syncBuffer) rotateFile(now time.Time) error { 830 | if sb.file != nil { 831 | sb.Flush() 832 | sb.file.Close() 833 | } 834 | var err error 835 | sb.file, _, err = create(severityName[sb.sev], now) 836 | sb.nbytes = 0 837 | if err != nil { 838 | return err 839 | } 840 | 841 | sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) 842 | 843 | // Write header. 844 | var buf bytes.Buffer 845 | fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) 846 | fmt.Fprintf(&buf, "Running on machine: %s\n", host) 847 | fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) 848 | fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") 849 | n, err := sb.file.Write(buf.Bytes()) 850 | sb.nbytes += uint64(n) 851 | return err 852 | } 853 | 854 | // bufferSize sizes the buffer associated with each log file. It's large 855 | // so that log records can accumulate without the logging thread blocking 856 | // on disk I/O. The flushDaemon will block instead. 857 | const bufferSize = 256 * 1024 858 | 859 | // createFiles creates all the log files for severity from sev down to infoLog. 860 | // l.mu is held. 861 | func (l *loggingT) createFiles(sev severity) error { 862 | now := time.Now() 863 | // Files are created in decreasing severity order, so as soon as we find one 864 | // has already been created, we can stop. 865 | for s := sev; s >= infoLog && l.file[s] == nil; s-- { 866 | sb := &syncBuffer{ 867 | logger: l, 868 | sev: s, 869 | } 870 | if err := sb.rotateFile(now); err != nil { 871 | return err 872 | } 873 | l.file[s] = sb 874 | } 875 | return nil 876 | } 877 | 878 | const flushInterval = 30 * time.Second 879 | 880 | // flushDaemon periodically flushes the log file buffers. 881 | func (l *loggingT) flushDaemon() { 882 | for _ = range time.NewTicker(flushInterval).C { 883 | l.lockAndFlushAll() 884 | } 885 | } 886 | 887 | // lockAndFlushAll is like flushAll but locks l.mu first. 888 | func (l *loggingT) lockAndFlushAll() { 889 | l.mu.Lock() 890 | l.flushAll() 891 | l.mu.Unlock() 892 | } 893 | 894 | // flushAll flushes all the logs and attempts to "sync" their data to disk. 895 | // l.mu is held. 896 | func (l *loggingT) flushAll() { 897 | // Flush from fatal down, in case there's trouble flushing. 898 | for s := fatalLog; s >= infoLog; s-- { 899 | file := l.file[s] 900 | if file != nil { 901 | file.Flush() // ignore error 902 | file.Sync() // ignore error 903 | } 904 | } 905 | } 906 | 907 | // CopyStandardLogTo arranges for messages written to the Go "log" package's 908 | // default logs to also appear in the Google logs for the named and lower 909 | // severities. Subsequent changes to the standard log's default output location 910 | // or format may break this behavior. 911 | // 912 | // Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not 913 | // recognized, CopyStandardLogTo panics. 914 | func CopyStandardLogTo(name string) { 915 | sev, ok := severityByName(name) 916 | if !ok { 917 | panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) 918 | } 919 | // Set a log format that captures the user's file and line: 920 | // d.go:23: message 921 | stdLog.SetFlags(stdLog.Lshortfile) 922 | stdLog.SetOutput(logBridge(sev)) 923 | } 924 | 925 | // logBridge provides the Write method that enables CopyStandardLogTo to connect 926 | // Go's standard logs to the logs provided by this package. 927 | type logBridge severity 928 | 929 | // Write parses the standard logging line and passes its components to the 930 | // logger for severity(lb). 931 | func (lb logBridge) Write(b []byte) (n int, err error) { 932 | var ( 933 | file = "???" 934 | line = 1 935 | text string 936 | ) 937 | // Split "d.go:23: message" into "d.go", "23", and "message". 938 | if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { 939 | text = fmt.Sprintf("bad log format: %s", b) 940 | } else { 941 | file = string(parts[0]) 942 | text = string(parts[2][1:]) // skip leading space 943 | line, err = strconv.Atoi(string(parts[1])) 944 | if err != nil { 945 | text = fmt.Sprintf("bad line number: %s", b) 946 | line = 1 947 | } 948 | } 949 | // printWithFileLine with alsoToStderr=true, so standard log messages 950 | // always appear on standard error. 951 | logging.printWithFileLine(severity(lb), file, line, true, text) 952 | return len(b), nil 953 | } 954 | 955 | // setV computes and remembers the V level for a given PC 956 | // when vmodule is enabled. 957 | // File pattern matching takes the basename of the file, stripped 958 | // of its .go suffix, and uses filepath.Match, which is a little more 959 | // general than the *? matching used in C++. 960 | // l.mu is held. 961 | func (l *loggingT) setV(pc uintptr) Level { 962 | fn := runtime.FuncForPC(pc) 963 | file, _ := fn.FileLine(pc) 964 | // The file is something like /a/b/c/d.go. We want just the d. 965 | if strings.HasSuffix(file, ".go") { 966 | file = file[:len(file)-3] 967 | } 968 | if slash := strings.LastIndex(file, "/"); slash >= 0 { 969 | file = file[slash+1:] 970 | } 971 | for _, filter := range l.vmodule.filter { 972 | if filter.match(file) { 973 | l.vmap[pc] = filter.level 974 | return filter.level 975 | } 976 | } 977 | l.vmap[pc] = 0 978 | return 0 979 | } 980 | 981 | // Verbose is a boolean type that implements Infof (like Printf) etc. 982 | // See the documentation of V for more information. 983 | type Verbose bool 984 | 985 | // V reports whether verbosity at the call site is at least the requested level. 986 | // The returned value is a boolean of type Verbose, which implements Info, Infoln 987 | // and Infof. These methods will write to the Info log if called. 988 | // Thus, one may write either 989 | // if glog.V(2) { glog.Info("log this") } 990 | // or 991 | // glog.V(2).Info("log this") 992 | // The second form is shorter but the first is cheaper if logging is off because it does 993 | // not evaluate its arguments. 994 | // 995 | // Whether an individual call to V generates a log record depends on the setting of 996 | // the -v and --vmodule flags; both are off by default. If the level in the call to 997 | // V is at least the value of -v, or of -vmodule for the source file containing the 998 | // call, the V call will log. 999 | func V(level Level) Verbose { 1000 | // This function tries hard to be cheap unless there's work to do. 1001 | // The fast path is two atomic loads and compares. 1002 | 1003 | // Here is a cheap but safe test to see if V logging is enabled globally. 1004 | if logging.verbosity.get() >= level { 1005 | return Verbose(true) 1006 | } 1007 | 1008 | // It's off globally but it vmodule may still be set. 1009 | // Here is another cheap but safe test to see if vmodule is enabled. 1010 | if atomic.LoadInt32(&logging.filterLength) > 0 { 1011 | // Now we need a proper lock to use the logging structure. The pcs field 1012 | // is shared so we must lock before accessing it. This is fairly expensive, 1013 | // but if V logging is enabled we're slow anyway. 1014 | logging.mu.Lock() 1015 | defer logging.mu.Unlock() 1016 | if runtime.Callers(2, logging.pcs[:]) == 0 { 1017 | return Verbose(false) 1018 | } 1019 | v, ok := logging.vmap[logging.pcs[0]] 1020 | if !ok { 1021 | v = logging.setV(logging.pcs[0]) 1022 | } 1023 | return Verbose(v >= level) 1024 | } 1025 | return Verbose(false) 1026 | } 1027 | 1028 | // Info is equivalent to the global Info function, guarded by the value of v. 1029 | // See the documentation of V for usage. 1030 | func (v Verbose) Info(args ...interface{}) { 1031 | if v { 1032 | logging.print(infoLog, args...) 1033 | } 1034 | } 1035 | 1036 | // Infoln is equivalent to the global Infoln function, guarded by the value of v. 1037 | // See the documentation of V for usage. 1038 | func (v Verbose) Infoln(args ...interface{}) { 1039 | if v { 1040 | logging.println(infoLog, args...) 1041 | } 1042 | } 1043 | 1044 | // Infof is equivalent to the global Infof function, guarded by the value of v. 1045 | // See the documentation of V for usage. 1046 | func (v Verbose) Infof(format string, args ...interface{}) { 1047 | if v { 1048 | logging.printf(infoLog, format, args...) 1049 | } 1050 | } 1051 | 1052 | // Info logs to the INFO log. 1053 | // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. 1054 | func Info(args ...interface{}) { 1055 | logging.print(infoLog, args...) 1056 | } 1057 | 1058 | // InfoDepth acts as Info but uses depth to determine which call frame to log. 1059 | // InfoDepth(0, "msg") is the same as Info("msg"). 1060 | func InfoDepth(depth int, args ...interface{}) { 1061 | logging.printDepth(infoLog, depth, args...) 1062 | } 1063 | 1064 | // Infoln logs to the INFO log. 1065 | // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. 1066 | func Infoln(args ...interface{}) { 1067 | logging.println(infoLog, args...) 1068 | } 1069 | 1070 | // Infof logs to the INFO log. 1071 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. 1072 | func Infof(format string, args ...interface{}) { 1073 | logging.printf(infoLog, format, args...) 1074 | } 1075 | 1076 | // Warning logs to the WARNING and INFO logs. 1077 | // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. 1078 | func Warning(args ...interface{}) { 1079 | logging.print(warningLog, args...) 1080 | } 1081 | 1082 | // WarningDepth acts as Warning but uses depth to determine which call frame to log. 1083 | // WarningDepth(0, "msg") is the same as Warning("msg"). 1084 | func WarningDepth(depth int, args ...interface{}) { 1085 | logging.printDepth(warningLog, depth, args...) 1086 | } 1087 | 1088 | // Warningln logs to the WARNING and INFO logs. 1089 | // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. 1090 | func Warningln(args ...interface{}) { 1091 | logging.println(warningLog, args...) 1092 | } 1093 | 1094 | // Warningf logs to the WARNING and INFO logs. 1095 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. 1096 | func Warningf(format string, args ...interface{}) { 1097 | logging.printf(warningLog, format, args...) 1098 | } 1099 | 1100 | // Error logs to the ERROR, WARNING, and INFO logs. 1101 | // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. 1102 | func Error(args ...interface{}) { 1103 | logging.print(errorLog, args...) 1104 | } 1105 | 1106 | // ErrorDepth acts as Error but uses depth to determine which call frame to log. 1107 | // ErrorDepth(0, "msg") is the same as Error("msg"). 1108 | func ErrorDepth(depth int, args ...interface{}) { 1109 | logging.printDepth(errorLog, depth, args...) 1110 | } 1111 | 1112 | // Errorln logs to the ERROR, WARNING, and INFO logs. 1113 | // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. 1114 | func Errorln(args ...interface{}) { 1115 | logging.println(errorLog, args...) 1116 | } 1117 | 1118 | // Errorf logs to the ERROR, WARNING, and INFO logs. 1119 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. 1120 | func Errorf(format string, args ...interface{}) { 1121 | logging.printf(errorLog, format, args...) 1122 | } 1123 | 1124 | // Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, 1125 | // including a stack trace of all running goroutines, then calls os.Exit(255). 1126 | // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. 1127 | func Fatal(args ...interface{}) { 1128 | logging.print(fatalLog, args...) 1129 | } 1130 | 1131 | // FatalDepth acts as Fatal but uses depth to determine which call frame to log. 1132 | // FatalDepth(0, "msg") is the same as Fatal("msg"). 1133 | func FatalDepth(depth int, args ...interface{}) { 1134 | logging.printDepth(fatalLog, depth, args...) 1135 | } 1136 | 1137 | // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, 1138 | // including a stack trace of all running goroutines, then calls os.Exit(255). 1139 | // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. 1140 | func Fatalln(args ...interface{}) { 1141 | logging.println(fatalLog, args...) 1142 | } 1143 | 1144 | // Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, 1145 | // including a stack trace of all running goroutines, then calls os.Exit(255). 1146 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. 1147 | func Fatalf(format string, args ...interface{}) { 1148 | logging.printf(fatalLog, format, args...) 1149 | } 1150 | 1151 | // fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. 1152 | // It allows Exit and relatives to use the Fatal logs. 1153 | var fatalNoStacks uint32 1154 | 1155 | // Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). 1156 | // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. 1157 | func Exit(args ...interface{}) { 1158 | atomic.StoreUint32(&fatalNoStacks, 1) 1159 | logging.print(fatalLog, args...) 1160 | } 1161 | 1162 | // ExitDepth acts as Exit but uses depth to determine which call frame to log. 1163 | // ExitDepth(0, "msg") is the same as Exit("msg"). 1164 | func ExitDepth(depth int, args ...interface{}) { 1165 | atomic.StoreUint32(&fatalNoStacks, 1) 1166 | logging.printDepth(fatalLog, depth, args...) 1167 | } 1168 | 1169 | // Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). 1170 | func Exitln(args ...interface{}) { 1171 | atomic.StoreUint32(&fatalNoStacks, 1) 1172 | logging.println(fatalLog, args...) 1173 | } 1174 | 1175 | // Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). 1176 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. 1177 | func Exitf(format string, args ...interface{}) { 1178 | atomic.StoreUint32(&fatalNoStacks, 1) 1179 | logging.printf(fatalLog, format, args...) 1180 | } 1181 | -------------------------------------------------------------------------------- /vendor/github.com/golang/glog/glog_file.go: -------------------------------------------------------------------------------- 1 | // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ 2 | // 3 | // Copyright 2013 Google Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | // File I/O for logs. 18 | 19 | package glog 20 | 21 | import ( 22 | "errors" 23 | "flag" 24 | "fmt" 25 | "os" 26 | "os/user" 27 | "path/filepath" 28 | "strings" 29 | "sync" 30 | "time" 31 | ) 32 | 33 | // MaxSize is the maximum size of a log file in bytes. 34 | var MaxSize uint64 = 1024 * 1024 * 1800 35 | 36 | // logDirs lists the candidate directories for new log files. 37 | var logDirs []string 38 | 39 | // If non-empty, overrides the choice of directory in which to write logs. 40 | // See createLogDirs for the full list of possible destinations. 41 | var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") 42 | 43 | func createLogDirs() { 44 | if *logDir != "" { 45 | logDirs = append(logDirs, *logDir) 46 | } 47 | logDirs = append(logDirs, os.TempDir()) 48 | } 49 | 50 | var ( 51 | pid = os.Getpid() 52 | program = filepath.Base(os.Args[0]) 53 | host = "unknownhost" 54 | userName = "unknownuser" 55 | ) 56 | 57 | func init() { 58 | h, err := os.Hostname() 59 | if err == nil { 60 | host = shortHostname(h) 61 | } 62 | 63 | current, err := user.Current() 64 | if err == nil { 65 | userName = current.Username 66 | } 67 | 68 | // Sanitize userName since it may contain filepath separators on Windows. 69 | userName = strings.Replace(userName, `\`, "_", -1) 70 | } 71 | 72 | // shortHostname returns its argument, truncating at the first period. 73 | // For instance, given "www.google.com" it returns "www". 74 | func shortHostname(hostname string) string { 75 | if i := strings.Index(hostname, "."); i >= 0 { 76 | return hostname[:i] 77 | } 78 | return hostname 79 | } 80 | 81 | // logName returns a new log file name containing tag, with start time t, and 82 | // the name for the symlink for tag. 83 | func logName(tag string, t time.Time) (name, link string) { 84 | name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", 85 | program, 86 | host, 87 | userName, 88 | tag, 89 | t.Year(), 90 | t.Month(), 91 | t.Day(), 92 | t.Hour(), 93 | t.Minute(), 94 | t.Second(), 95 | pid) 96 | return name, program + "." + tag 97 | } 98 | 99 | var onceLogDirs sync.Once 100 | 101 | // create creates a new log file and returns the file and its filename, which 102 | // contains tag ("INFO", "FATAL", etc.) and t. If the file is created 103 | // successfully, create also attempts to update the symlink for that tag, ignoring 104 | // errors. 105 | func create(tag string, t time.Time) (f *os.File, filename string, err error) { 106 | onceLogDirs.Do(createLogDirs) 107 | if len(logDirs) == 0 { 108 | return nil, "", errors.New("log: no log dirs") 109 | } 110 | name, link := logName(tag, t) 111 | var lastErr error 112 | for _, dir := range logDirs { 113 | fname := filepath.Join(dir, name) 114 | f, err := os.Create(fname) 115 | if err == nil { 116 | symlink := filepath.Join(dir, link) 117 | os.Remove(symlink) // ignore err 118 | os.Symlink(name, symlink) // ignore err 119 | return f, fname, nil 120 | } 121 | lastErr = err 122 | } 123 | return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) 124 | } 125 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: LWIe7rP7M3hBnAxpsMaZhrVBs2DSyhzoQ 2 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - linux 5 | 6 | go: 7 | - 1 8 | - 1.3 9 | - 1.4 10 | - 1.5 11 | - 1.6 12 | - 1.7.x 13 | - 1.8.x 14 | - tip 15 | 16 | before_install: 17 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 18 | - go get github.com/axw/gocov/gocov 19 | - go get github.com/modocache/gover 20 | - go get github.com/mattn/goveralls 21 | 22 | script: 23 | - go test -v -coverprofile=example.coverprofile ./example 24 | - go test -v -coverprofile=main.coverprofile 25 | - $HOME/gopath/bin/gover 26 | - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=gover.coverprofile 27 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## nightly 2 | * Added support for ignoring fields. 3 | 4 | ## v1.1.0 5 | 6 | * Added support for recursive data structures. 7 | * Fixed bug with embedded fixed length arrays in structs. 8 | * Added `example/` directory. 9 | * Minor test bug fixes for future go versions. 10 | * Added change log. 11 | 12 | ## v1.0.0 13 | 14 | Initial tagged release release. 15 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tristan Rice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/README.md: -------------------------------------------------------------------------------- 1 | # messagediff [![Build Status](https://travis-ci.org/d4l3k/messagediff.svg?branch=master)](https://travis-ci.org/d4l3k/messagediff) [![Coverage Status](https://coveralls.io/repos/github/d4l3k/messagediff/badge.svg?branch=master)](https://coveralls.io/github/d4l3k/messagediff?branch=master) [![GoDoc](https://godoc.org/github.com/d4l3k/messagediff?status.svg)](https://godoc.org/github.com/d4l3k/messagediff) 2 | 3 | A library for doing diffs of arbitrary Golang structs. 4 | 5 | If the unsafe package is available messagediff will diff unexported fields in 6 | addition to exported fields. This is primarily used for testing purposes as it 7 | allows for providing informative error messages. 8 | 9 | Optionally, fields in structs can be tagged as `testdiff:"ignore"` to make 10 | messagediff skip it when doing the comparison. 11 | 12 | 13 | ## Example Usage 14 | In a normal file: 15 | ```go 16 | package main 17 | 18 | import "gopkg.in/d4l3k/messagediff.v1" 19 | 20 | type someStruct struct { 21 | A, b int 22 | C []int 23 | } 24 | 25 | func main() { 26 | a := someStruct{1, 2, []int{1}} 27 | b := someStruct{1, 3, []int{1, 2}} 28 | diff, equal := messagediff.PrettyDiff(a, b) 29 | /* 30 | diff = 31 | `added: .C[1] = 2 32 | modified: .b = 3` 33 | 34 | equal = false 35 | */ 36 | } 37 | 38 | ``` 39 | In a test: 40 | ```go 41 | import "gopkg.in/d4l3k/messagediff.v1" 42 | 43 | ... 44 | 45 | type someStruct struct { 46 | A, b int 47 | C []int 48 | } 49 | 50 | func TestSomething(t *testing.T) { 51 | want := someStruct{1, 2, []int{1}} 52 | got := someStruct{1, 3, []int{1, 2}} 53 | if diff, equal := messagediff.PrettyDiff(want, got); !equal { 54 | t.Errorf("Something() = %#v\n%s", got, diff) 55 | } 56 | } 57 | ``` 58 | To ignore a field in a struct, just annotate it with testdiff:"ignore" like 59 | this: 60 | ```go 61 | package main 62 | 63 | import "gopkg.in/d4l3k/messagediff.v1" 64 | 65 | type someStruct struct { 66 | A int 67 | B int `testdiff:"ignore"` 68 | } 69 | 70 | func main() { 71 | a := someStruct{1, 2} 72 | b := someStruct{1, 3} 73 | diff, equal := messagediff.PrettyDiff(a, b) 74 | /* 75 | equal = true 76 | diff = "" 77 | */ 78 | } 79 | ``` 80 | 81 | See the `DeepDiff` function for using the diff results programmatically. 82 | 83 | ## License 84 | Copyright (c) 2015 [Tristan Rice](https://fn.lc) 85 | 86 | messagediff is licensed under the MIT license. See the LICENSE file for more information. 87 | 88 | bypass.go and bypasssafe.go are borrowed from 89 | [go-spew](https://github.com/davecgh/go-spew) and have a seperate copyright 90 | notice. 91 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/bypass.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is not running on Google App Engine and "-tags disableunsafe" 17 | // is not added to the go build command line. 18 | // +build !appengine,!disableunsafe 19 | 20 | package messagediff 21 | 22 | import ( 23 | "reflect" 24 | "unsafe" 25 | ) 26 | 27 | const ( 28 | // UnsafeDisabled is a build-time constant which specifies whether or 29 | // not access to the unsafe package is available. 30 | UnsafeDisabled = false 31 | 32 | // ptrSize is the size of a pointer on the current arch. 33 | ptrSize = unsafe.Sizeof((*byte)(nil)) 34 | ) 35 | 36 | var ( 37 | // offsetPtr, offsetScalar, and offsetFlag are the offsets for the 38 | // internal reflect.Value fields. These values are valid before golang 39 | // commit ecccf07e7f9d which changed the format. The are also valid 40 | // after commit 82f48826c6c7 which changed the format again to mirror 41 | // the original format. Code in the init function updates these offsets 42 | // as necessary. 43 | offsetPtr = uintptr(ptrSize) 44 | offsetScalar = uintptr(0) 45 | offsetFlag = uintptr(ptrSize * 2) 46 | 47 | // flagKindWidth and flagKindShift indicate various bits that the 48 | // reflect package uses internally to track kind information. 49 | // 50 | // flagRO indicates whether or not the value field of a reflect.Value is 51 | // read-only. 52 | // 53 | // flagIndir indicates whether the value field of a reflect.Value is 54 | // the actual data or a pointer to the data. 55 | // 56 | // These values are valid before golang commit 90a7c3c86944 which 57 | // changed their positions. Code in the init function updates these 58 | // flags as necessary. 59 | flagKindWidth = uintptr(5) 60 | flagKindShift = uintptr(flagKindWidth - 1) 61 | flagRO = uintptr(1 << 0) 62 | flagIndir = uintptr(1 << 1) 63 | ) 64 | 65 | func init() { 66 | // Older versions of reflect.Value stored small integers directly in the 67 | // ptr field (which is named val in the older versions). Versions 68 | // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named 69 | // scalar for this purpose which unfortunately came before the flag 70 | // field, so the offset of the flag field is different for those 71 | // versions. 72 | // 73 | // This code constructs a new reflect.Value from a known small integer 74 | // and checks if the size of the reflect.Value struct indicates it has 75 | // the scalar field. When it does, the offsets are updated accordingly. 76 | vv := reflect.ValueOf(0xf00) 77 | if unsafe.Sizeof(vv) == (ptrSize * 4) { 78 | offsetScalar = ptrSize * 2 79 | offsetFlag = ptrSize * 3 80 | } 81 | 82 | // Commit 90a7c3c86944 changed the flag positions such that the low 83 | // order bits are the kind. This code extracts the kind from the flags 84 | // field and ensures it's the correct type. When it's not, the flag 85 | // order has been changed to the newer format, so the flags are updated 86 | // accordingly. 87 | upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) 88 | upfv := *(*uintptr)(upf) 89 | flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { 91 | flagKindShift = 0 92 | flagRO = 1 << 5 93 | flagIndir = 1 << 6 94 | 95 | // Commit adf9b30e5594 modified the flags to separate the 96 | // flagRO flag into two bits which specifies whether or not the 97 | // field is embedded. This causes flagIndir to move over a bit 98 | // and means that flagRO is the combination of either of the 99 | // original flagRO bit and the new bit. 100 | // 101 | // This code detects the change by extracting what used to be 102 | // the indirect bit to ensure it's set. When it's not, the flag 103 | // order has been changed to the newer format, so the flags are 104 | // updated accordingly. 105 | if upfv&flagIndir == 0 { 106 | flagRO = 3 << 5 107 | flagIndir = 1 << 7 108 | } 109 | } 110 | } 111 | 112 | // unsafeReflectValue converts the passed reflect.Value into a one that bypasses 113 | // the typical safety restrictions preventing access to unaddressable and 114 | // unexported data. It works by digging the raw pointer to the underlying 115 | // value out of the protected value and generating a new unprotected (unsafe) 116 | // reflect.Value to it. 117 | // 118 | // This allows us to check for implementations of the Stringer and error 119 | // interfaces to be used for pretty printing ordinarily unaddressable and 120 | // inaccessible values such as unexported struct fields. 121 | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { 122 | indirects := 1 123 | vt := v.Type() 124 | upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) 125 | rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) 126 | if rvf&flagIndir != 0 { 127 | vt = reflect.PtrTo(v.Type()) 128 | indirects++ 129 | } else if offsetScalar != 0 { 130 | // The value is in the scalar field when it's not one of the 131 | // reference types. 132 | switch vt.Kind() { 133 | case reflect.Uintptr: 134 | case reflect.Chan: 135 | case reflect.Func: 136 | case reflect.Map: 137 | case reflect.Ptr: 138 | case reflect.UnsafePointer: 139 | default: 140 | upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 141 | offsetScalar) 142 | } 143 | } 144 | 145 | pv := reflect.NewAt(vt, upv) 146 | rv = pv 147 | for i := 0; i < indirects; i++ { 148 | rv = rv.Elem() 149 | } 150 | return rv 151 | } 152 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when either the code is running on Google App Engine or "-tags disableunsafe" 17 | // is added to the go build command line. 18 | // +build appengine disableunsafe 19 | 20 | package messagediff 21 | 22 | import "reflect" 23 | 24 | const ( 25 | // UnsafeDisabled is a build-time constant which specifies whether or 26 | // not access to the unsafe package is available. 27 | UnsafeDisabled = true 28 | ) 29 | 30 | // unsafeReflectValue typically converts the passed reflect.Value into a one 31 | // that bypasses the typical safety restrictions preventing access to 32 | // unaddressable and unexported data. However, doing this relies on access to 33 | // the unsafe package. This is a stub version which simply returns the passed 34 | // reflect.Value when the unsafe package is not available. 35 | func unsafeReflectValue(v reflect.Value) reflect.Value { 36 | return v 37 | } 38 | -------------------------------------------------------------------------------- /vendor/gopkg.in/d4l3k/messagediff.v1/messagediff.go: -------------------------------------------------------------------------------- 1 | package messagediff 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "strings" 8 | "unsafe" 9 | ) 10 | 11 | // PrettyDiff does a deep comparison and returns the nicely formated results. 12 | func PrettyDiff(a, b interface{}) (string, bool) { 13 | d, equal := DeepDiff(a, b) 14 | var dstr []string 15 | for path, added := range d.Added { 16 | dstr = append(dstr, fmt.Sprintf("added: %s = %#v\n", path.String(), added)) 17 | } 18 | for path, removed := range d.Removed { 19 | dstr = append(dstr, fmt.Sprintf("removed: %s = %#v\n", path.String(), removed)) 20 | } 21 | for path, modified := range d.Modified { 22 | dstr = append(dstr, fmt.Sprintf("modified: %s = %#v\n", path.String(), modified)) 23 | } 24 | sort.Strings(dstr) 25 | return strings.Join(dstr, ""), equal 26 | } 27 | 28 | // DeepDiff does a deep comparison and returns the results. 29 | func DeepDiff(a, b interface{}) (*Diff, bool) { 30 | d := newDiff() 31 | return d, d.diff(reflect.ValueOf(a), reflect.ValueOf(b), nil) 32 | } 33 | 34 | func newDiff() *Diff { 35 | return &Diff{ 36 | Added: make(map[*Path]interface{}), 37 | Removed: make(map[*Path]interface{}), 38 | Modified: make(map[*Path]interface{}), 39 | visited: make(map[visit]bool), 40 | } 41 | } 42 | 43 | func (d *Diff) diff(aVal, bVal reflect.Value, path Path) bool { 44 | // The array underlying `path` could be modified in subsequent 45 | // calls. Make sure we have a local copy. 46 | localPath := make(Path, len(path)) 47 | copy(localPath, path) 48 | 49 | // Validity checks. Should only trigger if nil is one of the original arguments. 50 | if !aVal.IsValid() && !bVal.IsValid() { 51 | return true 52 | } 53 | if !bVal.IsValid() { 54 | d.Modified[&localPath] = nil 55 | return false 56 | } else if !aVal.IsValid() { 57 | d.Modified[&localPath] = bVal.Interface() 58 | return false 59 | } 60 | 61 | if aVal.Type() != bVal.Type() { 62 | d.Modified[&localPath] = bVal.Interface() 63 | return false 64 | } 65 | kind := aVal.Kind() 66 | 67 | // Borrowed from the reflect package to handle recursive data structures. 68 | hard := func(k reflect.Kind) bool { 69 | switch k { 70 | case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: 71 | return true 72 | } 73 | return false 74 | } 75 | 76 | if aVal.CanAddr() && bVal.CanAddr() && hard(kind) { 77 | addr1 := unsafe.Pointer(aVal.UnsafeAddr()) 78 | addr2 := unsafe.Pointer(bVal.UnsafeAddr()) 79 | if uintptr(addr1) > uintptr(addr2) { 80 | // Canonicalize order to reduce number of entries in visited. 81 | // Assumes non-moving garbage collector. 82 | addr1, addr2 = addr2, addr1 83 | } 84 | 85 | // Short circuit if references are already seen. 86 | typ := aVal.Type() 87 | v := visit{addr1, addr2, typ} 88 | if d.visited[v] { 89 | return true 90 | } 91 | 92 | // Remember for later. 93 | d.visited[v] = true 94 | } 95 | // End of borrowed code. 96 | 97 | equal := true 98 | switch kind { 99 | case reflect.Map, reflect.Ptr, reflect.Func, reflect.Chan, reflect.Slice: 100 | if aVal.IsNil() && bVal.IsNil() { 101 | return true 102 | } 103 | if aVal.IsNil() || bVal.IsNil() { 104 | d.Modified[&localPath] = bVal.Interface() 105 | return false 106 | } 107 | } 108 | 109 | switch kind { 110 | case reflect.Array, reflect.Slice: 111 | aLen := aVal.Len() 112 | bLen := bVal.Len() 113 | for i := 0; i < min(aLen, bLen); i++ { 114 | localPath := append(localPath, SliceIndex(i)) 115 | if eq := d.diff(aVal.Index(i), bVal.Index(i), localPath); !eq { 116 | equal = false 117 | } 118 | } 119 | if aLen > bLen { 120 | for i := bLen; i < aLen; i++ { 121 | localPath := append(localPath, SliceIndex(i)) 122 | d.Removed[&localPath] = aVal.Index(i).Interface() 123 | equal = false 124 | } 125 | } else if aLen < bLen { 126 | for i := aLen; i < bLen; i++ { 127 | localPath := append(localPath, SliceIndex(i)) 128 | d.Added[&localPath] = bVal.Index(i).Interface() 129 | equal = false 130 | } 131 | } 132 | case reflect.Map: 133 | for _, key := range aVal.MapKeys() { 134 | aI := aVal.MapIndex(key) 135 | bI := bVal.MapIndex(key) 136 | localPath := append(localPath, MapKey{key.Interface()}) 137 | if !bI.IsValid() { 138 | d.Removed[&localPath] = aI.Interface() 139 | equal = false 140 | } else if eq := d.diff(aI, bI, localPath); !eq { 141 | equal = false 142 | } 143 | } 144 | for _, key := range bVal.MapKeys() { 145 | aI := aVal.MapIndex(key) 146 | if !aI.IsValid() { 147 | bI := bVal.MapIndex(key) 148 | localPath := append(localPath, MapKey{key.Interface()}) 149 | d.Added[&localPath] = bI.Interface() 150 | equal = false 151 | } 152 | } 153 | case reflect.Struct: 154 | typ := aVal.Type() 155 | for i := 0; i < typ.NumField(); i++ { 156 | index := []int{i} 157 | field := typ.FieldByIndex(index) 158 | if field.Tag.Get("testdiff") == "ignore" { // skip fields marked to be ignored 159 | continue 160 | } 161 | localPath := append(localPath, StructField(field.Name)) 162 | aI := unsafeReflectValue(aVal.FieldByIndex(index)) 163 | bI := unsafeReflectValue(bVal.FieldByIndex(index)) 164 | if eq := d.diff(aI, bI, localPath); !eq { 165 | equal = false 166 | } 167 | } 168 | case reflect.Ptr: 169 | equal = d.diff(aVal.Elem(), bVal.Elem(), localPath) 170 | default: 171 | if reflect.DeepEqual(aVal.Interface(), bVal.Interface()) { 172 | equal = true 173 | } else { 174 | d.Modified[&localPath] = bVal.Interface() 175 | equal = false 176 | } 177 | } 178 | return equal 179 | } 180 | 181 | func min(a, b int) int { 182 | if a < b { 183 | return a 184 | } 185 | return b 186 | } 187 | 188 | // During deepValueEqual, must keep track of checks that are 189 | // in progress. The comparison algorithm assumes that all 190 | // checks in progress are true when it reencounters them. 191 | // Visited comparisons are stored in a map indexed by visit. 192 | // This is borrowed from the reflect package. 193 | type visit struct { 194 | a1 unsafe.Pointer 195 | a2 unsafe.Pointer 196 | typ reflect.Type 197 | } 198 | 199 | // Diff represents a change in a struct. 200 | type Diff struct { 201 | Added, Removed, Modified map[*Path]interface{} 202 | visited map[visit]bool 203 | } 204 | 205 | // Path represents a path to a changed datum. 206 | type Path []PathNode 207 | 208 | func (p Path) String() string { 209 | var out string 210 | for _, n := range p { 211 | out += n.String() 212 | } 213 | return out 214 | } 215 | 216 | // PathNode represents one step in the path. 217 | type PathNode interface { 218 | String() string 219 | } 220 | 221 | // StructField is a path element representing a field of a struct. 222 | type StructField string 223 | 224 | func (n StructField) String() string { 225 | return fmt.Sprintf(".%s", string(n)) 226 | } 227 | 228 | // MapKey is a path element representing a key of a map. 229 | type MapKey struct { 230 | Key interface{} 231 | } 232 | 233 | func (n MapKey) String() string { 234 | return fmt.Sprintf("[%#v]", n.Key) 235 | } 236 | 237 | // SliceIndex is a path element representing a index of a slice. 238 | type SliceIndex int 239 | 240 | func (n SliceIndex) String() string { 241 | return fmt.Sprintf("[%d]", n) 242 | } 243 | --------------------------------------------------------------------------------