// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 package main import ( "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(domcfg *libvirtxml.Domain) (*DropletT, error) { if domcfg == nil { return nil, errors.New("domcfg == nil") } d, _ := findDomain(domcfg) if d != nil { return d, errors.New(d.pb.Hostname + " droplet exists. need to update instead") } // this is a new unknown droplet (not in the config file) d = new(DropletT) d.pb = me.cluster.AddDroplet(domcfg.UUID, domcfg.Name, 2, 2*1024*1024) d.pb.StartState = pb.DropletState_OFF d.CurrentState = pb.DropletState_UNKNOWN // if the domcfg doesn't have a uuid, make a new one here if d.pb.Uuid == "" { u := uuid.New() d.pb.Uuid = u.String() } me.droplets = append(me.droplets, d) me.changed = true err := updateDroplet(d, domcfg) if err != nil { log.Info("updateDroplet() failed for", d.pb.Hostname) return d, errors.New("update failed for " + domcfg.Name) } log.Info("added new droplet", domcfg.Name, domcfg.UUID) dumpNonStandardXML(domcfg) return d, nil } func findDomain(domcfg *libvirtxml.Domain) (*DropletT, error) { var found *DropletT if domcfg == nil { return nil, errors.New("domcfg == nil") } for _, d := range me.droplets { if d.pb.Hostname == domcfg.Name { if d.pb.Uuid != domcfg.UUID { if domcfg.UUID == "" { // ignore blank or nonexistent UUID's // todo: check to see if the uuid already exists ? domcfg.UUID = d.pb.Uuid } else { fmt.Println("Will Change UUID from", d.pb.Uuid, "to", domcfg.UUID, "for hostname", d.pb.Hostname) d.pb.Uuid = domcfg.UUID me.changed = true } } if found == nil { found = d } else { fmt.Println("FOUND TWICE", d.pb.Uuid, domcfg.Name, domcfg.UUID) return d, errors.New("Found Twice") } } if d.pb.Uuid == domcfg.UUID { if d.pb.Hostname != domcfg.Name { fmt.Println("protobuf has: UUID and Name:", d.pb.Uuid, d.pb.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 *DropletT, domcfg *libvirtxml.Domain) error { var alle []*pb.Event if d == nil { return errors.New("d == nil") } if domcfg == nil { return errors.New("domcfg == nil") } e, err := updateMemory(d, domcfg) if err != nil { log.Info("updateMemory() failed") return 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.pb.QemuArch != t.Arch { alle = append(alle, NewChangeEvent(d.pb, "Droplet.QemuArch", d.pb.QemuArch, t.Arch)) d.pb.QemuArch = t.Arch } if d.pb.QemuMachine != t.Machine { alle = append(alle, NewChangeEvent(d.pb, "Droplet.QemuMachine", d.pb.QemuMachine, t.Machine)) d.pb.QemuMachine = t.Machine } } // check cpus if d.pb.Cpus != int64(domcfg.VCPU.Value) { // fmt.Printf("cpus changed. VCPU = %+v\n", domcfg.VCPU) fmt.Printf("cpus changed. from %d to %d\n", d.pb.Cpus, domcfg.VCPU.Value) alle = append(alle, NewChangeEvent(d.pb, "Droplet.Cpus", d.pb.Cpus, domcfg.VCPU.Value)) d.pb.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.pb.SpicePort != int64(s.Port) { // print out, but ignore the port number d.pb.SpicePort = int64(s.Port) fmt.Printf("Spice Port set to = %d\n", s.Port) alle = append(alle, NewChangeEvent(d.pb, "Droplet.SpicePort", d.pb.SpicePort, s.Port)) } } } } // check type if domcfg.Type != "kvm" { fmt.Printf("not kvm. Virt type == %s\n", domcfg.Type) return errors.New("not kvm") } e, err = updateNetwork(d, domcfg) if err != nil { log.Info("updateNetwork() failed") return errors.New("updateNetwork() failed") } if e != nil { alle = append(alle, e) } if !updateDisk(d, domcfg) { return errors.New("updateDisk() failed") } if alle == nil { log.Info("libvirt xml import worked. nothing changed", domcfg.Name) return nil } log.Info("libvirt xml import worked. droplet changed", domcfg.Name) // log.Info("all change events", alle) me.changed = true // append each change event for _, e := range alle { me.events.Events = append(me.events.Events, e) } return nil } // returns false if something went wrong func updateMemory(d *DropletT, 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.pb.Memory), domcfg.Memory.Value, domcfg.Memory.Unit) d.pb.Memory = m // me.changed = true } return e, nil } func updateNetwork(d *DropletT, domcfg *libvirtxml.Domain) (*pb.Event, error) { var newEvent *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 } else { fmt.Printf("iface: %+v\n", iface) fmt.Printf("Interface Target: %+v, MAC Address not available\n", iface.Target) return nil, errors.New("network XML does not have a MAC Address") } if iface.Source != nil { if iface.Source.Bridge != nil { brname = iface.Source.Bridge.Bridge } } macs[hwaddr] = brname } for mac, brname := range macs { var found bool = false // log.Info("XML has mac address:", mac, brname) for _, eth := range d.pb.Networks { if eth.Mac == mac { // log.Info("OKAY. FOUND ETH:", eth.Mac, eth.Name, brname) found = true if eth.Name != brname { log.Info("network changed bridge name:", eth.Mac, eth.Name, brname) return nil, errors.New("bridge name changed") } } } if !found { var eth *pb.Network eth = new(pb.Network) eth.Mac = mac if brname == "" { brname = "worldbr" } eth.Name = brname d.pb.Networks = append(d.pb.Networks, eth) newEvent = NewChangeEvent(d.pb, "Droplet NewNetwork", "", mac+" "+brname) } } log.Verbose("mac addrs:", macs) return newEvent, nil } /* from vm3-with-nvme-1.5GB-sec.xml
*/ // returns false if something went wrong func updateDisk(d *DropletT, domcfg *libvirtxml.Domain) bool { if (d == nil) || (domcfg == nil) { return false } 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 } var found bool = false for _, disk := range d.pb.Disks { if disk.Filename == filename { log.Verbose("OKAY. FOUND filename", filename) found = true } } if !found { var disk *pb.Disk disk = new(pb.Disk) disk.Filename = filename d.pb.Disks = append(d.pb.Disks, disk) log.Info("New filename", filename) me.changed = true } } return true }