Compare commits

..

No commits in common. "master" and "v0.1.1" have entirely different histories.

22 changed files with 741 additions and 821 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
go.* go.*
*.pb.go *.pb.go
*.swp
example/example

View File

@ -1,3 +0,0 @@
droplet.proto
event.proto
hypervisor.proto

View File

@ -1,17 +1,23 @@
# You must use the current google protoc-gen-go # You must use the current protoc-gen-go
# #
# go-clone --go-src google.golang.org/protobuf
# cd ~/go/src/google.golang.org/protobuf/cmd/protoc-gen-go # cd ~/go/src/google.golang.org/protobuf/cmd/protoc-gen-go
# go install # go install
all: proto goimports vet
vet: all: droplet.pb.go hypervisor.pb.go cluster.pb.go event.pb.go experiments.pb.go
@GO111MODULE=off go vet make -C example
@echo this go library package builds okay
vet: lint
GO111MODULE=off go vet
lint:
-buf lint droplet.proto
# autofixes your import headers in your golang files # autofixes your import headers in your golang files
goimports: goimports:
goimports -w *.go goimports -w *.go
make -C example goimports
redomod: redomod:
rm -f go.* rm -f go.*
@ -21,20 +27,40 @@ redomod:
clean: clean:
rm -f *.pb.go rm -f *.pb.go
-rm -f go.* -rm -f go.*
make -C example clean
proto:droplet.pb.go hypervisor.pb.go event.pb.go cluster.pb.go
droplet.pb.go: droplet.proto droplet.pb.go: droplet.proto
autogenpb --proto droplet.proto # protoc --go_out=. droplet.proto
# This is switched over to use the new protoc-gen-go from google.golang.org/protobuf/cmd/protoc-gen-go
# the debian one (2024/10/21) seems to be the older/original one from github.com/golang/protobuf/protoc-gen-go
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mdroplet.proto=go.wit.com/lib/protobuf/virtbuf \
droplet.proto
hypervisor.pb.go: hypervisor.proto hypervisor.pb.go: hypervisor.proto
autogenpb --proto hypervisor.proto cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mhypervisor.proto=go.wit.com/lib/protobuf/virtbuf \
hypervisor.proto
event.pb.go: event.proto event.pb.go: event.proto
autogenpb --proto event.proto cd ~/go/src && protoc --go_out=. \
--proto_path=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mevent.proto=go.wit.com/lib/protobuf/virtbuf \
event.proto
experiments.pb.go: experiments.proto
cd ~/go/src && protoc --go_out=. \
--proto_path=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mexperiments.proto=go.wit.com/lib/protobuf/virtbuf \
experiments.proto
cluster.pb.go: cluster.proto cluster.pb.go: cluster.proto
autogenpb --proto cluster.proto cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mdroplet.proto=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mcluster.proto=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mhypervisor.proto=go.wit.com/lib/protobuf/virtbuf \
--go_opt=Mevent.proto=go.wit.com/lib/protobuf/virtbuf \
cluster.proto
deps: deps:
apt install golang-goprotobuf-dev apt install golang-goprotobuf-dev

View File

@ -1,6 +1,13 @@
This go library handles the protobuf files They are what to use for starting droplets with virsh,
and various functions for virtigo. but for making a cluster or "home cloud" with virtigo
You must build the protobuf files using autogenpb When possible & sensible, use the same variable names as libvirt
virtigo needs to coordinate all the virtual machine
information centrally. The information in the libvirt xml
files are imported into these protobuf definitions and
passed around as protobufs
virtigo recreates the libvirt xml files and calls
'virsh create'
go install go.wit.com/apps/autogenpb@latest

211
add.go
View File

@ -1,211 +0,0 @@
package virtpb
import (
"fmt"
"time"
"github.com/google/uuid"
"go.wit.com/log"
)
/*
func (c *OldCluster) InitDroplet(hostname string) (*Droplet, error) {
var d *Droplet
d = new(Droplet)
d.Current = new(Current)
d = c.FindDropletByName(hostname)
if d != nil {
return d, errors.New("duplicate hostname: " + hostname)
}
d.Hostname = hostname
// d.Uuid = uuid.New() // not appropriate here
// set some defaults
d.StartState = DropletState_OFF
d.Current.State = DropletState_UNKNOWN
c.appendDroplet(d)
return d, nil
}
func (c *Cluster) appendDroplet(d *Droplet) {
c.Lock()
defer c.Unlock()
c.d.Droplets = append(c.d.Droplets, d)
}
*/
// can the json protobuf output use a string and have a type handler
// to convert it back to int64?
func SetGB(gb int) int64 {
return int64(gb * 1024 * 1024 * 1024)
}
func SetMB(mb int) int64 {
return int64(mb * 1024 * 1024)
}
func (x *Hypervisor) SetMemoryGB(gb int) {
x.Memory = int64(gb * 1024 * 1024 * 1024)
}
func (c *OldCluster) FindDropletByName(name string) *Droplet {
loop := c.d.All() // get the list of droplets
for loop.Scan() {
d := loop.Next()
if d.Hostname == name {
return d
}
}
return nil
}
func (c *OldCluster) FindDropletByUuid(id string) *Droplet {
/*
log.Info("START FIND", id)
loop := c.d.All() // get the list of droplets
for loop.Scan() {
d := loop.Next()
log.Info("droplet:", d.Hostname, d.Uuid)
}
log.Info("END FIND", id)
*/
return c.d.FindByUuid(id)
}
func (c *OldCluster) FindHypervisorByName(name string) *Hypervisor {
for _, h := range c.H.Hypervisors {
if h.Hostname == name {
return h
}
}
return nil
}
func (c *OldCluster) AddHypervisor(hostname string, cpus int, mem int) *Hypervisor {
h := c.FindHypervisorByName(hostname)
if h != nil {
return h
}
// Generate a new UUID
id := uuid.New()
h = &Hypervisor{
Uuid: id.String(),
Hostname: hostname,
Cpus: int64(cpus),
Comment: "this is a fake hypervisor",
}
if cpus < 0 {
h.Cpus = 1
}
h.SetMemoryGB(mem * 32)
c.H.Hypervisors = append(c.H.Hypervisors, h)
return h
}
func (c *OldCluster) AddEvent(e *Event) {
c.e.Events = append(c.e.Events, e)
}
// creates a new droplet with default values
func NewDefaultDroplet(hostname string) *Droplet {
id := uuid.New() // Generate a new UUID
d := &Droplet{
Uuid: id.String(),
Hostname: hostname,
Cpus: int64(2),
}
d.Memory = SetGB(2)
d.StartState = DropletState_OFF
return d
}
func (c *OldCluster) AddDropletSimple(uuid string, hostname string, cpus int, mem int) *Droplet {
d := c.FindDropletByName(hostname)
if d != nil {
return d
}
d = &Droplet{
Uuid: uuid,
Hostname: hostname,
Cpus: int64(cpus),
}
if cpus < 0 {
d.Cpus = 1
}
d.Memory = SetGB(mem * 32)
d.StartState = DropletState_OFF
c.AddDroplet(d)
return d
}
// This isn't for the marketing department
func (c *OldCluster) AddDropletLocal(name string, hypername string) *Droplet {
d := &Droplet{
Hostname: name,
}
d.LocalOnly = "yes on: " + hypername
// by default, on locally imported domains, set the preferred hypervisor!
d.PreferredHypervisor = hypername
d.Current = new(Current)
d.Current.Hypervisor = hypername
d.StartState = DropletState_OFF
d.Current.State = DropletState_OFF
c.AddDroplet(d)
return d
}
func (c *OldCluster) BlankFields() {
loop := c.d.All() // get the list of droplets
for loop.Scan() {
d := loop.Next()
d.Current = nil
}
}
func (epb *Events) AppendEvent(e *Event) {
epb.Events = append(epb.Events, e)
}
func (c *OldCluster) ClusterStable() (bool, string) {
last := time.Since(c.Unstable.AsTime())
if last > c.UnstableTimeout.AsDuration() {
// the cluster has not been stable for 133 seconds
log.Warn("clusterReady() is stable for ", FormatDuration(c.UnstableTimeout.AsDuration()), " secs")
return true, fmt.Sprintln("clusterReady() is stable ", FormatDuration(c.UnstableTimeout.AsDuration()), " secs")
}
log.Warn("clusterReady() is unstable for", FormatDuration(last))
return false, "clusterReady() is unstable for " + FormatDuration(last)
}
// check the cluster and droplet to make sure it's ready to start
func (c *OldCluster) DropletReady(d *Droplet) (bool, string) {
if c == nil {
return false, "cluster == nil"
}
if d == nil {
return false, "droplet == nil"
}
if d.Current == nil {
return false, "droplet.Current == nil"
}
// can't start already started droplet
if d.Current.State == DropletState_ON {
return false, "EVENT start droplet is already ON"
}
if d.Current.State != DropletState_OFF {
return false, "EVENT start droplet is not OFF state = " + string(d.Current.State)
}
if d.Current.StartAttempts > 2 {
// reason := "EVENT start droplet has already been started " + d.starts + " times"
return false, fmt.Sprintln("EVENT start droplet has already been started ", d.Current.StartAttempts, " times")
}
return true, ""
}

