From 967c729a049434d84c4a6d2e5c650f7c57257060 Mon Sep 17 00:00:00 2001 From: Clinton Kitson Date: Fri, 11 Dec 2015 10:44:32 -0800 Subject: [PATCH] Update for a docker driver This commit enables a fully functioning volume driver that is able to run from VMs in VirtualBox and perform introspection necessary to issue commands to underlying VB SOAP API to perform storage orchestration for volume attachemnt. --- vboxwebsrv/vboxwebsrv.go | 23 +- virtualboxclient/hard_disk.go | 49 ++++ virtualboxclient/machine.go | 242 ++++++++++++++++++- virtualboxclient/medium.go | 310 ++++++++++++++++++++++++- virtualboxclient/medium_attachment.go | 13 ++ virtualboxclient/progress.go | 31 +++ virtualboxclient/session.go | 49 ++++ virtualboxclient/state.go | 7 + virtualboxclient/storage_controller.go | 77 ++++++ virtualboxclient/system_properties.go | 24 ++ virtualboxclient/virtualbox.go | 165 +++++++++++-- 11 files changed, 954 insertions(+), 36 deletions(-) create mode 100644 virtualboxclient/hard_disk.go create mode 100644 virtualboxclient/medium_attachment.go create mode 100644 virtualboxclient/session.go create mode 100644 virtualboxclient/state.go diff --git a/vboxwebsrv/vboxwebsrv.go b/vboxwebsrv/vboxwebsrv.go index 93ad76b..f5c2bf6 100644 --- a/vboxwebsrv/vboxwebsrv.go +++ b/vboxwebsrv/vboxwebsrv.go @@ -15800,7 +15800,7 @@ type IAdditionsFacility struct { } type IMediumAttachment struct { - XMLName xml.Name `xml:"http://www.virtualbox.org/ IMediumAttachment"` + // XMLName xml.Name `xml:"http://www.virtualbox.org/ IMediumAttachment"` Medium string `xml:"medium,omitempty"` Controller string `xml:"controller,omitempty"` @@ -20347,6 +20347,8 @@ func (service *VboxPortType) IMachinegetMedium(request *IMachinegetMedium) (*IMa // - InvalidObjectFault // - RuntimeFault +type Returnval *IMediumAttachment + func (service *VboxPortType) IMachinegetMediumAttachmentsOfController(request *IMachinegetMediumAttachmentsOfController) (*IMachinegetMediumAttachmentsOfControllerResponse, error) { response := new(IMachinegetMediumAttachmentsOfControllerResponse) err := service.client.Call("", request, response) @@ -33330,17 +33332,20 @@ func (s *SOAPClient) Call(soapAction string, request, response interface{}) erro encoder := xml.NewEncoder(buffer) //encoder.Indent(" ", " ") - err := encoder.Encode(envelope) - if err == nil { - err = encoder.Flush() + if err := encoder.Encode(envelope); err != nil { + return err } - log.Println(buffer.String()) - if err != nil { + if err := encoder.Flush(); err != nil { return err } + // log.Println(buffer.String()) + req, err := http.NewRequest("POST", s.url, buffer) + if err != nil { + return err + } if s.auth != nil { req.SetBasicAuth(s.auth.Login, s.auth.Password) } @@ -33368,12 +33373,16 @@ func (s *SOAPClient) Call(soapAction string, request, response interface{}) erro defer res.Body.Close() rawbody, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if len(rawbody) == 0 { log.Println("empty response") return nil } - log.Println(string(rawbody)) + // log.Println(string(rawbody)) respEnvelope := new(SOAPEnvelope) respEnvelope.Body = SOAPBody{Content: response} err = xml.Unmarshal(rawbody, respEnvelope) diff --git a/virtualboxclient/hard_disk.go b/virtualboxclient/hard_disk.go new file mode 100644 index 0000000..5fad1d8 --- /dev/null +++ b/virtualboxclient/hard_disk.go @@ -0,0 +1,49 @@ +package virtualboxclient + +type HardDisk struct { + virtualbox *VirtualBox + managedObjectId string +} + +type HardDisks struct { + disks []*HardDisk +} + +func (h *HardDisk) getMedium() *Medium { + return &Medium{virtualbox: h.virtualbox, managedObjectId: h.managedObjectId} +} + +func isSet(value string) bool { + return value != "" +} + +func (hs *HardDisks) GetMedium(objectID, name string) ([]*Medium, error) { + var ms []*Medium + for _, hardDisk := range hs.disks { + om := hardDisk.getMedium() + var m *Medium + if isSet(name) || isSet(objectID) { + var err error + m, err = om.GetIDName() + if err != nil { + return nil, err + } + } + + if isSet(name) && m.Name != name { + continue + } + + if isSet(objectID) && m.ID != objectID { + continue + } + + medium, err := om.Get() + if err != nil { + return nil, err + } + ms = append(ms, medium) + } + + return ms, nil +} diff --git a/virtualboxclient/machine.go b/virtualboxclient/machine.go index 7bb2bb2..c1fab22 100644 --- a/virtualboxclient/machine.go +++ b/virtualboxclient/machine.go @@ -1,12 +1,16 @@ package virtualboxclient import ( + "errors" + "github.com/appropriate/go-virtualboxclient/vboxwebsrv" ) type Machine struct { virtualbox *VirtualBox managedObjectId string + ID string + Name string } func (m *Machine) GetChipsetType() (*vboxwebsrv.ChipsetType, error) { @@ -28,6 +32,18 @@ func (m *Machine) GetMediumAttachments() ([]*vboxwebsrv.IMediumAttachment, error return nil, err // TODO: Wrap the error } + ret := response.Returnval + return ret, nil +} + +func (m *Machine) GetMediumAttachmentsOfController(cName string) ([]*vboxwebsrv.IMediumAttachment, error) { + request := vboxwebsrv.IMachinegetMediumAttachmentsOfController{This: m.managedObjectId, Name: cName} + + response, err := m.virtualbox.IMachinegetMediumAttachmentsOfController(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + return response.Returnval, nil } @@ -53,6 +69,29 @@ func (m *Machine) GetSettingsFilePath() (string, error) { return response.Returnval, nil } +func (m *Machine) SaveSettings() error { + request := vboxwebsrv.IMachinesaveSettings{This: m.managedObjectId} + + _, err := m.virtualbox.IMachinesaveSettings(&request) + if err != nil { + defer m.DiscardSettings() + return err // TODO: Wrap the error + } + + return nil +} + +func (m *Machine) DiscardSettings() error { + request := vboxwebsrv.IMachinediscardSettings{This: m.managedObjectId} + + _, err := m.virtualbox.IMachinediscardSettings(&request) + if err != nil { + return err // TODO: Wrap the error + } + + return nil +} + func (m *Machine) GetStorageControllers() ([]*StorageController, error) { request := vboxwebsrv.IMachinegetStorageControllers{This: m.managedObjectId} @@ -63,8 +102,209 @@ func (m *Machine) GetStorageControllers() ([]*StorageController, error) { storageControllers := make([]*StorageController, len(response.Returnval)) for i, oid := range response.Returnval { - storageControllers[i] = &StorageController{m.virtualbox, oid} + storageControllers[i] = &StorageController{virtualbox: m.virtualbox, managedObjectId: oid} } return storageControllers, nil } + +func (m *Machine) GetStorageController(name string) (*StorageController, error) { + if name == "" { + return nil, errors.New("storage controller name not specified") + } + scs, err := m.GetStorageControllers() + if err != nil { + return nil, err + } + + for _, sc := range scs { + scName, err := sc.GetName() + if err != nil { + return nil, err + } + if scName == name { + sc.Name = scName + return sc, nil + } + } + return nil, errors.New("storage controller not found") +} + +func (m *Machine) AttachDevice(medium *Medium) error { + session, err := m.virtualbox.GetSession() + if err != nil { + return err + } + // defer session.Release() + + if err := m.Lock(session, vboxwebsrv.LockTypeShared); err != nil { + return err + } + defer m.Unlock(session) + + sm, err := session.GetMachine() + if err != nil { + return err + } + defer sm.Release() + + if m.virtualbox.controllerName == "" { + return errors.New("missing controllerName") + } + + sc, err := sm.GetStorageController(m.virtualbox.controllerName) + if err != nil { + return err + } + + pn, err := sc.GetNextAvailablePort(m) + if err != nil { + return err + } + + pc, err := sc.GetPortCount() + if err != nil { + return err + } + + spc := uint32(pn) + 1 + if spc > pc { + if err := sc.SetPortCount(spc); err != nil { + return err + } + } + + request := vboxwebsrv.IMachineattachDevice{ + This: sm.managedObjectId, + Name: sc.Name, + ControllerPort: pn, + Device: 0, + Type_: &medium.DeviceType, + Medium: medium.managedObjectId, + } + + _, err = m.virtualbox.IMachineattachDevice(&request) + if err != nil { + return err + } + + if err := sm.SaveSettings(); err != nil { + return err + } + + if err := m.Unlock(session); err != nil { + return err + } + + return nil +} + +func (m *Machine) DetachDevice(medium *Medium) error { + + session, err := m.virtualbox.GetSession() + if err != nil { + return err + } + // defer session.Release() + + if err := m.Lock(session, vboxwebsrv.LockTypeShared); err != nil { + return err + } + defer m.Unlock(session) + + sm, err := session.GetMachine() + if err != nil { + return err + } + defer sm.Release() + + mediumAttachments, err := m.GetMediumAttachments() + if err != nil { + return err + } + + var request *vboxwebsrv.IMachinedetachDevice + for _, ma := range mediumAttachments { + am := &Medium{virtualbox: m.virtualbox, managedObjectId: ma.Medium} + defer am.Release() + amID, err := am.GetID() + if err != nil { + return err + } + + if amID != medium.ID { + continue + } + request = &vboxwebsrv.IMachinedetachDevice{ + This: sm.managedObjectId, + Name: ma.Controller, + ControllerPort: ma.Port, + Device: 0, + } + } + if request == nil { + return errors.New("couldn't find attached medium") + } + + _, err = m.virtualbox.IMachinedetachDevice(request) + if err != nil { + return err + } + + if err := sm.SaveSettings(); err != nil { + return err + } + + return nil +} + +func (m *Machine) Unlock(session *Session) error { + if err := session.UnlockMachine(); err != nil { + return err + } + return nil +} + +func (m *Machine) Lock(session *Session, lockType vboxwebsrv.LockType) error { + if err := session.LockMachine(m, lockType); err != nil { + return err + } + return nil +} + +func (m *Machine) GetID() (string, error) { + request := vboxwebsrv.IMachinegetId{This: m.managedObjectId} + + response, err := m.virtualbox.IMachinegetId(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Machine) GetName() (string, error) { + request := vboxwebsrv.IMachinegetName{This: m.managedObjectId} + + response, err := m.virtualbox.IMachinegetName(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Machine) Release() error { + return m.virtualbox.Release(m.managedObjectId) +} + +func (m *Machine) Refresh() error { + if mr, err := m.virtualbox.FindMachine(m.ID); err != nil { + return err + } else { + m.managedObjectId = mr.managedObjectId + } + return nil +} diff --git a/virtualboxclient/medium.go b/virtualboxclient/medium.go index ffe0104..370e447 100644 --- a/virtualboxclient/medium.go +++ b/virtualboxclient/medium.go @@ -1,12 +1,24 @@ package virtualboxclient -import ( - "github.com/appropriate/go-virtualboxclient/vboxwebsrv" -) +import "github.com/appropriate/go-virtualboxclient/vboxwebsrv" type Medium struct { virtualbox *VirtualBox managedObjectId string + Location string + Name string + DeviceType vboxwebsrv.DeviceType + Description string + LogicalSize int64 + Size int64 + Format string + MediumFormat string + HostDrive bool + Children []string + Parent string + ID string + MachineIDs []string + SnapshotIDs []string } func (m *Medium) CreateBaseStorage(logicalSize int64, variant []*vboxwebsrv.MediumVariant) (*Progress, error) { @@ -18,7 +30,7 @@ func (m *Medium) CreateBaseStorage(logicalSize int64, variant []*vboxwebsrv.Medi } // TODO: See if we need to do anything with the response - return &Progress{managedObjectId: response.Returnval}, nil + return &Progress{virtualbox: m.virtualbox, managedObjectId: response.Returnval}, nil } func (m *Medium) DeleteStorage() (*Progress, error) { @@ -30,5 +42,293 @@ func (m *Medium) DeleteStorage() (*Progress, error) { } // TODO: See if we need to do anything with the response - return &Progress{managedObjectId: response.Returnval}, nil + return &Progress{virtualbox: m.virtualbox, managedObjectId: response.Returnval}, nil +} + +func (m *Medium) Release() error { + return m.virtualbox.Release(m.managedObjectId) +} + +func (m *Medium) GetLocation() (string, error) { + request := vboxwebsrv.IMediumgetLocation{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetLocation(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetName() (string, error) { + request := vboxwebsrv.IMediumgetName{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetName(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetDeviceType() (*vboxwebsrv.DeviceType, error) { + request := vboxwebsrv.IMediumgetDeviceType{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetDeviceType(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetDescription() (string, error) { + request := vboxwebsrv.IMediumgetDescription{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetDescription(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetSize() (int64, error) { + request := vboxwebsrv.IMediumgetSize{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetSize(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetLogicalSize() (int64, error) { + request := vboxwebsrv.IMediumgetLogicalSize{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetLogicalSize(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetState() (*vboxwebsrv.MediumState, error) { + request := vboxwebsrv.IMediumgetState{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetState(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetFormat() (string, error) { + request := vboxwebsrv.IMediumgetFormat{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetFormat(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetMediumFormat() (string, error) { + request := vboxwebsrv.IMediumgetMediumFormat{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetMediumFormat(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetHostDrive() (bool, error) { + request := vboxwebsrv.IMediumgetHostDrive{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetHostDrive(&request) + if err != nil { + return false, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetParent() (string, error) { + request := vboxwebsrv.IMediumgetParent{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetParent(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetChildren() ([]string, error) { + request := vboxwebsrv.IMediumgetChildren{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetChildren(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) DetachMachines() error { + for _, mid := range m.MachineIDs { + machine, err := m.virtualbox.FindMachine(mid) + if err != nil { + return err + } + defer machine.Release() + + if err := machine.DetachDevice(m); err != nil { + return err + } + } + return nil +} + +func (m *Medium) GetID() (string, error) { + request := vboxwebsrv.IMediumgetId{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetId(&request) + if err != nil { + return "", err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetSnapshotIDs() ([]string, error) { + request := vboxwebsrv.IMediumgetSnapshotIds{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetSnapshotIds(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) GetMachineIDs() ([]string, error) { + request := vboxwebsrv.IMediumgetMachineIds{This: m.managedObjectId} + + response, err := m.virtualbox.IMediumgetMachineIds(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (m *Medium) Get() (*Medium, error) { + var err error + m.Location, err = m.GetLocation() + if err != nil { + return nil, err + } + m.Name, err = m.GetName() + if err != nil { + return nil, err + } + + m.Description, err = m.GetDescription() + if err != nil { + return nil, err + } + + m.Size, err = m.GetSize() + if err != nil { + return nil, err + } + + m.LogicalSize, err = m.GetLogicalSize() + if err != nil { + return nil, err + } + + dt, err := m.GetDeviceType() + if err != nil { + return nil, err + } + m.DeviceType = *dt + + m.Format, err = m.GetFormat() + if err != nil { + return nil, err + } + + m.MediumFormat, err = m.GetMediumFormat() + if err != nil { + return nil, err + } + + m.HostDrive, err = m.GetHostDrive() + if err != nil { + return nil, err + } + + m.Children, err = m.GetChildren() + if err != nil { + return nil, err + } + + m.Parent, err = m.GetParent() + if err != nil { + return nil, err + } + + m.ID, err = m.GetID() + if err != nil { + return nil, err + } + + m.MachineIDs, err = m.GetMachineIDs() + if err != nil { + return nil, err + } + + m.SnapshotIDs, err = m.GetSnapshotIDs() + if err != nil { + return nil, err + } + + return m, nil + +} + +func (m *Medium) GetIDName() (*Medium, error) { + var err error + m.ID, err = m.GetID() + if err != nil { + return nil, err + } + m.Name, err = m.GetName() + if err != nil { + return nil, err + } + return m, nil } diff --git a/virtualboxclient/medium_attachment.go b/virtualboxclient/medium_attachment.go new file mode 100644 index 0000000..b39c606 --- /dev/null +++ b/virtualboxclient/medium_attachment.go @@ -0,0 +1,13 @@ +package virtualboxclient + +import "github.com/appropriate/go-virtualboxclient/vboxwebsrv" + +type MediumAttachment struct { + *vboxwebsrv.IMediumAttachment + virtualbox *VirtualBox + managedObjectId string +} + +func (m *MediumAttachment) GetMedium() (*Medium, error) { + return &Medium{virtualbox: m.virtualbox, managedObjectId: m.Medium}, nil +} diff --git a/virtualboxclient/progress.go b/virtualboxclient/progress.go index dbac7a7..fd44fea 100644 --- a/virtualboxclient/progress.go +++ b/virtualboxclient/progress.go @@ -1,7 +1,38 @@ package virtualboxclient +import "github.com/appropriate/go-virtualboxclient/vboxwebsrv" + type Progress struct { virtualbox *VirtualBox managedObjectId string } + +func (p *Progress) WaitForCompletion(timeout int32) error { + request := vboxwebsrv.IProgresswaitForCompletion{This: p.managedObjectId} + request.Timeout = timeout + + _, err := p.virtualbox.IProgresswaitForCompletion(&request) + if err != nil { + return err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return nil +} + +func (p *Progress) GetPercent() (uint32, error) { + request := vboxwebsrv.IProgressgetPercent{This: p.managedObjectId} + + response, err := p.virtualbox.IProgressgetPercent(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return response.Returnval, nil +} + +func (p *Progress) Release() error { + return p.virtualbox.Release(p.managedObjectId) +} diff --git a/virtualboxclient/session.go b/virtualboxclient/session.go new file mode 100644 index 0000000..dddea09 --- /dev/null +++ b/virtualboxclient/session.go @@ -0,0 +1,49 @@ +package virtualboxclient + +import "github.com/appropriate/go-virtualboxclient/vboxwebsrv" + +type Session struct { + virtualbox *VirtualBox + managedObjectId string +} + +func (s *Session) UnlockMachine() error { + request := vboxwebsrv.ISessionunlockMachine{This: s.managedObjectId} + _, err := s.virtualbox.ISessionunlockMachine(&request) + if err != nil { + return err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return nil +} + +func (s *Session) LockMachine(m *Machine, l vboxwebsrv.LockType) error { + request := vboxwebsrv.IMachinelockMachine{ + This: m.managedObjectId, + Session: s.managedObjectId, + LockType: &l, + } + _, err := s.virtualbox.IMachinelockMachine(&request) + if err != nil { + return err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return nil +} + +func (s *Session) GetMachine() (*Machine, error) { + request := vboxwebsrv.ISessiongetMachine{This: s.managedObjectId} + response, err := s.virtualbox.ISessiongetMachine(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return &Machine{managedObjectId: response.Returnval, virtualbox: s.virtualbox}, nil +} + +func (s *Session) Release() error { + return s.virtualbox.Release(s.managedObjectId) +} diff --git a/virtualboxclient/state.go b/virtualboxclient/state.go new file mode 100644 index 0000000..485cc90 --- /dev/null +++ b/virtualboxclient/state.go @@ -0,0 +1,7 @@ +package virtualboxclient + +type State struct { + virtualbox *VirtualBox + + managedObjectId string +} diff --git a/virtualboxclient/storage_controller.go b/virtualboxclient/storage_controller.go index a68a293..da7f8a3 100644 --- a/virtualboxclient/storage_controller.go +++ b/virtualboxclient/storage_controller.go @@ -1,12 +1,19 @@ package virtualboxclient import ( + "errors" + "github.com/appropriate/go-virtualboxclient/vboxwebsrv" ) type StorageController struct { virtualbox *VirtualBox managedObjectId string + Name string +} + +type StorageControllers struct { + storageControllers []*StorageController } func (sc *StorageController) GetName() (string, error) { @@ -30,3 +37,73 @@ func (sc *StorageController) GetPortCount() (uint32, error) { return response.Returnval, nil } + +func (sc *StorageController) GetStorageBus() (vboxwebsrv.StorageBus, error) { + mapStorageBus := make(map[string]vboxwebsrv.StorageBus) + mapStorageBus["SATA Controller"] = vboxwebsrv.StorageBusSATA + mapStorageBus["IDE Controller"] = vboxwebsrv.StorageBusIDE + mapStorageBus["SCSI"] = vboxwebsrv.StorageBusSCSI + mapStorageBus["SAS"] = vboxwebsrv.StorageBusSAS + + scName, err := sc.GetName() + if err != nil { + return vboxwebsrv.StorageBusNull, err + } + + if bus, ok := mapStorageBus[scName]; ok { + return bus, nil + } + + return vboxwebsrv.StorageBusNull, errors.New("bad controller controller specified") +} + +func (sc *StorageController) GetMaxPortCount() (uint32, error) { + request := vboxwebsrv.IStorageControllergetMaxPortCount{This: sc.managedObjectId} + + response, err := sc.virtualbox.IStorageControllergetMaxPortCount(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + return response.Returnval, nil +} + +func (sc *StorageController) SetPortCount(count uint32) error { + request := vboxwebsrv.IStorageControllersetPortCount{This: sc.managedObjectId, PortCount: count} + + _, err := sc.virtualbox.IStorageControllersetPortCount(&request) + if err != nil { + return err // TODO: Wrap the error + } + + return nil +} + +func (sc *StorageController) GetNextAvailablePort(m *Machine) (int32, error) { + c, err := sc.GetMaxPortCount() + if err != nil { + return 0, err + } + + ams, err := m.GetMediumAttachmentsOfController(sc.Name) + if err != nil { + return 0, nil + } + + portMap := make(map[int32]bool) + for _, am := range ams { + portMap[am.Port] = true + } + + intArr := make([]int32, c) + for i := range intArr { + if _, isUsed := portMap[int32(i)]; !isUsed { + return int32(i), nil + } + } + return 0, errors.New("no available ports") +} + +func (sc *StorageController) Release() error { + return sc.virtualbox.Release(sc.managedObjectId) +} diff --git a/virtualboxclient/system_properties.go b/virtualboxclient/system_properties.go index aded96c..4d154bf 100644 --- a/virtualboxclient/system_properties.go +++ b/virtualboxclient/system_properties.go @@ -19,3 +19,27 @@ func (sp *SystemProperties) GetMaxNetworkAdapters(chipset *vboxwebsrv.ChipsetTyp return response.Returnval, nil } + +func (sp *SystemProperties) GetMaxDevicesPerPortForStorageBus(bus vboxwebsrv.StorageBus) (uint32, error) { + request := vboxwebsrv.ISystemPropertiesgetMaxDevicesPerPortForStorageBus{This: sp.managedObjectId, Bus: &bus} + response, err := sp.virtualbox.ISystemPropertiesgetMaxDevicesPerPortForStorageBus(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + return response.Returnval, nil +} + +func (sp *SystemProperties) GetMinPortCountForStorageBus(bus vboxwebsrv.StorageBus) (uint32, error) { + request := vboxwebsrv.ISystemPropertiesgetMinPortCountForStorageBus{This: sp.managedObjectId, Bus: &bus} + response, err := sp.virtualbox.ISystemPropertiesgetMinPortCountForStorageBus(&request) + if err != nil { + return 0, err // TODO: Wrap the error + } + + return response.Returnval, nil +} + +func (sp *SystemProperties) Release() error { + return sp.virtualbox.Release(sp.managedObjectId) +} diff --git a/virtualboxclient/virtualbox.go b/virtualboxclient/virtualbox.go index ef90922..6c3a0f6 100644 --- a/virtualboxclient/virtualbox.go +++ b/virtualboxclient/virtualbox.go @@ -1,30 +1,31 @@ package virtualboxclient import ( + "errors" + "github.com/appropriate/go-virtualboxclient/vboxwebsrv" ) type VirtualBox struct { *vboxwebsrv.VboxPortType - - username string - password string - managedObjectId string + basicAuth *vboxwebsrv.BasicAuth + controllerName string } -func New(username, password, url string) *VirtualBox { +func New(username, password, url string, tls bool, controllerName string) *VirtualBox { + basicAuth := &vboxwebsrv.BasicAuth{ + Login: username, + Password: password, + } return &VirtualBox{ - VboxPortType: vboxwebsrv.NewVboxPortType(url, false, nil), - - username: username, - password: password, + VboxPortType: vboxwebsrv.NewVboxPortType(url, tls, basicAuth), + basicAuth: basicAuth, + controllerName: controllerName, } } func (vb *VirtualBox) CreateHardDisk(format, location string) (*Medium, error) { - vb.Logon() - request := vboxwebsrv.IVirtualBoxcreateHardDisk{This: vb.managedObjectId, Format: format, Location: location} response, err := vb.IVirtualBoxcreateHardDisk(&request) @@ -36,8 +37,6 @@ func (vb *VirtualBox) CreateHardDisk(format, location string) (*Medium, error) { } func (vb *VirtualBox) GetMachines() ([]*Machine, error) { - vb.Logon() - request := vboxwebsrv.IVirtualBoxgetMachines{This: vb.managedObjectId} response, err := vb.IVirtualBoxgetMachines(&request) @@ -47,15 +46,13 @@ func (vb *VirtualBox) GetMachines() ([]*Machine, error) { machines := make([]*Machine, len(response.Returnval)) for n, oid := range response.Returnval { - machines[n] = &Machine{vb, oid} + machines[n] = &Machine{virtualbox: vb, managedObjectId: oid} } return machines, nil } func (vb *VirtualBox) GetSystemProperties() (*SystemProperties, error) { - vb.Logon() - request := vboxwebsrv.IVirtualBoxgetSystemProperties{This: vb.managedObjectId} response, err := vb.IVirtualBoxgetSystemProperties(&request) @@ -67,14 +64,9 @@ func (vb *VirtualBox) GetSystemProperties() (*SystemProperties, error) { } func (vb *VirtualBox) Logon() error { - if vb.managedObjectId != "" { - // Already logged in - return nil - } - request := vboxwebsrv.IWebsessionManagerlogon{ - Username: vb.username, - Password: vb.password, + Username: vb.basicAuth.Login, + Password: vb.basicAuth.Password, } response, err := vb.IWebsessionManagerlogon(&request) @@ -86,3 +78,130 @@ func (vb *VirtualBox) Logon() error { return nil } + +func (vb *VirtualBox) GetHardDisk(objectID string) (*HardDisks, error) { + request := vboxwebsrv.IVirtualBoxgetHardDisks{This: vb.managedObjectId} + + response, err := vb.IVirtualBoxgetHardDisks(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + var hardDisks []*HardDisk + for _, oid := range response.Returnval { + if objectID == "" || objectID == oid { + hardDisks = append(hardDisks, &HardDisk{vb, oid}) + } + } + + return &HardDisks{disks: hardDisks}, nil +} + +func (vb *VirtualBox) CreateMedium(format string, location string, size int64) (*Medium, error) { + + medium, err := vb.CreateHardDisk(format, location) + if err != nil { + return nil, err + } + defer medium.Release() + + progress, err := medium.CreateBaseStorage(size, nil) + if err != nil { + return nil, err + } + + if err := progress.WaitForCompletion(-1); err != nil { + return nil, err + } + + if p, err := progress.GetPercent(); err != nil { + return nil, err + } else if p != 100 { + return nil, errors.New("failed to create medium") + } + + return medium.Get() +} + +func (vb *VirtualBox) GetMedium(mediumID, mediumName string) ([]*Medium, error) { + hardDisks, err := vb.GetHardDisk("") + if err != nil { + return nil, err + } + + return hardDisks.GetMedium(mediumID, mediumName) +} + +func (vb *VirtualBox) RemoveMedium(mediumID string) error { + if mediumID == "" { + return errors.New("mediumID is empty") + } + + mediums, err := vb.GetMedium(mediumID, "") + if err != nil { + return err + } + + if len(mediums) == 0 { + return errors.New("no mediums returned") + } + + progress, err := mediums[0].DeleteStorage() + if err != nil { + return err + } + + if err := progress.WaitForCompletion(-1); err != nil { + return err + } + + if p, err := progress.GetPercent(); err != nil { + return err + } else if p != 100 { + return errors.New("failed to remove medium") + } + + return nil +} + +func (vb *VirtualBox) GetSession() (*Session, error) { + request := vboxwebsrv.IWebsessionManagergetSessionObject{RefIVirtualBox: vb.managedObjectId} + response, err := vb.IWebsessionManagergetSessionObject(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + + return &Session{managedObjectId: response.Returnval, virtualbox: vb}, nil +} + +func (vb *VirtualBox) FindMachine(nameOrID string) (*Machine, error) { + request := vboxwebsrv.IVirtualBoxfindMachine{This: vb.managedObjectId, NameOrId: nameOrID} + response, err := vb.IVirtualBoxfindMachine(&request) + if err != nil { + return nil, err // TODO: Wrap the error + } + + return &Machine{managedObjectId: response.Returnval, virtualbox: vb}, nil +} + +func (vb *VirtualBox) Release(managedObjectId string) error { + request := vboxwebsrv.IManagedObjectRefrelease{This: managedObjectId} + + _, err := vb.IManagedObjectRefrelease(&request) + if err != nil { + return err // TODO: Wrap the error + } + + // TODO: See if we need to do anything with the response + return nil +} + +func (vb *VirtualBox) GetMOID() string { + return vb.managedObjectId +} + +func (vb *VirtualBox) NewMedium(moid string) *Medium { + return &Medium{virtualbox: vb, managedObjectId: moid} +}