package main /* validate / sanity check / consistancy check the data here is some code to do smart things like: * check mac addresses are unique * check uuid's are unique * double check filenames are unique * return a unique mac address * return a unique uuid */ import ( "errors" "fmt" "path/filepath" "strings" "github.com/google/uuid" pb "go.wit.com/lib/protobuf/virtbuf" "go.wit.com/log" ) // will make sure the mac address is unique func ValidateUniqueMac(cluster *pb.Cluster, mac string) bool { for _, d := range cluster.Droplets { for _, n := range d.Networks { if n.Mac == mac { log.Info("duplicate MAC", n.Mac, "in droplet", d.Hostname) return false } } } return true } // records all the known paths. this should go in the protobuf func addClusterFilepath(cluster *pb.Cluster, dir string) *pb.Event { var found bool = false var e *pb.Event for _, d := range cluster.Dirs { if d == dir { // found dir found = true break } } if !found { if dir != "." { // make a new Add Event e = pb.NewAddEvent(nil, "Add Cluster Directory", dir) cluster.Dirs = append(cluster.Dirs, dir) } } return e } // returns the droplet using a filename func lookupFilename(cluster *pb.Cluster, filename string) *pb.Droplet { filebase := filepath.Base(filename) for _, d := range cluster.Droplets { for _, disk := range d.Disks { if filebase == disk.Filename { return d } } } return nil } func ValidateUniqueFilenames(cluster *pb.Cluster) bool { var ok bool = true var disks map[string]string disks = make(map[string]string) for _, d := range cluster.Droplets { for _, disk := range d.Disks { filename := disk.Filename addClusterFilepath(cluster, disk.Filepath) if _, ok := disks[filename]; ok { /* if argv.IgnDisk { log.Info("ignore dup disk", filename, disks[filename], d.Hostname) } else { } */ log.Info("file", filename, "on droplet", disks[filename]) log.Info("file", filename, "on droplet", d.Hostname) log.Info("duplicate disk names (--xml-ignore-disk to ignore)") ok = false } disks[filename] = d.Hostname } } if ok { log.Println("validated okay: no duplicate disk images") } return ok } func ValidateDiskFilenames(cluster *pb.Cluster) ([]*pb.Event, error) { var alle []*pb.Event for _, d := range cluster.Droplets { var found bool = false for _, disk := range d.Disks { filename := disk.Filename filebase := filepath.Base(filename) dir := filepath.Dir(filename) addClusterFilepath(cluster, dir) if disk.Filename != filebase { // update filename e := d.NewChangeEvent("Disk.Filename", disk.Filename, filebase) alle = append(alle, e) disk.Filename = filebase } // make sure the filename is the hostname + .qcow2 filetype := filepath.Ext(filebase) if filetype == ".img" { found = true continue } if filetype != ".qcow2" { log.Info("file type", filetype, "not supported for", filebase, "on", d.Hostname) return nil, errors.New("only supporting qcow2 images for now") } test := strings.TrimSuffix(filebase, filetype) if test == d.Hostname { found = true } if dir == "." { continue } if dir == "" { continue } if disk.Filepath != dir { // update filename e := d.NewChangeEvent("Disk.Filepath", disk.Filepath, dir) alle = append(alle, e) disk.Filepath = dir } } if !found { log.Info("droplet", d.Hostname, d.Disks) return nil, errors.New("droplet " + d.Hostname + " has nonstandard disk names") } } return alle, nil } func getNewMac() string { // mac address map to check for duplicates var macs map[string]string macs = make(map[string]string) for _, d := range me.cluster.Droplets { for _, n := range d.Networks { // log.Println("network:", n.Mac, d.Uuid, d.Hostname) if _, ok := macs[n.Mac]; ok { // UUID already exists log.Info("duplicate MAC", n.Mac, macs[n.Mac]) log.Info("duplicate MAC", n.Mac, d.Hostname) return "" } macs[n.Mac] = d.Hostname } } var i int = 9 var mac string for { mac = fmt.Sprintf("22:22:22:22:22:%02d", i) if _, ok := macs[mac]; ok { log.Info("MAC already defined", mac, macs[mac]) i += 1 continue } log.Info("using new MAC:", mac) return mac } return "" } // runs on startup. dies if there are duplicates // the config file must then be edited by hand func ValidateDroplets(cluster *pb.Cluster) (map[string]string, map[string]string, error) { // uuid map to check for duplicates var umap map[string]string umap = make(map[string]string) // mac address map to check for duplicates var macs map[string]string macs = make(map[string]string) for _, d := range cluster.Droplets { // Generate a new UUID if d.Uuid == "" { u := uuid.New() d.Uuid = u.String() } // seconds, ok := timeZone[tz]; ok { if _, ok := umap[d.Uuid]; ok { // UUID already exists log.Info("duplicate UUID", d.Uuid, umap[d.Uuid]) log.Info("duplicate UUID", d.Uuid, d.Hostname) return umap, macs, errors.New("duplicate UUID: " + d.Uuid) } umap[d.Uuid] = d.Hostname for _, n := range d.Networks { // log.Println("network:", n.Mac, d.Uuid, d.Hostname) if _, ok := macs[n.Mac]; ok { // UUID already exists log.Info("duplicate MAC", n.Mac, macs[n.Mac], umap[macs[n.Mac]]) log.Info("duplicate MAC", n.Mac, d.Hostname) return umap, macs, errors.New("duplicate MAC: " + n.Mac) } macs[n.Mac] = d.Uuid } } log.Println("validated okay: no duplicate MAC addr") log.Println("validated okay: no duplicate UUID") return umap, macs, nil } // checks a droplet right before a start event // verify ethernet mac address // verify uuid (but probably can ignore this since it's not used) // check qemu domain id // check spice and vnc ports // check filenames func ValidateDroplet(check *pb.Droplet) error { // check for duplicate uuid's for _, d := range me.cluster.Droplets { if check == d { continue } if d.Uuid == check.Uuid { // UUID already exists log.Info("duplicate UUID", d.Uuid, d.Hostname) log.Info("duplicate UUID", d.Uuid, check.Hostname) return errors.New("duplicate UUID: " + d.Uuid) } } // check for duplicate mac addresses for _, checkn := range check.Networks { for _, d := range me.cluster.Droplets { if check == d { continue } for _, n := range d.Networks { if checkn.Mac == n.Mac { // MAC already exists log.Info("duplicate MAC", n.Mac, d.Hostname) log.Info("duplicate MAC", n.Mac, check.Hostname) return errors.New("duplicate MAC: " + n.Mac) } } } } if err := setUniqueSpicePort(check); err != nil { return err } return nil } func setUniqueSpicePort(check *pb.Droplet) error { var ports map[int64]*pb.Droplet ports = make(map[int64]*pb.Droplet) // check spice ports // checkn.SpicePort = getUniqueSpicePort() for _, d := range me.cluster.Droplets { if d.SpicePort == 0 { continue } if dup, ok := ports[d.SpicePort]; ok { // dup := ports[d.SpicePort] log.Warn("duplicate ports", d.SpicePort, d.Hostname, d.CurrentState) if d.CurrentState != pb.DropletState_ON { // hack for now. should be safe to erase this d.SpicePort = 0 log.Warn("erasing port for non-ON droplet", d.SpicePort, d.Hostname, d.CurrentState) } log.Warn("duplicate ports", dup.SpicePort, dup.Hostname, dup.CurrentState) if dup.CurrentState != pb.DropletState_ON { // hack for now. should be safe to erase this dup.SpicePort = 0 log.Warn("erasing port for non-ON droplet", dup.SpicePort, dup.Hostname, dup.CurrentState) } // todo: fix this somewhow return errors.New("duplicate ports") } ports[d.SpicePort] = d } for p, d := range ports { log.Info("found spice port", p, "on", d.Hostname) } var start int64 start = 5910 for { if start == 6000 { // x11 might use this on dom0's running a desktop // maybe qemu uses it iternally start += 1 continue } if _, ok := ports[start]; ok { d := ports[start] log.Info("already using port", start, "on", d.Hostname) if d == check { log.Info("this is good because it's me!", check.Hostname, d.Hostname) return nil } start += 1 continue } // generate change port event log.Info("going to try port", start, "on", check.Hostname) e := check.NewChangeEvent("SpicePort", check.SpicePort, start) me.cluster.E.Events = append(me.cluster.E.Events, e) // set port to start check.SpicePort = start // write out config file if err := me.cluster.ConfigSave(); err != nil { log.Info("config save error inside here is bad", err) return err } return nil } // for loop never gets here return nil }