View File

@ -1,4 +1,4 @@
package virtpb package virtbuf
// thank chatgpt for this because why. why write this if you can have it // thank chatgpt for this because why. why write this if you can have it
// kick this out in 30 seconds // kick this out in 30 seconds
@ -10,20 +10,8 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"time"
) )
func backupConfig() error {
// make a new dir to backup the files
now := time.Now()
// timestamp := now.Format("2022.07.18.190545") // 50yr shout out to K&R
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
srcDir := filepath.Join(os.Getenv("VIRTIGO_HOME"))
destDir := filepath.Join(os.Getenv("VIRTIGO_HOME"), timestamp)
return backupFiles(srcDir, destDir)
}
func backupFiles(srcDir string, destDir string) error { func backupFiles(srcDir string, destDir string) error {
// Create the destination directory // Create the destination directory
err := os.MkdirAll(destDir, os.ModePerm) err := os.MkdirAll(destDir, os.ModePerm)

View File

@ -1,4 +1,4 @@
package virtpb package virtbuf
// thank chatgpt for this because why. why write this if you can have it // thank chatgpt for this because why. why write this if you can have it
// kick this out in 30 seconds // kick this out in 30 seconds
@ -14,7 +14,7 @@ func backupDir(srcDir string, destDir string) error {
// Create the destination directory // Create the destination directory
err := os.MkdirAll(destDir, os.ModePerm) err := os.MkdirAll(destDir, os.ModePerm)
if err != nil { if err != nil {
log.Printf("Failed to create directory: %v\n", err) log.Println("Failed to create directory: %v", err)
return err return err
} }
@ -41,7 +41,7 @@ func backupDir(srcDir string, destDir string) error {
}) })
if err != nil { if err != nil {
log.Printf("Failed to copy files: %v\n", err) log.Println("Failed to copy files: %v", err)
return err return err
} }
return nil return nil

View File

