diff --git a/create.go b/create.go new file mode 100644 index 0000000..3b6de72 --- /dev/null +++ b/create.go @@ -0,0 +1,152 @@ +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "time" + + "github.com/google/uuid" + "go.wit.com/lib/gui/shell" + pb "go.wit.com/lib/protobuf/virtbuf" + "go.wit.com/log" +) + +// attempts to create a new virtual machine + +func create(w http.ResponseWriter, r *http.Request) error { + msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte + if err != nil { + fmt.Fprintln(w, "ReadAll() error =", err) + return err + } + + var d *pb.Droplet + d = new(pb.Droplet) + if err := d.UnmarshalJSON(msg); err != nil { + return err + } + + log.Info("Got msg:", string(msg)) + log.Info("hostname =", d.Hostname) + name := d.Hostname + tmpd := findDroplet(name) + if tmpd != nil { + result := "create error: Droplet " + name + " is already defined" + fmt.Fprintln(w, result) + return errors.New(result) + } + if d.Uuid == "" { + u := uuid.New() + d.Uuid = u.String() + } + return nil + if len(d.Networks) == 0 { + mac, err := getNewMac() + if err != nil { + return err + } + var newNet *pb.Network + newNet = new(pb.Network) + newNet.Mac = mac + d.Networks = append(d.Networks, newNet) + // d.AddDefaultNetwork(mac) + } + me.cluster.Droplets = append(me.cluster.Droplets, d) + + result, err := startDroplet(d) + if err != nil { + fmt.Fprintln(w, result) + fmt.Fprintln(w, "startDroplet(d) failed:", err) + return err + } + fmt.Fprintln(w, result) + fmt.Fprintln(w, "START=OK") + return nil +} + +// for now, because sometimes this should write to stdout and +// sometimes to http socket, it returns a string +func startDroplet(d *pb.Droplet) (string, error) { + var result string + name := d.Hostname + // validate the droplet + if err := ValidateDroplet(d); err != nil { + result = "ValidateDroplet() failed droplet " + d.Hostname + return result, err + } + + // is the droplet already on? + if d.CurrentState == pb.DropletState_ON { + result = "EVENT start droplet " + d.Hostname + " is already ON" + return result, errors.New(result) + } + + // how long has the cluster been stable? + // wait until it is stable. use this to throttle droplet starts + dur := time.Since(me.unstable) + result = fmt.Sprintln("should start droplet", name, "here. grid stable for:", shell.FormatDuration(dur)) + if dur < me.unstableTimeout { + tmp := shell.FormatDuration(me.unstableTimeout) + result += "grid is still too unstable (unstable timeout = " + tmp + ")" + return result, errors.New("grid is still unstable") + } + + // make the list of hypervisors that are active and can start new droplets + var pool []*HyperT + for _, h := range me.hypers { + // this droplet is set to use this and only this hypervisor + if d.ForceHypervisor == h.pb.Hostname { + ok, b := h.start(d) + if ok { + return result + b, nil + } + return result + b, errors.New("start " + name + " on hypervisor " + h.pb.Hostname) + } + + // skip hypervisors marked inactive + if h.pb.Active != true { + result += fmt.Sprintln("hypervisor is inactive:", name, "for", h.pb.Hostname, h.pb.Active) + continue + } + + // the config file says this droplet should run on this hypervisor + // attempt to start the droplet here. use this even if the hypervisor is inactive? + if d.PreferredHypervisor == h.pb.Hostname { + ok, b := h.start(d) + if ok { + return result + b, nil + } + return result + b, errors.New("start " + name + " on hypervisor " + h.pb.Hostname) + } + + result += fmt.Sprintln("hypervisor ready:", name, "for", h.pb.Hostname, h.pb.Active) + pool = append(pool, h) + } + + // left here as an example of how to actually do random numbers + // it's complete mathematical chaos. Randomness is simple when + // human interaction occurs -- which is exactly what happens most + // of the time. most random shit is bullshit. all you really need + // is exactly this to make sure the random functions work as they + // should. Probably, just use this everywhere in all cases. --jcarr + rand.Seed(time.Now().UnixNano()) + a := 0 + b := len(pool) + n := a + rand.Intn(b-a) + result += fmt.Sprintln("pool has", len(pool), "members", "rand =", n) + h := pool[n] + + // update/resend the search directories to the hypervisor + result += fmt.Sprintln("h.sendDirs() HERE") + result += fmt.Sprintln("h.sendDirs() HERE") + h.sendDirs() + + ok, output := h.start(d) + if ok { + return result + output, nil + } + return result + output, errors.New("start " + name + " on hypervisor " + h.pb.Hostname) +} diff --git a/dump.go b/dump.go index e8522d5..6d46c30 100644 --- a/dump.go +++ b/dump.go @@ -31,14 +31,14 @@ func dumpCluster(w http.ResponseWriter) { // list running droplets and droplets that should be running func dumpDroplets(w http.ResponseWriter, full bool) { - for i, d := range me.cluster.Droplets { + for _, d := range me.cluster.Droplets { var macs []string for _, n := range d.Networks { macs = append(macs, n.Mac) } // this line in golang could replace 80 lines of COBOL - header := fmt.Sprintf("%3d %20s %3s %8s", i, strings.Join(macs, " "), d.CurrentState, d.CurrentHypervisor) + header := fmt.Sprintf("%-3s %20s %-8s", d.CurrentState, strings.Join(macs, " "), d.CurrentHypervisor) var filenames string for _, disk := range d.Disks { diff --git a/http.go b/http.go index 8bb74b9..a6092a2 100644 --- a/http.go +++ b/http.go @@ -57,6 +57,11 @@ func okHandler(w http.ResponseWriter, r *http.Request) { return } + if route == "/create" { + create(w, r) + return + } + // toggle poll logging if route == "/poll" { if POLL.Get() { diff --git a/validate.go b/validate.go index a95ce29..a4caa52 100644 --- a/validate.go +++ b/validate.go @@ -133,6 +133,35 @@ func ValidateDiskFilenames(cluster *pb.Cluster) []*pb.Event { return alle } +func getNewMac() (string, error) { + // 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 "", errors.New("duplicate MAC: " + d.Hostname + " and " + macs[n.Mac]) + } + macs[n.Mac] = d.Hostname + } + } + + var mac string = "22:22:22:22:22:09" + for { + if _, ok := macs[mac]; ok { + log.Info("MAC already defined", mac, macs[mac]) + continue + } + return mac, nil + } + return mac, nil +} + // 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) {