Compare commits

...

49 Commits
v0.2 ... master

Author SHA1 Message Date
Jeff Carr e6fb7352ae quiet debugging 2025-04-23 02:43:18 -05:00
Jeff Carr c9ef4f0b82 make an EDIT event 2025-04-22 20:50:00 -05:00
Jeff Carr 688b5039a0 new ConfigSave() 2025-04-21 20:54:45 -05:00
Jeff Carr 814d36b9c9 working on droplet start event 2025-04-21 20:54:43 -05:00
Jeff Carr a07033d181 move these into cluster.pb 2025-04-21 20:54:38 -05:00
Jeff Carr 7cdb2a33ef do a whole cluster protobuf at once 2025-04-20 19:41:02 -05:00
Jeff Carr 63148556af add Name and allow multiple URLs 2025-04-12 11:28:05 -05:00
Jeff Carr a510dd6474 this is actually a 'scanner' 2025-03-27 07:31:50 -05:00
Jeff Carr e4345c8ad6 moving to a cluster.proto config file 2025-03-24 21:54:13 -05:00
Jeff Carr 276c5cec2f pass out hypervisors 2025-03-11 04:02:45 -05:00
Jeff Carr e78fc1235e rm code now made by autogenpb 2025-03-10 18:10:40 -05:00
Jeff Carr c82997ed61 hypervisor poll time attempt 1 2025-02-23 13:13:30 -06:00
Jeff Carr 4aef241137 update worked to gocui 2025-02-23 13:13:30 -06:00
Jeff Carr c6cb62c86d sends the table across 2025-02-23 13:13:30 -06:00
Jeff Carr 0f546d57fc set uuid 2025-02-23 13:13:30 -06:00
Jeff Carr 13159b5b64 add daemon killcount and lastpoll 2025-02-23 13:13:30 -06:00
Jeff Carr 9dfcbb0432 early attempt at pb table update() 2025-02-23 13:13:30 -06:00
Jeff Carr 3ca7403aa6 minor 2025-02-23 13:13:30 -06:00
Jeff Carr 65f9089a7a remove _ names rather than fix the autogenpb parser 2025-02-22 18:17:31 -06:00
Jeff Carr b25d86f277 also need gui table support for this 2025-02-22 18:14:49 -06:00
Jeff Carr d40dc91130 switch virtbuf to virtpb 2025-02-22 17:45:59 -06:00
Jeff Carr 2381c65887 cleanups for gui pb tables 2025-02-22 15:23:40 -06:00
Jeff Carr ddc0410126 fix proto files to conform with autogenpb 2025-02-07 04:41:25 -06:00
Jeff Carr 9160268326 duh. TRUNCATE on new files 2024-12-11 13:54:36 -06:00
Jeff Carr 0888e51c91 trick to easily detect protobuf libraries 2024-12-02 05:15:58 -06:00
Jeff Carr 8f3953159a autogenpb now configured in .proto files 2024-12-01 22:24:01 -06:00
Jeff Carr aeac6b5af7 minor autogenpb fixes 2024-12-01 18:59:34 -06:00
Jeff Carr afd0bd6428 switched to autogenpb 2024-12-01 18:41:00 -06:00
Jeff Carr 5b883de7b9 never put binaries in go libraries 2024-12-01 17:57:35 -06:00
Jeff Carr 3046ff335f stuff
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-18 05:32:37 -06:00
Jeff Carr 81cbb6e9d7 things for create. might be duplicates
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-16 05:22:11 -06:00
Jeff Carr 74da63276e rename
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-16 01:52:43 -06:00
Jeff Carr 3b63a3af24 better formatting
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-13 15:29:37 -06:00
Jeff Carr 907981a92d rename Cluster
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-07 05:04:11 -06:00
Jeff Carr b9766ce266 minor
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-06 19:23:56 -06:00
Jeff Carr 4fdb1934ff not much here yet
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-06 19:14:14 -06:00
Jeff Carr 2f715b47d5 attempt at instructions
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-02 23:56:56 -05:00
Jeff Carr 17f8c31027 things are working again
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 12:49:51 -05:00
Jeff Carr d8c3744f20 virtigod compiles again
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 10:23:07 -05:00
Jeff Carr 1a72fdceef set preferred hypervisor
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 08:45:52 -05:00
Jeff Carr f36c19f04f import worked
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 08:31:25 -05:00
Jeff Carr 36e69dd84c Merge branch 'jcarr' of git.wit.com:jcarr/virtbuf into jcarr 2024-11-01 04:09:54 -05:00
Jeff Carr c2e30a373a DumpDroplet()
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 04:09:41 -05:00
Jeff Carr 6337988092 cleanup old example
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 04:03:19 -05:00
Jeff Carr 20e958559e fixes for libvirt domain import
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 02:01:11 -05:00
Jeff Carr adb44a864f more COBOL
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 00:58:12 -05:00
Jeff Carr 706dbbc533 pretty output for humans d.SprintHeader()
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 00:41:34 -05:00
Jeff Carr b6f5594fe6 add droplet defaults to start state = off
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-31 22:14:52 -05:00
Jeff Carr 2b77b8a89d AddDomainLocal()
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-31 15:44:29 -05:00
24 changed files with 615 additions and 669 deletions