@ -1,4 +1,4 @@
package virtpb package virtbuf
import ( import (
// "reflect" // "reflect"
@ -34,6 +34,7 @@ func convertToAnypb(x any) *anypb.Any {
return a return a
default: default:
log.Error(errors.New("convertToAnypb() unknown type"), "v =", v, "x =", x) log.Error(errors.New("convertToAnypb() unknown type"), "v =", v, "x =", x)
return nil
} }
return nil return nil
} }
@ -48,14 +49,6 @@ func convertToString(x any) string {
return fmt.Sprintf("%d", x.(int)) return fmt.Sprintf("%d", x.(int))
case uint: case uint:
return fmt.Sprintf("%d", x.(uint)) return fmt.Sprintf("%d", x.(uint))
case *DropletState:
var s *DropletState
s = x.(*DropletState)
return s.String()
case DropletState:
var s DropletState
s = x.(DropletState)
return s.String()
case bool: case bool:
if x.(bool) { if x.(bool) {
return "true" return "true"
@ -64,6 +57,7 @@ func convertToString(x any) string {
default: default:
log.Info("convertToSTring() unknown type", v) log.Info("convertToSTring() unknown type", v)
log.Error(errors.New("convertToSTring() unknown type"), "v =", v, "x =", x) log.Error(errors.New("convertToSTring() unknown type"), "v =", v, "x =", x)
return ""
} }
return "" return ""
} }
@ -73,7 +67,7 @@ func (d *Droplet) NewChangeEvent(fname string, origval any, newval any) *Event {
var e *Event var e *Event
e = new(Event) e = new(Event)
e.DropletName = d.Hostname e.Droplet = d.Hostname
e.OrigVal = convertToString(origval) e.OrigVal = convertToString(origval)
e.NewVal = convertToString(newval) e.NewVal = convertToString(newval)
e.FieldName = fname e.FieldName = fname
@ -99,12 +93,14 @@ func NewAddEvent(a any, fname string, newval any) *Event {
case *Droplet: case *Droplet:
var d *Droplet var d *Droplet
d = a.(*Droplet) d = a.(*Droplet)
e.DropletName = d.Hostname e.Droplet = d.Hostname
case *Cluster:
e.Droplet = "Cluster"
case nil: case nil:
e.DropletName = "<nil>" e.Droplet = "<nil>"
default: default:
log.Info("newAddEvent() unknown type", v) log.Info("newAddEvent() unknown type", v)
e.DropletName = "on something somewhere" e.Droplet = "on something somewhere"
} }
e.NewVal = convertToString(newval) e.NewVal = convertToString(newval)
@ -134,63 +130,35 @@ func (d *Droplet) SetCpus(b int64) {
log.Info("Set the number of cpus for the droplet", b) log.Info("Set the number of cpus for the droplet", b)
} }
// update the droplet memory
func (d *Droplet) SetState(newState DropletState) {
if d.Current == nil {
d.Current = new(Current)
}
if d.Current.State == newState {
// nothing has changed
return
}
switch newState {
case DropletState_ON:
d.Current.OnSince = timestamppb.New(time.Now())
d.Current.OffSince = nil
case DropletState_OFF:
d.Current.OffSince = timestamppb.New(time.Now())
d.Current.OnSince = nil
default:
// zero on OnSince to indicate something hickup'd?
// not sure if this should be done here. probably trust qemu dom0 instead
// but I can't do that right now so for now this will work
d.Current.OnSince = timestamppb.New(time.Now())
d.Current.OffSince = timestamppb.New(time.Now())
}
d.Current.State = newState
d.NewChangeEvent("STATE", d.Current.State, newState)
log.Info("Droplet", d.Hostname, "changed state from", d.Current.State, "to", newState)
}
// records an event that the droplet changed state (aka turned on, turned off, etc) // records an event that the droplet changed state (aka turned on, turned off, etc)
func (c *OldCluster) ChangeDropletState(d *Droplet, newState DropletState) error { func (c *Cluster) ChangeDropletState(d *Droplet, newState DropletState) error {
if c == nil { if c == nil {
return errors.New("cluster is nil") return errors.New("cluster is nil")
} }
if d == nil { if d == nil {
return errors.New("droplet is nil") return errors.New("droplet is nil")
} }
if d.Current.State == newState { if d.CurrentState == newState {
// droplet status didn't change // droplet status didn't change
return nil return nil
} }
var e *Event var e *Event
e = new(Event) e = new(Event)
e.DropletName = d.Hostname e.Droplet = d.Hostname
e.OrigVal = convertToString(d.Current.State) e.OrigVal = convertToString(d.CurrentState)
e.NewVal = convertToString(newState) e.NewVal = convertToString(newState)
e.FieldName = "status" e.FieldName = "status"
now := time.Now() now := time.Now()
e.Start = timestamppb.New(now) e.Start = timestamppb.New(now)
c.e.Events = append(c.e.Events, e) c.Events = append(c.Events, e)
return nil return nil
} }
// records an event that the droplet migrated to another hypervisor // records an event that the droplet migrated to another hypervisor
func (c *OldCluster) DropletMoved(d *Droplet, newh *Hypervisor) error { func (c *Cluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
if c == nil { if c == nil {
return errors.New("cluster is nil") return errors.New("cluster is nil")
} }
@ -200,7 +168,7 @@ func (c *OldCluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
if newh == nil { if newh == nil {
return errors.New("hypervisor is nil") return errors.New("hypervisor is nil")
} }
if d.Current.Hypervisor == newh.Hostname { if d.CurrentHypervisor == newh.Hostname {
// droplet didn't move // droplet didn't move
return nil return nil
} }
@ -209,17 +177,17 @@ func (c *OldCluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
var e *Event var e *Event
e = new(Event) e = new(Event)
e.DropletName = d.Hostname e.Droplet = d.Hostname
e.OrigVal = d.Current.Hypervisor e.OrigVal = d.CurrentHypervisor
e.NewVal = newh.Hostname e.NewVal = newh.Hostname
e.FieldName = "droplet migrate" e.FieldName = "droplet migrate"
now := time.Now() now := time.Now()
e.Start = timestamppb.New(now) e.Start = timestamppb.New(now)
c.e.Events = append(c.e.Events, e) c.Events = append(c.Events, e)
// update the droplet record // update the droplet record
d.Current.Hypervisor = newh.Hostname d.CurrentHypervisor = newh.Hostname
return nil return nil
} }

View File

@ -1,23 +1,14 @@
syntax = "proto3"; syntax = "proto3";
package virtpb; package virtbuf;
import "google/protobuf/timestamp.proto";
import "droplet.proto"; import "droplet.proto";
import "hypervisor.proto"; import "hypervisor.proto";
import "event.proto"; import "event.proto";
message Cluster { // `autogenpb:marshal` message Cluster {
string uuid = 1; // `autogenpb:unique` int64 id = 1;
string name = 2; repeated string dirs = 2;
repeated string URL = 3; repeated Droplet droplets = 3;
google.protobuf.Timestamp ctime = 4; // when the cluster was created repeated Hypervisor hypervisors = 4;
Droplets droplets = 5; repeated Event events = 5;
Hypervisors hypervisors = 6;
Events events = 7;
}
message Clusters { // `autogenpb:marshal`
string uuid = 1; // `autogenpb:uuid:57ddd763-75f6-4003-bf0e-8dd0f8a44044`
string version = 2; // `autogenpb:version:v0.0.1`
repeated Cluster clusters = 3;
} }

396
config.go
View File

@ -1,4 +1,4 @@
package virtpb package virtbuf
// functions to import and export the protobuf // functions to import and export the protobuf
// data to and from config files // data to and from config files
@ -8,139 +8,187 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"time"
"go.wit.com/log"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
) )
func (c *Cluster) ConfigSave() error {
name := c.Name
if name == "" {
name = c.Uuid
}
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), name+".pb")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return err
}
log.Info("ConfigSave()", fullname)
data, err := c.Marshal()
if err != nil {
fmt.Println("cluster Marshal() err:", err)
return err
}
fmt.Fprintln(cfgfile, data)
return nil
}
// writes out the cluster information it seperate files // writes out the cluster information it seperate files
// to make it humanly possible to hand edit things as needed // to make it humanly possible to hand edit things as needed
func (c *OldCluster) ConfigSave() error { func (c *Cluster) ConfigSave() error {
// try to backup the current cluster config files // try to backup the current cluster config files
if err := backupConfig(); err != nil { if err := backupConfigFiles(); err != nil {
return err return err
} }
// make a new droplets struct var d *Droplets
var dcopy *Droplets d = new(Droplets)
dcopy = new(Droplets) d.Droplets = c.Droplets
loop := c.d.All() // get the list of droplets if err := ConfigWriteJSON(d, "droplets.json"); err != nil {
for loop.Scan() { fmt.Println("droplets.json write failed")
d := loop.Next() return err
dcopy.Droplets = append(dcopy.Droplets, d) }
} if err := ConfigWriteTEXT(d, "droplets.text"); err != nil {
// delete all the Current data so it's not put in the config file
for _, drop := range dcopy.Droplets {
drop.Current = nil
}
if err := ConfigWriteTEXT(dcopy, "droplets.text"); err != nil {
fmt.Println("droplets.json write failed") fmt.Println("droplets.json write failed")
return err return err
} }
c.configWriteDroplets()
if err := ConfigWriteTEXT(c.H, "hypervisors.text"); err != nil { var h *Hypervisors
h = new(Hypervisors)
h.Hypervisors = c.Hypervisors
if err := ConfigWriteJSON(h, "hypervisors.json"); err != nil {
fmt.Println("hypervisors.json write failed")
return err
}
if err := ConfigWriteTEXT(h, "hypervisors.text"); err != nil {
fmt.Println("hypervisors.json write failed") fmt.Println("hypervisors.json write failed")
return err return err
} }
if err := ConfigWriteJSON(c.e, "events.json"); err != nil { var e *Events
e = new(Events)
e.Events = c.Events
if err := ConfigWriteJSON(e, "events.json"); err != nil {
fmt.Println("events.json write failed") fmt.Println("events.json write failed")
return err return err
} }
if err := ConfigWriteTEXT(c.e, "events.text"); err != nil { if err := ConfigWriteTEXT(e, "events.text"); err != nil {
fmt.Println("events.json write failed") fmt.Println("events.json write failed")
return err return err
} }
if err := ConfigWriteTEXT(c, "cluster.full.text"); err != nil {
fmt.Println("Cluster.json write failed")
return err
}
var newc Cluster
newc.Dirs = c.Dirs
newc.Droplets = nil
newc.Hypervisors = nil
newc.Events = nil
if err := ConfigWriteTEXT(&newc, "cluster.text"); err != nil {
fmt.Println("cluster.json write failed")
return err
}
return nil return nil
} }
func (c *OldCluster) ConfigLoad() error { func backupConfigFiles() error {
// make a new dir to backup the files
now := time.Now()
// timestamp := now.Format("2022.07.18.190545") // 50yr shout out to K&R
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
srcDir := filepath.Join(os.Getenv("VIRTIGO_HOME"))
destDir := filepath.Join(os.Getenv("VIRTIGO_HOME"), timestamp)
return backupFiles(srcDir, destDir)
}
func (c *Cluster) ConfigLoadOld2() error {
if c == nil { if c == nil {
return errors.New("It's not safe to run ConfigLoad() on a nil cluster") return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
} }
if data, err := loadFile("droplets.text"); err == nil { // erase or zero fields that shouldn't ever be written to the config file
if err = prototext.Unmarshal(data, c.d); err != nil { c.BlankFields()
fmt.Println("broken droplets.text config file")
return err // load the cluster config file
if data, err := loadFile("virtigo.json"); err == nil {
if err = protojson.Unmarshal(data, c); err != nil {
fmt.Println("broken cluster.json config file")
fmt.Println(err)
return errors.New("cluster.json file is broken")
} }
} else { } else {
return err return err
} }
if data, err := loadFile("hypervisors.text"); err == nil { var e *Events
if err = prototext.Unmarshal(data, c.H); err != nil { e = new(Events)
fmt.Println("broken hypervisors.text config file") // load the events config file
if data, err := loadFile("events.json"); err == nil {
if err = protojson.Unmarshal(data, e); err != nil {
fmt.Println("broken events.json config file")
return err return err
} }
} else { } else {
log.Warn("ERROR HERE IN Hypervisors")
return err return err
} }
// copy them over. is this needed? does the memory free otherwise?
if c.e == nil { for _, a := range e.Events {
// this seems to panic on nil. something is wrong about doing this c.Events = append(c.Events, a)
// does it not stay allocated after this function ends?
c.e = new(Events)
}
if err := c.e.loadEvents(); err != nil {
// ignore events.pb since these should be sent elsewhere
log.Warn("Events failed to load, ignoring:", err)
return nil
} }
return nil return nil
} }
func (e *Events) loadEvents() error { func (c *Cluster) ConfigLoad() error {
var data []byte if c == nil {
var err error return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
}
// load the events config file // load the cluster config file
if data, err = loadFile("events.json"); err != nil { if data, err := loadFile("cluster.text"); err == nil {
fmt.Println("broken events.json config file") if err = prototext.Unmarshal(data, c); err != nil {
fmt.Println("broken cluster.text config file")
fmt.Println(err)
return errors.New("cluster.text file is broken")
}
} else {
return err return err
} }
err = protojson.Unmarshal(data, e) var d *Droplets
if err != nil { d = new(Droplets)
fmt.Println("broken events.json config file") // load the droplet config file
// json load failed. try loading prototext if data, err := loadFile("droplets.json"); err == nil {
if data, err = loadFile("events.text"); err != nil { if err = protojson.Unmarshal(data, d); err != nil {
fmt.Println("broken events.text config file") fmt.Println("broken droplets.json config file")
fmt.Println(err) return err
return errors.New("events.text file is broken")
} }
if err = prototext.Unmarshal(data, e); err != nil { } else {
fmt.Println("broken events.text config file") return err
fmt.Println(err) }
return errors.New("events.text file is broken") // copy them over. is this needed? does the memory free otherwise?
// also set initial values
for _, drop := range d.Droplets {
c.Droplets = append(c.Droplets, drop)
}
var h *Hypervisors
h = new(Hypervisors)
// load the hypervisors config file
if data, err := loadFile("hypervisors.json"); err == nil {
if err = protojson.Unmarshal(data, h); err != nil {
fmt.Println("broken hypervisors.json config file")
return err
} }
} else {
fmt.Println("ERROR HERE IN Hypervisors")
return err
}
// copy them over. is this needed? does the memory free otherwise?
for _, a := range h.Hypervisors {
c.Hypervisors = append(c.Hypervisors, a)
}
var e *Events
e = new(Events)
// load the events config file
if data, err := loadFile("events.json"); err == nil {
if err = protojson.Unmarshal(data, e); err != nil {
fmt.Println("broken events.json config file")
return err
}
} else {
return err
}
// copy them over. is this needed? does the memory free otherwise?
for _, a := range e.Events {
c.Events = append(c.Events, a)
} }
return nil return nil
} }
@ -160,7 +208,7 @@ func loadFile(filename string) ([]byte, error) {
func ConfigWriteJSON(a any, filename string) error { func ConfigWriteJSON(a any, filename string) error {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), filename) fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), filename)
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close() defer cfgfile.Close()
if err != nil { if err != nil {
fmt.Println("open config file :", err) fmt.Println("open config file :", err)
@ -175,26 +223,9 @@ func ConfigWriteJSON(a any, filename string) error {
return nil return nil
} }
func (c *OldCluster) configWriteDroplets() error {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "droplets.new.text")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return err
}
loop := c.d.All() // get the list of droplets
for loop.Scan() {
d := loop.Next()
text := prototext.Format(d)
fmt.Fprintln(cfgfile, text)
}
return nil
}
func ConfigWriteTEXT(a any, filename string) error { func ConfigWriteTEXT(a any, filename string) error {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), filename) fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), filename)
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close() defer cfgfile.Close()
if err != nil { if err != nil {
fmt.Println("open config file :", err) fmt.Println("open config file :", err)
@ -209,18 +240,165 @@ func ConfigWriteTEXT(a any, filename string) error {
return nil return nil
} }
func (c *Clusters) ConfigLoad() error { /*
if c == nil { func WriteConfig(d *Droplets, h *Hypervisors, e *Events) bool {
return errors.New("It's not safe to run ConfigLoad() on a nil cluster") if !d.WriteConfigJSON() {
return false
}
if !d.WriteConfigTEXT() {
return false
} }
if data, err := loadFile("cluster.text"); err == nil { if err := e.WriteConfigJSON(); err != nil {
if err = prototext.Unmarshal(data, c); err != nil { return false
fmt.Println("broken cluster.textconfig file") }
return err if err := e.WriteConfigTEXT(); err != nil {
} return false
} else { }
return true
}
// read in events.json
func ReadEventsConfig() (*Events, error) {
e := new(Events)
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "events.json")
data, err := os.ReadFile(fullname)
if err != nil {
// log.Info("open config file :", err)
return nil, err
}
err = e.UnmarshalJSON(data)
if err != nil {
// log.Info("read json failed", err)
return nil, err
}
return e, nil
}
// export as json
func (e *Events) WriteConfigJSON() error {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "events.json")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return err return err
} }
text := e.FormatJSON()
fmt.Fprintln(cfgfile, text)
fmt.Println("Write:", fullname, "OK")
return nil return nil
} }
// export as prototext
func (e *Events) WriteConfigTEXT() error {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "events.text")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return err
}
text := e.FormatTEXT()
fmt.Fprintln(cfgfile, text)
fmt.Println("Write:", fullname, "OK")
return nil
}
// export as json
func (d *Droplets) WriteConfigJSON() bool {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "droplets.json")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return false
}
text := d.FormatJSON()
fmt.Fprintln(cfgfile, text)
fmt.Println("Write:", fullname, "OK")
return true
}
// export as prototext
func (d *Droplets) WriteConfigTEXT() bool {
fullname := filepath.Join(os.Getenv("VIRTIGO_HOME"), "droplets.text")
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close()
if err != nil {
fmt.Println("open config file :", err)
return false
}
text := d.FormatTEXT()
fmt.Fprintln(cfgfile, text)
fmt.Println("Write:", fullname, "OK")
return true
}
*/
// human readable JSON
func (c *Cluster) FormatJSON() string {
return protojson.Format(c)
}
func (d *Droplets) FormatJSON() string {
return protojson.Format(d)
}
func (d *Droplet) FormatJSON() string {
return protojson.Format(d)
}
func (e *Events) FormatJSON() string {
return protojson.Format(e)
}
func (h *Hypervisors) FormatJSON() string {
return protojson.Format(h)
}
// apparently this isn't supposed to be used?
// https://protobuf.dev/reference/go/faq/#unstable-text
// this is a shame because this is much nicer output than JSON Format()
func (c *Cluster) FormatTEXT() string {
return prototext.Format(c)
}
func (d *Droplets) FormatTEXT() string {
return prototext.Format(d)
}
func (e *Events) FormatTEXT() string {
return prototext.Format(e)
}
// marshal
func (c *Cluster) MarshalJSON() ([]byte, error) {
return protojson.Marshal(c)
}
func (d *Droplets) MarshalJSON() ([]byte, error) {
return protojson.Marshal(d)
}
func (e *Events) MarshalJSON() ([]byte, error) {
return protojson.Marshal(e)
}
// unmarshal
func (c *Cluster) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, c)
}
func (d *Droplets) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, d)
}
func (d *Droplet) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, d)
}
func (e *Events) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, e)
}

