// Package guest implements utilities and structured data for libvirt virtual machines package guest import ( "errors" "log" "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, err } if g.ID, err = g.dom.GetID(); 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 } } } } // Not errors, but still log the warnings when this happens if g.BackupXML, err = g.dom.BackupGetXMLDesc(0); err != nil { log.Printf("WARNING: While loading backup information: %s", err) } 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 { log.Println("Closing VM", g.Name) close(g.close) return <-g.closeErr }