// Package guest implements utilities and structured data for libvirt virtual machines package guest import ( "errors" "time" "git.staur.ca/stobbsm/clustvirt/util" log "git.staur.ca/stobbsm/simplelog" "libvirt.org/go/libvirt" ) // Errors // ErrVMNotFound error when virtual machine is not found var ErrVMNotFound = errors.New("virtual machine not found") // VMState represents different states a virtual machine can be in // Some states are mutually exclusive, but some can be applied at the // same time. // ie. A machine can be running, and migrating at the same time. // A machine can be running, but had a failed migration type VMState uint8 const ( // Steady States // StateStopped means the VM isn't running StateStopped VMState = iota // StateRunning means the VM is currently running StateRunning // StateFailed means the VM is in a failed state, which can indicate a few things: // The VM healthcheck (not yet defined, but will be) has failed // A Migration has failed // Adding or removing a device has failed StateFailed // StateMigrated means the VM was successfully migrated to a new host StateMigrated // StateHealthy means the VM had a successful healthcheck the last time it was done StateHealthy // StateUnhealthy means the opposite of healthy, as in the VM has failed a health check StateUnhealthy // Transient states // StateStarted means the VM has been turned on, but is not nesicarily available yet StateStarted // StateRestarted means a reboot was triggered, but hasn't be finished yet StateRestarted // StatePoweroff means a shutdown was triggered, but isn't finished yet StatePoweroff // StateMigrate means a migration was started, but isn't done yet StateMigrate // StateCreate means a VM is being created StateCreate // StateInstall means a VM is being installed. This usually implies from an ISO and // not an automated installation StateInstall // StateDestroy means a VM is scheduled or is in progress of being destroyed and deleted StateDestroy // StateBackup means a VM is in process of being backed up StateBackup // StateRestore means a VM is in process of being restored StateRestore ) // 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 = util.SetNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet) g.BlockIOParameters.DeviceReadIops = util.SetNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet) g.BlockIOParameters.DeviceWriteIops = util.SetNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet) g.BlockIOParameters.DeviceReadBps = util.SetNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet) g.BlockIOParameters.DeviceWriteBps = util.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 = util.SetNotSet(n.Name, n.NameSet) g.Disks[i].Alias = util.SetNotSet(n.Alias, n.AliasSet) g.Disks[i].Partition = n.Partition g.Disks[i].GuestAlias = util.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] = util.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 = util.SetNotSet(n.Name, n.NameSet) g.Users[i].Domain = util.SetNotSet(n.Domain, n.DomainSet) g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0) } } g.OS.ID = util.SetNotSet(info.OS.ID, info.OS.IDSet) g.OS.Name = util.SetNotSet(info.OS.Name, info.OS.NameSet) g.OS.Machine = util.SetNotSet(info.OS.Machine, info.OS.MachineSet) g.OS.Variant = util.SetNotSet(info.OS.Variant, info.OS.VariantSet) g.OS.Version = util.SetNotSet(info.OS.Version, info.OS.VersionSet) g.OS.VariantID = util.SetNotSet(info.OS.VariantID, info.OS.VariantIDSet) g.OS.VersionID = util.SetNotSet(info.OS.VersionID, info.OS.VersionIDSet) g.OS.PrettyName = util.SetNotSet(info.OS.PrettyName, info.OS.PrettyNameSet) g.OS.KernelRelease = util.SetNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet) g.OS.KernelVersion = util.SetNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet) g.TimeZone.Name = util.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 = util.SetNotSet(n.Hwaddr, n.HwaddrSet) g.NetIFace[i].Name = util.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 = util.SetNotSet(a.Addr, a.AddrSet) g.NetIFace[i].IP[j].Type = util.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.Warn("guest.GetGuest").Str("guest", g.Name).Err(err).Send() } go func() { defer close(g.closeErr) <-g.close g.closeErr <- g.dom.Free() }() return g, nil } // Close closes an open connection func (g *VM) Close() error { log.Info("guest.Close").Str("guest", g.Name).Msg("closing vm") close(g.close) return <-g.closeErr }