View File

@ -1,82 +1,54 @@
syntax = "proto3"; syntax = "proto3";
package virtpb; package virtbuf;
import "google/protobuf/duration.proto"; // Import the well-known type for Timestamp import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
// global settings for autogenpb `autogenpb:mutex` message Droplets {
string uuid = 1; // I guess why not just have this on each file
message Droplets { // `autogenpb:marshal` `autogenpb:gui` string version = 2; // maybe can be used for protobuf schema change violations
string uuid = 1; // `autogenpb:uuid:d5d492e2-38d4-476b-86f3-f5abf01f9d6d` repeated Droplet droplets = 3;
string version = 2; // `autogenpb:version:v0.0.1`
repeated Droplet droplets = 3;
} }
message Droplet { // `autogenpb:marshal` message Droplet {
string uuid = 1; // `autogenpb:unique` // should be unique across the cluster string uuid = 1; // should be unique across the cluster
string hostname = 2; // `autogenpb:unique` // should be unique and work in DNS string hostname = 2; // should be unique and work in DNS
int64 cpus = 3; // what's the point of int64 vs int32 int64 cpus = 3; // what's the point of int64 vs int32
int64 memory = 4; // in bytes int64 memory = 4; // in bytes
Current current = 5; // what the state and values of the droplet is DropletState start_state = 5; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off')
DropletState startState = 6; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off') string notes = 6; // maybe useful for something
string qemuMachine = 7; // the qemu machine type to use "pc-q35-9.0" string preferred_hypervisor = 7; // the hypervisor to prefer to run the droplet on
int64 spicePort = 8; // preferred port to use for spice string qemu_arch = 8; // what arch. example: "x86_64" or "riscv64"
string qemu_cpu = 9; // qemu-system -cpu help
string qemu_machine = 10; // qemu-system -machine help
int64 spice_port = 11; // preferred port to use for spice
string preferredHypervisor = 9; // the hypervisor to prefer to run the droplet on repeated Network networks = 12; // really just mac addresses. should be unique across cluster
string forceHypervisor = 10; // use this hypervisor and this hypervisor only repeated Disk disks = 13; // disks to attach
string preferredArch = 11; // the cpu arch to use "x86_64" (should really get this from the disk?)
repeated Network networks = 12; // really just mac addresses. should be unique across cluster
repeated Disk disks = 13; // disks to attach
string localOnly = 14; // this is only defined locally on the hypervisor DropletState state = 14; // if the droplet is on, off, etc
string customXml = 15; // if needed, string image_url = 15; // url to the image
Archive archive = 16; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off') DropletState current_state = 16; // used to track the current state before taking any action
int64 starts = 17; // how many times a start has been attempted
google.protobuf.Timestamp unstable = 39; // the last time we heard anything from this droplet string current_hypervisor = 18; // the current hypervisor the droplet is running on
google.protobuf.Duration unstableTimeout = 40; // the last time we heard anything from this droplet google.protobuf.Timestamp last_poll = 19; // the last time we heard anything from this droplet
} string force_hypervisor = 20; // use this hypervisor and this hypervisor only
// volatile data. the current settings and values of things.
// These are passed around while the cluster to monitor and control the systems
// but they are not saved to the config file
message Current {
DropletState state = 1; // used to track the current state before taking any action
string hypervisor = 2; // the current hypervisor the droplet is running on
int64 startAttempts = 3; // how many times a start has been attempted
string fullXml = 4; // the full libvirt xml to import
google.protobuf.Timestamp lastPoll = 5; // the last time we heard anything from this droplet
string imageUrl = 6; // url to the image
google.protobuf.Timestamp offSince = 7; // when the droplet was turned off
google.protobuf.Timestamp onSince = 8; // when the droplet was turned on
}
message Archive {
DropletArchive reason = 1; // why the droplet was archived
google.protobuf.Timestamp when = 2; // when it was archived
} }
enum DropletState { enum DropletState {
ON = 0; ON = 0;
OFF = 1; OFF = 1;
UNKNOWN = 2; // qemu says 'Shutdown' UNKNOWN = 2;
PAUSED = 3; MIGRATING = 3;
CRASHED = 4;
INMIGRATE = 5;
}
enum DropletArchive {
DUP = 0;
USER = 1;
} }
message Network { message Network {
string mac = 1; string mac = 1;
string name = 2; string name = 2;
} }
message Disk { message Disk {
string filename = 1; string filename = 1;
string filepath = 2; string filepath = 2;
int64 size = 3; int64 size = 3;
string qemuArch = 4; // what arch. example: "x86_64" or "riscv64"
} }

