diff --git a/domains.go b/domains.go deleted file mode 100644 index d6d8671..0000000 --- a/domains.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "libvirt.org/go/libvirt" -) -// GuestVM holds a handle is used to communicate to Domains -type GuestVM struct { - Name string - ID uint - BackupXML string - - close chan struct{} - closeErr chan error - dom *libvirt.Domain -} - -// GetGuest loads guest information by name and returns it -func GetGuest(name string, conn *libvirt.Connect) (*GuestVM, error) { - g := &GuestVM{ - Name: name, - close: make(chan struct{}), - closeErr: make(chan error), - } - - var err error - - if g.dom, err = conn.LookupDomainByName(name); err != nil { - return nil, err - } - if g.ID, err = g.dom.GetID(); err != nil { - return nil, err - } - if g.BackupXML, err = g.dom.BackupGetXMLDesc(0); err != nil { - return nil, err - } - - go func() { - defer close(g.closeErr) - <-g.close - g.closeErr <- g.dom.Free() - }() - - return g, nil -} - -// Close closes an open connection -func (g *GuestVM) Close() error { - close(g.close) - return <-g.closeErr -} diff --git a/lib/guest/domains.go b/lib/guest/domains.go new file mode 100644 index 0000000..9dad745 --- /dev/null +++ b/lib/guest/domains.go @@ -0,0 +1,221 @@ +// Package guest implements utilities and structured data for libvirt virtual machines +package guest + +import ( + "errors" + "time" + + "libvirt.org/go/libvirt" +) + +// Errors + +// ErrVMNotFound error when virtual machine is not found +var ErrVMNotFound = errors.New("virtual machine not found") + +// VM holds a handle is used to communicate to Domains +type VM struct { + Name string + HostName string + ID uint + BackupXML string + Autostart bool + BlockIOParameters BlockIOParametersInfo + Disks []DiskInfo + Users []UserInfo + OS OSInfo + TimeZone TimeZoneInfo + NetIFace []NetIFaceInfo + + close chan struct{} + closeErr chan error + dom *libvirt.Domain +} + +// BlockIOParametersInfo is the struct describing VM block IO parameters +type BlockIOParametersInfo struct { + Weight uint + DeviceWeight string + DeviceReadIops string + DeviceWriteIops string + DeviceReadBps string + DeviceWriteBps string +} + +// DiskInfo is the struct for guest disk information, and is used in VM +type DiskInfo struct { + Name string + Partition bool + Alias string + GuestAlias string + DiskDependency []string +} + +// UserInfo is the struct for VM user information, and is used in VM +type UserInfo struct { + Name string + Domain string + LoginTime time.Time +} + +// OSInfo is the struct for guest operating system information, and is used in VM +type OSInfo struct { + ID string + Name string + PrettyName string + Version string + VersionID string + KernelRelease string + KernelVersion string + Machine string + Variant string + VariantID string +} + +// TimeZoneInfo provides guest timezone information, and is used in VM +type TimeZoneInfo struct { + Name string + Offset int +} + +// NetIFaceInfo provides guest Network Interface information, and is used in VM +type NetIFaceInfo struct { + Name string + HWAddr string + IP []IPInfo +} + +// IPInfo provides guest IP network information, and is used in NetIFaceInfo +type IPInfo struct { + Type string + Addr string + Prefix uint +} + +// GetGuest loads guest information by name and returns it +func GetGuest(name string, conn *libvirt.Connect) (*VM, error) { + g := &VM{ + Name: name, + close: make(chan struct{}), + closeErr: make(chan error), + } + + var err error + + // If the domain can't be found by name, exit early with an error + if g.dom, err = conn.LookupDomainByName(name); err != nil { + return g, ErrVMNotFound + } + if g.ID, err = g.dom.GetID(); err != nil { + return g, err + } + if g.BackupXML, err = g.dom.BackupGetXMLDesc(0); err != nil { + return g, err + } + if g.Autostart, err = g.dom.GetAutostart(); err != nil { + return g, err + } + + blkiop, err := g.dom.GetBlkioParameters(0) + if err != nil { + return g, err + } + g.BlockIOParameters.Weight = blkiop.Weight + g.BlockIOParameters.DeviceWeight = setNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet) + g.BlockIOParameters.DeviceReadIops = setNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet) + g.BlockIOParameters.DeviceWriteIops = setNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet) + g.BlockIOParameters.DeviceReadBps = setNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet) + g.BlockIOParameters.DeviceWriteBps = setNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet) + + // Set as much guest info as possible + info, err := g.dom.GetGuestInfo( + libvirt.DOMAIN_GUEST_INFO_USERS|libvirt.DOMAIN_GUEST_INFO_OS| + libvirt.DOMAIN_GUEST_INFO_DISKS|libvirt.DOMAIN_GUEST_INFO_HOSTNAME| + libvirt.DOMAIN_GUEST_INFO_INTERFACES|libvirt.DOMAIN_GUEST_INFO_TIMEZONE| + libvirt.DOMAIN_GUEST_INFO_FILESYSTEM, + 0, + ) + if err != nil { + return g, err + } + // Set disk info + if len(info.Disks) > 0 { + g.Disks = make([]DiskInfo, len(info.Disks)) + for i, n := range info.Disks { + g.Disks[i].Name = setNotSet(n.Name, n.NameSet) + g.Disks[i].Alias = setNotSet(n.Alias, n.AliasSet) + g.Disks[i].Partition = n.Partition + g.Disks[i].GuestAlias = setNotSet(n.GuestAlias, n.GuestAliasSet) + g.Disks[i].DiskDependency = make([]string, len(n.Dependencies)) + for j, k := range n.Dependencies { + g.Disks[i].DiskDependency[j] = setNotSet(k.Name, k.NameSet) + } + } + } + + if len(info.Users) > 0 { + g.Users = make([]UserInfo, len(info.Users)) + for i, n := range info.Users { + g.Users[i].Name = setNotSet(n.Name, n.NameSet) + g.Users[i].Domain = setNotSet(n.Domain, n.DomainSet) + g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0) + } + } + + g.OS.ID = setNotSet(info.OS.ID, info.OS.IDSet) + g.OS.Name = setNotSet(info.OS.Name, info.OS.NameSet) + g.OS.Machine = setNotSet(info.OS.Machine, info.OS.MachineSet) + g.OS.Variant = setNotSet(info.OS.Variant, info.OS.VariantSet) + g.OS.Version = setNotSet(info.OS.Version, info.OS.VersionSet) + g.OS.VariantID = setNotSet(info.OS.VariantID, info.OS.VariantIDSet) + g.OS.VersionID = setNotSet(info.OS.VersionID, info.OS.VersionIDSet) + g.OS.PrettyName = setNotSet(info.OS.PrettyName, info.OS.PrettyNameSet) + g.OS.KernelRelease = setNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet) + g.OS.KernelVersion = setNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet) + g.TimeZone.Name = setNotSet(info.TimeZone.Name, info.TimeZone.NameSet) + g.TimeZone.Offset = info.TimeZone.Offset + + // Set the hostname from the guest if it is set, otherwise use the VM name + if info.HostnameSet { + g.HostName = info.Hostname + } else { + g.HostName = g.Name + } + if len(info.Interfaces) > 0 { + g.NetIFace = make([]NetIFaceInfo, len(info.Interfaces)) + for i, n := range info.Interfaces { + g.NetIFace[i].HWAddr = setNotSet(n.Hwaddr, n.HwaddrSet) + g.NetIFace[i].Name = setNotSet(n.Name, n.NameSet) + if len(n.Addrs) > 0 { + g.NetIFace[i].IP = make([]IPInfo, len(n.Addrs)) + for j, a := range n.Addrs { + g.NetIFace[i].IP[j].Addr = setNotSet(a.Addr, a.AddrSet) + g.NetIFace[i].IP[j].Type = setNotSet(a.Type, a.TypeSet) + g.NetIFace[i].IP[j].Prefix = a.Prefix + } + } + } + } + + go func() { + defer close(g.closeErr) + <-g.close + g.closeErr <- g.dom.Free() + }() + + return g, nil +} + +// helper function to set a string value to "NotSet" if it wasn't actually set +func setNotSet(v string, s bool) string { + if s { + return v + } + return "Notset" +} + +// Close closes an open connection +func (g *VM) Close() error { + close(g.close) + return <-g.closeErr +} diff --git a/host.go b/lib/host/host.go similarity index 75% rename from host.go rename to lib/host/host.go index 03b21e5..bc4dfbf 100644 --- a/host.go +++ b/lib/host/host.go @@ -1,8 +1,9 @@ -package main +package host import ( "fmt" + "git.staur.ca/stobbsm/clustvirt/lib/guest" "libvirt.org/go/libvirt" ) @@ -49,17 +50,18 @@ func (h *Host) connect() error { } // GetGuestByName returns a GuestVM instance that exists on the given host -func (h *Host) GetGuestByName(name string) (*GuestVM, error) { - if g, err := GetGuest(name, h.conn); err == nil { +func (h *Host) GetGuestByName(name string) (*guest.VM, error) { + g, err := guest.GetGuest(name, h.conn) + if err == nil { return g, nil - } else { - lverr, ok := err.(libvirt.Error) - if ok && lverr.Code == libvirt.ERR_INVALID_CONN { - // try again after creating a new connection - return GetGuest(name, h.conn) - } - return nil, err } + lverr, ok := err.(libvirt.Error) + if ok && lverr.Code == libvirt.ERR_INVALID_CONN { + // try again after creating a new connection + return guest.GetGuest(name, h.conn) + } + return nil, err + } // Close triggers closing the host connection