diff options
Diffstat (limited to 'addDroplet.go')
| -rw-r--r-- | addDroplet.go | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/addDroplet.go b/addDroplet.go new file mode 100644 index 0000000..092040a --- /dev/null +++ b/addDroplet.go @@ -0,0 +1,390 @@ +// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 + +package virtigoxml + +import ( + "encoding/xml" + "errors" + "fmt" + + "github.com/google/uuid" + "go.wit.com/log" + "libvirt.org/go/libvirtxml" + pb "go.wit.com/lib/protobuf/virtbuf" +) + +// import a libvirt xml file +func AddDomainDroplet(cluster *pb.Cluster, 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.AddDroplet(domcfg.UUID, domcfg.Name, 2, 2*1024*1024) + d.StartState = pb.DropletState_OFF + d.CurrentState = 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(cluster, 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.Cluster, domcfg *libvirtxml.Domain) (*pb.Droplet, error) { + var found *pb.Droplet + if domcfg == nil { + return nil, errors.New("domcfg == nil") + } + + for _, d := range c.Droplets { + 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(cluster *pb.Cluster, 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.QemuArch != t.Arch { + e := d.NewChangeEvent("Droplet.QemuArch", d.QemuArch, t.Arch) + alle = append(alle, e) + d.QemuArch = 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(cluster, d, domcfg) + if err != nil { + log.Info("updateNetwork() failed", err) + return alle, err + } + + for _, e := range nete { + alle = append(alle, e) + } + + nete, err = updateDisk(cluster, 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) + + // append each change event + for _, e := range alle { + cluster.Events = append(cluster.Events, e) + } + 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(cluster *pb.Cluster, 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 + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/nvme4n1'/> + <backingStore/> + <target dev='vdb' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> +*/ + +// returns false if something went wrong +func updateDisk(cluster *pb.Cluster, 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(cluster, d, filename) + if err != nil { + return alle, err + } + if e == nil { + continue + } + alle = append(alle, e) + + /* + var found bool = false + for _, disk := range d.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.Disks = append(d.Disks, disk) + log.Info("New filename", filename) + } + */ + } + return alle, nil +} |