View File

@ -1,17 +1,14 @@
syntax = "proto3"; syntax = "proto3";
package virtpb; package virtbuf;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
import "google/protobuf/any.proto"; // Import the well-known type for Timestamp import "google/protobuf/any.proto"; // Import the well-known type for Timestamp
import "droplet.proto";
// global settings for autogenpb `autogenpb:no-sort` `autogenpb:mutex` message Events {
string uuid = 1; // I guess why not just have this on each file
message Events { // `autogenpb:marshal` `autogenpb:gui` string version = 2; // maybe can be used for protobuf schema change violations
string uuid = 1; // `autogenpb:uuid:1e3a50c7-5916-4423-b33c-f0b977a7e446` int64 event_size = 3; // max events to store in a single
string version = 2; // `autogenpb:version:v0.0.1` repeated Event events = 4; // all the events
int64 eventSize = 3; // max events to store in a single
repeated Event events = 4; // all the events
} }
// this information leans towards being human readable not programatic // this information leans towards being human readable not programatic
@ -19,42 +16,34 @@ message Events { // `autogenpb:marsh
// at least for now in the early days. but maybe forever. // at least for now in the early days. but maybe forever.
// homelab clouds normally don't have many events. // homelab clouds normally don't have many events.
// we are talking less than 1 a minute. even 1 an hour is often a lot // we are talking less than 1 a minute. even 1 an hour is often a lot
message Event { // `autogenpb:marshal` message Event {
enum status { int32 id = 1;
DONE = 0; EventType etype = 2;
FAIL = 1; string droplet = 3; // name of the droplet
RUNNING = 2; string droplet_uuid = 4; // uuid of the droplet
} string hypervisor = 5; // name of the hypervisor
int32 id = 1; // `autogenpb:unique` // should be unique across the cluster string hypervisor_uuid = 6; // uuid of the hypervisor
EventType etype = 2; google.protobuf.Timestamp start = 7; // start time
string dropletName = 3; // name of the droplet google.protobuf.Timestamp end = 8; // end time
string dropletUuid = 4; // uuid of the droplet string field_name = 9; // the field name that changed
string hypervisor = 5; // name of the hypervisor string orig_val = 10; // original value
string hypervisorUuid = 6; // uuid of the hypervisor string new_val = 11; // new value
google.protobuf.Timestamp start = 7; // start time google.protobuf.Any orig_any = 12; // anypb format. probably overkill
google.protobuf.Timestamp end = 8; // end time google.protobuf.Any new_any = 13; // anypb format
string fieldName = 9; // the field name that changed
string origVal = 10; // original value
string newVal = 11; // new value
google.protobuf.Any origAny = 12; // anypb format. probably overkill
google.protobuf.Any newAny = 13; // anypb format
string error = 14; // what went wrong
status state = 15; // state of the event
Droplet droplet = 16; // droplet
} }
enum EventType { enum EventType {
ADD = 0; ADD = 0;
DELETE = 1; DELETE = 1;
POWERON = 2; POWERON = 2;
POWEROFF = 3; // should indicate a "normal" shutdown POWEROFF = 3; // should indicate a "normal" shutdown
HIBERNATE = 4; HIBERNATE = 4;
MIGRATE = 5; MIGRATE = 5;
DEMO = 6; DEMO = 6;
GET = 7; // request something GET = 7; // request something
LOGIN = 8; // attempt to login LOGIN = 8; // attempt to login
OK = 9; // everything is ok OK = 9; // everything is ok
FAIL = 10; // everything failed FAIL = 10; // everything failed
CRASH = 11; // droplet hard crashed CRASH = 11; // droplet hard crashed
CHANGE = 12; // droplet or hypervisor config change CHANGE = 12; // droplet or hypervisor config change
EDIT = 13; // edit droplet settings
} }