4
.gitignore vendored
View File

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

3
.protobuf Normal file
View File

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

View File

@ -1,23 +1,17 @@
# You must use the current protoc-gen-go # You must use the current google 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
all: droplet.pb.go hypervisor.pb.go cluster.pb.go event.pb.go experiments.pb.go vet:
make -C example @GO111MODULE=off go vet
@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.*
@ -27,40 +21,20 @@ 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
# protoc --go_out=. droplet.proto autogenpb --proto 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
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/virtbuf \ autogenpb --proto hypervisor.proto
--go_opt=Mhypervisor.proto=go.wit.com/lib/protobuf/virtbuf \
hypervisor.proto
event.pb.go: event.proto event.pb.go: event.proto
cd ~/go/src && protoc --go_out=. \ autogenpb --proto event.proto
--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
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/virtbuf \ autogenpb --proto cluster.proto
--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,13 +1,6 @@
They are what to use for starting droplets with virsh, This go library handles the protobuf files
but for making a cluster or "home cloud" with virtigo and various functions for virtigo.
When possible & sensible, use the same variable names as libvirt You must build the protobuf files using autogenpb
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

135
add.go
View File

@ -1,4 +1,4 @@
package virtbuf package virtpb
import ( import (
"fmt" "fmt"
@ -8,6 +8,34 @@ import (
"go.wit.com/log" "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 // can the json protobuf output use a string and have a type handler
// to convert it back to int64? // to convert it back to int64?
func SetGB(gb int) int64 { func SetGB(gb int) int64 {
@ -22,24 +50,10 @@ func (x *Hypervisor) SetMemoryGB(gb int) {
x.Memory = int64(gb * 1024 * 1024 * 1024) x.Memory = int64(gb * 1024 * 1024 * 1024)
} }
func (x *Hypervisor) GetMemoryPrintable() string { func (c *OldCluster) FindDropletByName(name string) *Droplet {
i := x.Memory / (1024 * 1024 * 1024) loop := c.d.All() // get the list of droplets
return fmt.Sprintf("%d GB", i)
}
func (all *Droplets) oldFindDroplet(name string) *Droplet {
for _, d := range all.Droplets {
if d.Hostname == name {
return d
}
}
return nil
}
func (c *NewCluster) FindDropletByName(name string) *Droplet {
loop := c.DropletsAll() // get the list of droplets
for loop.Scan() { for loop.Scan() {
d := loop.Droplet() d := loop.Next()
if d.Hostname == name { if d.Hostname == name {
return d return d
} }
@ -47,7 +61,20 @@ func (c *NewCluster) FindDropletByName(name string) *Droplet {
return nil return nil
} }
func (c *NewCluster) FindHypervisorByName(name string) *Hypervisor { 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 { for _, h := range c.H.Hypervisors {
if h.Hostname == name { if h.Hostname == name {
return h return h
@ -56,7 +83,7 @@ func (c *NewCluster) FindHypervisorByName(name string) *Hypervisor {
return nil return nil
} }
func (c *NewCluster) AddHypervisor(hostname string, cpus int, mem int) *Hypervisor { func (c *OldCluster) AddHypervisor(hostname string, cpus int, mem int) *Hypervisor {
h := c.FindHypervisorByName(hostname) h := c.FindHypervisorByName(hostname)
if h != nil { if h != nil {
return h return h
@ -77,15 +104,25 @@ func (c *NewCluster) AddHypervisor(hostname string, cpus int, mem int) *Hypervis
return h return h
} }
func (c *NewCluster) AddEvent(e *Event) { func (c *OldCluster) AddEvent(e *Event) {
c.e.Events = append(c.e.Events, e) c.e.Events = append(c.e.Events, e)
} }
func (c *NewCluster) AddDroplet(d *Droplet) { // creates a new droplet with default values
c.d.Droplets = append(c.d.Droplets, d) 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 *NewCluster) AddDropletSimple(uuid string, hostname string, cpus int, mem int) *Droplet { func (c *OldCluster) AddDropletSimple(uuid string, hostname string, cpus int, mem int) *Droplet {
d := c.FindDropletByName(hostname) d := c.FindDropletByName(hostname)
if d != nil { if d != nil {
return d return d
@ -101,40 +138,34 @@ func (c *NewCluster) AddDropletSimple(uuid string, hostname string, cpus int, me
d.Cpus = 1 d.Cpus = 1
} }
d.Memory = SetGB(mem * 32) d.Memory = SetGB(mem * 32)
d.StartState = DropletState_OFF
c.AddDroplet(d) c.AddDroplet(d)
return d return d
} }
// This isn't for the marketing department // This isn't for the marketing department
// so this isn't going to use 'MiB' and 'GiB' func (c *OldCluster) AddDropletLocal(name string, hypername string) *Droplet {
func HumanFormatBytes(b int64) string { d := &Droplet{
if b < 2000 { Hostname: name,
return fmt.Sprintf("%d B", b) }
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
} }
kb := int(b / 1024) func (c *OldCluster) BlankFields() {
if kb < 2000 { loop := c.d.All() // get the list of droplets
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 *NewCluster) BlankFields() {
loop := c.DropletsAll() // get the list of droplets
for loop.Scan() { for loop.Scan() {
d := loop.Droplet() d := loop.Next()
d.Current = nil d.Current = nil
} }
} }
@ -143,7 +174,7 @@ func (epb *Events) AppendEvent(e *Event) {
epb.Events = append(epb.Events, e) epb.Events = append(epb.Events, e)
} }
func (c *NewCluster) ClusterStable() (bool, string) { func (c *OldCluster) ClusterStable() (bool, string) {
last := time.Since(c.Unstable.AsTime()) last := time.Since(c.Unstable.AsTime())
if last > c.UnstableTimeout.AsDuration() { if last > c.UnstableTimeout.AsDuration() {
// the cluster has not been stable for 133 seconds // the cluster has not been stable for 133 seconds
@ -155,7 +186,7 @@ func (c *NewCluster) ClusterStable() (bool, string) {
} }
// check the cluster and droplet to make sure it's ready to start // check the cluster and droplet to make sure it's ready to start
func (c *NewCluster) DropletReady(d *Droplet) (bool, string) { func (c *OldCluster) DropletReady(d *Droplet) (bool, string) {
if c == nil { if c == nil {
return false, "cluster == nil" return false, "cluster == nil"
} }

View File

@ -1,4 +1,4 @@
package virtbuf package virtpb
// 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.Println("Failed to create directory: %v", err) log.Printf("Failed to create directory: %v\n", err)
return err return err
} }
@ -41,7 +41,7 @@ func backupDir(srcDir string, destDir string) error {
}) })
if err != nil { if err != nil {
log.Println("Failed to copy files: %v", err) log.Printf("Failed to copy files: %v\n", err)
return err return err
} }
return nil return nil

View File

@ -1,4 +1,4 @@
package virtbuf package virtpb
import ( import (
// "reflect" // "reflect"
@ -34,7 +34,6 @@ 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
} }
@ -49,6 +48,14 @@ 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"
@ -57,7 +64,6 @@ 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 ""
} }
@ -67,7 +73,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.Droplet = d.Hostname e.DropletName = d.Hostname
e.OrigVal = convertToString(origval) e.OrigVal = convertToString(origval)
e.NewVal = convertToString(newval) e.NewVal = convertToString(newval)
e.FieldName = fname e.FieldName = fname
@ -93,12 +99,12 @@ 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.Droplet = d.Hostname e.DropletName = d.Hostname
case nil: case nil:
e.Droplet = "<nil>" e.DropletName = "<nil>"
default: default:
log.Info("newAddEvent() unknown type", v) log.Info("newAddEvent() unknown type", v)
e.Droplet = "on something somewhere" e.DropletName = "on something somewhere"
} }
e.NewVal = convertToString(newval) e.NewVal = convertToString(newval)
@ -128,8 +134,36 @@ 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 *NewCluster) ChangeDropletState(d *Droplet, newState DropletState) error { func (c *OldCluster) ChangeDropletState(d *Droplet, newState DropletState) error {
if c == nil { if c == nil {
return errors.New("cluster is nil") return errors.New("cluster is nil")
} }
@ -143,7 +177,7 @@ func (c *NewCluster) ChangeDropletState(d *Droplet, newState DropletState) error
var e *Event var e *Event
e = new(Event) e = new(Event)
e.Droplet = d.Hostname e.DropletName = d.Hostname
e.OrigVal = convertToString(d.Current.State) e.OrigVal = convertToString(d.Current.State)
e.NewVal = convertToString(newState) e.NewVal = convertToString(newState)
e.FieldName = "status" e.FieldName = "status"
@ -156,7 +190,7 @@ func (c *NewCluster) ChangeDropletState(d *Droplet, newState DropletState) error
} }
// records an event that the droplet migrated to another hypervisor // records an event that the droplet migrated to another hypervisor
func (c *NewCluster) DropletMoved(d *Droplet, newh *Hypervisor) error { func (c *OldCluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
if c == nil { if c == nil {
return errors.New("cluster is nil") return errors.New("cluster is nil")
} }
@ -175,7 +209,7 @@ func (c *NewCluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
var e *Event var e *Event
e = new(Event) e = new(Event)
e.Droplet = d.Hostname e.DropletName = d.Hostname
e.OrigVal = d.Current.Hypervisor e.OrigVal = d.Current.Hypervisor
e.NewVal = newh.Hostname e.NewVal = newh.Hostname
e.FieldName = "droplet migrate" e.FieldName = "droplet migrate"

View File

@ -1,23 +1,23 @@
syntax = "proto3"; syntax = "proto3";
package virtbuf; package virtpb;
import "google/protobuf/timestamp.proto";
import "droplet.proto"; import "droplet.proto";
import "hypervisor.proto"; import "hypervisor.proto";
import "event.proto"; import "event.proto";
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
import "google/protobuf/duration.proto"; // Import the well-known type for Timestamp
message OldCluster { message Cluster { // `autogenpb:marshal`
int64 id = 1; string uuid = 1; // `autogenpb:unique`
repeated string dirs = 2; string name = 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;
// Droplets d = 6; }
Hypervisors h = 7;
Events e = 8; message Clusters { // `autogenpb:marshal`
google.protobuf.Timestamp unstable = 9; // the last time we heard anything from this droplet string uuid = 1; // `autogenpb:uuid:57ddd763-75f6-4003-bf0e-8dd0f8a44044`
google.protobuf.Duration unstable_timeout = 10; // the last time we heard anything from this droplet string version = 2; // `autogenpb:version:v0.0.1`
repeated Cluster clusters = 3;
} }

109
config.go
View File

@ -1,4 +1,4 @@
package virtbuf package virtpb
// 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
@ -9,43 +9,60 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"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 *NewCluster) ConfigSave() error { func (c *OldCluster) 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 := backupConfig(); err != nil {
return err return err
} }
// make a new droplets struct // make a new droplets struct
var d *Droplets var dcopy *Droplets
d = new(Droplets) dcopy = new(Droplets)
// copy all the records over to the new struct loop := c.d.All() // get the list of droplets
for _, drop := range c.d.Droplets { for loop.Scan() {
d.Droplets = append(d.Droplets, drop) d := loop.Next()
dcopy.Droplets = append(dcopy.Droplets, d)
} }
// delete all the Current data so it's not put in the config file // delete all the Current data so it's not put in the config file
for _, drop := range d.Droplets { for _, drop := range dcopy.Droplets {
drop.Current = nil drop.Current = nil
} }
if err := ConfigWriteJSON(d, "droplets.json"); err != nil { if err := ConfigWriteTEXT(dcopy, "droplets.text"); err != nil {
fmt.Println("droplets.json write failed")
return err
}
if err := ConfigWriteTEXT(d, "droplets.text"); err != nil {
fmt.Println("droplets.json write failed") fmt.Println("droplets.json write failed")
return err return err
} }
c.configWriteDroplets()
if err := ConfigWriteJSON(c.H, "hypervisors.json"); err != nil {
fmt.Println("hypervisors.json write failed")
return err
}
if err := ConfigWriteTEXT(c.H, "hypervisors.text"); err != nil { if err := ConfigWriteTEXT(c.H, "hypervisors.text"); err != nil {
fmt.Println("hypervisors.json write failed") fmt.Println("hypervisors.json write failed")
return err return err
@ -62,27 +79,27 @@ func (c *NewCluster) ConfigSave() error {
return nil return nil
} }
func (c *NewCluster) ConfigLoad() error { func (c *OldCluster) ConfigLoad() 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.json"); err == nil { if data, err := loadFile("droplets.text"); err == nil {
if err = protojson.Unmarshal(data, c.d); err != nil { if err = prototext.Unmarshal(data, c.d); err != nil {
fmt.Println("broken droplets.json config file") fmt.Println("broken droplets.text config file")
return err return err
} }
} else { } else {
return err return err
} }
if data, err := loadFile("hypervisors.json"); err == nil { if data, err := loadFile("hypervisors.text"); err == nil {
if err = protojson.Unmarshal(data, c.H); err != nil { if err = prototext.Unmarshal(data, c.H); err != nil {
fmt.Println("broken hypervisors.json config file") fmt.Println("broken hypervisors.text config file")
return err return err
} }
} else { } else {
fmt.Println("ERROR HERE IN Hypervisors") log.Warn("ERROR HERE IN Hypervisors")
return err return err
} }
@ -91,8 +108,11 @@ func (c *NewCluster) ConfigLoad() error {
// does it not stay allocated after this function ends? // does it not stay allocated after this function ends?
c.e = new(Events) c.e = new(Events)
} }
if err := c.e.loadEvents(); err != nil { if err := c.e.loadEvents(); err != nil {
return err // ignore events.pb since these should be sent elsewhere
log.Warn("Events failed to load, ignoring:", err)
return nil
} }
return nil return nil
} }
@ -140,7 +160,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, 0666) cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close() defer cfgfile.Close()
if err != nil { if err != nil {
fmt.Println("open config file :", err) fmt.Println("open config file :", err)
@ -155,9 +175,26 @@ 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, 0666) cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close() defer cfgfile.Close()
if err != nil { if err != nil {
fmt.Println("open config file :", err) fmt.Println("open config file :", err)
@ -171,3 +208,19 @@ func ConfigWriteTEXT(a any, filename string) error {
fmt.Fprintln(cfgfile, text) fmt.Fprintln(cfgfile, text)
return nil return nil
} }
func (c *Clusters) ConfigLoad() error {
if c == nil {
return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
}
if data, err := loadFile("cluster.text"); err == nil {
if err = prototext.Unmarshal(data, c); err != nil {
fmt.Println("broken cluster.textconfig file")
return err
}
} else {
return err
}
return nil
}

View File

@ -1,4 +1,4 @@
package virtbuf package virtpb
// 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

View File

@ -1,37 +1,39 @@
syntax = "proto3"; syntax = "proto3";
package virtbuf; package virtpb;
import "google/protobuf/duration.proto"; // Import the well-known type for Timestamp import "google/protobuf/duration.proto"; // Import the well-known type for Timestamp
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message Droplets { // global settings for autogenpb `autogenpb:mutex`
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations message Droplets { // `autogenpb:marshal` `autogenpb:gui`
string uuid = 1; // `autogenpb:uuid:d5d492e2-38d4-476b-86f3-f5abf01f9d6d`
string version = 2; // `autogenpb:version:v0.0.1`
repeated Droplet droplets = 3; repeated Droplet droplets = 3;
} }
message Droplet { message Droplet { // `autogenpb:marshal`
string uuid = 1; // should be unique across the cluster string uuid = 1; // `autogenpb:unique` // should be unique across the cluster
string hostname = 2; // should be unique and work in DNS string hostname = 2; // `autogenpb:unique` // 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 Current current = 5; // what the state and values of the droplet is
DropletState start_state = 6; // 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 qemu_machine = 7; // the qemu machine type to use "pc-q35-9.0" string qemuMachine = 7; // the qemu machine type to use "pc-q35-9.0"
int64 spice_port = 8; // preferred port to use for spice int64 spicePort = 8; // preferred port to use for spice
string preferred_hypervisor = 9; // the hypervisor to prefer to run the droplet on string preferredHypervisor = 9; // the hypervisor to prefer to run the droplet on
string force_hypervisor = 10; // use this hypervisor and this hypervisor only string forceHypervisor = 10; // use this hypervisor and this hypervisor only
string preferred_arch = 11; // the cpu arch to use "x86_64" (should really get this from the disk?) 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 Network networks = 12; // really just mac addresses. should be unique across cluster
repeated Disk disks = 13; // disks to attach repeated Disk disks = 13; // disks to attach
string local_only = 14; // this is only defined locally on the hypervisor string localOnly = 14; // this is only defined locally on the hypervisor
string custom_xml = 15; // if needed, string customXml = 15; // if needed,
Archive archive = 16; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off') Archive archive = 16; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off')
google.protobuf.Timestamp unstable = 39; // the last time we heard anything from this droplet google.protobuf.Timestamp unstable = 39; // the last time we heard anything from this droplet
google.protobuf.Duration unstable_timeout = 40; // the last time we heard anything from this droplet google.protobuf.Duration unstableTimeout = 40; // the last time we heard anything from this droplet
} }
// volatile data. the current settings and values of things. // volatile data. the current settings and values of things.
@ -40,10 +42,12 @@ message Droplet {
message Current { message Current {
DropletState state = 1; // used to track the current state before taking any action DropletState state = 1; // used to track the current state before taking any action
string hypervisor = 2; // the current hypervisor the droplet is running on string hypervisor = 2; // the current hypervisor the droplet is running on
int64 start_attempts = 3; // how many times a start has been attempted int64 startAttempts = 3; // how many times a start has been attempted
string full_xml = 4; // the full libvirt xml to import string fullXml = 4; // the full libvirt xml to import
google.protobuf.Timestamp last_poll = 5; // the last time we heard anything from this droplet google.protobuf.Timestamp lastPoll = 5; // the last time we heard anything from this droplet
string image_url = 6; // url to the image 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 { message Archive {
@ -51,7 +55,6 @@ message Archive {
google.protobuf.Timestamp when = 2; // when it was archived google.protobuf.Timestamp when = 2; // when it was archived
} }
// virtual machine state
enum DropletState { enum DropletState {
ON = 0; ON = 0;
OFF = 1; OFF = 1;
@ -75,5 +78,5 @@ message Disk {
string filename = 1; string filename = 1;
string filepath = 2; string filepath = 2;
int64 size = 3; int64 size = 3;
string qemu_arch = 4; // what arch. example: "x86_64" or "riscv64" string qemuArch = 4; // what arch. example: "x86_64" or "riscv64"
} }

View File

@ -1,13 +1,16 @@
syntax = "proto3"; syntax = "proto3";
package virtbuf; package virtpb;
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";
message Events { // global settings for autogenpb `autogenpb:no-sort` `autogenpb:mutex`
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations message Events { // `autogenpb:marshal` `autogenpb:gui`
int64 event_size = 3; // max events to store in a single string uuid = 1; // `autogenpb:uuid:1e3a50c7-5916-4423-b33c-f0b977a7e446`
string version = 2; // `autogenpb:version:v0.0.1`
int64 eventSize = 3; // max events to store in a single
repeated Event events = 4; // all the events repeated Event events = 4; // all the events
} }
@ -16,22 +19,29 @@ message Events {
// 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 { message Event { // `autogenpb:marshal`
int32 id = 1; enum status {
DONE = 0;
FAIL = 1;
RUNNING = 2;
}
int32 id = 1; // `autogenpb:unique` // should be unique across the cluster
EventType etype = 2; EventType etype = 2;
string droplet = 3; // name of the droplet string dropletName = 3; // name of the droplet
string droplet_uuid = 4; // uuid of the droplet string dropletUuid = 4; // uuid of the droplet
string hypervisor = 5; // name of the hypervisor string hypervisor = 5; // name of the hypervisor
string hypervisor_uuid = 6; // uuid of the hypervisor string hypervisorUuid = 6; // uuid of the hypervisor
google.protobuf.Timestamp start = 7; // start time google.protobuf.Timestamp start = 7; // start time
google.protobuf.Timestamp end = 8; // end time google.protobuf.Timestamp end = 8; // end time
string field_name = 9; // the field name that changed string fieldName = 9; // the field name that changed
string orig_val = 10; // original value string origVal = 10; // original value
string new_val = 11; // new value string newVal = 11; // new value
google.protobuf.Any orig_any = 12; // anypb format. probably overkill google.protobuf.Any origAny = 12; // anypb format. probably overkill
google.protobuf.Any new_any = 13; // anypb format 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;
@ -46,4 +56,5 @@ enum EventType {
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
} }

View File

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

View File

@ -1,98 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
pb "go.wit.com/lib/protobuf/virtbuf"
)
//
// saves entries in a config file
//
func main() {
TestWriteCluster()
_, err := ioutil.ReadFile("/tmp/testing4.protobuf")
if err != nil {
log.Fatalln("Error reading file:", err)
}
var c *pb.NewCluster
c = pb.InitCluster()
// log.Println(aCluster.String())
// show the droplets to STDOUT
loop := c.DropletsAll() // get the list of droplets
for loop.Scan() {
d := loop.Droplet()
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.NewCluster) {
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() {
c := pb.CreateSampleCluster(7)
os.Setenv("VIRTIGO_HOME", "/tmp/virtigo/")
if err := c.ConfigSave(); err != nil {
fmt.Println("configsave error", err)
os.Exit(-1)
}
// 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")
}
}
*/

View File

@ -1,19 +0,0 @@
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,77 +1,17 @@
package virtbuf package virtpb
// 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
import ( func InitCluster() *OldCluster {
"google.golang.org/protobuf/encoding/protojson" var c *OldCluster
"google.golang.org/protobuf/encoding/prototext" c = new(OldCluster)
"google.golang.org/protobuf/proto"
)
func InitCluster() *NewCluster {
var c *NewCluster
c = new(NewCluster)
c.d = new(Droplets) c.d = new(Droplets)
c.H = new(Hypervisors) c.H = new(Hypervisors)
c.e = new(Events) c.e = new(Events)
return c return c
} }
// human readable JSON func (c *OldCluster) DropletsAll() *DropletScanner {
func (d *Droplets) FormatJSON() string { return c.d.All()
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 (d *Droplets) FormatTEXT() string {
return prototext.Format(d)
}
func (e *Events) FormatTEXT() string {
return prototext.Format(e)
}
// marshal
func (d *Droplets) MarshalJSON() ([]byte, error) {
return protojson.Marshal(d)
}
func (d *Droplet) MarshalJSON() ([]byte, error) {
return protojson.Marshal(d)
}
func (e *Events) MarshalJSON() ([]byte, error) {
return protojson.Marshal(e)
}
// unmarshal
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)
}
func (d *Droplet) Unmarshal(data []byte) error {
return proto.Unmarshal(data, d)
} }

198
human.go Normal file
View File

@ -0,0 +1,198 @@
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,24 +1,27 @@
syntax = "proto3"; syntax = "proto3";
package virtbuf; package virtpb;
message Hypervisors { import "google/protobuf/timestamp.proto";
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations message Hypervisors { // `autogenpb:marshal` `autogenpb:gui`
string uuid = 1; // `autogenpb:uuid:6e3aa8b9-cf98-40f6-af58-3c6ad1edf4d4`
string version = 2; // `autogenpb:version:v0.0.1`
repeated Hypervisor hypervisors = 3; repeated Hypervisor hypervisors = 3;
} }
message Hypervisor { message Hypervisor {
string uuid = 1; string uuid = 1; // `autogenpb:unique`
string hostname = 2; string hostname = 2; // `autogenpb:unique`
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; HypervisorArch arch = 8;
int64 killcount = 9; // in bytes
google.protobuf.Timestamp lastPoll = 10; // the last time we heard anything
} }
// think about this more
enum HypervisorArch { enum HypervisorArch {
RISCV64 = 0; RISCV64 = 0;
X86_64 = 1; X86_64 = 1;

View File

@ -1,19 +0,0 @@
package virtbuf
import (
sync "sync"
durationpb "google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type NewCluster struct {
sync.RWMutex
Dirs []string
d *Droplets
H *Hypervisors
e *Events
Unstable *timestamppb.Timestamp
UnstableTimeout *durationpb.Duration
}

48
oldCluster.go Normal file
View File

@ -0,0 +1,48 @@
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 virtbuf package virtpb
import ( import (
"fmt" "fmt"
@ -39,22 +39,6 @@ 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)
@ -67,7 +51,7 @@ func CreateSampleEvents(total int) *Events {
return e return e
} }
func CreateSampleCluster(total int) *NewCluster { func CreateSampleCluster(total int) *OldCluster {
c := InitCluster() c := InitCluster()
for i := 0; i < total; i++ { for i := 0; i < total; i++ {

View File

@ -1,106 +0,0 @@
package virtbuf
import (
"fmt"
"os"
"go.wit.com/log"
)
type DropletIterator struct {
droplets []*Droplet
index int
}
// NewDropletIterator initializes a new iterator.
func NewDropletIterator(droplets []*Droplet) *DropletIterator {
return &DropletIterator{droplets: droplets}
}
// Scan moves to the next element and returns false if there are no more droplets.
func (it *DropletIterator) Scan() bool {
if it.index >= len(it.droplets) {
return false
}
it.index++
return true
}
// Droplet returns the current droplet.
func (it *DropletIterator) Droplet() *Droplet {
if it.droplets[it.index-1] == nil {
for i, d := range it.droplets {
fmt.Println("i =", i, d)
}
fmt.Println("len =", len(it.droplets))
fmt.Println("droplet == nil", it.index, it.index-1)
os.Exit(-1)
}
return it.droplets[it.index-1]
}
// Use Scan() in a loop, similar to a while loop
//
// for iterator.Scan() {
// d := iterator.Droplet()
// fmt.Println("Droplet UUID:", d.Uuid)
// }
func (c *NewCluster) GetDropletIterator() *DropletIterator {
dropletPointers := c.SelectDropletPointers()
iterator := NewDropletIterator(dropletPointers)
return iterator
}
func (c *NewCluster) DropletsAll() *DropletIterator {
dropletPointers := c.SelectDropletAll()
iterator := NewDropletIterator(dropletPointers)
return iterator
}
// SelectDropletPointers safely returns a slice of pointers to Droplet records.
func (c *NewCluster) SelectDropletAll() []*Droplet {
c.RLock()
defer c.RUnlock()
// Create a new slice to hold pointers to each Droplet
// dropletPointers := make([]*Droplet, len(c.E.Droplets))
var dropletPointers []*Droplet
if c.d == nil {
log.Info("SelectDropletsAll() c.d == nil")
// os.Exit(-1)
}
for _, d := range c.d.Droplets {
if d == nil {
continue
}
if d.Archive != nil {
continue
}
dropletPointers = append(dropletPointers, d) // Copy pointers for safe iteration
}
return dropletPointers
}
// SelectDropletPointers safely returns a slice of pointers to Droplet records.
func (c *NewCluster) SelectDropletPointers() []*Droplet {
c.RLock()
defer c.RUnlock()
// Create a new slice to hold pointers to each Droplet
// dropletPointers := make([]*Droplet, len(c.E.Droplets))
dropletPointers := make([]*Droplet, 1)
if c.d == nil {
log.Info("c.d == nil")
os.Exit(-1)
}
for _, d := range c.d.Droplets {
dropletPointers = append(dropletPointers, d) // Copy pointers for safe iteration
}
return dropletPointers
}

View File

@ -1,11 +1,6 @@
package virtbuf package virtpb
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)
@ -37,7 +32,6 @@ func (s *WhatInfo) UnmarshalJSON(data []byte) error {
return nil return nil
} }
/*
func main() { func main() {
info := WhatInfo{Capacity: 64} info := WhatInfo{Capacity: 64}

64
time.go
View File

@ -1,64 +0,0 @@
package virtbuf
import (
"fmt"
"time"
)
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", seconds/1000)
}
result += fmt.Sprintf("%dms", ms)
return result
}
func GetDurationStamp(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)
}