Compare commits
68 Commits
Author | SHA1 | Date |
---|---|---|
|
e6fb7352ae | |
|
c9ef4f0b82 | |
|
688b5039a0 | |
|
814d36b9c9 | |
|
a07033d181 | |
|
7cdb2a33ef | |
|
63148556af | |
|
a510dd6474 | |
|
e4345c8ad6 | |
|
276c5cec2f | |
|
e78fc1235e | |
|
c82997ed61 | |
|
4aef241137 | |
|
c6cb62c86d | |
|
0f546d57fc | |
|
13159b5b64 | |
|
9dfcbb0432 | |
|
3ca7403aa6 | |
|
65f9089a7a | |
|
b25d86f277 | |
|
d40dc91130 | |
|
2381c65887 | |
|
ddc0410126 | |
|
9160268326 | |
|
0888e51c91 | |
|
8f3953159a | |
|
aeac6b5af7 | |
|
afd0bd6428 | |
|
5b883de7b9 | |
|
3046ff335f | |
|
81cbb6e9d7 | |
|
74da63276e | |
|
3b63a3af24 | |
|
907981a92d | |
|
b9766ce266 | |
|
4fdb1934ff | |
|
2f715b47d5 | |
|
17f8c31027 | |
|
d8c3744f20 | |
|
1a72fdceef | |
|
f36c19f04f | |
|
36e69dd84c | |
|
c2e30a373a | |
|
6337988092 | |
|
20e958559e | |
|
adb44a864f | |
|
706dbbc533 | |
|
b6f5594fe6 | |
|
2b77b8a89d | |
|
284e9161de | |
|
9ad173a845 | |
|
e8834578fb | |
|
10793e365d | |
|
f4cb9e27ce | |
|
08757bc315 | |
|
cc0ca1dd7c | |
|
1ca936e98e | |
|
e58f78eb2d | |
|
67cb013c83 | |
|
96f29d6f3b | |
|
c2229b65ab | |
|
1352c3c49f | |
|
89f0d31e0f | |
|
301fbfc3b0 | |
|
18053caca8 | |
|
9608bb680f | |
|
6a0d4d3e38 | |
|
42e34f41cf |
|
@ -1,5 +1,3 @@
|
|||
go.*
|
||||
|
||||
*.pb.go
|
||||
|
||||
example/example
|
||||
*.swp
|
||||
|
|
48
Makefile
48
Makefile
|
@ -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
|
||||
# go install
|
||||
|
||||
all: proto goimports vet
|
||||
|
||||
all: droplet.pb.go hypervisor.pb.go cluster.pb.go event.pb.go experiments.pb.go
|
||||
make -C example
|
||||
|
||||
vet: lint
|
||||
GO111MODULE=off go vet
|
||||
|
||||
lint:
|
||||
-buf lint droplet.proto
|
||||
vet:
|
||||
@GO111MODULE=off go vet
|
||||
@echo this go library package builds okay
|
||||
|
||||
# autofixes your import headers in your golang files
|
||||
goimports:
|
||||
goimports -w *.go
|
||||
make -C example goimports
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
|
@ -27,40 +21,20 @@ redomod:
|
|||
clean:
|
||||
rm -f *.pb.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
|
||||
# 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
|
||||
autogenpb --proto droplet.proto
|
||||
|
||||
hypervisor.pb.go: 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
|
||||
autogenpb --proto hypervisor.proto
|
||||
|
||||
event.pb.go: 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
|
||||
autogenpb --proto event.proto
|
||||
|
||||
cluster.pb.go: 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
|
||||
autogenpb --proto cluster.proto
|
||||
|
||||
deps:
|
||||
apt install golang-goprotobuf-dev
|
||||
|
|
15
README.md
15
README.md
|
@ -1,13 +1,6 @@
|
|||
They are what to use for starting droplets with virsh,
|
||||
but for making a cluster or "home cloud" with virtigo
|
||||
This go library handles the protobuf files
|
||||
and various functions for virtigo.
|
||||
|
||||
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'
|
||||
You must build the protobuf files using autogenpb
|
||||
|
||||
go install go.wit.com/apps/autogenpb@latest
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
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, ""
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
// thank chatgpt for this because why. why write this if you can have it
|
||||
// kick this out in 30 seconds
|
||||
|
@ -14,7 +14,7 @@ func backupDir(srcDir string, destDir string) error {
|
|||
// Create the destination directory
|
||||
err := os.MkdirAll(destDir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Println("Failed to create directory: %v", err)
|
||||
log.Printf("Failed to create directory: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func backupDir(srcDir string, destDir string) error {
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("Failed to copy files: %v", err)
|
||||
log.Printf("Failed to copy files: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
74
change.go
74
change.go
|
@ -1,4 +1,4 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
import (
|
||||
// "reflect"
|
||||
|
@ -34,7 +34,6 @@ func convertToAnypb(x any) *anypb.Any {
|
|||
return a
|
||||
default:
|
||||
log.Error(errors.New("convertToAnypb() unknown type"), "v =", v, "x =", x)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -49,6 +48,14 @@ func convertToString(x any) string {
|
|||
return fmt.Sprintf("%d", x.(int))
|
||||
case 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:
|
||||
if x.(bool) {
|
||||
return "true"
|
||||
|
@ -57,7 +64,6 @@ func convertToString(x any) string {
|
|||
default:
|
||||
log.Info("convertToSTring() unknown type", v)
|
||||
log.Error(errors.New("convertToSTring() unknown type"), "v =", v, "x =", x)
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -67,7 +73,7 @@ func (d *Droplet) NewChangeEvent(fname string, origval any, newval any) *Event {
|
|||
var e *Event
|
||||
e = new(Event)
|
||||
|
||||
e.Droplet = d.Hostname
|
||||
e.DropletName = d.Hostname
|
||||
e.OrigVal = convertToString(origval)
|
||||
e.NewVal = convertToString(newval)
|
||||
e.FieldName = fname
|
||||
|
@ -93,14 +99,12 @@ func NewAddEvent(a any, fname string, newval any) *Event {
|
|||
case *Droplet:
|
||||
var d *Droplet
|
||||
d = a.(*Droplet)
|
||||
e.Droplet = d.Hostname
|
||||
case *Cluster:
|
||||
e.Droplet = "Cluster"
|
||||
e.DropletName = d.Hostname
|
||||
case nil:
|
||||
e.Droplet = "<nil>"
|
||||
e.DropletName = "<nil>"
|
||||
default:
|
||||
log.Info("newAddEvent() unknown type", v)
|
||||
e.Droplet = "on something somewhere"
|
||||
e.DropletName = "on something somewhere"
|
||||
}
|
||||
|
||||
e.NewVal = convertToString(newval)
|
||||
|
@ -130,35 +134,63 @@ func (d *Droplet) SetCpus(b int64) {
|
|||
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)
|
||||
func (c *Cluster) ChangeDropletState(d *Droplet, newState DropletState) error {
|
||||
func (c *OldCluster) ChangeDropletState(d *Droplet, newState DropletState) error {
|
||||
if c == nil {
|
||||
return errors.New("cluster is nil")
|
||||
}
|
||||
if d == nil {
|
||||
return errors.New("droplet is nil")
|
||||
}
|
||||
if d.CurrentState == newState {
|
||||
if d.Current.State == newState {
|
||||
// droplet status didn't change
|
||||
return nil
|
||||
}
|
||||
var e *Event
|
||||
e = new(Event)
|
||||
|
||||
e.Droplet = d.Hostname
|
||||
e.OrigVal = convertToString(d.CurrentState)
|
||||
e.DropletName = d.Hostname
|
||||
e.OrigVal = convertToString(d.Current.State)
|
||||
e.NewVal = convertToString(newState)
|
||||
e.FieldName = "status"
|
||||
|
||||
now := time.Now()
|
||||
e.Start = timestamppb.New(now)
|
||||
|
||||
c.Events = append(c.Events, e)
|
||||
c.e.Events = append(c.e.Events, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// records an event that the droplet migrated to another hypervisor
|
||||
func (c *Cluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
|
||||
func (c *OldCluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
|
||||
if c == nil {
|
||||
return errors.New("cluster is nil")
|
||||
}
|
||||
|
@ -168,7 +200,7 @@ func (c *Cluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
|
|||
if newh == nil {
|
||||
return errors.New("hypervisor is nil")
|
||||
}
|
||||
if d.CurrentHypervisor == newh.Hostname {
|
||||
if d.Current.Hypervisor == newh.Hostname {
|
||||
// droplet didn't move
|
||||
return nil
|
||||
}
|
||||
|
@ -177,17 +209,17 @@ func (c *Cluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
|
|||
var e *Event
|
||||
e = new(Event)
|
||||
|
||||
e.Droplet = d.Hostname
|
||||
e.OrigVal = d.CurrentHypervisor
|
||||
e.DropletName = d.Hostname
|
||||
e.OrigVal = d.Current.Hypervisor
|
||||
e.NewVal = newh.Hostname
|
||||
e.FieldName = "droplet migrate"
|
||||
|
||||
now := time.Now()
|
||||
e.Start = timestamppb.New(now)
|
||||
|
||||
c.Events = append(c.Events, e)
|
||||
|
||||
c.e.Events = append(c.e.Events, e)
|
||||
|
||||
// update the droplet record
|
||||
d.CurrentHypervisor = newh.Hostname
|
||||
d.Current.Hypervisor = newh.Hostname
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
syntax = "proto3";
|
||||
package virtbuf;
|
||||
package virtpb;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "droplet.proto";
|
||||
import "hypervisor.proto";
|
||||
import "event.proto";
|
||||
|
||||
message Cluster {
|
||||
int64 id = 1;
|
||||
repeated string dirs = 2;
|
||||
repeated Droplet droplets = 3;
|
||||
repeated Hypervisor hypervisors = 4;
|
||||
repeated Event events = 5;
|
||||
message Cluster { // `autogenpb:marshal`
|
||||
string uuid = 1; // `autogenpb:unique`
|
||||
string name = 2;
|
||||
repeated string URL = 3;
|
||||
google.protobuf.Timestamp ctime = 4; // when the cluster was created
|
||||
Droplets droplets = 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;
|
||||
}
|
||||
|
|
404
config.go
404
config.go
|
@ -1,4 +1,4 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
// functions to import and export the protobuf
|
||||
// data to and from config files
|
||||
|
@ -8,187 +8,139 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"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
|
||||
// to make it humanly possible to hand edit things as needed
|
||||
func (c *Cluster) ConfigSave() error {
|
||||
func (c *OldCluster) ConfigSave() error {
|
||||
// try to backup the current cluster config files
|
||||
if err := backupConfigFiles(); err != nil {
|
||||
if err := backupConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var d *Droplets
|
||||
d = new(Droplets)
|
||||
d.Droplets = c.Droplets
|
||||
if err := ConfigWriteJSON(d, "droplets.json"); err != nil {
|
||||
fmt.Println("droplets.json write failed")
|
||||
return err
|
||||
}
|
||||
if err := ConfigWriteTEXT(d, "droplets.text"); err != nil {
|
||||
// make a new droplets struct
|
||||
var dcopy *Droplets
|
||||
dcopy = new(Droplets)
|
||||
loop := c.d.All() // get the list of droplets
|
||||
for loop.Scan() {
|
||||
d := loop.Next()
|
||||
dcopy.Droplets = append(dcopy.Droplets, d)
|
||||
}
|
||||
// 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")
|
||||
return err
|
||||
}
|
||||
c.configWriteDroplets()
|
||||
|
||||
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 {
|
||||
if err := ConfigWriteTEXT(c.H, "hypervisors.text"); err != nil {
|
||||
fmt.Println("hypervisors.json write failed")
|
||||
return err
|
||||
}
|
||||
|
||||
var e *Events
|
||||
e = new(Events)
|
||||
e.Events = c.Events
|
||||
if err := ConfigWriteJSON(e, "events.json"); err != nil {
|
||||
if err := ConfigWriteJSON(c.e, "events.json"); err != nil {
|
||||
fmt.Println("events.json write failed")
|
||||
return err
|
||||
}
|
||||
if err := ConfigWriteTEXT(e, "events.text"); err != nil {
|
||||
if err := ConfigWriteTEXT(c.e, "events.text"); err != nil {
|
||||
fmt.Println("events.json write failed")
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
|
||||
}
|
||||
|
||||
// erase or zero fields that shouldn't ever be written to the config file
|
||||
c.BlankFields()
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Cluster) ConfigLoad() error {
|
||||
func (c *OldCluster) ConfigLoad() error {
|
||||
if c == nil {
|
||||
return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
|
||||
}
|
||||
|
||||
// load the cluster config file
|
||||
if data, err := loadFile("cluster.text"); err == nil {
|
||||
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
|
||||
}
|
||||
|
||||
var d *Droplets
|
||||
d = new(Droplets)
|
||||
// load the droplet config file
|
||||
if data, err := loadFile("droplets.json"); err == nil {
|
||||
if err = protojson.Unmarshal(data, d); err != nil {
|
||||
fmt.Println("broken droplets.json config file")
|
||||
if data, err := loadFile("droplets.text"); err == nil {
|
||||
if err = prototext.Unmarshal(data, c.d); err != nil {
|
||||
fmt.Println("broken droplets.text config file")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
// 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")
|
||||
if data, err := loadFile("hypervisors.text"); err == nil {
|
||||
if err = prototext.Unmarshal(data, c.H); err != nil {
|
||||
fmt.Println("broken hypervisors.text config file")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println("ERROR HERE IN Hypervisors")
|
||||
log.Warn("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)
|
||||
|
||||
if c.e == nil {
|
||||
// this seems to panic on nil. something is wrong about doing this
|
||||
// does it not stay allocated after this function ends?
|
||||
c.e = new(Events)
|
||||
}
|
||||
|
||||
var e *Events
|
||||
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
|
||||
}
|
||||
|
||||
func (e *Events) loadEvents() error {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
// 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 {
|
||||
if data, err = loadFile("events.json"); err != nil {
|
||||
fmt.Println("broken events.json config file")
|
||||
return err
|
||||
}
|
||||
// copy them over. is this needed? does the memory free otherwise?
|
||||
for _, a := range e.Events {
|
||||
c.Events = append(c.Events, a)
|
||||
|
||||
err = protojson.Unmarshal(data, e)
|
||||
if err != nil {
|
||||
fmt.Println("broken events.json config file")
|
||||
// json load failed. try loading prototext
|
||||
if data, err = loadFile("events.text"); err != nil {
|
||||
fmt.Println("broken events.text config file")
|
||||
fmt.Println(err)
|
||||
return errors.New("events.text file is broken")
|
||||
}
|
||||
if err = prototext.Unmarshal(data, e); err != nil {
|
||||
fmt.Println("broken events.text config file")
|
||||
fmt.Println(err)
|
||||
return errors.New("events.text file is broken")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -208,7 +160,7 @@ func loadFile(filename string) ([]byte, error) {
|
|||
|
||||
func ConfigWriteJSON(a any, filename string) error {
|
||||
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()
|
||||
if err != nil {
|
||||
fmt.Println("open config file :", err)
|
||||
|
@ -223,9 +175,26 @@ func ConfigWriteJSON(a any, filename string) error {
|
|||
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 {
|
||||
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()
|
||||
if err != nil {
|
||||
fmt.Println("open config file :", err)
|
||||
|
@ -240,165 +209,18 @@ func ConfigWriteTEXT(a any, filename string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func WriteConfig(d *Droplets, h *Hypervisors, e *Events) bool {
|
||||
if !d.WriteConfigJSON() {
|
||||
return false
|
||||
}
|
||||
if !d.WriteConfigTEXT() {
|
||||
return false
|
||||
func (c *Clusters) ConfigLoad() error {
|
||||
if c == nil {
|
||||
return errors.New("It's not safe to run ConfigLoad() on a nil cluster")
|
||||
}
|
||||
|
||||
if err := e.WriteConfigJSON(); err != nil {
|
||||
return false
|
||||
}
|
||||
if err := e.WriteConfigTEXT(); err != nil {
|
||||
return false
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
text := e.FormatJSON()
|
||||
fmt.Fprintln(cfgfile, text)
|
||||
fmt.Println("Write:", fullname, "OK")
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
// thank chatgpt for this because why. why write this if you can have it
|
||||
// kick this out in 30 seconds
|
||||
|
@ -10,8 +10,20 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"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 {
|
||||
// Create the destination directory
|
||||
err := os.MkdirAll(destDir, os.ModePerm)
|
100
droplet.proto
100
droplet.proto
|
@ -1,54 +1,82 @@
|
|||
syntax = "proto3";
|
||||
package virtbuf;
|
||||
package virtpb;
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/duration.proto"; // Import the well-known type for Timestamp
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
message Droplets {
|
||||
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
|
||||
repeated Droplet droplets = 3;
|
||||
// global settings for autogenpb `autogenpb:mutex`
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
message Droplet {
|
||||
string uuid = 1; // should be unique across the cluster
|
||||
string hostname = 2; // should be unique and work in DNS
|
||||
int64 cpus = 3; // what's the point of int64 vs int32
|
||||
int64 memory = 4; // in bytes
|
||||
DropletState start_state = 5; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off')
|
||||
string notes = 6; // maybe useful for something
|
||||
string preferred_hypervisor = 7; // the hypervisor to prefer to run the droplet on
|
||||
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
|
||||
message Droplet { // `autogenpb:marshal`
|
||||
string uuid = 1; // `autogenpb:unique` // should be unique across the cluster
|
||||
string hostname = 2; // `autogenpb:unique` // should be unique and work in DNS
|
||||
int64 cpus = 3; // what's the point of int64 vs int32
|
||||
int64 memory = 4; // in bytes
|
||||
Current current = 5; // what the state and values of the droplet is
|
||||
DropletState startState = 6; // what the state of the droplet is SUPPOSED TO BE ('on' or 'off')
|
||||
string qemuMachine = 7; // the qemu machine type to use "pc-q35-9.0"
|
||||
int64 spicePort = 8; // preferred port to use for spice
|
||||
|
||||
repeated Network networks = 12; // really just mac addresses. should be unique across cluster
|
||||
repeated Disk disks = 13; // disks to attach
|
||||
string preferredHypervisor = 9; // the hypervisor to prefer to run the droplet on
|
||||
string forceHypervisor = 10; // use this hypervisor and this hypervisor only
|
||||
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
|
||||
|
||||
DropletState state = 14; // if the droplet is on, off, etc
|
||||
string image_url = 15; // url to the image
|
||||
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
|
||||
string current_hypervisor = 18; // the current hypervisor the droplet is running on
|
||||
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
|
||||
string localOnly = 14; // this is only defined locally on the hypervisor
|
||||
string customXml = 15; // if needed,
|
||||
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.Duration unstableTimeout = 40; // the last time we heard anything from this droplet
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ON = 0;
|
||||
OFF = 1;
|
||||
UNKNOWN = 2;
|
||||
MIGRATING = 3;
|
||||
ON = 0;
|
||||
OFF = 1;
|
||||
UNKNOWN = 2; // qemu says 'Shutdown'
|
||||
PAUSED = 3;
|
||||
CRASHED = 4;
|
||||
INMIGRATE = 5;
|
||||
}
|
||||
|
||||
enum DropletArchive {
|
||||
DUP = 0;
|
||||
USER = 1;
|
||||
}
|
||||
|
||||
message Network {
|
||||
string mac = 1;
|
||||
string name = 2;
|
||||
string mac = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message Disk {
|
||||
string filename = 1;
|
||||
string filepath = 2;
|
||||
int64 size = 3;
|
||||
string filename = 1;
|
||||
string filepath = 2;
|
||||
int64 size = 3;
|
||||
string qemuArch = 4; // what arch. example: "x86_64" or "riscv64"
|
||||
}
|
||||
|
|
79
event.proto
79
event.proto
|
@ -1,14 +1,17 @@
|
|||
syntax = "proto3";
|
||||
package virtbuf;
|
||||
package virtpb;
|
||||
|
||||
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 "droplet.proto";
|
||||
|
||||
message Events {
|
||||
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
|
||||
int64 event_size = 3; // max events to store in a single
|
||||
repeated Event events = 4; // all the events
|
||||
// global settings for autogenpb `autogenpb:no-sort` `autogenpb:mutex`
|
||||
|
||||
message Events { // `autogenpb:marshal` `autogenpb:gui`
|
||||
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
|
||||
}
|
||||
|
||||
// this information leans towards being human readable not programatic
|
||||
|
@ -16,34 +19,42 @@ message Events {
|
|||
// at least for now in the early days. but maybe forever.
|
||||
// homelab clouds normally don't have many events.
|
||||
// we are talking less than 1 a minute. even 1 an hour is often a lot
|
||||
message Event {
|
||||
int32 id = 1;
|
||||
EventType etype = 2;
|
||||
string droplet = 3; // name of the droplet
|
||||
string droplet_uuid = 4; // uuid of the droplet
|
||||
string hypervisor = 5; // name of the hypervisor
|
||||
string hypervisor_uuid = 6; // uuid of the hypervisor
|
||||
google.protobuf.Timestamp start = 7; // start time
|
||||
google.protobuf.Timestamp end = 8; // end time
|
||||
string field_name = 9; // the field name that changed
|
||||
string orig_val = 10; // original value
|
||||
string new_val = 11; // new value
|
||||
google.protobuf.Any orig_any = 12; // anypb format. probably overkill
|
||||
google.protobuf.Any new_any = 13; // anypb format
|
||||
message Event { // `autogenpb:marshal`
|
||||
enum status {
|
||||
DONE = 0;
|
||||
FAIL = 1;
|
||||
RUNNING = 2;
|
||||
}
|
||||
int32 id = 1; // `autogenpb:unique` // should be unique across the cluster
|
||||
EventType etype = 2;
|
||||
string dropletName = 3; // name of the droplet
|
||||
string dropletUuid = 4; // uuid of the droplet
|
||||
string hypervisor = 5; // name of the hypervisor
|
||||
string hypervisorUuid = 6; // uuid of the hypervisor
|
||||
google.protobuf.Timestamp start = 7; // start time
|
||||
google.protobuf.Timestamp end = 8; // end time
|
||||
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 {
|
||||
ADD = 0;
|
||||
DELETE = 1;
|
||||
POWERON = 2;
|
||||
POWEROFF = 3; // should indicate a "normal" shutdown
|
||||
HIBERNATE = 4;
|
||||
MIGRATE = 5;
|
||||
DEMO = 6;
|
||||
GET = 7; // request something
|
||||
LOGIN = 8; // attempt to login
|
||||
OK = 9; // everything is ok
|
||||
FAIL = 10; // everything failed
|
||||
CRASH = 11; // droplet hard crashed
|
||||
CHANGE = 12; // droplet or hypervisor config change
|
||||
ADD = 0;
|
||||
DELETE = 1;
|
||||
POWERON = 2;
|
||||
POWEROFF = 3; // should indicate a "normal" shutdown
|
||||
HIBERNATE = 4;
|
||||
MIGRATE = 5;
|
||||
DEMO = 6;
|
||||
GET = 7; // request something
|
||||
LOGIN = 8; // attempt to login
|
||||
OK = 9; // everything is ok
|
||||
FAIL = 10; // everything failed
|
||||
CRASH = 11; // droplet hard crashed
|
||||
CHANGE = 12; // droplet or hypervisor config change
|
||||
EDIT = 13; // edit droplet settings
|
||||
}
|
||||
|
|
|
@ -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
|
Binary file not shown.
|
@ -1,98 +0,0 @@
|
|||
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")
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
137
helpers.go
137
helpers.go
|
@ -1,130 +1,17 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
// functions to import and export the protobuf
|
||||
// data to and from config files
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// 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 InitCluster() *OldCluster {
|
||||
var c *OldCluster
|
||||
c = new(OldCluster)
|
||||
c.d = new(Droplets)
|
||||
c.H = new(Hypervisors)
|
||||
c.e = new(Events)
|
||||
return c
|
||||
}
|
||||
|
||||
func SetMB(mb int) int64 {
|
||||
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)
|
||||
func (c *OldCluster) DropletsAll() *DropletScanner {
|
||||
return c.d.All()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,18 +1,29 @@
|
|||
syntax = "proto3";
|
||||
package virtbuf;
|
||||
package virtpb;
|
||||
|
||||
message Hypervisors {
|
||||
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
|
||||
repeated Hypervisor hypervisors = 3;
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
message Hypervisor {
|
||||
string uuid = 1;
|
||||
string hostname = 2;
|
||||
bool active = 3; // is allowed to start new droplets
|
||||
int64 cpus = 4;
|
||||
int64 memory = 5; // in bytes
|
||||
string comment = 6;
|
||||
bool autoscan = 7; // to scan or not to scan by virtigo
|
||||
string uuid = 1; // `autogenpb:unique`
|
||||
string hostname = 2; // `autogenpb:unique`
|
||||
bool active = 3; // is allowed to start new droplets
|
||||
int64 cpus = 4;
|
||||
int64 memory = 5; // in bytes
|
||||
string comment = 6;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package virtbuf
|
||||
package virtpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -21,7 +21,6 @@ func CreateSampleDroplet(hostname string) *Droplet {
|
|||
d := &Droplet{
|
||||
Uuid: id.String(),
|
||||
Hostname: hostname,
|
||||
Notes: "this is a droplet for testing",
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
@ -40,22 +39,6 @@ func CreateSampleHypervisor(hostname string, mem int) *Hypervisor {
|
|||
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 {
|
||||
var e *Events
|
||||
e = new(Events)
|
||||
|
@ -68,21 +51,19 @@ func CreateSampleEvents(total int) *Events {
|
|||
return e
|
||||
}
|
||||
|
||||
func CreateSampleCluster(total int) *Cluster {
|
||||
var c *Cluster
|
||||
c = new(Cluster)
|
||||
func CreateSampleCluster(total int) *OldCluster {
|
||||
c := InitCluster()
|
||||
|
||||
for i := 0; i < total; i++ {
|
||||
hostname := fmt.Sprintf("bmath%d.wit.com", i)
|
||||
d := CreateSampleDroplet(hostname)
|
||||
d.Notes = fmt.Sprintf("Sample Droplet %d", i)
|
||||
d.PreferredHypervisor = fmt.Sprintf("farm%d", i)
|
||||
if d.PreferredHypervisor == "farm4" {
|
||||
d.Cpus = 16
|
||||
d.Memory = SetGB(256)
|
||||
}
|
||||
|
||||
c.Droplets = append(c.Droplets, d)
|
||||
c.d.Droplets = append(c.d.Droplets, d)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
|
@ -90,7 +71,7 @@ func CreateSampleCluster(total int) *Cluster {
|
|||
h := CreateSampleHypervisor(hostname, i+1)
|
||||
h.Comment = fmt.Sprintf("Sample hypervisor %d", i)
|
||||
|
||||
c.Hypervisors = append(c.Hypervisors, h)
|
||||
c.H.Hypervisors = append(c.H.Hypervisors, h)
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
package virtbuf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
package virtpb
|
||||
|
||||
/*
|
||||
// MarshalJSON custom marshals the WhatInfo struct to JSON
|
||||
func (s WhatInfo) MarshalJSON() ([]byte, error) {
|
||||
capacityStr := fmt.Sprintf("%d GB", s.Capacity)
|
||||
|
@ -37,7 +32,6 @@ func (s *WhatInfo) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func main() {
|
||||
info := WhatInfo{Capacity: 64}
|
||||
|
||||
|
|
Loading…
Reference in New Issue