15
example/Makefile Normal file
View File

@ -0,0 +1,15 @@
build:
GO111MODULE=off go build
./example
goimports:
goimports -w *.go
prep:
go get -v -t -u
run:
go run *.go
clean:
-rm -f example

BIN
example/configfile Executable file

Binary file not shown.

98
example/main.go Normal file
View File

@ -0,0 +1,98 @@
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"google.golang.org/protobuf/proto"
pb "go.wit.com/lib/protobuf/virtbuf"
)
//
// saves entries in a config file
//
func main() {
TestWriteCluster()
in, err := ioutil.ReadFile("/tmp/testing4.protobuf")
if err != nil {
log.Fatalln("Error reading file:", err)
}
var aCluster pb.Cluster
if err := proto.Unmarshal(in, &aCluster); err != nil {
log.Fatalln("Failed to parse droplet:", err)
}
log.Println(aCluster.String())
// show the droplets to STDOUT
for _, d := range aCluster.Droplets {
fmt.Println("\tdroplet =", d.Hostname, "preffered host:", d.PreferredHypervisor)
}
// show the hypervisors to STDOUT
for _, h := range aCluster.Hypervisors {
fmt.Println("\thypervisor =", h.Hostname, h.GetMemoryPrintable())
}
json := aCluster.FormatJSON()
fmt.Println(json)
data, _ := aCluster.MarshalJSON()
fmt.Println(string(data))
text := aCluster.FormatTEXT()
fmt.Println(text)
}
func marshalWriteToFile(myWriter *bufio.Writer, c *pb.Cluster) {
buf, err := proto.Marshal(c)
if err != nil {
log.Fatal("marshaling error: ", err)
}
tmp, err := myWriter.Write(buf)
myWriter.Flush()
log.Println("bufio.Write() tmp, err = ", tmp, err)
buf, err = proto.Marshal(c)
tmp2, err := myWriter.Write(buf)
myWriter.Flush()
log.Println("bufio.Write() tmp2, err = ", tmp2, err)
}
func TestWriteCluster() {
buf := new(bytes.Buffer)
c := pb.CreateSampleCluster(7)
got := buf.String()
log.Println(got)
newfile, _ := os.Create("/tmp/testing4.protobuf")
myWriter := bufio.NewWriter(newfile)
marshalWriteToFile(myWriter, c)
// marshalUnmarshal()
}
func marshalUnmarshal() {
test := pb.CreateSampleCluster(7)
data, err := proto.Marshal(test)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newTest := &pb.Cluster{}
err = proto.Unmarshal(data, newTest)
if err != nil {
log.Fatal("unmarshaling error: ", err)
} else {
log.Println("proto.Marshal() and proto.Unmarshal() worked")
}
}

19
experiments.proto Normal file
View File

@ -0,0 +1,19 @@
syntax = "proto3";
package virtbuf;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
import "google/protobuf/any.proto"; // Import the well-known type for Timestamp
message WhatsThis {
// is it possible to have custom formatting in JSON and TEXT marshal/unmarshal ?
WhatInfo humantest = 1;
google.protobuf.Timestamp end = 2; // end time
google.protobuf.Any orig_val = 3; // original value
google.protobuf.Any new_val = 4; // new value
}
// this is for exerimenting
message WhatInfo {
int64 capacity = 1; // Stores the storage capacity in bytes.
}

View File

