// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 package virtigolib import ( "encoding/xml" "errors" "fmt" "github.com/google/uuid" pb "go.wit.com/lib/protobuf/virtbuf" "go.wit.com/log" "libvirt.org/go/libvirtxml" ) // import a libvirt xml file func AddDomainDroplet(cluster *pb.NewCluster, domcfg *libvirtxml.Domain) (*pb.Droplet, []*pb.Event, error) { var alle []*pb.Event if domcfg == nil { return nil, alle, errors.New("domcfg == nil") } d, err := findDomain(cluster, domcfg) if err != nil { return nil, alle, err } if d == nil { d = cluster.AddDropletSimple(domcfg.UUID, domcfg.Name, 2, 2*1024*1024) d.StartState = pb.DropletState_OFF d.Current.State = pb.DropletState_UNKNOWN // if the domcfg doesn't have a uuid, make a new one here if d.Uuid == "" { u := uuid.New() d.Uuid = u.String() } } alle, err = updateDroplet(d, domcfg) if err != nil { log.Info("updateDroplet() failed for", d.Hostname) return d, alle, errors.New("update failed for " + domcfg.Name) } log.Info("added new droplet", domcfg.Name, domcfg.UUID) DumpNonStandardXML(domcfg) return d, alle, nil } func findDomain(c *pb.NewCluster, domcfg *libvirtxml.Domain) (*pb.Droplet, error) { var found *pb.Droplet if domcfg == nil { return nil, errors.New("domcfg == nil") } loop := c.DropletsAll() // get the list of droplets for loop.Scan() { d := loop.Droplet() if d.Hostname == domcfg.Name { if d.Uuid != domcfg.UUID { if domcfg.UUID == "" { // ignore blank or nonexistent UUID's // todo: check to see if the uuid already exists ? domcfg.UUID = d.Uuid } else { fmt.Println("Will Change UUID from", d.Uuid, "to", domcfg.UUID, "for hostname", d.Hostname) d.Uuid = domcfg.UUID } } if found == nil { found = d } else { fmt.Println("FOUND TWICE", d.Uuid, domcfg.Name, domcfg.UUID) return d, errors.New("Found Twice") } } if d.Uuid == domcfg.UUID { if d.Hostname != domcfg.Name { fmt.Println("protobuf has: UUID and Name:", d.Uuid, d.Hostname) fmt.Println("libvirt has: UUID and Name:", domcfg.UUID, domcfg.Name) fmt.Println("FOUND UUID WITH MIS-MATCHED NAME", domcfg.Name, domcfg.UUID) return d, errors.New("UUID with mis-matched names") } } } return found, nil } func updateDroplet(d *pb.Droplet, domcfg *libvirtxml.Domain) ([]*pb.Event, error) { var alle []*pb.Event if d == nil { return alle, errors.New("d == nil") } if domcfg == nil { return alle, errors.New("domcfg == nil") } e, err := updateMemory(d, domcfg) if err != nil { log.Info("updateMemory() failed") return alle, err } if e != nil { alle = append(alle, e) } // update arch & machine if (domcfg.OS != nil) && (domcfg.OS.Type != nil) { // OS Type: &{Arch:x86_64 Machine:pc-i440fx-5.2 Type:hvm} t := domcfg.OS.Type if d.PreferredArch != t.Arch { e := d.NewChangeEvent("Droplet.QemuArch", d.PreferredArch, t.Arch) alle = append(alle, e) d.PreferredArch = t.Arch } if d.QemuMachine != t.Machine { e := d.NewChangeEvent("Droplet.QemuMachine", d.QemuMachine, t.Machine) alle = append(alle, e) d.QemuMachine = t.Machine } } // check cpus if d.Cpus != int64(domcfg.VCPU.Value) { // fmt.Printf("cpus changed. VCPU = %+v\n", domcfg.VCPU) fmt.Printf("cpus changed. from %d to %d\n", d.Cpus, domcfg.VCPU.Value) alle = append(alle, d.NewChangeEvent("Droplet.Cpus", d.Cpus, domcfg.VCPU.Value)) d.Cpus = int64(domcfg.VCPU.Value) } // update spice port if domcfg.Devices.Graphics != nil { for _, g := range domcfg.Devices.Graphics { if g.Spice == nil { continue } var s *libvirtxml.DomainGraphicSpice s = g.Spice // fmt.Printf("Spice: %d %+v %s\n", i, s, s.AutoPort) if s.AutoPort == "yes" { // should ignore either way } else { if d.SpicePort != int64(s.Port) { // print out, but ignore the port number d.SpicePort = int64(s.Port) fmt.Printf("Spice Port set to = %d\n", s.Port) alle = append(alle, d.NewChangeEvent("Droplet.SpicePort", d.SpicePort, s.Port)) } } } } // check type if domcfg.Type != "kvm" { fmt.Printf("not kvm. Virt type == %s\n", domcfg.Type) return alle, errors.New("not kvm") } nete, err := updateNetwork(d, domcfg) if err != nil { log.Info("updateNetwork() failed", err) return alle, err } for _, e := range nete { alle = append(alle, e) } nete, err = updateDisk(d, domcfg) if err != nil { return alle, err } for _, e := range nete { alle = append(alle, e) } if alle == nil { log.Info("libvirt xml import worked. nothing changed", domcfg.Name) return alle, nil } log.Info("libvirt xml import worked. droplet changed", domcfg.Name) return alle, nil } // returns false if something went wrong func updateMemory(d *pb.Droplet, domcfg *libvirtxml.Domain) (*pb.Event, error) { if (d == nil) || (domcfg == nil) { return nil, errors.New("domcfg == nil") } if domcfg.Memory == nil { // nothing to do. libvirt xml file didn't define memory size return nil, nil } var m int64 = 0 switch domcfg.Memory.Unit { case "KiB": m = int64(domcfg.Memory.Value * 1024) case "MiB": m = int64(domcfg.Memory.Value * 1024 * 1024) case "GiB": m = int64(domcfg.Memory.Value * 1024 * 1024 * 1024) default: fmt.Println("Unknown Memory Unit", domcfg.Memory.Unit) return nil, errors.New("Unknown Memory Unit " + domcfg.Memory.Unit) } e := d.SetMemory(m) if e != nil { fmt.Printf("Memory changed %s to %d %s\n", pb.HumanFormatBytes(d.Memory), domcfg.Memory.Value, domcfg.Memory.Unit) d.Memory = m } return e, nil } func updateNetwork(d *pb.Droplet, domcfg *libvirtxml.Domain) ([]*pb.Event, error) { var allEvents []*pb.Event if (d == nil) || (domcfg == nil) { return nil, errors.New("domcfg == nil") } // mac address & bridge name var macs map[string]string macs = make(map[string]string) // Iterate over the network interfaces and print the MAC addresses for _, iface := range domcfg.Devices.Interfaces { var hwaddr string var brname string // fmt.Printf("iface: %+v\n", iface) // fmt.Printf("MAC: %+v\n", iface.MAC) // fmt.Printf("Source: %+v\n", iface.Source) // fmt.Printf("Bridge: %+v\n", iface.Source.Bridge) // fmt.Printf("Model: %+v\n", iface.Model) if iface.MAC != nil { // iface.MAC.Address = "aa:bb:aa:bb:aa:ff" // log.Info("Interface:", iface.Target, "MAC Address:", iface.MAC.Address) // fmt.Printf("source: %+v\n", iface.Source) hwaddr = iface.MAC.Address } if iface.Source == nil { // fmt.Printf("non-standard network: %+v\n", iface) updatedXML, _ := xml.MarshalIndent(domcfg.Devices.Interfaces, "", " ") log.Info("Non-Standard Network XML Start") fmt.Println(string(updatedXML)) log.Info("Non-Standard Network XML End") return nil, errors.New("non-standard network. source == nil") } if iface.Source.Bridge == nil { if hwaddr == "" { fmt.Printf("non-standard network: %+v\n", iface) updatedXML, _ := xml.MarshalIndent(domcfg.Devices.Interfaces, "", " ") log.Info("Non-Standard Network XML Start") fmt.Println(string(updatedXML)) log.Info("Non-Standard Network XML End") return nil, errors.New("bridge is nil and no mac address") } brname = "" } else { if iface.Source.Bridge.Bridge == "" { if hwaddr == "" { fmt.Printf("non-standard network: %+v\n", iface) fmt.Printf("iface.Mac: %+v\n", iface) updatedXML, _ := xml.MarshalIndent(domcfg.Devices.Interfaces, "", " ") log.Info("Non-Standard Network XML Start") fmt.Println(string(updatedXML)) log.Info("Non-Standard Network XML End") return nil, errors.New("bridge is blank and no mac address") } brname = iface.Source.Bridge.Bridge } } // log.Info("network has bridge:", iface.Source.Bridge.Bridge) if hwaddr == "" { hwaddr = "generate " + domcfg.Name log.Info("need to generate mac addr for bridge:", brname) // return nil, errors.New("need to generate mac addr for bridge: " + brname) } macs[hwaddr] = brname } for mac, brname := range macs { var found bool = false // log.Info("XML has mac address:", mac, brname) for _, eth := range d.Networks { if eth.Mac == mac { // log.Info("OKAY. FOUND ETH:", eth.Mac, eth.Name, brname) found = true if brname == "" { // if new bridge name is blank, keep the old one brname = eth.Name } if eth.Name != brname { // if argv.IgnoreBr { log.Info("network was:", eth.Mac, eth.Name) log.Info("network now:", eth.Mac, brname) log.Info("ignoring network change (--xml-ignore-net)") // } else { // return nil, errors.New("bridge name changed") // } } } } if !found { /* if CheckUniqueMac(cluster, mac) { } else { log.Info("droplet", d.Hostname, "duplicate mac address", mac) return nil, errors.New("duplicate mac address") } */ var eth *pb.Network eth = new(pb.Network) eth.Mac = mac if brname == "" { brname = "worldbr" } eth.Name = brname d.Networks = append(d.Networks, eth) allEvents = append(allEvents, d.NewChangeEvent("Droplet NewNetwork", "", mac+" "+brname)) } } log.Verbose("mac addrs:", macs) return allEvents, nil } /* from vm3-with-nvme-1.5GB-sec.xml
*/ // returns false if something went wrong func updateDisk(d *pb.Droplet, domcfg *libvirtxml.Domain) ([]*pb.Event, error) { var alle []*pb.Event if (d == nil) || (domcfg == nil) { return nil, errors.New("domcfg == nil") } for _, disk := range domcfg.Devices.Disks { var t *libvirtxml.DomainDiskSourceFile t = disk.Source.File if t == nil { fmt.Println("disk.Source.File == nil") continue } filename := t.File if filename == "" { fmt.Println("No disk source file found.") continue } e, err := InsertFilename(d, filename) if err != nil { return alle, err } if e == nil { continue } alle = append(alle, e) } return alle, nil }