@ -1,17 +1,130 @@
package virtpb package virtbuf
// functions to import and export the protobuf import (
// data to and from config files "fmt"
func InitCluster() *OldCluster { "github.com/google/uuid"
var c *OldCluster )
c = new(OldCluster)
c.d = new(Droplets) // can the json protobuf output use a string and have a type handler
c.H = new(Hypervisors) // to convert it back to int64?
c.e = new(Events) func SetGB(gb int) int64 {
return c return int64(gb * 1024 * 1024 * 1024)
} }
func (c *OldCluster) DropletsAll() *DropletScanner { func SetMB(mb int) int64 {
return c.d.All() return int64(mb * 1024 * 1024)
}
func (x *Hypervisor) SetMemoryGB(gb int) {
x.Memory = int64(gb * 1024 * 1024 * 1024)
}
func (x *Hypervisor) GetMemoryPrintable() string {
i := x.Memory / (1024 * 1024 * 1024)
return fmt.Sprintf("%d GB", i)
}
func (all *Droplets) FindDroplet(name string) *Droplet {
for _, d := range all.Droplets {
if d.Hostname == name {
return d
}
}
return nil
}
func (c *Cluster) FindDroplet(name string) *Droplet {
for _, d := range c.Droplets {
if d.Hostname == name {
return d
}
}
return nil
}
func (c *Cluster) FindHypervisor(name string) *Hypervisor {
for _, h := range c.Hypervisors {
if h.Hostname == name {
return h
}
}
return nil
}
func (c *Cluster) AddHypervisor(hostname string, cpus int, mem int) *Hypervisor {
h := c.FindHypervisor(hostname)
if h != nil {
return h
}
// Generate a new UUID
id := uuid.New()
h = &Hypervisor{
Uuid: id.String(),
Hostname: hostname,
Cpus: int64(cpus),
Comment: "this is a fake hypervisor",
}
if cpus < 0 {
h.Cpus = 1
}
h.SetMemoryGB(mem * 32)
c.Hypervisors = append(c.Hypervisors, h)
return h
}
func (c *Cluster) AddDroplet(uuid string, hostname string, cpus int, mem int) *Droplet {
d := c.FindDroplet(hostname)
if d != nil {
return d
}
d = &Droplet{
Uuid: uuid,
Hostname: hostname,
Cpus: int64(cpus),
}
if cpus < 0 {
d.Cpus = 1
}
d.Memory = SetGB(mem * 32)
c.Droplets = append(c.Droplets, d)
return d
}
// This isn't for the marketing department
// so this isn't going to use 'MiB' and 'GiB'
func HumanFormatBytes(b int64) string {
if b < 2000 {
return fmt.Sprintf("%d B", b)
}
kb := int(b / 1024)
if kb < 2000 {
return fmt.Sprintf("%d KB", kb)
}
mb := int(b / (1024 * 1024))
if mb < 2000 {
return fmt.Sprintf("%d MB", mb)
}
gb := int(b / (1024 * 1024 * 1024))
if gb < 2000 {
return fmt.Sprintf("%d GB", gb)
}
tb := int(b / (1024 * 1024 * 1024 * 1024))
return fmt.Sprintf("%d TB", tb)
}
func (c *Cluster) BlankFields() {
for _, d := range c.Droplets {
d.CurrentState = 0
}
}
func (c *Cluster) AppendEvent(e *Event) {
c.Events = append(c.Events, e)
} }

198
human.go
View File

@ -1,198 +0,0 @@
package virtpb
// mostly just functions related to making STDOUT
// more readable by us humans
// also function shortcuts the do fixed limited formatting (it's like COBOL)
// so reporting tables of the status of what droplets and hypervisors
// are in text columns and rows that can be easily read in a terminal
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"google.golang.org/protobuf/types/known/timestamppb"
"go.wit.com/log"
)
func oldGetDurationStamp(t time.Time) string {
// Get the current time
currentTime := time.Now()
// Calculate the duration between t current time
duration := currentTime.Sub(t)
return FormatDuration(duration)
}
// This isn't for the marketing department
// so this isn't going to use 'MiB' and 'GiB'
func HumanFormatBytes(b int64) string {
if b < 2000 {
return fmt.Sprintf("%d B", b)
}
kb := int(b / 1024)
if kb < 2000 {
return fmt.Sprintf("%d KB", kb)
}
mb := int(b / (1024 * 1024))
if mb < 2000 {
return fmt.Sprintf("%d MB", mb)
}
gb := int(b / (1024 * 1024 * 1024))
if gb < 2000 {
return fmt.Sprintf("%d GB", gb)
}
tb := int(b / (1024 * 1024 * 1024 * 1024))
return fmt.Sprintf("%d TB", tb)
}
func FormatDuration(d time.Duration) string {
result := ""
// check if it's more than a year
years := int(d.Hours()) / (24 * 365)
if years > 0 {
result += fmt.Sprintf("%dy", years)
return result
}
// check if it's more than a day
days := int(d.Hours()) / 24
if days > 0 {
result += fmt.Sprintf("%dd", days)
return result
}
// check if it's more than an hour
hours := int(d.Hours()) % 24
if hours > 0 {
result += fmt.Sprintf("%dh", hours)
return result
}
// check if it's more than a minute
minutes := int(d.Minutes()) % 60
if minutes > 0 {
result += fmt.Sprintf("%dm", minutes)
return result
}
// check if it's more than a second
seconds := int(d.Seconds()) % 60
if seconds > 0 {
result += fmt.Sprintf("%ds", seconds)
return result
}
// report in milliseconds
ms := int(d.Milliseconds())
if ms > 100 {
// todo: print .3s, etc ?
return fmt.Sprintf("%1.2fs", float64(seconds)/1000)
}
if ms > 0 {
result += fmt.Sprintf("%dms", ms)
}
// totally not necessary but wth
var t time.Duration
t = time.Duration(ms) * time.Millisecond
nanos := d - t
result += fmt.Sprintf("%dnanos", nanos)
return result
}
func (d *Droplet) SprintHeader() string {
if d.Current == nil {
d.Current = new(Current)
}
header := fmt.Sprintf("%-3.3s %-9.9s %-20.20s", d.Current.State, d.Current.Hypervisor, d.Hostname)
switch d.Current.State {
case DropletState_ON:
var dur string
if d.Current.OnSince != nil {
dur = ""
} else {
t := time.Since(d.Current.OnSince.AsTime()) // time since 'OFF'
dur = FormatDuration(t)
}
header += fmt.Sprintf(" (on :%3s)", dur)
case DropletState_OFF:
var dur string
if d.Current.OffSince != nil {
dur = ""
} else {
t := time.Since(d.Current.OffSince.AsTime()) // time since 'OFF'
dur = FormatDuration(t)
}
header += fmt.Sprintf(" (off:%3s)", dur)
default:
header += fmt.Sprintf(" (?? :%3s)", "")
}
return header
}
func (d *Droplet) SprintDumpHeader() string {
var macs []string
for _, n := range d.Networks {
macs = append(macs, n.Mac)
}
header := fmt.Sprintf("%-4.4s%20s %-8s", d.Current.State, strings.Join(macs, " "), d.Current.Hypervisor)
if d.Current == nil {
return header
}
if d.Current.OnSince == nil {
d.Current.OnSince = timestamppb.New(time.Now())
}
t := time.Since(d.Current.OnSince.AsTime()) // time since 'ON'
dur := FormatDuration(t)
switch d.Current.State {
case DropletState_ON:
header += fmt.Sprintf(" (on :%3s)", dur)
case DropletState_OFF:
header += fmt.Sprintf(" (off:%3s)", dur)
default:
header += fmt.Sprintf(" (?? :%3s)", dur)
}
return header
}
func (d *Droplet) DumpDroplet(w http.ResponseWriter, r *http.Request) (string, error) {
if d == nil {
reason := "DumpDroplet() got d == nil"
log.Warn(reason)
fmt.Fprintln(w, reason)
return "", errors.New(reason)
}
t := d.FormatTEXT()
log.Info(t)
fmt.Fprintln(w, t)
return t, nil
}
func (c *OldCluster) DumpDroplet(w http.ResponseWriter, r *http.Request) (string, error) {
hostname := r.URL.Query().Get("hostname")
d := c.FindDropletByName(hostname)
if d == nil {
result := "can not find droplet hostname=" + hostname
log.Info(result)
fmt.Fprintln(w, result)
return result, errors.New(result)
}
return d.DumpDroplet(w, r)
}

View File

@ -1,29 +1,18 @@
syntax = "proto3"; syntax = "proto3";
package virtpb; package virtbuf;
import "google/protobuf/timestamp.proto"; message Hypervisors {
string uuid = 1; // I guess why not just have this on each file
message Hypervisors { // `autogenpb:marshal` `autogenpb:gui` string version = 2; // maybe can be used for protobuf schema change violations
string uuid = 1; // `autogenpb:uuid:6e3aa8b9-cf98-40f6-af58-3c6ad1edf4d4` repeated Hypervisor hypervisors = 3;
string version = 2; // `autogenpb:version:v0.0.1`
repeated Hypervisor hypervisors = 3;
} }
message Hypervisor { message Hypervisor {
string uuid = 1; // `autogenpb:unique` string uuid = 1;
string hostname = 2; // `autogenpb:unique` string hostname = 2;
bool active = 3; // is allowed to start new droplets bool active = 3; // is allowed to start new droplets
int64 cpus = 4; int64 cpus = 4;
int64 memory = 5; // in bytes int64 memory = 5; // in bytes
string comment = 6; string comment = 6;
bool autoscan = 7; // to scan or not to scan by virtigo bool autoscan = 7; // to scan or not to scan by virtigo
HypervisorArch arch = 8;
int64 killcount = 9; // in bytes
google.protobuf.Timestamp lastPoll = 10; // the last time we heard anything
}
enum HypervisorArch {
RISCV64 = 0;
X86_64 = 1;
ARM64 = 2;
} }

View File

@ -1,48 +0,0 @@
package virtpb
import (
sync "sync"
durationpb "google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type OldCluster struct {
sync.RWMutex
Dirs []string
d *Droplets
H *Hypervisors
e *Events
Unstable *timestamppb.Timestamp
UnstableTimeout *durationpb.Duration
}
func (c *OldCluster) GetDropletsPB() *Droplets {
return c.d
}
func (c *OldCluster) GetHypervisorsPB() *Hypervisors {
return c.H
}
func (c *OldCluster) GetEventsPB() *Events {
return c.e
}
// adds a new droplet. enforce unique hostnames
func (c *OldCluster) AddDroplet(newd *Droplet) bool {
c.Lock()
defer c.Unlock()
for _, d := range c.d.Droplets {
if newd.Hostname == d.Hostname {
// boo. that one is already here
return false
}
}
// everything is ok, this hostname is new
c.d.Droplets = append(c.d.Droplets, newd)
return true
}

View File

@ -1,4 +1,4 @@
package virtpb package virtbuf
import ( import (
"fmt" "fmt"
@ -21,6 +21,7 @@ func CreateSampleDroplet(hostname string) *Droplet {
d := &Droplet{ d := &Droplet{
Uuid: id.String(), Uuid: id.String(),
Hostname: hostname, Hostname: hostname,
Notes: "this is a droplet for testing",
} }
return d return d
} }
@ -39,6 +40,22 @@ func CreateSampleHypervisor(hostname string, mem int) *Hypervisor {
return h return h
} }
func CreateExperiment(total int) *WhatsThis {
var e *WhatsThis
e = new(WhatsThis)
// info := StorageInfo{Capacity: 64}
// e.Humantest = &info
if e.Humantest == nil {
var newInfo WhatInfo
newInfo = WhatInfo{Capacity: 64}
e.Humantest = &newInfo
} else {
e.Humantest.Capacity = SetGB(total * 32)
}
return e
}
func CreateSampleEvents(total int) *Events { func CreateSampleEvents(total int) *Events {
var e *Events var e *Events
e = new(Events) e = new(Events)
@ -51,19 +68,21 @@ func CreateSampleEvents(total int) *Events {
return e return e
} }
func CreateSampleCluster(total int) *OldCluster { func CreateSampleCluster(total int) *Cluster {
c := InitCluster() var c *Cluster
c = new(Cluster)
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
hostname := fmt.Sprintf("bmath%d.wit.com", i) hostname := fmt.Sprintf("bmath%d.wit.com", i)
d := CreateSampleDroplet(hostname) d := CreateSampleDroplet(hostname)
d.Notes = fmt.Sprintf("Sample Droplet %d", i)
d.PreferredHypervisor = fmt.Sprintf("farm%d", i) d.PreferredHypervisor = fmt.Sprintf("farm%d", i)
if d.PreferredHypervisor == "farm4" { if d.PreferredHypervisor == "farm4" {
d.Cpus = 16 d.Cpus = 16
d.Memory = SetGB(256) d.Memory = SetGB(256)
} }
c.d.Droplets = append(c.d.Droplets, d) c.Droplets = append(c.Droplets, d)
} }
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
@ -71,7 +90,7 @@ func CreateSampleCluster(total int) *OldCluster {
h := CreateSampleHypervisor(hostname, i+1) h := CreateSampleHypervisor(hostname, i+1)
h.Comment = fmt.Sprintf("Sample hypervisor %d", i) h.Comment = fmt.Sprintf("Sample hypervisor %d", i)
c.H.Hypervisors = append(c.H.Hypervisors, h) c.Hypervisors = append(c.Hypervisors, h)
} }
return c return c

View File

@ -1,6 +1,11 @@
package virtpb package virtbuf
import (
"encoding/json"
"fmt"
"strconv"
)
/*
// MarshalJSON custom marshals the WhatInfo struct to JSON // MarshalJSON custom marshals the WhatInfo struct to JSON
func (s WhatInfo) MarshalJSON() ([]byte, error) { func (s WhatInfo) MarshalJSON() ([]byte, error) {
capacityStr := fmt.Sprintf("%d GB", s.Capacity) capacityStr := fmt.Sprintf("%d GB", s.Capacity)
@ -32,6 +37,7 @@ func (s *WhatInfo) UnmarshalJSON(data []byte) error {
return nil return nil
} }
/*
func main() { func main() {
info := WhatInfo{Capacity: 64} info := WhatInfo{Capacity: 64}