Compare commits
32 Commits
Author | SHA1 | Date |
---|---|---|
|
dbcd3b5686 | |
|
f67c81d499 | |
|
f39c7d4a51 | |
|
65563eb8e2 | |
|
bf01596f30 | |
|
d261a220df | |
|
7fd5089917 | |
|
82ebc25936 | |
|
4faca63da8 | |
|
c8a50fbb18 | |
|
4332b3d31a | |
|
2c5701eeca | |
|
a24448a9d4 | |
|
4121e66e01 | |
|
a4dd085a47 | |
|
d3f809b25d | |
|
8eda4cf2da | |
|
3cd1f64d15 | |
|
69ee3b95d0 | |
|
16558e1b72 | |
|
1fd6b1d36d | |
|
a7e639cdb0 | |
|
03b03cb197 | |
|
a97379d76f | |
|
46472fa868 | |
|
599fe4251f | |
|
50d16b3d86 | |
|
19b1588512 | |
|
0a452c005b | |
|
7ee465da56 | |
|
8517dbc948 | |
|
d2d04da122 |
13
Makefile
13
Makefile
|
@ -8,6 +8,10 @@ REDOMOD = $(shell if [ -e go.sum ]; then echo go.sum exists; else GO111MODULE=
|
||||||
|
|
||||||
all: install
|
all: install
|
||||||
@echo build worked
|
@echo build worked
|
||||||
|
virtigo list droplets
|
||||||
|
virtigo list droplets --on
|
||||||
|
virtigo droplet show --name check.lab.wit.org
|
||||||
|
virtigo droplet start --name check.lab.wit.org
|
||||||
|
|
||||||
build: goimports vet
|
build: goimports vet
|
||||||
GO111MODULE=off go build \
|
GO111MODULE=off go build \
|
||||||
|
@ -88,7 +92,7 @@ redomod:
|
||||||
clean:
|
clean:
|
||||||
rm -f go.*
|
rm -f go.*
|
||||||
rm -f virtigo*
|
rm -f virtigo*
|
||||||
go-mod-clean --purge
|
go-mod-clean purge
|
||||||
|
|
||||||
# git clone the sources and all the golang dependancies into ~/go/src
|
# git clone the sources and all the golang dependancies into ~/go/src
|
||||||
# if you don't have go-clone, you can get it from http://go.wit.com/
|
# if you don't have go-clone, you can get it from http://go.wit.com/
|
||||||
|
@ -113,3 +117,10 @@ http-dumplibvirtxml:
|
||||||
protogen:
|
protogen:
|
||||||
go-clone google.golang.org/protobuf
|
go-clone google.golang.org/protobuf
|
||||||
cd ~/go/src/google.golang.org/protobuf/cmd/protoc-gen-go && go install
|
cd ~/go/src/google.golang.org/protobuf/cmd/protoc-gen-go && go install
|
||||||
|
|
||||||
|
gocui: install
|
||||||
|
virtigo --gui gocui --gui-verbose --gui-file ../../toolkits/gocui/gocui.so --admin
|
||||||
|
# virtigo --gui gocui --gui-verbose --gui-file ../../toolkits/gocui/gocui.so --admin >/tmp/forge.log 2>&1
|
||||||
|
|
||||||
|
log:
|
||||||
|
journalctl -f -xeu virtigod.service
|
||||||
|
|
87
argv.go
87
argv.go
|
@ -1,6 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "go.wit.com/log"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
this parses the command line arguements
|
this parses the command line arguements
|
||||||
|
@ -11,52 +16,58 @@ import "go.wit.com/log"
|
||||||
var argv args
|
var argv args
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
Verbose bool `arg:"--verbose" help:"talk more"`
|
List *ListCmd `arg:"subcommand:list" help:"list things"`
|
||||||
|
Droplet *DropletCmd `arg:"subcommand:droplet" help:"send events to a droplet"`
|
||||||
Config string `arg:"env:VIRTIGO_HOME" help:"defaults to ~/.config/virtigo/"`
|
Config string `arg:"env:VIRTIGO_HOME" help:"defaults to ~/.config/virtigo/"`
|
||||||
Port int `arg:"--port" default:"8080" help:"allow droplet events via http"`
|
|
||||||
Server string `arg:"env:VIRTIGO_SERVER" help:"what virtigo cluster to connect to"`
|
Server string `arg:"env:VIRTIGO_SERVER" help:"what virtigo cluster to connect to"`
|
||||||
|
Localhost bool `arg:"--localhost" help:"use the local libvirt"`
|
||||||
|
Daemon bool `arg:"--daemon" help:"run as a daemon"`
|
||||||
|
Verbose bool `arg:"--verbose" help:"talk more"`
|
||||||
|
Port int `arg:"--port" default:"8080" help:"allow droplet events via http"`
|
||||||
Xml []string `arg:"--libvirt" help:"import qemu xml files: --libvirt /etc/libvirt/qemu/*.xml"`
|
Xml []string `arg:"--libvirt" help:"import qemu xml files: --libvirt /etc/libvirt/qemu/*.xml"`
|
||||||
|
Admin bool `arg:"--admin" help:"enter admin mode"`
|
||||||
|
Bash bool `arg:"--bash" help:"generate bash completion"`
|
||||||
|
BashAuto []string `arg:"--auto-complete" help:"todo: move this to go-arg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Daemon bool `arg:"--daemon" help:"run in daemon mode"`
|
type EmptyCmd struct {
|
||||||
// IgnoreCpu bool `arg:"--xml-ignore-cpu" default:"true" help:"ignore non-standard libvirt xml cpus"`
|
}
|
||||||
// IgnoreBr bool `arg:"--xml-ignore-net" default:"true" help:"ignore network bridge name changes"`
|
|
||||||
// IgnDisk bool `arg:"--xml-ignore-disk" default:"false" help:"ignore duplicate disk names"`
|
|
||||||
|
|
||||||
// Save bool `arg:"--save" default:"false" help:"save protobuf config after import"`
|
type testCmd string
|
||||||
// Start string `arg:"--start" help:"start a droplet"`
|
|
||||||
// Uptime bool `arg:"--uptime" default:"true" help:"allow uptime checks for things like Kuma"`
|
type ListCmd struct {
|
||||||
// Hosts []string `arg:"--hosts" help:"hosts to connect to"`
|
Droplets *EmptyCmd `arg:"subcommand:droplets" help:"list droplets"`
|
||||||
|
Hypervisors *EmptyCmd `arg:"subcommand:hypervisors" help:"list hypervisors"`
|
||||||
|
On bool `arg:"--on" help:"only show things that are on"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DropletCmd struct {
|
||||||
|
Start *EmptyCmd `arg:"subcommand:start" help:"start droplet"`
|
||||||
|
Stop *EmptyCmd `arg:"subcommand:stop" help:"stop droplet"`
|
||||||
|
Show *EmptyCmd `arg:"subcommand:show" help:"show droplet"`
|
||||||
|
Console *EmptyCmd `arg:"subcommand:console" help:"open serial console"`
|
||||||
|
VNC *EmptyCmd `arg:"subcommand:vnc" help:"open VNC console"`
|
||||||
|
Spice *EmptyCmd `arg:"subcommand:spice" help:"open spiceconsole"`
|
||||||
|
Name string `arg:"--name" help:"what droplet to start"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a args) Description() string {
|
func (a args) Description() string {
|
||||||
return `
|
return `
|
||||||
virtigo will help control your cluster
|
virtigo: control your cluster
|
||||||
|
|
||||||
This maintains a master list of all your vm's (aka 'droplets')
|
This maintains a master list of all your vm's (aka 'droplets')
|
||||||
in your homelab cloud. You can import libvirt xml files.
|
in your homelab cloud. You can import libvirt xml files.
|
||||||
This app talks to your hypervisors via the virtigod daemon.
|
This app talks to your hypervisors via the virtigod daemon.
|
||||||
|
|
||||||
At this time, this _only_ supports qcow2 images. If you need
|
|
||||||
something else you'll have to add it in virtigolib.
|
|
||||||
|
|
||||||
This runs a http server so you can control your virtual machines.
|
|
||||||
For example to start a vm called 'www.wit.com' your cluster 'foo.bar.com':
|
|
||||||
|
|
||||||
curl http://virtigo.foo.com/start?www.wit.com
|
|
||||||
|
|
||||||
Use 'virtigoctl' to import xml files from libvirt and configure new
|
|
||||||
hypervisors in your cluster.
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args) Version() string {
|
func (args) Version() string {
|
||||||
return "virtigo " + Version
|
return ARGNAME + " " + VERSION + " Built on " + BUILDTIME
|
||||||
}
|
}
|
||||||
|
|
||||||
var INFO *log.LogFlag
|
var INFO *log.LogFlag
|
||||||
var POLL *log.LogFlag
|
var POLL *log.LogFlag
|
||||||
var WARN *log.LogFlag
|
var WARN *log.LogFlag
|
||||||
var SPEW *log.LogFlag
|
|
||||||
var EVENT *log.LogFlag
|
var EVENT *log.LogFlag
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -66,6 +77,30 @@ func init() {
|
||||||
INFO = log.NewFlag("INFO", false, full, short, "general virtigo")
|
INFO = log.NewFlag("INFO", false, full, short, "general virtigo")
|
||||||
POLL = log.NewFlag("POLL", false, full, short, "virtigo polling")
|
POLL = log.NewFlag("POLL", false, full, short, "virtigo polling")
|
||||||
WARN = log.NewFlag("WARN", true, full, short, "bad things")
|
WARN = log.NewFlag("WARN", true, full, short, "bad things")
|
||||||
SPEW = log.NewFlag("SPEW", true, full, short, "dump everything")
|
|
||||||
EVENT = log.NewFlag("EVENT", true, full, short, "hypeprvisor/droplet events")
|
EVENT = log.NewFlag("EVENT", true, full, short, "hypeprvisor/droplet events")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
handles shell autocomplete
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (a args) DoAutoComplete(argv []string) {
|
||||||
|
switch argv[0] {
|
||||||
|
case "list":
|
||||||
|
fmt.Println("droplets hypervisors")
|
||||||
|
case "droplet":
|
||||||
|
fmt.Println("start stop")
|
||||||
|
case "devel":
|
||||||
|
fmt.Println("--force")
|
||||||
|
case "master":
|
||||||
|
fmt.Println("")
|
||||||
|
case "verify":
|
||||||
|
fmt.Println("user devel master")
|
||||||
|
default:
|
||||||
|
if argv[0] == ARGNAME {
|
||||||
|
// list the subcommands here
|
||||||
|
fmt.Println("--bash list droplet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
2
control
2
control
|
@ -4,7 +4,7 @@ Package: virtigo
|
||||||
Maintainer: Jeff Carr <jcarr@wit.com>
|
Maintainer: Jeff Carr <jcarr@wit.com>
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Recommends: virtigod
|
Recommends: virtigod
|
||||||
Depends:
|
Depends: gus, remmina, remmina-plugin-spice
|
||||||
URL: https://go.wit.com/apps/virtigo
|
URL: https://go.wit.com/apps/virtigo
|
||||||
Description: control your virtual machines in your cluster
|
Description: control your virtual machines in your cluster
|
||||||
lets you start,stop, etc virtual machines
|
lets you start,stop, etc virtual machines
|
||||||
|
|
369
doAdminGui.go
369
doAdminGui.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,20 +22,134 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// refresh the windows & tables the user has open
|
// refresh the windows & tables the user has open
|
||||||
func refresh() {
|
func (admin *adminT) refresh() error {
|
||||||
time.Sleep(90 * time.Second)
|
|
||||||
if argv.Verbose {
|
if argv.Verbose {
|
||||||
log.Info("virtigo scan here")
|
log.Info("virtigo scan here")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if admin.url == nil {
|
||||||
|
log.Info("admin url == nil")
|
||||||
|
return fmt.Errorf("admin url == nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := []byte(`{"message": "Hello"}`)
|
||||||
|
|
||||||
|
// display the uptime
|
||||||
|
if data, err := postData(admin.url.String()+"/uptime", msg); err != nil {
|
||||||
|
log.Info("/uptime Error:", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Response:", string(data))
|
||||||
|
admin.uptime.SetText(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the droplet list
|
||||||
|
if data, err := postData(admin.url.String()+"/DropletsPB", msg); err != nil {
|
||||||
|
log.Info("/DropletsPB Error:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("DropletsPB Response len:", len(data))
|
||||||
|
admin.cluster.Droplets = new(virtpb.Droplets)
|
||||||
|
if err := admin.cluster.Droplets.Unmarshal(data); err != nil {
|
||||||
|
fmt.Println("droplets marshal failed", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Droplet len=", admin.cluster.Droplets.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the hypervisor list
|
||||||
|
if data, err := postData(admin.url.String()+"/HypervisorsPB", msg); err != nil {
|
||||||
|
log.Info("Error:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("HypervisorsPB Response len:", len(data))
|
||||||
|
admin.cluster.Hypervisors = new(virtpb.Hypervisors)
|
||||||
|
if err := admin.cluster.Hypervisors.Unmarshal(data); err != nil {
|
||||||
|
fmt.Println("hypervisors marshal failed", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Hypervisors len=", admin.cluster.Hypervisors.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the events list
|
||||||
|
if data, err := postData(admin.url.String()+"/EventsPB", msg); err != nil {
|
||||||
|
log.Info("Error:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("EventsPB Response len:", len(data))
|
||||||
|
admin.cluster.Events = new(virtpb.Events)
|
||||||
|
if err := admin.cluster.Events.Unmarshal(data); err != nil {
|
||||||
|
fmt.Println("events marshal failed", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Events len=", admin.cluster.Events.Len())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
|
|
||||||
func doAdminGui() {
|
func doLocalhostAdminGui() *adminT {
|
||||||
me.myGui = gui.New()
|
admin := new(adminT)
|
||||||
me.myGui.InitEmbed(resources)
|
|
||||||
me.myGui.Default()
|
|
||||||
|
|
||||||
|
admin.uptime = me.gwin.Group.NewLabel("uptime")
|
||||||
|
|
||||||
|
grid := me.gwin.Group.RawGrid()
|
||||||
|
|
||||||
|
grid.NewButton("show hypervisors", func() {
|
||||||
|
if admin.cluster.Hypervisors == nil {
|
||||||
|
log.Info("hypervisors not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Hypervisors len=", admin.cluster.Hypervisors.Len())
|
||||||
|
admin.hwin = newHypervisorsWindow()
|
||||||
|
admin.hwin.doStdHypervisors(admin.cluster.Hypervisors)
|
||||||
|
admin.hwin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("droplets", func() {
|
||||||
|
if admin.cluster.Droplets == nil {
|
||||||
|
log.Info("droplets not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
admin.dwin = newDropletsWindow(admin)
|
||||||
|
admin.dwin.win.Custom = func() {
|
||||||
|
log.Info("hiding droplet table window")
|
||||||
|
}
|
||||||
|
var found *virtpb.Droplets
|
||||||
|
found = virtpb.NewDroplets()
|
||||||
|
all := admin.cluster.Droplets.All()
|
||||||
|
for all.Scan() {
|
||||||
|
vm := all.Next()
|
||||||
|
if vm.Current.State != virtpb.DropletState_ON {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found.Append(vm)
|
||||||
|
}
|
||||||
|
admin.dwin.doActiveDroplets(found)
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("events", func() {
|
||||||
|
if admin.cluster.Events == nil {
|
||||||
|
log.Info("events are not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Events len=", admin.cluster.Events.Len())
|
||||||
|
admin.ewin = newEventsWindow()
|
||||||
|
admin.ewin.doStdEvents(admin.cluster.Events)
|
||||||
|
admin.ewin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
grid.NewButton("refresh", func() {
|
||||||
|
admin.refresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
return admin
|
||||||
|
}
|
||||||
|
|
||||||
|
func doAdminGui() {
|
||||||
// Initialize a persistent client with a custom Transport
|
// Initialize a persistent client with a custom Transport
|
||||||
client = &http.Client{
|
client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
|
@ -43,45 +158,73 @@ func doAdminGui() {
|
||||||
Timeout: 10 * time.Second, // Set a reasonable timeout
|
Timeout: 10 * time.Second, // Set a reasonable timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
win := gadgets.NewGenericWindow("Virtigo: (run your cluster)", "virtigo stuff")
|
me.gwin = gadgets.NewGenericWindow("Virtigo: (run your cluster)", "")
|
||||||
win.Custom = func() {
|
me.gwin.Custom = func() {
|
||||||
log.Warn("Main window close")
|
log.Warn("Main window close")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
me.cmap = make(map[*virtpb.Cluster]*adminT)
|
||||||
|
for c := range me.clusters.IterAll() {
|
||||||
|
a := new(adminT)
|
||||||
|
me.cmap[c] = a
|
||||||
|
log.Info("found in the config file", c.URL[0])
|
||||||
|
a.makeClusterGroup(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sit here forever refreshing the GUI
|
||||||
|
for {
|
||||||
|
// admin.refresh()
|
||||||
|
log.Info("todo: refresh() protobufs here")
|
||||||
|
time.Sleep(90 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (admin *adminT) doAdminGui() {
|
||||||
|
// Initialize a persistent client with a custom Transport
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DisableKeepAlives: false, // Ensure Keep-Alive is enabled
|
||||||
|
},
|
||||||
|
Timeout: 10 * time.Second, // Set a reasonable timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
win := gadgets.NewGenericWindow("Virtigo: (run your cluster)", "localhost")
|
||||||
|
win.Custom = func() {
|
||||||
|
log.Warn("Main window close")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
me.gwin = win
|
||||||
|
|
||||||
|
admin.uptime = win.Group.NewLabel("uptime")
|
||||||
|
|
||||||
grid := win.Group.RawGrid()
|
grid := win.Group.RawGrid()
|
||||||
|
|
||||||
// url := "http://example.com/endpoint"
|
|
||||||
url := argv.Server
|
|
||||||
data := []byte(`{"message": "Hello"}`)
|
|
||||||
|
|
||||||
grid.NewButton("show hypervisors", func() {
|
grid.NewButton("show hypervisors", func() {
|
||||||
response, err := postData(url, data)
|
if admin.cluster.Hypervisors == nil {
|
||||||
if err != nil {
|
log.Info("hypervisors not initialized")
|
||||||
fmt.Println("Error:", err)
|
return
|
||||||
} else {
|
}
|
||||||
fmt.Println("Response:", string(response))
|
log.Info("Hypervisors len=", admin.cluster.Hypervisors.Len())
|
||||||
|
admin.hwin = newHypervisorsWindow()
|
||||||
|
admin.hwin.doStdHypervisors(admin.cluster.Hypervisors)
|
||||||
|
admin.hwin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
grid.NewButton("show droplets", func() {
|
grid.NewButton("droplets", func() {
|
||||||
durl := url + "/DropletsPB"
|
if admin.cluster.Droplets == nil {
|
||||||
data, err := postData(durl, data)
|
log.Info("droplets not initialized")
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error:", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Response len:", len(data))
|
admin.dwin = newDropletsWindow(admin)
|
||||||
me.droplets = new(virtpb.Droplets)
|
admin.dwin.win.Custom = func() {
|
||||||
if err := me.droplets.Unmarshal(data); err != nil {
|
log.Info("hiding droplet table window")
|
||||||
fmt.Println("marshal failed", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
fmt.Println("Droplet len=", me.droplets.Len())
|
|
||||||
|
|
||||||
var found *virtpb.Droplets
|
var found *virtpb.Droplets
|
||||||
found = virtpb.NewDroplets()
|
found = virtpb.NewDroplets()
|
||||||
all := me.droplets.All()
|
all := admin.cluster.Droplets.All()
|
||||||
for all.Scan() {
|
for all.Scan() {
|
||||||
vm := all.Next()
|
vm := all.Next()
|
||||||
if vm.Current.State != virtpb.DropletState_ON {
|
if vm.Current.State != virtpb.DropletState_ON {
|
||||||
|
@ -89,32 +232,132 @@ func doAdminGui() {
|
||||||
}
|
}
|
||||||
found.Append(vm)
|
found.Append(vm)
|
||||||
}
|
}
|
||||||
dropWin, _ := makeDropletsWindow(found)
|
admin.dwin.doActiveDroplets(found)
|
||||||
dropWin.Win.Custom = func() {
|
})
|
||||||
log.Info("hiding droplet table window")
|
|
||||||
|
grid.NewButton("events", func() {
|
||||||
|
if admin.cluster.Events == nil {
|
||||||
|
log.Info("events are not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Events len=", admin.cluster.Events.Len())
|
||||||
|
admin.ewin = newEventsWindow()
|
||||||
|
admin.ewin.doStdEvents(admin.cluster.Events)
|
||||||
|
admin.ewin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
grid.NewButton("uptime", func() {
|
grid.NextRow()
|
||||||
durl := url + "/uptime"
|
|
||||||
response, err := postData(durl, data)
|
grid.NewButton("refresh", func() {
|
||||||
if err != nil {
|
admin.refresh()
|
||||||
fmt.Println("Error:", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Response:", string(response))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
grid.NewButton("clean exit", func() {
|
grid.NewButton("test gui close", func() {
|
||||||
okExit("admin close")
|
gui.StandardExit()
|
||||||
|
// okExit("admin close")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
me.cmap = make(map[*virtpb.Cluster]*adminT)
|
||||||
|
for c := range me.clusters.IterAll() {
|
||||||
|
a := new(adminT)
|
||||||
|
me.cmap[c] = a
|
||||||
|
log.Info("found in the config file", c.URL[0])
|
||||||
|
a.makeClusterGroup(c)
|
||||||
|
}
|
||||||
|
|
||||||
// sit here forever refreshing the GUI
|
// sit here forever refreshing the GUI
|
||||||
for {
|
for {
|
||||||
refresh()
|
admin.refresh()
|
||||||
|
time.Sleep(90 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (admin *adminT) makeClusterGroup(c *virtpb.Cluster) {
|
||||||
|
var err error
|
||||||
|
admin.url, err = url.Parse(c.URL[0])
|
||||||
|
if err != nil {
|
||||||
|
badExit(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if admin.cluster == nil {
|
||||||
|
admin.cluster = new(virtpb.Cluster)
|
||||||
|
admin.cluster.Name = c.Name
|
||||||
|
admin.cluster.Uuid = c.Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.GetName()
|
||||||
|
if name == "" {
|
||||||
|
name = admin.url.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
group := me.gwin.Bottom.NewGroup(name)
|
||||||
|
admin.uptime = group.NewLabel("uptime")
|
||||||
|
|
||||||
|
grid := group.RawGrid()
|
||||||
|
|
||||||
|
grid.NewButton("show hypervisors", func() {
|
||||||
|
if admin.cluster.Hypervisors == nil {
|
||||||
|
log.Info("hypervisors not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Hypervisors len=", admin.cluster.Hypervisors.Len())
|
||||||
|
admin.hwin = newHypervisorsWindow()
|
||||||
|
admin.hwin.doStdHypervisors(admin.cluster.Hypervisors)
|
||||||
|
admin.hwin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("droplets", func() {
|
||||||
|
if admin.cluster.Droplets == nil {
|
||||||
|
log.Info("droplets not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
admin.dwin = newDropletsWindow(admin)
|
||||||
|
admin.dwin.win.Custom = func() {
|
||||||
|
log.Info("hiding droplet table window")
|
||||||
|
}
|
||||||
|
var found *virtpb.Droplets
|
||||||
|
found = virtpb.NewDroplets()
|
||||||
|
all := admin.cluster.Droplets.All()
|
||||||
|
for all.Scan() {
|
||||||
|
vm := all.Next()
|
||||||
|
if vm.Current.State != virtpb.DropletState_ON {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found.Append(vm)
|
||||||
|
}
|
||||||
|
admin.dwin.doActiveDroplets(found)
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("events", func() {
|
||||||
|
if admin.cluster.Events == nil {
|
||||||
|
log.Info("events are not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Events len=", admin.cluster.Events.Len())
|
||||||
|
admin.ewin = newEventsWindow()
|
||||||
|
admin.ewin.doStdEvents(admin.cluster.Events)
|
||||||
|
admin.ewin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("refresh", func() {
|
||||||
|
admin.refresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := admin.refresh(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.NewButton("save cluster.pb", func() {
|
||||||
|
admin.cluster.ConfigSave()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func postData(url string, data []byte) ([]byte, error) {
|
func postData(url string, data []byte) ([]byte, error) {
|
||||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -139,21 +382,35 @@ func postData(url string, data []byte) ([]byte, error) {
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func (admin *adminT) postEvent(e *virtpb.Event) error {
|
||||||
func main() {
|
var result *virtpb.Event
|
||||||
url := "http://example.com/endpoint"
|
result = new(virtpb.Event)
|
||||||
data := []byte(`{"message": "Hello"}`)
|
|
||||||
|
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
msg, err := e.Marshal()
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
response, err := postData(url, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error:", err)
|
log.Info("postEvent() marshal() failed", err, e)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := admin.url.String() + "/event"
|
||||||
|
|
||||||
|
// update the droplet list
|
||||||
|
if data, err := postData(url, msg); err != nil {
|
||||||
|
log.Info("postEvent() /event Error:", err)
|
||||||
|
return err
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Response:", string(response))
|
if err := result.Unmarshal(data); err != nil {
|
||||||
|
log.Println("postEvent() result marshal failed", err, "len(dat) =", len(data))
|
||||||
|
log.Println("postEvent() data =", string(data))
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
log.Println("postEvent() result marshal worked on len(dat) =", len(data))
|
||||||
|
log.Println("postEvent() result =", result.FormatTEXT())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if result.Error != "" {
|
||||||
|
return fmt.Errorf("%s", result.Error)
|
||||||
|
}
|
||||||
|
log.Printf("Event worked to %s uuid=%s\n", url, result.DropletUuid)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2024 WIT.COM Inc Licensed GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/lib/virtigolib"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doDaemon() error {
|
||||||
|
// set defaults
|
||||||
|
me.unstable = time.Now() // initialize the grid as unstable
|
||||||
|
me.changed = false
|
||||||
|
me.hmap = make(map[*virtpb.Hypervisor]*HyperT)
|
||||||
|
|
||||||
|
// how long a droplet can be missing until it's declared dead
|
||||||
|
me.unstableTimeout = 17 * time.Second
|
||||||
|
me.missingDropletTimeout = time.Minute // not sure the difference between these values
|
||||||
|
|
||||||
|
// how often to poll the hypervisors
|
||||||
|
me.hyperPollDelay = 5 * time.Second
|
||||||
|
|
||||||
|
// how long the cluster must be stable before new droplets can be started
|
||||||
|
me.clusterStableDuration = 37 * time.Second
|
||||||
|
|
||||||
|
me.cluster = virtpb.InitCluster()
|
||||||
|
if err := me.cluster.ConfigLoad(); err != nil {
|
||||||
|
log.Info("config load error", err)
|
||||||
|
log.Info("")
|
||||||
|
log.Info("You have never run this before")
|
||||||
|
log.Info("init example cloud here")
|
||||||
|
log.Sleep(2)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := me.cluster.DropletsAll() // get the list of droplets
|
||||||
|
for loop.Scan() {
|
||||||
|
d := loop.Next()
|
||||||
|
if d == nil {
|
||||||
|
fmt.Println("d == nil")
|
||||||
|
return fmt.Errorf("d == nil")
|
||||||
|
}
|
||||||
|
fmt.Println("Droplet UUID:", d.Uuid)
|
||||||
|
if d.Current == nil {
|
||||||
|
d.Current = new(virtpb.Current)
|
||||||
|
}
|
||||||
|
d.SetState(virtpb.DropletState_OFF)
|
||||||
|
log.Info("droplet", d.Hostname)
|
||||||
|
}
|
||||||
|
hmm := "pihole.wit.com"
|
||||||
|
d := me.cluster.FindDropletByName(hmm)
|
||||||
|
if d == nil {
|
||||||
|
log.Info("did not find found droplet", hmm)
|
||||||
|
} else {
|
||||||
|
log.Info("found droplet", d.Hostname, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEvents []*virtpb.Event
|
||||||
|
|
||||||
|
// sanity check the cluster & droplets
|
||||||
|
if _, _, err := ValidateDroplets(); err != nil {
|
||||||
|
log.Info("todo: add flag to ignore. for now, fix problems in the config file.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newe, err := ValidateDiskFilenames()
|
||||||
|
if err != nil {
|
||||||
|
log.Info(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// this is a new droplet. add it to the cluster
|
||||||
|
for _, e := range newe {
|
||||||
|
newEvents = append(newEvents, e)
|
||||||
|
}
|
||||||
|
ValidateUniqueFilenames()
|
||||||
|
|
||||||
|
for _, filename := range argv.Xml {
|
||||||
|
domcfg, err := virtigolib.ReadXml(filename)
|
||||||
|
if err != nil {
|
||||||
|
// parsing the libvirt xml file failed
|
||||||
|
log.Info("error:", filename, err)
|
||||||
|
log.Info("readXml() error", filename)
|
||||||
|
log.Info("readXml() error", err)
|
||||||
|
log.Info("libvirt XML will have to be fixed by hand")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// this is a new droplet. add it to the cluster
|
||||||
|
log.Info("Add XML Droplet here", domcfg.Name)
|
||||||
|
_, newe, err := virtigolib.AddDomainDroplet(me.cluster, domcfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("addDomainDroplet() error", filename)
|
||||||
|
log.Info("addDomainDroplet() error", err)
|
||||||
|
log.Info("libvirt XML will have to be fixed by hand")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range newe {
|
||||||
|
newEvents = append(newEvents, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, e := range newEvents {
|
||||||
|
log.Info(i, "Event:", e.Droplet, e.FieldName, "orig:", e.OrigVal, "new:", e.NewVal)
|
||||||
|
me.changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if me.changed {
|
||||||
|
if err := me.cluster.ConfigSave(); err != nil {
|
||||||
|
log.Info("configsave error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("XML changes saved in protobuf config")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(argv.Xml) != 0 {
|
||||||
|
log.Info("No XML changes found")
|
||||||
|
return fmt.Errorf("No XML changes found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize each hypervisor
|
||||||
|
for _, pbh := range me.cluster.H.Hypervisors {
|
||||||
|
// this is a new unknown droplet (not in the config file)
|
||||||
|
var h *HyperT
|
||||||
|
h = new(HyperT)
|
||||||
|
h.pb = pbh
|
||||||
|
h.lastDroplets = make(map[string]time.Time)
|
||||||
|
h.lastpoll = time.Now()
|
||||||
|
|
||||||
|
me.hmap[pbh] = h
|
||||||
|
me.hypers = append(me.hypers, h)
|
||||||
|
log.Log(EVENT, "config new hypervisors", h.pb.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the watchdog polling for each hypervisor
|
||||||
|
for _, h := range me.hypers {
|
||||||
|
log.Info("starting polling on", h.pb.Hostname)
|
||||||
|
|
||||||
|
// start a watchdog on each hypervisor
|
||||||
|
go h.NewWatchdog()
|
||||||
|
}
|
||||||
|
|
||||||
|
var cloud *virtigolib.CloudManager
|
||||||
|
cloud = virtigolib.NewCloud()
|
||||||
|
found, _ := cloud.FindDropletByName("www.wit.com")
|
||||||
|
if found == nil {
|
||||||
|
log.Info("d == nil")
|
||||||
|
} else {
|
||||||
|
log.Info("d == ", found)
|
||||||
|
}
|
||||||
|
|
||||||
|
startHTTP()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,361 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// An app to submit patches for the 30 GO GUI repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.wit.com/lib/gui/shell"
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doDroplet() (string, error) {
|
||||||
|
err := me.clusters.ConfigLoad()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := []byte(`{"message": "Hello"}`)
|
||||||
|
|
||||||
|
// Initialize a persistent client with a custom Transport
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DisableKeepAlives: false, // Ensure Keep-Alive is enabled
|
||||||
|
},
|
||||||
|
Timeout: 10 * time.Second, // Set a reasonable timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
me.cmap = make(map[*virtpb.Cluster]*adminT)
|
||||||
|
for c := range me.clusters.IterAll() {
|
||||||
|
var err error
|
||||||
|
admin := new(adminT)
|
||||||
|
if admin.cluster == nil {
|
||||||
|
admin.cluster = new(virtpb.Cluster)
|
||||||
|
}
|
||||||
|
me.cmap[c] = admin
|
||||||
|
log.Info("found in the config file", c.URL[0])
|
||||||
|
// a.makeClusterGroup(c)
|
||||||
|
admin.url, err = url.Parse(c.URL[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the droplet list
|
||||||
|
if data, err := postData(admin.url.String()+"/DropletsPB", msg); err != nil {
|
||||||
|
log.Info("/DropletsPB Error:", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
admin.cluster.Droplets = new(virtpb.Droplets)
|
||||||
|
if err := admin.cluster.Droplets.Unmarshal(data); err != nil {
|
||||||
|
log.Printf("DropletsPB Response len:%d\n", len(data))
|
||||||
|
log.Println("droplets marshal failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Cluster Name: %s\n", c.Name)
|
||||||
|
log.Printf("Number of Droplets: %d\n", admin.cluster.Droplets.Len())
|
||||||
|
|
||||||
|
if argv.Droplet.Name == "" {
|
||||||
|
return "", fmt.Errorf("--name droplet name was empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *virtpb.Droplets
|
||||||
|
found = virtpb.NewDroplets()
|
||||||
|
all := admin.cluster.Droplets.All()
|
||||||
|
for all.Scan() {
|
||||||
|
vm := all.Next()
|
||||||
|
if argv.Droplet.Name == vm.Hostname {
|
||||||
|
if argv.Droplet.Show != nil {
|
||||||
|
log.Info(vm.SprintHeader())
|
||||||
|
txt := vm.FormatTEXT()
|
||||||
|
log.Info(txt)
|
||||||
|
return "droplet status", nil
|
||||||
|
}
|
||||||
|
if argv.Droplet.Start != nil {
|
||||||
|
log.Info("should start droplet here")
|
||||||
|
log.Info(vm.SprintHeader())
|
||||||
|
e := new(virtpb.Event)
|
||||||
|
e.Etype = virtpb.EventType_POWERON
|
||||||
|
e.DropletUuid = vm.Uuid
|
||||||
|
|
||||||
|
if err := admin.postEvent(e); err != nil {
|
||||||
|
return "droplet start err", err
|
||||||
|
}
|
||||||
|
return "droplet start", nil
|
||||||
|
}
|
||||||
|
return "droplet found", fmt.Errorf("do what to the droplet?")
|
||||||
|
}
|
||||||
|
found.Append(vm)
|
||||||
|
}
|
||||||
|
log.Println("On Droplet count=", found.Len())
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("droplet %s not found", argv.Droplet.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doEvent(e *virtpb.Event) *virtpb.Event {
|
||||||
|
result := new(virtpb.Event)
|
||||||
|
if e.Etype == virtpb.EventType_POWERON {
|
||||||
|
log.Println("power on droplet on local cluster here", e.DropletUuid)
|
||||||
|
result.State = virtpb.Event_DONE
|
||||||
|
rs, err := Start(e.DropletUuid)
|
||||||
|
log.Println("Start() returned", rs)
|
||||||
|
log.Println("Start() returned err", err)
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("%v", err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Etype == virtpb.EventType_EDIT {
|
||||||
|
log.Println("edit event", e.DropletUuid)
|
||||||
|
result.State = virtpb.Event_DONE
|
||||||
|
if e.Droplet != nil {
|
||||||
|
return updateDroplet(e.Droplet)
|
||||||
|
}
|
||||||
|
log.Println("unknown edit event")
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Etype == virtpb.EventType_ADD {
|
||||||
|
log.Println("START ADD droplet event", e.Droplet.FormatTEXT())
|
||||||
|
if e.Droplet == nil {
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result.DropletName = e.Droplet.Hostname
|
||||||
|
result.Error = e.Droplet.FormatTEXT() // feedback to the other side for debugging
|
||||||
|
|
||||||
|
// attempt to create the new droplet
|
||||||
|
if err := createDroplet(e.Droplet, result); err != nil {
|
||||||
|
result.Error += fmt.Sprintf("createDroplet() err: %v", err)
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
log.Println("create droplet worked", e.Droplet.FormatTEXT())
|
||||||
|
result.State = virtpb.Event_DONE
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("unknown event", e)
|
||||||
|
result.Etype = e.Etype
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDroplet(newd *virtpb.Droplet) *virtpb.Event {
|
||||||
|
var changed bool = false
|
||||||
|
result := new(virtpb.Event)
|
||||||
|
|
||||||
|
if newd == nil {
|
||||||
|
result.Error = "updateDroplet() d == nil"
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
d := me.cluster.FindDropletByUuid(newd.Uuid)
|
||||||
|
if d == nil {
|
||||||
|
result.Error = "updateDroplet() could not find uuid"
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
log.Println("found droplet to update:", newd.Uuid, newd.Hostname, newd.Cpus, newd.Memory)
|
||||||
|
|
||||||
|
if d.Hostname != newd.Hostname && newd.Hostname != "" {
|
||||||
|
d.Hostname = newd.Hostname
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Cpus != newd.Cpus && newd.Cpus > 0 {
|
||||||
|
d.Cpus = newd.Cpus
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// arbitrary check. don't make vm's with less than 64 MB of RAM
|
||||||
|
// big enough most things will load with some stdout
|
||||||
|
if d.Memory != newd.Memory && newd.Memory > (64*1024*1024) {
|
||||||
|
d.Memory = newd.Memory
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
if err := me.cluster.ConfigSave(); err != nil {
|
||||||
|
log.Info("configsave error", err)
|
||||||
|
result.Error = fmt.Sprintf("%v", err)
|
||||||
|
result.State = virtpb.Event_FAIL
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("nothing changed in", newd.Uuid, newd.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.State = virtpb.Event_DONE
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDroplet(newd *virtpb.Droplet, result *virtpb.Event) error {
|
||||||
|
if newd == nil {
|
||||||
|
return fmt.Errorf("droplet protobuf == nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newd.Uuid == "" {
|
||||||
|
newd.Uuid = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
d := me.cluster.FindDropletByUuid(newd.Uuid)
|
||||||
|
if d != nil {
|
||||||
|
return fmt.Errorf("droplet uuid already used")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("found droplet to update:", newd.Uuid, newd.Hostname, newd.Cpus, newd.Memory)
|
||||||
|
|
||||||
|
if newd.Hostname == "" {
|
||||||
|
return fmt.Errorf("Hostname can not be blank")
|
||||||
|
}
|
||||||
|
d = me.cluster.FindDropletByName(newd.Hostname)
|
||||||
|
if d != nil {
|
||||||
|
return fmt.Errorf("hostname already defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
// by default, on locally imported domains, set the preferred hypervisor!
|
||||||
|
newd.LocalOnly = "yes on: " + "farm03"
|
||||||
|
|
||||||
|
newd.PreferredHypervisor = "farm03"
|
||||||
|
newd.StartState = virtpb.DropletState_OFF
|
||||||
|
|
||||||
|
newd.Current = new(virtpb.Current)
|
||||||
|
newd.Current.State = virtpb.DropletState_OFF
|
||||||
|
|
||||||
|
// create the network
|
||||||
|
if err := createNetwork(newd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the disks
|
||||||
|
if err := createDisks(newd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the protobuf and save it
|
||||||
|
me.cluster.AddDroplet(newd)
|
||||||
|
if err := me.cluster.ConfigSave(); err != nil {
|
||||||
|
log.Info("configsave error", err)
|
||||||
|
return fmt.Errorf("ConfigSave() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findDisks(d *virtpb.Droplet) error {
|
||||||
|
log.Info("need to do this")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDisks(d *virtpb.Droplet) error {
|
||||||
|
if d.Disks != nil {
|
||||||
|
return findDisks(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
newdisk := new(virtpb.Disk)
|
||||||
|
newdisk.Filename = d.Hostname + ".qcow2"
|
||||||
|
newdisk.Filepath = "/home/nfs2"
|
||||||
|
d.Disks = append(d.Disks, newdisk)
|
||||||
|
|
||||||
|
basefile := "/home/nfs2/base2025.wit-5.qcow2"
|
||||||
|
newfile := filepath.Join(newdisk.Filepath, newdisk.Filename)
|
||||||
|
|
||||||
|
if !shell.Exists(newdisk.Filepath) {
|
||||||
|
return fmt.Errorf("disk image path missing: %s", newdisk.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shell.Exists(basefile) {
|
||||||
|
return fmt.Errorf("basefile %s missing", basefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if shell.Exists(newfile) {
|
||||||
|
return fmt.Errorf("disk image already exists: %s", newfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := []string{"dd", "bs=100M", "status=progress", "oflag=dsync", "if=" + basefile, "of=" + newfile}
|
||||||
|
result := shell.RunRealtime(cmd)
|
||||||
|
if result.Exit != 0 {
|
||||||
|
return fmt.Errorf("dd to %s failed %d\n%s\n%s", newfile, result.Exit, strings.Join(result.Stdout, "\n"), strings.Join(result.Stderr, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNetwork(d *virtpb.Droplet) error {
|
||||||
|
if d.Networks != nil {
|
||||||
|
// network already done
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(d.Networks) > 0 {
|
||||||
|
// network already done
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := new(virtpb.Network)
|
||||||
|
n.Mac = getNewMac()
|
||||||
|
n.Name = "worldbr"
|
||||||
|
d.Networks = append(d.Networks, n)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNewMac() string {
|
||||||
|
// mac address map to check for duplicates
|
||||||
|
var macs map[string]string
|
||||||
|
macs = make(map[string]string)
|
||||||
|
|
||||||
|
loop := me.cluster.DropletsAll() // get the list of droplets
|
||||||
|
for loop.Scan() {
|
||||||
|
d := loop.Next()
|
||||||
|
for _, n := range d.Networks {
|
||||||
|
// log.Println("network:", n.Mac, d.Uuid, d.Hostname)
|
||||||
|
if _, ok := macs[n.Mac]; ok {
|
||||||
|
// UUID already exists
|
||||||
|
log.Info("duplicate MAC", n.Mac, macs[n.Mac])
|
||||||
|
log.Info("duplicate MAC", n.Mac, d.Hostname)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
macs[n.Mac] = d.Hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateMAC(macs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateMAC(macs map[string]string) string {
|
||||||
|
prefix := []byte{0x22, 0x22, 0x22}
|
||||||
|
for {
|
||||||
|
// Generate last 3 bytes randomly
|
||||||
|
suffix := make([]byte, 3)
|
||||||
|
if _, err := rand.Read(suffix); err != nil {
|
||||||
|
log.Fatalf("Failed to generate random bytes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format full MAC address
|
||||||
|
mac := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
|
||||||
|
prefix[0], prefix[1], prefix[2],
|
||||||
|
suffix[0], suffix[1], suffix[2])
|
||||||
|
|
||||||
|
// Check if MAC is already used
|
||||||
|
if _, exists := macs[mac]; !exists {
|
||||||
|
log.Println("Using new MAC:", mac)
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
log.Println("MAC already defined:", mac)
|
||||||
|
}
|
||||||
|
}
|
138
doGui.go
138
doGui.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.wit.com/gui"
|
|
||||||
"go.wit.com/lib/gadgets"
|
"go.wit.com/lib/gadgets"
|
||||||
"go.wit.com/lib/protobuf/virtpb"
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
"go.wit.com/log"
|
"go.wit.com/log"
|
||||||
|
@ -25,43 +24,34 @@ func debug() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doGui() {
|
func doGui() {
|
||||||
me.myGui = gui.New()
|
mainWindow := gadgets.NewGenericWindow("Virtigo: (inventory your cluster)", "Local Cluster Settings")
|
||||||
me.myGui.InitEmbed(resources)
|
|
||||||
me.myGui.Default()
|
|
||||||
|
|
||||||
mainWindow := gadgets.RawBasicWindow("Virtigo: (inventory your cluster)")
|
|
||||||
mainWindow.Make()
|
|
||||||
mainWindow.Show()
|
|
||||||
mainWindow.Custom = func() {
|
mainWindow.Custom = func() {
|
||||||
log.Warn("Main window close")
|
log.Warn("Main window close")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawWindow(mainWindow)
|
drawWindow(mainWindow)
|
||||||
|
|
||||||
// sits here forever
|
|
||||||
debug()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawWindow(win *gadgets.BasicWindow) {
|
func drawWindow(win *gadgets.GenericWindow) {
|
||||||
box := win.Box()
|
grid := win.Group.RawGrid()
|
||||||
|
|
||||||
vbox := box.NewVerticalBox("BOX2")
|
var newHyperWin *stdHypervisorTableWin
|
||||||
|
grid.NewButton("show hypervisors", func() {
|
||||||
group1 := vbox.NewGroup("Virtigo Settings")
|
if newHyperWin != nil {
|
||||||
grid := group1.NewGrid("buildOptions", 0, 0)
|
log.Info("redraw hypervisors")
|
||||||
|
newHyperWin.doNewStdHypervisors(me.cluster.H)
|
||||||
var hyperWin *GenericWindow
|
|
||||||
grid.NewButton("hypervisors", func() {
|
|
||||||
if hyperWin != nil {
|
|
||||||
hyperWin.Toggle()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hyperWin = makeHypervisorsWindow(me.cluster.H)
|
log.Info("Hypervisors len=", me.cluster.H.Len())
|
||||||
|
newHyperWin = newHypervisorsWindow()
|
||||||
|
newHyperWin.doNewStdHypervisors(me.cluster.H)
|
||||||
|
newHyperWin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var dropWin *GenericWindow
|
var dropWin *gadgets.GenericWindow
|
||||||
grid.NewButton("droplets", func() {
|
grid.NewButton("droplets", func() {
|
||||||
if dropWin != nil {
|
if dropWin != nil {
|
||||||
dropWin.Toggle()
|
dropWin.Toggle()
|
||||||
|
@ -84,22 +74,31 @@ func drawWindow(win *gadgets.BasicWindow) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var eventWin *GenericWindow
|
var ewin *stdEventTableWin
|
||||||
grid.NewButton("events)", func() {
|
grid.NewButton("events", func() {
|
||||||
log.Info("todo: make code for this")
|
if ewin != nil {
|
||||||
if eventWin != nil {
|
log.Info("update events here")
|
||||||
eventWin.Toggle()
|
e := me.cluster.GetEventsPB()
|
||||||
|
log.Info("Events len=", e.Len())
|
||||||
|
ewin.doStdEvents(e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ewin = newEventsWindow()
|
||||||
|
ewin.win.Custom = func() {
|
||||||
|
log.Info("hiding table window")
|
||||||
|
}
|
||||||
|
|
||||||
e := me.cluster.GetEventsPB()
|
e := me.cluster.GetEventsPB()
|
||||||
eventWin = makeEventsWindow(e)
|
log.Info("Events len=", e.Len())
|
||||||
|
ewin.doStdEvents(e)
|
||||||
})
|
})
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
grid.NewButton("ConfigSave()", func() {
|
grid.NewButton("ConfigSave()", func() {
|
||||||
log.Info("todo: make code for this")
|
log.Info("todo: make code for this")
|
||||||
})
|
})
|
||||||
|
|
||||||
var testWin *GenericWindow
|
var testWin *gadgets.GenericWindow
|
||||||
grid.NewButton("create droplet", func() {
|
grid.NewButton("create droplet", func() {
|
||||||
if testWin != nil {
|
if testWin != nil {
|
||||||
testWin.Toggle()
|
testWin.Toggle()
|
||||||
|
@ -108,27 +107,32 @@ func drawWindow(win *gadgets.BasicWindow) {
|
||||||
d := me.cluster.GetDropletsPB()
|
d := me.cluster.GetDropletsPB()
|
||||||
testWin, _ = makeDropletsWindow(d)
|
testWin, _ = makeDropletsWindow(d)
|
||||||
})
|
})
|
||||||
grid.NextRow()
|
|
||||||
|
|
||||||
me.status = grid.NewLabel("cur status")
|
|
||||||
grid.NextRow()
|
|
||||||
me.lastuptime = grid.NewLabel("last uptime")
|
|
||||||
grid.NextRow()
|
|
||||||
|
|
||||||
grid.NewButton("uptime", func() {
|
grid.NewButton("uptime", func() {
|
||||||
updateUptimeGui("kuma uptime should update this")
|
updateUptimeGui("kuma uptime should update this")
|
||||||
})
|
})
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
grid = win.Middle.RawGrid()
|
||||||
|
me.status = grid.NewLabel("cur status")
|
||||||
|
grid.NextRow()
|
||||||
|
me.lastuptime = grid.NewLabel("last uptime")
|
||||||
|
grid.NextRow()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUptimeGui(uptime string) {
|
func updateUptimeGui(uptime string) {
|
||||||
|
if me.status == nil {
|
||||||
|
// gui is not initialized
|
||||||
|
return
|
||||||
|
}
|
||||||
me.status.SetLabel(uptime)
|
me.status.SetLabel(uptime)
|
||||||
|
|
||||||
datestamp := time.Now().Format("2006-01-02 15:04:03")
|
datestamp := time.Now().Format("2006-01-02 15:04:03")
|
||||||
me.lastuptime.SetLabel("last uptime at " + datestamp)
|
me.lastuptime.SetLabel("last uptime at " + datestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDropletsWindow(pb *virtpb.Droplets) (*GenericWindow, *virtpb.DropletsTable) {
|
func makeDropletsWindow(pb *virtpb.Droplets) (*gadgets.GenericWindow, *virtpb.DropletsTable) {
|
||||||
win := NewGenericWindow("Droplets registered with Virtigo", "Buttons of things")
|
win := gadgets.NewGenericWindow("Droplets registered with Virtigo", "Buttons of things")
|
||||||
t := pb.NewTable("testDroptable")
|
t := pb.NewTable("testDroptable")
|
||||||
t.NewUuid()
|
t.NewUuid()
|
||||||
|
|
||||||
|
@ -154,6 +158,7 @@ func makeDropletsWindow(pb *virtpb.Droplets) (*GenericWindow, *virtpb.DropletsTa
|
||||||
})
|
})
|
||||||
t.AddMemory()
|
t.AddMemory()
|
||||||
t.AddCpus()
|
t.AddCpus()
|
||||||
|
t.AddSpicePort()
|
||||||
t.AddTimeFunc("age", func(d *virtpb.Droplet) time.Time {
|
t.AddTimeFunc("age", func(d *virtpb.Droplet) time.Time {
|
||||||
age := d.Current.OnSince.AsTime()
|
age := d.Current.OnSince.AsTime()
|
||||||
log.Info("age", d.Hostname, virtpb.FormatDuration(time.Since(age)))
|
log.Info("age", d.Hostname, virtpb.FormatDuration(time.Since(age)))
|
||||||
|
@ -180,55 +185,8 @@ func makeDropletsWindow(pb *virtpb.Droplets) (*GenericWindow, *virtpb.DropletsTa
|
||||||
return win, t
|
return win, t
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeHypervisorsWindow(pb *virtpb.Hypervisors) *GenericWindow {
|
func makeEventsWindow(pb *virtpb.Events) *gadgets.GenericWindow {
|
||||||
win := NewGenericWindow("Hypervisors registered with Virtigo", "Buttons of things")
|
win := gadgets.NewGenericWindow("Cluster Events", "Buttons of things")
|
||||||
t := pb.NewTable("testHyper")
|
|
||||||
grid := win.Group.RawGrid()
|
|
||||||
grid.NewButton("List", func() {
|
|
||||||
log.Info("list...")
|
|
||||||
})
|
|
||||||
/*
|
|
||||||
grid.NewButton("Update", func() {
|
|
||||||
t.Update()
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
tbox := win.Bottom.Box() // a vertical box (like a stack of books)
|
|
||||||
t.NewUuid()
|
|
||||||
t.SetParent(tbox)
|
|
||||||
t.AddHostname()
|
|
||||||
t.AddMemory()
|
|
||||||
t.AddCpus()
|
|
||||||
t.AddTimeFunc("last poll", func(h *virtpb.Hypervisor) time.Time {
|
|
||||||
hm := me.hmap[h]
|
|
||||||
tmp := hm.lastpoll
|
|
||||||
log.Info("poll age", h.Hostname, virtpb.FormatDuration(time.Since(tmp)))
|
|
||||||
return tmp
|
|
||||||
})
|
|
||||||
t.AddKillcount()
|
|
||||||
t.AddStringFunc("droplets", func(h *virtpb.Hypervisor) string {
|
|
||||||
var totalDroplets int
|
|
||||||
var totalUnknownDroplets int
|
|
||||||
// dur := time.Since(h.lastpoll)
|
|
||||||
// tmp := virtpb.FormatDuration(dur)
|
|
||||||
// fmt.Fprintln(w, h.pb.Hostname, "killcount =", h.killcount, "lastpoll:", tmp)
|
|
||||||
hm := me.hmap[h]
|
|
||||||
for name, _ := range hm.lastDroplets {
|
|
||||||
totalDroplets += 1
|
|
||||||
d := me.cluster.FindDropletByName(name)
|
|
||||||
if d == nil {
|
|
||||||
totalUnknownDroplets += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Total Droplets %d total libvirt only droplets = %d\n", totalDroplets, totalUnknownDroplets)
|
|
||||||
return fmt.Sprintf("%d", totalDroplets)
|
|
||||||
})
|
|
||||||
t.ShowTable()
|
|
||||||
return win
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeEventsWindow(pb *virtpb.Events) *GenericWindow {
|
|
||||||
win := NewGenericWindow("Cluster Events", "Buttons of things")
|
|
||||||
grid := win.Group.RawGrid()
|
grid := win.Group.RawGrid()
|
||||||
grid.NewButton("List", func() {
|
grid.NewButton("List", func() {
|
||||||
log.Info("list...")
|
log.Info("list...")
|
||||||
|
@ -240,7 +198,7 @@ func makeEventsWindow(pb *virtpb.Events) *GenericWindow {
|
||||||
t := pb.NewTable("test 2")
|
t := pb.NewTable("test 2")
|
||||||
t.NewUuid()
|
t.NewUuid()
|
||||||
t.SetParent(tbox)
|
t.SetParent(tbox)
|
||||||
t.AddDroplet()
|
t.AddDropletName()
|
||||||
t.AddHypervisor()
|
t.AddHypervisor()
|
||||||
t.ShowTable()
|
t.ShowTable()
|
||||||
return win
|
return win
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// An app to submit patches for the 30 GO GUI repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doList() {
|
||||||
|
msg := []byte(`{"message": "Hello"}`)
|
||||||
|
|
||||||
|
// Initialize a persistent client with a custom Transport
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DisableKeepAlives: false, // Ensure Keep-Alive is enabled
|
||||||
|
},
|
||||||
|
Timeout: 10 * time.Second, // Set a reasonable timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
me.cmap = make(map[*virtpb.Cluster]*adminT)
|
||||||
|
for c := range me.clusters.IterAll() {
|
||||||
|
var err error
|
||||||
|
admin := new(adminT)
|
||||||
|
admin.cluster = new(virtpb.Cluster)
|
||||||
|
me.cmap[c] = admin
|
||||||
|
log.Info("found in the config file", c.URL[0])
|
||||||
|
// a.makeClusterGroup(c)
|
||||||
|
admin.url, err = url.Parse(c.URL[0])
|
||||||
|
if err != nil {
|
||||||
|
badExit(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the droplet list
|
||||||
|
if data, err := postData(admin.url.String()+"/DropletsPB", msg); err != nil {
|
||||||
|
log.Info("/DropletsPB Error:", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
admin.cluster.Droplets = new(virtpb.Droplets)
|
||||||
|
if err := admin.cluster.Droplets.Unmarshal(data); err != nil {
|
||||||
|
log.Printf("DropletsPB Response len:%d\n", len(data))
|
||||||
|
log.Println("droplets marshal failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Cluster Name: %s\n", c.Name)
|
||||||
|
log.Printf("Number of Droplets: %d\n", admin.cluster.Droplets.Len())
|
||||||
|
|
||||||
|
var found *virtpb.Droplets
|
||||||
|
found = virtpb.NewDroplets()
|
||||||
|
all := admin.cluster.Droplets.All()
|
||||||
|
for all.Scan() {
|
||||||
|
vm := all.Next()
|
||||||
|
if vm.Current == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if argv.List.On && (vm.Current.State == virtpb.DropletState_OFF) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found.Append(vm)
|
||||||
|
log.Info(vm.SprintHeader())
|
||||||
|
}
|
||||||
|
log.Println("On Droplet count=", found.Len())
|
||||||
|
}
|
||||||
|
}
|
21
exit.go
21
exit.go
|
@ -6,19 +6,32 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"go.wit.com/gui"
|
||||||
"go.wit.com/log"
|
"go.wit.com/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func okExit(note string) {
|
func okExit(note string) {
|
||||||
if note != "" {
|
if note != "" {
|
||||||
log.Info("virtigo exit:", note, "ok")
|
log.Info(ARGNAME, "exit:", note, "ok")
|
||||||
}
|
}
|
||||||
me.myGui.Close()
|
gui.StandardExit()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func badExit(err error) {
|
func badExit(err error) {
|
||||||
log.Info("virtigo failed: ", err)
|
log.Info(ARGNAME, "failed: ", err)
|
||||||
me.myGui.Close()
|
gui.StandardExit()
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit(note string, err error) {
|
||||||
|
if note != "" {
|
||||||
|
log.Info(ARGNAME, "exit:", note, "ok")
|
||||||
|
}
|
||||||
|
gui.StandardExit()
|
||||||
|
if err == nil {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
log.Info(ARGNAME, "failed: ", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
64
http.go
64
http.go
|
@ -21,8 +21,8 @@ func cleanURL(url string) string {
|
||||||
func okHandler(w http.ResponseWriter, r *http.Request) {
|
func okHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var route string
|
var route string
|
||||||
route = cleanURL(r.URL.Path)
|
route = cleanURL(r.URL.Path)
|
||||||
log.HttpMode(w)
|
// log.HttpMode(w)
|
||||||
defer log.HttpMode(nil)
|
// defer log.HttpMode(nil)
|
||||||
|
|
||||||
msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte
|
msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,7 +31,7 @@ func okHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
if route == "/uptime" {
|
if route == "/uptime" {
|
||||||
ok, s := uptimeCheck()
|
ok, s := uptimeCheck()
|
||||||
fmt.Fprint(w, s)
|
fmt.Fprintln(w, s)
|
||||||
// log.Info(s)
|
// log.Info(s)
|
||||||
updateUptimeGui(s)
|
updateUptimeGui(s)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -70,26 +70,24 @@ func okHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if route == "/start" {
|
if route == "/event" {
|
||||||
hostname := r.URL.Query().Get("hostname")
|
var e *virtpb.Event
|
||||||
if hostname == "" {
|
e = new(virtpb.Event)
|
||||||
log.Warn("start failed. hostname is blank", cleanURL(r.URL.Path))
|
if err := e.Unmarshal(msg); err != nil {
|
||||||
|
log.Info("proto.Unmarshal() failed on wire message len", len(msg))
|
||||||
|
log.Info("error =", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Warn("hostname is", hostname)
|
log.Info("/event proto.Unmarshal() worked on msg len", len(msg), "hostname =", e.DropletUuid)
|
||||||
|
result := doEvent(e)
|
||||||
// log.Warn("Handling URL:", tmp, "start droplet", start)
|
data, err := result.Marshal()
|
||||||
result, err := Start(hostname)
|
if err != nil {
|
||||||
if err == nil {
|
log.Info("/event marshal failed", err, "len(data) =", len(data))
|
||||||
log.Info(result)
|
fmt.Fprintln(w, "/event failed", err)
|
||||||
log.Info(hostname, "started output ok")
|
return
|
||||||
log.Info(hostname, "need to parse the output here")
|
|
||||||
log.Info(hostname, "todo: switch to protobuf here")
|
|
||||||
} else {
|
|
||||||
log.Info(result)
|
|
||||||
log.Info(err)
|
|
||||||
log.Info(hostname, "start failed")
|
|
||||||
}
|
}
|
||||||
|
w.Write(data)
|
||||||
|
// fmt.Fprintln("droplet marshal failed", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +143,32 @@ func okHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if route == "/HypervisorsPB" {
|
||||||
|
pb := me.cluster.GetHypervisorsPB()
|
||||||
|
data, err := pb.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("hypervisors marshal failed", err)
|
||||||
|
fmt.Fprintln(w, "hypervisors marshal failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
// fmt.Fprintln("droplet marshal failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if route == "/EventsPB" {
|
||||||
|
pb := me.cluster.GetEventsPB()
|
||||||
|
data, err := pb.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("events marshal failed", err)
|
||||||
|
fmt.Fprintln(w, "events marshal failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
// fmt.Fprintln("droplet marshal failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if route == "/dumpdropletsfull" {
|
if route == "/dumpdropletsfull" {
|
||||||
dumpDroplets(w, true)
|
dumpDroplets(w, true)
|
||||||
return
|
return
|
||||||
|
|
203
main.go
203
main.go
|
@ -4,30 +4,34 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"go.wit.com/dev/alexflint/arg"
|
"go.wit.com/dev/alexflint/arg"
|
||||||
"go.wit.com/gui"
|
"go.wit.com/lib/gui/prep"
|
||||||
"go.wit.com/lib/protobuf/virtpb"
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
"go.wit.com/lib/virtigolib"
|
|
||||||
"go.wit.com/log"
|
"go.wit.com/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string
|
// sent via -ldflags
|
||||||
|
var VERSION string
|
||||||
|
var BUILDTIME string
|
||||||
|
|
||||||
|
var ARGNAME string = "virtigo"
|
||||||
|
|
||||||
//go:embed resources/*
|
//go:embed resources/*
|
||||||
var resources embed.FS
|
var resources embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var pp *arg.Parser
|
me = new(virtigoT)
|
||||||
gui.InitArg()
|
prep.Bash(ARGNAME, argv.DoAutoComplete) // this line should be: prep.Bash(argv)
|
||||||
pp = arg.MustParse(&argv)
|
me.myGui = prep.Gui() // prepares the GUI package for go-args
|
||||||
|
me.pp = arg.MustParse(&argv)
|
||||||
|
|
||||||
if pp == nil {
|
if me.pp == nil {
|
||||||
pp.WriteHelp(os.Stdout)
|
me.pp.WriteHelp(os.Stdout)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,149 +41,64 @@ func main() {
|
||||||
os.Setenv("VIRTIGO_HOME", fullpath)
|
os.Setenv("VIRTIGO_HOME", fullpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if argv.Server != "" {
|
me.clusters = virtpb.NewClusters()
|
||||||
|
|
||||||
|
if argv.List != nil {
|
||||||
|
err := me.clusters.ConfigLoad()
|
||||||
|
if err != nil {
|
||||||
|
badExit(err)
|
||||||
|
}
|
||||||
|
doList()
|
||||||
|
okExit("virtigo list")
|
||||||
|
}
|
||||||
|
|
||||||
|
if argv.Droplet != nil {
|
||||||
|
exit(doDroplet())
|
||||||
|
}
|
||||||
|
|
||||||
|
me.myGui.Start() // loads the GUI toolkit
|
||||||
|
|
||||||
|
if argv.Admin {
|
||||||
|
err := me.clusters.ConfigLoad()
|
||||||
|
if err != nil {
|
||||||
|
badExit(err)
|
||||||
|
}
|
||||||
|
|
||||||
doAdminGui()
|
doAdminGui()
|
||||||
okExit("admin close")
|
okExit("admin close")
|
||||||
}
|
}
|
||||||
|
|
||||||
// set defaults
|
if argv.Server != "" {
|
||||||
me.unstable = time.Now() // initialize the grid as unstable
|
log.Info("start admin interface")
|
||||||
me.changed = false
|
admin := new(adminT)
|
||||||
me.hmap = make(map[*virtpb.Hypervisor]*HyperT)
|
var err error
|
||||||
|
admin.url, err = url.Parse(argv.Server)
|
||||||
// how long a droplet can be missing until it's declared dead
|
|
||||||
me.unstableTimeout = 17 * time.Second
|
|
||||||
me.missingDropletTimeout = time.Minute // not sure the difference between these values
|
|
||||||
|
|
||||||
// how often to poll the hypervisors
|
|
||||||
me.hyperPollDelay = 5 * time.Second
|
|
||||||
|
|
||||||
// how long the cluster must be stable before new droplets can be started
|
|
||||||
me.clusterStableDuration = 37 * time.Second
|
|
||||||
|
|
||||||
me.cluster = virtpb.InitCluster()
|
|
||||||
if err := me.cluster.ConfigLoad(); err != nil {
|
|
||||||
log.Info("config load error", err)
|
|
||||||
log.Info("")
|
|
||||||
log.Info("You have never run this before")
|
|
||||||
log.Info("init example cloud here")
|
|
||||||
log.Sleep(2)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
loop := me.cluster.DropletsAll() // get the list of droplets
|
|
||||||
for loop.Scan() {
|
|
||||||
d := loop.Next()
|
|
||||||
if d == nil {
|
|
||||||
fmt.Println("d == nil")
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
fmt.Println("Droplet UUID:", d.Uuid)
|
|
||||||
if d.Current == nil {
|
|
||||||
d.Current = new(virtpb.Current)
|
|
||||||
}
|
|
||||||
d.SetState(virtpb.DropletState_OFF)
|
|
||||||
log.Info("droplet", d.Hostname)
|
|
||||||
}
|
|
||||||
hmm := "pihole.wit.com"
|
|
||||||
d := me.cluster.FindDropletByName(hmm)
|
|
||||||
if d == nil {
|
|
||||||
log.Info("did not find found droplet", hmm)
|
|
||||||
} else {
|
|
||||||
log.Info("found droplet", d.Hostname, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newEvents []*virtpb.Event
|
|
||||||
|
|
||||||
// sanity check the cluster & droplets
|
|
||||||
if _, _, err := ValidateDroplets(); err != nil {
|
|
||||||
log.Info("todo: add flag to ignore. for now, fix problems in the config file.")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
newe, err := ValidateDiskFilenames()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info(err)
|
badExit(err)
|
||||||
os.Exit(-1)
|
|
||||||
}
|
}
|
||||||
// this is a new droplet. add it to the cluster
|
err = me.clusters.ConfigLoad()
|
||||||
for _, e := range newe {
|
|
||||||
newEvents = append(newEvents, e)
|
|
||||||
}
|
|
||||||
ValidateUniqueFilenames()
|
|
||||||
|
|
||||||
for _, filename := range argv.Xml {
|
|
||||||
domcfg, err := virtigolib.ReadXml(filename)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// parsing the libvirt xml file failed
|
clusters := virtpb.NewClusters()
|
||||||
log.Info("error:", filename, err)
|
c := new(virtpb.Cluster)
|
||||||
log.Info("readXml() error", filename)
|
c.Uuid = uuid.New().String()
|
||||||
log.Info("readXml() error", err)
|
c.URL = append(c.URL, argv.Server)
|
||||||
log.Info("libvirt XML will have to be fixed by hand")
|
clusters.Append(c)
|
||||||
os.Exit(-1)
|
virtpb.ConfigWriteTEXT(clusters, "cluster.text")
|
||||||
}
|
|
||||||
// this is a new droplet. add it to the cluster
|
badExit(err)
|
||||||
log.Info("Add XML Droplet here", domcfg.Name)
|
|
||||||
_, newe, err := virtigolib.AddDomainDroplet(me.cluster, domcfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("addDomainDroplet() error", filename)
|
|
||||||
log.Info("addDomainDroplet() error", err)
|
|
||||||
log.Info("libvirt XML will have to be fixed by hand")
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
for _, e := range newe {
|
|
||||||
newEvents = append(newEvents, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, e := range newEvents {
|
|
||||||
log.Info(i, "Event:", e.Droplet, e.FieldName, "orig:", e.OrigVal, "new:", e.NewVal)
|
|
||||||
me.changed = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if me.changed {
|
admin.doAdminGui()
|
||||||
if err := me.cluster.ConfigSave(); err != nil {
|
okExit("admin close")
|
||||||
log.Info("configsave error", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
log.Info("XML changes saved in protobuf config")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
if len(argv.Xml) != 0 {
|
|
||||||
log.Info("No XML changes found")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize each hypervisor
|
if argv.Daemon {
|
||||||
for _, pbh := range me.cluster.H.Hypervisors {
|
if err := doDaemon(); err != nil {
|
||||||
// this is a new unknown droplet (not in the config file)
|
badExit(err)
|
||||||
var h *HyperT
|
}
|
||||||
h = new(HyperT)
|
okExit("")
|
||||||
h.pb = pbh
|
|
||||||
h.lastDroplets = make(map[string]time.Time)
|
|
||||||
h.lastpoll = time.Now()
|
|
||||||
|
|
||||||
me.hmap[pbh] = h
|
|
||||||
me.hypers = append(me.hypers, h)
|
|
||||||
log.Log(EVENT, "config new hypervisors", h.pb.Hostname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the watchdog polling for each hypervisor
|
doGui() // start making our forge GUI
|
||||||
for _, h := range me.hypers {
|
startHTTP() // sit here forever
|
||||||
log.Info("starting polling on", h.pb.Hostname)
|
|
||||||
|
|
||||||
// start a watchdog on each hypervisor
|
|
||||||
go h.NewWatchdog()
|
|
||||||
}
|
|
||||||
|
|
||||||
var cloud *virtigolib.CloudManager
|
|
||||||
cloud = virtigolib.NewCloud()
|
|
||||||
found, _ := cloud.FindDropletByName("www.wit.com")
|
|
||||||
if found == nil {
|
|
||||||
log.Info("d == nil")
|
|
||||||
} else {
|
|
||||||
log.Info("d == ", found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sit here
|
|
||||||
go startHTTP()
|
|
||||||
doGui()
|
|
||||||
}
|
}
|
||||||
|
|
60
post.go
60
post.go
|
@ -2,8 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
||||||
|
@ -36,3 +38,61 @@ func httpPost(url string, data []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseURL() (string, string) {
|
||||||
|
parsedURL, err := url.Parse(argv.Server)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error parsing URL:", err)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Host (includes hostname/IP and port if present)
|
||||||
|
host := parsedURL.Host
|
||||||
|
fmt.Println("Host:", host)
|
||||||
|
|
||||||
|
// Extract Hostname (without port)
|
||||||
|
hostname := parsedURL.Hostname()
|
||||||
|
fmt.Println("Hostname:", hostname)
|
||||||
|
|
||||||
|
// Extract Port
|
||||||
|
port := parsedURL.Port()
|
||||||
|
fmt.Println("Port:", port)
|
||||||
|
|
||||||
|
return parsedURL.Hostname(), parsedURL.Port()
|
||||||
|
}
|
||||||
|
|
||||||
|
func gusPost(port string, dest string) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
var req *http.Request
|
||||||
|
|
||||||
|
gus, _ := parseURL()
|
||||||
|
url := fmt.Sprintf("http://%s:%d/%s?port=%s&dest=%s", gus, 2522, "enable", port, dest)
|
||||||
|
|
||||||
|
data := []byte("hello world")
|
||||||
|
|
||||||
|
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
|
||||||
|
|
||||||
|
usr, _ := user.Current()
|
||||||
|
req.Header.Set("author", usr.Username)
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
req.Header.Set("hostname", hostname)
|
||||||
|
req.Header.Set("port", port)
|
||||||
|
req.Header.Set("dest", dest)
|
||||||
|
|
||||||
|
log.Printf("gusPust url(%s) port(%s) dest(%s) hostname(%s)\n", url, port, dest, hostname)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
20
start.go
20
start.go
|
@ -29,7 +29,7 @@ func isClusterStable() (string, error) {
|
||||||
|
|
||||||
// for now, because sometimes this should write to stdout and
|
// for now, because sometimes this should write to stdout and
|
||||||
// sometimes to http socket, it returns a string
|
// sometimes to http socket, it returns a string
|
||||||
func Start(name string) (string, error) {
|
func Start(id string) (string, error) {
|
||||||
var result string
|
var result string
|
||||||
|
|
||||||
if s, err := isClusterStable(); err != nil {
|
if s, err := isClusterStable(); err != nil {
|
||||||
|
@ -38,9 +38,9 @@ func Start(name string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup the droplet by name
|
// lookup the droplet by name
|
||||||
d := me.cluster.FindDropletByName(name)
|
d := me.cluster.FindDropletByUuid(id)
|
||||||
if d == nil {
|
if d == nil {
|
||||||
result = "can't start unknown droplet: " + name
|
result = "can't start unknown droplet: " + id
|
||||||
return result, errors.New(result)
|
return result, errors.New(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@ func Start(name string) (string, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.Current == nil {
|
||||||
|
d.Current = new(virtpb.Current)
|
||||||
|
}
|
||||||
|
|
||||||
// is the droplet already on?
|
// is the droplet already on?
|
||||||
if d.Current.State == virtpb.DropletState_ON {
|
if d.Current.State == virtpb.DropletState_ON {
|
||||||
result = "EVENT start droplet " + d.Hostname + " is already ON"
|
result = "EVENT start droplet " + d.Hostname + " is already ON"
|
||||||
|
@ -66,12 +70,12 @@ func Start(name string) (string, error) {
|
||||||
if ok {
|
if ok {
|
||||||
return result + b, nil
|
return result + b, nil
|
||||||
}
|
}
|
||||||
return result + b, errors.New("start " + name + " on hypervisor " + h.pb.Hostname)
|
return result + b, errors.New("start " + d.Hostname + " on hypervisor " + h.pb.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip hypervisors marked inactive
|
// skip hypervisors marked inactive
|
||||||
if h.pb.Active != true {
|
if h.pb.Active != true {
|
||||||
result += fmt.Sprintln("hypervisor is inactive:", name, "for", h.pb.Hostname, h.pb.Active)
|
result += fmt.Sprintln("hypervisor is inactive:", d.Hostname, "for", h.pb.Hostname, h.pb.Active)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,10 +86,10 @@ func Start(name string) (string, error) {
|
||||||
if ok {
|
if ok {
|
||||||
return result + b, nil
|
return result + b, nil
|
||||||
}
|
}
|
||||||
return result + b, errors.New("start " + name + " on hypervisor " + h.pb.Hostname)
|
return result + b, errors.New("start " + d.Hostname + " on hypervisor " + h.pb.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
result += fmt.Sprintln("hypervisor ready:", name, "for", h.pb.Hostname, h.pb.Active)
|
result += fmt.Sprintln("hypervisor ready:", d.Hostname, "for", h.pb.Hostname, h.pb.Active)
|
||||||
pool = append(pool, h)
|
pool = append(pool, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,5 +110,5 @@ func Start(name string) (string, error) {
|
||||||
if ok {
|
if ok {
|
||||||
return result + output, nil
|
return result + output, nil
|
||||||
}
|
}
|
||||||
return result + output, errors.New("start " + name + " on hypervisor " + h.pb.Hostname)
|
return result + output, errors.New("start " + d.Hostname + " on hypervisor " + h.pb.Hostname)
|
||||||
}
|
}
|
||||||
|
|
28
structs.go
28
structs.go
|
@ -1,13 +1,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.wit.com/dev/alexflint/arg"
|
||||||
"go.wit.com/gui"
|
"go.wit.com/gui"
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/lib/gui/prep"
|
||||||
"go.wit.com/lib/protobuf/virtpb"
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var me virtigoT
|
var me *virtigoT
|
||||||
|
|
||||||
// disable the GUI
|
// disable the GUI
|
||||||
func (b *virtigoT) Disable() {
|
func (b *virtigoT) Disable() {
|
||||||
|
@ -21,8 +25,8 @@ func (b *virtigoT) Enable() {
|
||||||
|
|
||||||
// this app's variables
|
// this app's variables
|
||||||
type virtigoT struct {
|
type virtigoT struct {
|
||||||
cluster *virtpb.Cluster // basic cluster settings
|
pp *arg.Parser // go-arg parser
|
||||||
myGui *gui.Node // the gui toolkit handle
|
myGui *prep.GuiPrep // the gui toolkit handle
|
||||||
e *virtpb.Events // virt protobuf events
|
e *virtpb.Events // virt protobuf events
|
||||||
hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct
|
hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct
|
||||||
names []string // ?
|
names []string // ?
|
||||||
|
@ -36,11 +40,21 @@ type virtigoT struct {
|
||||||
missingDropletTimeout time.Duration // how long a droplet can be missing for
|
missingDropletTimeout time.Duration // how long a droplet can be missing for
|
||||||
status *gui.Node // the cluster status
|
status *gui.Node // the cluster status
|
||||||
lastuptime *gui.Node // the last time uptime was checked by Kuma
|
lastuptime *gui.Node // the last time uptime was checked by Kuma
|
||||||
|
clusters *virtpb.Clusters // clusters protobuf
|
||||||
|
cmap map[*virtpb.Cluster]*adminT // map to local GUI objects and the protobuf
|
||||||
|
gwin *gadgets.GenericWindow // main window
|
||||||
|
cluster *virtpb.OldCluster // basic cluster settings
|
||||||
|
// admin *adminT // the admin struct
|
||||||
|
}
|
||||||
|
|
||||||
// admin mode
|
// cluster "admin" mode
|
||||||
droplets *virtpb.Droplets // your droplets
|
type adminT struct {
|
||||||
hypervisors *virtpb.Hypervisors // yep
|
cluster *virtpb.Cluster // the cluster protobuf
|
||||||
events *virtpb.Events // yep
|
uptime *gui.Node // the uptime message
|
||||||
|
dwin *stdDropletTableWin // the droplet window
|
||||||
|
hwin *stdHypervisorTableWin // the hypervisor window
|
||||||
|
ewin *stdEventTableWin // the events window
|
||||||
|
url *url.URL // URL for the cloud
|
||||||
}
|
}
|
||||||
|
|
||||||
// the stuff that is needed for a hypervisor
|
// the stuff that is needed for a hypervisor
|
||||||
|
|
35
validate.go
35
validate.go
|
@ -15,7 +15,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -163,40 +162,6 @@ func ValidateDiskFilenames() ([]*virtpb.Event, error) {
|
||||||
return alle, nil
|
return alle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewMac() string {
|
|
||||||
// mac address map to check for duplicates
|
|
||||||
var macs map[string]string
|
|
||||||
macs = make(map[string]string)
|
|
||||||
|
|
||||||
loop := me.cluster.DropletsAll() // get the list of droplets
|
|
||||||
for loop.Scan() {
|
|
||||||
d := loop.Next()
|
|
||||||
for _, n := range d.Networks {
|
|
||||||
// log.Println("network:", n.Mac, d.Uuid, d.Hostname)
|
|
||||||
if _, ok := macs[n.Mac]; ok {
|
|
||||||
// UUID already exists
|
|
||||||
log.Info("duplicate MAC", n.Mac, macs[n.Mac])
|
|
||||||
log.Info("duplicate MAC", n.Mac, d.Hostname)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
macs[n.Mac] = d.Hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var i int = 9
|
|
||||||
var mac string
|
|
||||||
for {
|
|
||||||
mac = fmt.Sprintf("22:22:22:22:22:%02d", i)
|
|
||||||
if _, ok := macs[mac]; ok {
|
|
||||||
log.Info("MAC already defined", mac, macs[mac])
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Info("using new MAC:", mac)
|
|
||||||
return mac
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// consistancy check. run on a regular basis
|
// consistancy check. run on a regular basis
|
||||||
//
|
//
|
||||||
// runs on startup. dies if there are duplicates
|
// runs on startup. dies if there are duplicates
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// An app to submit patches for the 30 GO GUI repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createWindow() *gadgets.GenericWindow {
|
||||||
|
createWindow := gadgets.NewGenericWindow("Create Droplet", "settings")
|
||||||
|
createWindow.Custom = func() {
|
||||||
|
log.Warn("create window close")
|
||||||
|
}
|
||||||
|
|
||||||
|
grid := createWindow.Group.RawGrid()
|
||||||
|
|
||||||
|
gadgets.NewBasicEntry(grid, "memory")
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
grid.NewLabel("name")
|
||||||
|
grid.NewTextbox("something")
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
grid.NewButton("Start", func() {
|
||||||
|
log.Info("make a box")
|
||||||
|
})
|
||||||
|
|
||||||
|
return createWindow
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// An app to submit patches for the 30 GO GUI repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"go.wit.com/gui"
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (admin *adminT) createDropletWindow() *gadgets.GenericWindow {
|
||||||
|
d := new(virtpb.Droplet)
|
||||||
|
|
||||||
|
win := gadgets.NewGenericWindow("Create Droplet "+d.Hostname, "settings")
|
||||||
|
win.Custom = func() {
|
||||||
|
log.Warn("edit window close")
|
||||||
|
}
|
||||||
|
|
||||||
|
grid := win.Group.RawGrid()
|
||||||
|
|
||||||
|
var save *gui.Node
|
||||||
|
|
||||||
|
grid.NewLabel("name")
|
||||||
|
name := grid.NewTextbox("new2.wit.com")
|
||||||
|
d.Hostname = "new2.wit.com"
|
||||||
|
name.SetText(d.Hostname)
|
||||||
|
name.Custom = func() {
|
||||||
|
if d.Hostname == name.String() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Hostname = name.String()
|
||||||
|
log.Info("changed droplet name to", d.Hostname)
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
mem := gadgets.NewBasicEntry(grid, "memory (GB)")
|
||||||
|
mem.SetText("16")
|
||||||
|
d.Memory = int64(16 * 1024 * 2024 * 1024)
|
||||||
|
grid.NextRow()
|
||||||
|
mem.Custom = func() {
|
||||||
|
newmem, err := strconv.Atoi(mem.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Info("mem value error", mem.String(), err)
|
||||||
|
mem.SetText(fmt.Sprintf("%d", d.Memory/(1024*1024*1024)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newmem < 1 {
|
||||||
|
log.Info("mem can not be < 1")
|
||||||
|
mem.SetText(fmt.Sprintf("%d", d.Memory/(1024*1024*1024)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Memory = int64(newmem * (1024 * 2024 * 1024))
|
||||||
|
log.Info("changed mem value. new val =", d.Memory)
|
||||||
|
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
grid.NextRow() // each entry is on it's own row
|
||||||
|
|
||||||
|
cpus := gadgets.NewBasicEntry(grid, "cpus")
|
||||||
|
cpus.SetText("4")
|
||||||
|
d.Cpus = int64(4)
|
||||||
|
cpus.Custom = func() {
|
||||||
|
newcpu, err := strconv.Atoi(cpus.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Info("cpus value error", cpus.String(), err)
|
||||||
|
cpus.SetText(fmt.Sprintf("%d", d.Cpus))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newcpu < 1 {
|
||||||
|
log.Info("cpus can not be < 1")
|
||||||
|
cpus.SetText(fmt.Sprintf("%d", d.Cpus))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Cpus = int64(newcpu)
|
||||||
|
log.Info("changed cpus value. new val =", d.Cpus)
|
||||||
|
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
grid.NextRow() // each entry is on it's own row
|
||||||
|
|
||||||
|
/*
|
||||||
|
save = grid.NewButton("postEvent() EDIT", func() {
|
||||||
|
log.Info("save droplet changes here")
|
||||||
|
|
||||||
|
e := new(virtpb.Event)
|
||||||
|
e.Etype = virtpb.EventType_EDIT
|
||||||
|
e.Droplet = d
|
||||||
|
|
||||||
|
if err := admin.postEvent(e); err != nil {
|
||||||
|
log.Info("event edit err", err)
|
||||||
|
} else {
|
||||||
|
log.Info("admin.postEvent() worked (?)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
save = grid.NewButton("Create", func() {
|
||||||
|
log.Info("save droplet changes here")
|
||||||
|
|
||||||
|
e := new(virtpb.Event)
|
||||||
|
e.Etype = virtpb.EventType_ADD
|
||||||
|
e.Droplet = d
|
||||||
|
|
||||||
|
if err := admin.postEvent(e); err != nil {
|
||||||
|
log.Info("event edit err", err)
|
||||||
|
} else {
|
||||||
|
log.Info("admin.postEvent() worked (?)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// save.Disable()
|
||||||
|
return win
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// An app to submit patches for the 30 GO GUI repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"go.wit.com/gui"
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (admin *adminT) editDropletWindow(d *virtpb.Droplet) *gadgets.GenericWindow {
|
||||||
|
win := gadgets.NewGenericWindow("Edit Droplet "+d.Hostname, "settings")
|
||||||
|
win.Custom = func() {
|
||||||
|
log.Warn("edit window close")
|
||||||
|
}
|
||||||
|
|
||||||
|
grid := win.Group.RawGrid()
|
||||||
|
|
||||||
|
var save *gui.Node
|
||||||
|
|
||||||
|
grid.NewLabel("name")
|
||||||
|
name := grid.NewTextbox("something")
|
||||||
|
name.SetText(d.Hostname)
|
||||||
|
name.Custom = func() {
|
||||||
|
if d.Hostname == name.String() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Hostname = name.String()
|
||||||
|
log.Info("changed droplet name to", d.Hostname)
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
mem := gadgets.NewBasicEntry(grid, "memory (GB)")
|
||||||
|
mem.SetText(fmt.Sprintf("%d", d.Memory/(1024*1024*1024)))
|
||||||
|
grid.NextRow()
|
||||||
|
mem.Custom = func() {
|
||||||
|
newmem, err := strconv.Atoi(mem.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Info("mem value error", mem.String(), err)
|
||||||
|
mem.SetText(fmt.Sprintf("%d", d.Memory/(1024*1024*1024)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newmem < 1 {
|
||||||
|
log.Info("mem can not be < 1")
|
||||||
|
mem.SetText(fmt.Sprintf("%d", d.Memory/(1024*1024*1024)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Memory = int64(newmem * (1024 * 2024 * 1024))
|
||||||
|
log.Info("changed mem value. new val =", d.Memory)
|
||||||
|
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
|
||||||
|
cpus := gadgets.NewBasicEntry(grid, "cpus")
|
||||||
|
cpus.SetText(fmt.Sprintf("%d", d.Cpus))
|
||||||
|
grid.NextRow()
|
||||||
|
cpus.Custom = func() {
|
||||||
|
newcpu, err := strconv.Atoi(cpus.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Info("cpus value error", cpus.String(), err)
|
||||||
|
cpus.SetText(fmt.Sprintf("%d", d.Cpus))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newcpu < 1 {
|
||||||
|
log.Info("cpus can not be < 1")
|
||||||
|
cpus.SetText(fmt.Sprintf("%d", d.Cpus))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Cpus = int64(newcpu)
|
||||||
|
log.Info("changed cpus value. new val =", d.Cpus)
|
||||||
|
|
||||||
|
save.Enable()
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.NewLabel("hypervisor")
|
||||||
|
hyper := grid.NewDropdown()
|
||||||
|
hyper.AddText("farm03")
|
||||||
|
hyper.AddText("farm04")
|
||||||
|
hyper.AddText("farm05")
|
||||||
|
if d.Current != nil {
|
||||||
|
hyper.SetText(d.Current.Hypervisor)
|
||||||
|
} else {
|
||||||
|
hyper.SetText("farm03")
|
||||||
|
}
|
||||||
|
grid.NextRow()
|
||||||
|
|
||||||
|
grid.NewButton("Start", func() {
|
||||||
|
log.Info("make a box")
|
||||||
|
})
|
||||||
|
|
||||||
|
save = grid.NewButton("save", func() {
|
||||||
|
log.Info("save droplet changes here")
|
||||||
|
|
||||||
|
e := new(virtpb.Event)
|
||||||
|
e.Etype = virtpb.EventType_EDIT
|
||||||
|
e.Droplet = d
|
||||||
|
/*
|
||||||
|
e.Droplet = new(virtpb.Droplet)
|
||||||
|
e.Droplet.Uuid = d.Uuid
|
||||||
|
e.Droplet.Cpus = 4
|
||||||
|
e.Droplet.Memory = 8 * (1024 * 1024 * 1024)
|
||||||
|
e.Droplet.Hostname = name.String()
|
||||||
|
*/
|
||||||
|
|
||||||
|
if err := admin.postEvent(e); err != nil {
|
||||||
|
log.Info("event edit err", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
save.Disable()
|
||||||
|
|
||||||
|
grid.NewButton("dump", func() {
|
||||||
|
t := d.FormatTEXT()
|
||||||
|
log.Info(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
return win
|
||||||
|
}
|
|
@ -4,7 +4,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.wit.com/gui"
|
"go.wit.com/gui"
|
||||||
"go.wit.com/lib/gadgets"
|
"go.wit.com/lib/gadgets"
|
||||||
|
@ -16,8 +19,11 @@ type stdDropletTableWin struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
win *gadgets.GenericWindow // the machines gui window
|
win *gadgets.GenericWindow // the machines gui window
|
||||||
box *gui.Node // the machines gui parent box widget
|
box *gui.Node // the machines gui parent box widget
|
||||||
|
pb *virtpb.Droplets // the droplets protobuf
|
||||||
TB *virtpb.DropletsTable // the gui table buffer
|
TB *virtpb.DropletsTable // the gui table buffer
|
||||||
update bool // if the window should be updated
|
update bool // if the window should be updated
|
||||||
|
Close func() // this function is called when the window is closed
|
||||||
|
admin *adminT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *stdDropletTableWin) Toggle() {
|
func (w *stdDropletTableWin) Toggle() {
|
||||||
|
@ -30,88 +36,192 @@ func (w *stdDropletTableWin) Toggle() {
|
||||||
w.win.Toggle()
|
w.win.Toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDropletsWin() *stdDropletTableWin {
|
func newDropletsWindow(admin *adminT) *stdDropletTableWin {
|
||||||
dwin := new(stdDropletTableWin)
|
dwin := new(stdDropletTableWin)
|
||||||
dwin.win = gadgets.NewGenericWindow("virtigo current droplets", "who is squirreling around?")
|
dwin.admin = admin
|
||||||
|
dwin.win = gadgets.NewGenericWindow("virtigo current droplets", "Options")
|
||||||
dwin.win.Custom = func() {
|
dwin.win.Custom = func() {
|
||||||
log.Info("test delete window here")
|
log.Info("test delete window here")
|
||||||
}
|
}
|
||||||
grid := dwin.win.Group.RawGrid()
|
grid := dwin.win.Group.RawGrid()
|
||||||
|
|
||||||
grid.NewButton("show droplets", func() {
|
grid.NewButton("Active", func() {
|
||||||
var count int
|
var found *virtpb.Droplets
|
||||||
found := virtpb.NewDroplets()
|
found = virtpb.NewDroplets()
|
||||||
all := me.droplets.All()
|
all := admin.cluster.Droplets.All()
|
||||||
for all.Scan() {
|
for all.Scan() {
|
||||||
e := all.Next()
|
vm := all.Next()
|
||||||
count += 1
|
if vm.Current.State != virtpb.DropletState_ON {
|
||||||
found.Append(e)
|
continue
|
||||||
if count > 20 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
found.Append(vm)
|
||||||
}
|
}
|
||||||
dwin.doDropletsTable(found)
|
dwin.doActiveDroplets(found)
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
grid.NewButton("Inactive", func() {
|
||||||
grid.NewButton("Save Current", func() {
|
var found *virtpb.Droplets
|
||||||
log.Info("droplets len =", me.droplets.Len())
|
found = virtpb.NewDroplets()
|
||||||
me.droplets.Save()
|
all := admin.cluster.Droplets.All()
|
||||||
|
for all.Scan() {
|
||||||
|
vm := all.Next()
|
||||||
|
if vm.Current.State == virtpb.DropletState_ON {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found.Append(vm)
|
||||||
|
}
|
||||||
|
dwin.doInactiveDroplets(found)
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.NewButton("Create", func() {
|
||||||
|
log.Info("create droplet here")
|
||||||
|
admin.createDropletWindow()
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|
||||||
// make a box at the bottom of the window for the protobuf table
|
// make a box at the bottom of the window for the protobuf table
|
||||||
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
|
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
|
||||||
// doTable(me.droplets)
|
|
||||||
|
|
||||||
return dwin
|
return dwin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dwin *stdDropletTableWin) doDropletsTable(currentDroplets *virtpb.Droplets) {
|
// default window for active running droplets
|
||||||
dwin.Lock()
|
func (dw *stdDropletTableWin) doInactiveDroplets(pb *virtpb.Droplets) {
|
||||||
defer dwin.Unlock()
|
dw.Lock()
|
||||||
if dwin.TB != nil {
|
defer dw.Unlock()
|
||||||
dwin.TB.Delete()
|
|
||||||
dwin.TB = nil
|
// erase the old table
|
||||||
|
if dw.TB != nil {
|
||||||
|
dw.TB.Delete()
|
||||||
|
dw.TB = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// display the protobuf
|
// init the table
|
||||||
dwin.TB = AddDropletsPB(dwin.box, currentDroplets)
|
dw.pb = pb
|
||||||
|
t := dw.pb.NewTable("DropletsPB Off")
|
||||||
|
t.NewUuid()
|
||||||
|
t.SetParent(dw.box)
|
||||||
|
|
||||||
|
dropedit := t.AddButtonFunc("Edit", func(d *virtpb.Droplet) string {
|
||||||
|
return "edit"
|
||||||
|
})
|
||||||
|
dropedit.Custom = func(d *virtpb.Droplet) {
|
||||||
|
log.Info("edit droplet here", d.Hostname)
|
||||||
|
dw.admin.editDropletWindow(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
dropon := t.AddButtonFunc("Start", func(d *virtpb.Droplet) string {
|
||||||
|
return "poweron"
|
||||||
|
})
|
||||||
|
dropon.Custom = func(d *virtpb.Droplet) {
|
||||||
|
log.Info("start droplet here", d.Hostname)
|
||||||
|
log.Info("should start droplet here")
|
||||||
|
log.Info(d.SprintHeader())
|
||||||
|
e := new(virtpb.Event)
|
||||||
|
e.Etype = virtpb.EventType_POWERON
|
||||||
|
e.DropletUuid = d.Uuid
|
||||||
|
|
||||||
|
if err := dw.admin.postEvent(e); err != nil {
|
||||||
|
log.Info("droplet start err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vp := t.AddButtonFunc("Verify Config", func(p *virtpb.Droplet) string {
|
||||||
|
return p.Hostname
|
||||||
|
})
|
||||||
|
vp.Custom = func(d *virtpb.Droplet) {
|
||||||
|
log.Info("open config window", d.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddMemory()
|
||||||
|
t.AddCpus()
|
||||||
|
|
||||||
|
// final setup and display the table
|
||||||
|
dw.TB = t
|
||||||
f := func(e *virtpb.Droplet) {
|
f := func(e *virtpb.Droplet) {
|
||||||
log.Info("Triggered. do something here", e.Hostname)
|
log.Info("Triggered. do something here", e.Hostname)
|
||||||
// m.Enabled = true
|
// m.Enabled = true
|
||||||
}
|
}
|
||||||
dwin.TB.Custom(f)
|
dw.TB.Custom(f)
|
||||||
|
dw.TB.ShowTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDropletsPB(tbox *gui.Node, pb *virtpb.Droplets) *virtpb.DropletsTable {
|
// default window for active running droplets
|
||||||
t := pb.NewTable("DropletsPB")
|
func (dw *stdDropletTableWin) doActiveDroplets(pb *virtpb.Droplets) {
|
||||||
|
dw.Lock()
|
||||||
|
defer dw.Unlock()
|
||||||
|
if dw.TB != nil {
|
||||||
|
dw.TB.Delete()
|
||||||
|
dw.TB = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dw.pb = pb
|
||||||
|
|
||||||
|
t := dw.pb.NewTable("DropletsPB On")
|
||||||
t.NewUuid()
|
t.NewUuid()
|
||||||
t.SetParent(tbox)
|
|
||||||
|
|
||||||
/*
|
t.SetParent(dw.box)
|
||||||
ctimef := func(e *virtpb.Droplet) string {
|
|
||||||
ctime := e.Ctime.AsTime()
|
|
||||||
return ctime.Format("2006/01/02 15:04")
|
|
||||||
}
|
|
||||||
t.AddStringFunc("ctime", ctimef)
|
|
||||||
|
|
||||||
etimef := func(e *virtpb.Droplet) string {
|
serial := t.AddButtonFunc("serial", func(p *virtpb.Droplet) string {
|
||||||
etime := e.Etime.AsTime()
|
return "ttyS0"
|
||||||
s := etime.Format("2006/01/02 15:04")
|
})
|
||||||
if strings.HasPrefix(s, "1970/") {
|
serial.Custom = func(d *virtpb.Droplet) {
|
||||||
// just show a blank if it's not set
|
log.Printf("run %s: socat telnet somewhere %s:%d\n", d.Hostname, argv.Server, d.SpicePort)
|
||||||
return ""
|
log.Info("socat TCP-LISTEN:5000,reuseaddr,fork EXEC:\"virsh console myvm\"")
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
t.AddStringFunc("etime", etimef)
|
|
||||||
*/
|
|
||||||
|
|
||||||
t.AddHostname()
|
}
|
||||||
// t.AddAddress()
|
|
||||||
// t.AddWhere()
|
fb := t.AddButtonFunc("fb0 console", func(p *virtpb.Droplet) string {
|
||||||
// t.AddLocalPort()
|
return "remmina"
|
||||||
|
})
|
||||||
|
fb.Custom = func(d *virtpb.Droplet) {
|
||||||
|
log.Printf("connect to %s on %s: remmina spice://%s:%d\n", d.Hostname, d.Current.Hypervisor, argv.Server, 10000+d.SpicePort)
|
||||||
|
data, err := gusPost(fmt.Sprintf("%d", 10000+d.SpicePort), d.Current.Hypervisor)
|
||||||
|
log.Info("data", string(data), "err =", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.AddHostname()
|
||||||
|
vp := t.AddButtonFunc("Hostname", func(p *virtpb.Droplet) string {
|
||||||
|
return p.Hostname
|
||||||
|
})
|
||||||
|
vp.Custom = func(d *virtpb.Droplet) {
|
||||||
|
log.Info("edit droplet here", d.Hostname)
|
||||||
|
dw.admin.editDropletWindow(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddStringFunc("location", func(d *virtpb.Droplet) string {
|
||||||
|
return d.Current.Hypervisor
|
||||||
|
})
|
||||||
|
t.AddMemory()
|
||||||
|
t.AddCpus()
|
||||||
|
t.AddSpicePort()
|
||||||
|
t.AddTimeFunc("age", func(d *virtpb.Droplet) time.Time {
|
||||||
|
age := d.Current.OnSince.AsTime()
|
||||||
|
// log.Info("age", d.Hostname, virtpb.FormatDuration(time.Since(age)))
|
||||||
|
return age
|
||||||
|
})
|
||||||
|
t.AddStringFunc("State", func(d *virtpb.Droplet) string {
|
||||||
|
if d.Current.State == virtpb.DropletState_ON {
|
||||||
|
return "ON"
|
||||||
|
}
|
||||||
|
if d.Current.State == virtpb.DropletState_OFF {
|
||||||
|
return "OFF"
|
||||||
|
}
|
||||||
|
return "UNKNOWN"
|
||||||
|
})
|
||||||
|
t.AddStringFunc("mac addr", func(d *virtpb.Droplet) string {
|
||||||
|
var macs []string
|
||||||
|
for _, n := range d.Networks {
|
||||||
|
macs = append(macs, n.Mac)
|
||||||
|
}
|
||||||
|
tmp := strings.Join(macs, "\n")
|
||||||
|
return strings.TrimSpace(tmp)
|
||||||
|
})
|
||||||
t.ShowTable()
|
t.ShowTable()
|
||||||
return t
|
|
||||||
|
// display the protobuf
|
||||||
|
dw.TB = t
|
||||||
|
f := func(e *virtpb.Droplet) {
|
||||||
|
log.Info("Triggered. do something here", e.Hostname)
|
||||||
|
// m.Enabled = true
|
||||||
|
}
|
||||||
|
dw.TB.Custom(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.wit.com/gui"
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stdEventTableWin struct {
|
||||||
|
sync.Mutex
|
||||||
|
win *gadgets.GenericWindow // the machines gui window
|
||||||
|
box *gui.Node // the machines gui parent box widget
|
||||||
|
pb *virtpb.Events // the protobuf
|
||||||
|
TB *virtpb.EventsTable // the gui table buffer
|
||||||
|
update bool // if the window should be updated
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *stdEventTableWin) Toggle() {
|
||||||
|
if w == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.win == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.win.Toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEventsWindow() *stdEventTableWin {
|
||||||
|
dwin := new(stdEventTableWin)
|
||||||
|
dwin.win = gadgets.NewGenericWindow("virtigo current events", "things to do")
|
||||||
|
dwin.win.Custom = func() {
|
||||||
|
log.Info("test delete window here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a box at the bottom of the window for the protobuf table
|
||||||
|
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
|
||||||
|
return dwin
|
||||||
|
}
|
||||||
|
|
||||||
|
// default table protobuf window
|
||||||
|
func (dw *stdEventTableWin) doStdEvents(pb *virtpb.Events) {
|
||||||
|
dw.Lock()
|
||||||
|
defer dw.Unlock()
|
||||||
|
|
||||||
|
// erase the old table
|
||||||
|
if dw.TB != nil {
|
||||||
|
dw.TB.Delete()
|
||||||
|
dw.TB = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the table
|
||||||
|
dw.pb = pb
|
||||||
|
t := dw.pb.NewTable("EventsPB Off")
|
||||||
|
t.NewUuid()
|
||||||
|
t.SetParent(dw.box)
|
||||||
|
|
||||||
|
// pick the columns
|
||||||
|
t.AddDropletName()
|
||||||
|
t.AddDropletUuid()
|
||||||
|
t.AddHypervisor()
|
||||||
|
|
||||||
|
// display the protobuf
|
||||||
|
dw.TB = t
|
||||||
|
f := func(e *virtpb.Event) {
|
||||||
|
log.Info("std EventWindow() something here", e.Droplet)
|
||||||
|
// m.Enabled = true
|
||||||
|
}
|
||||||
|
dw.TB.Custom(f)
|
||||||
|
|
||||||
|
dw.TB.ShowTable()
|
||||||
|
}
|
107
windowGeneric.go
107
windowGeneric.go
|
@ -1,107 +0,0 @@
|
||||||
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the GPL 3.0
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// This model works for 99.9% of all windows
|
|
||||||
// This is the Default Standard Window Model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.wit.com/lib/gadgets"
|
|
||||||
"go.wit.com/log"
|
|
||||||
|
|
||||||
"go.wit.com/gui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GenericWindow struct {
|
|
||||||
Win *gadgets.BasicWindow // the window widget itself
|
|
||||||
Shelf *gui.Node // the overall box: the shelf
|
|
||||||
Stack *gui.Node // the first box is a stack
|
|
||||||
Top *gui.Node // the first item in the stack is always a shelf like box
|
|
||||||
Group *gui.Node // the first item top box is always a group
|
|
||||||
Middle *gui.Node // the middle box (shelf style)
|
|
||||||
Bottom *gui.Node // the bottom box (stack style)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Hidden() bool {
|
|
||||||
if gw == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if gw.Win == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return gw.Win.Hidden()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Toggle() {
|
|
||||||
if gw.Hidden() {
|
|
||||||
gw.Show()
|
|
||||||
} else {
|
|
||||||
gw.Hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Show() {
|
|
||||||
if gw == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gw.Win == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gw.Win.Show()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Hide() {
|
|
||||||
if gw == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gw.Win == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gw.Win.Hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Disable() {
|
|
||||||
if gw == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gw.Shelf == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gw.Shelf.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GenericWindow) Enable() {
|
|
||||||
if gw == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gw.Shelf == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gw.Shelf.Enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGenericWindow(title string, grouptxt string) *GenericWindow {
|
|
||||||
gw := new(GenericWindow)
|
|
||||||
gw.Win = gadgets.RawBasicWindow(title)
|
|
||||||
gw.Win.Make()
|
|
||||||
|
|
||||||
gw.Win.Custom = func() {
|
|
||||||
log.Warn("Found Window close. setting hidden=true")
|
|
||||||
// sets the hidden flag to false so Toggle() works
|
|
||||||
gw.Win.Hide()
|
|
||||||
}
|
|
||||||
gw.Shelf = gw.Win.Box()
|
|
||||||
// gw.Shelf.Vertical().SetProgName("ShelfBox")
|
|
||||||
gw.Stack = gw.Shelf.NewVerticalBox("Stackbox")
|
|
||||||
|
|
||||||
gw.Top = gw.Stack.NewVerticalBox("Stackbox")
|
|
||||||
gw.Middle = gw.Stack.Box()
|
|
||||||
gw.Bottom = gw.Stack.Box()
|
|
||||||
|
|
||||||
gw.Group = gw.Top.NewGroup(grouptxt)
|
|
||||||
|
|
||||||
gw.Show()
|
|
||||||
|
|
||||||
return gw
|
|
||||||
}
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the GPL 3.0
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.wit.com/gui"
|
||||||
|
"go.wit.com/lib/gadgets"
|
||||||
|
"go.wit.com/lib/protobuf/virtpb"
|
||||||
|
"go.wit.com/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stdHypervisorTableWin struct {
|
||||||
|
sync.Mutex
|
||||||
|
win *gadgets.GenericWindow // the machines gui window
|
||||||
|
box *gui.Node // the machines gui parent box widget
|
||||||
|
pb *virtpb.Hypervisors // the protobuf
|
||||||
|
TB *virtpb.HypervisorsTable // the gui table buffer
|
||||||
|
update bool // if the window should be updated
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *stdHypervisorTableWin) Toggle() {
|
||||||
|
if w == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.win == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.win.Toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHypervisorsWindow() *stdHypervisorTableWin {
|
||||||
|
dwin := new(stdHypervisorTableWin)
|
||||||
|
dwin.win = gadgets.NewGenericWindow("virtigo current hypervisors", "things to do")
|
||||||
|
dwin.win.Custom = func() {
|
||||||
|
log.Info("test delete window here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a box at the bottom of the window for the protobuf table
|
||||||
|
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
|
||||||
|
return dwin
|
||||||
|
}
|
||||||
|
|
||||||
|
// default table protobuf window
|
||||||
|
func (dw *stdHypervisorTableWin) doStdHypervisors(pb *virtpb.Hypervisors) {
|
||||||
|
dw.Lock()
|
||||||
|
defer dw.Unlock()
|
||||||
|
|
||||||
|
// erase the old table
|
||||||
|
if dw.TB != nil {
|
||||||
|
dw.TB.Delete()
|
||||||
|
dw.TB = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the table
|
||||||
|
dw.pb = pb
|
||||||
|
t := dw.pb.NewTable("HypervisorsPB Off")
|
||||||
|
t.NewUuid()
|
||||||
|
t.SetParent(dw.box)
|
||||||
|
|
||||||
|
// pick the columns
|
||||||
|
t.AddHostname()
|
||||||
|
t.AddMemory()
|
||||||
|
t.AddCpus()
|
||||||
|
t.AddKillcount()
|
||||||
|
t.AddTimeFunc("last poll", func(h *virtpb.Hypervisor) time.Time {
|
||||||
|
// hm := me.hmap[h]
|
||||||
|
// tmp := hm.lastpoll
|
||||||
|
// log.Info("poll age", h.Hostname, virtpb.FormatDuration(time.Since(tmp)))
|
||||||
|
return time.Now()
|
||||||
|
})
|
||||||
|
t.AddStringFunc("droplets", func(h *virtpb.Hypervisor) string {
|
||||||
|
/*
|
||||||
|
var totalDroplets int
|
||||||
|
var totalUnknownDroplets int
|
||||||
|
// dur := time.Since(h.lastpoll)
|
||||||
|
// tmp := virtpb.FormatDuration(dur)
|
||||||
|
// fmt.Fprintln(w, h.pb.Hostname, "killcount =", h.killcount, "lastpoll:", tmp)
|
||||||
|
hm := me.hmap[h]
|
||||||
|
for name, _ := range hm.lastDroplets {
|
||||||
|
totalDroplets += 1
|
||||||
|
d := me.cluster.FindDropletByName(name)
|
||||||
|
if d == nil {
|
||||||
|
totalUnknownDroplets += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Total Droplets %d total libvirt only droplets = %d\n", totalDroplets, totalUnknownDroplets)
|
||||||
|
return fmt.Sprintf("%d", totalDroplets)
|
||||||
|
*/
|
||||||
|
return "todo"
|
||||||
|
})
|
||||||
|
|
||||||
|
// display the protobuf
|
||||||
|
dw.TB = t
|
||||||
|
f := func(e *virtpb.Hypervisor) {
|
||||||
|
log.Info("std HypervisorWindow() something here", e.Hostname)
|
||||||
|
// m.Enabled = true
|
||||||
|
}
|
||||||
|
dw.TB.Custom(f)
|
||||||
|
|
||||||
|
dw.TB.ShowTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// default table protobuf window
|
||||||
|
func (dw *stdHypervisorTableWin) doNewStdHypervisors(pb *virtpb.Hypervisors) {
|
||||||
|
dw.Lock()
|
||||||
|
defer dw.Unlock()
|
||||||
|
|
||||||
|
// erase the old table
|
||||||
|
if dw.TB != nil {
|
||||||
|
dw.TB.Delete()
|
||||||
|
dw.TB = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the table
|
||||||
|
dw.pb = pb
|
||||||
|
t := dw.pb.NewTable("HypervisorsPB Off")
|
||||||
|
t.NewUuid()
|
||||||
|
t.SetParent(dw.box)
|
||||||
|
|
||||||
|
// pick the columns
|
||||||
|
t.AddHostname()
|
||||||
|
t.AddMemory()
|
||||||
|
t.AddCpus()
|
||||||
|
t.AddKillcount()
|
||||||
|
t.AddTimeFunc("last poll", func(h *virtpb.Hypervisor) time.Time {
|
||||||
|
// hm := me.hmap[h]
|
||||||
|
// tmp := hm.lastpoll
|
||||||
|
// log.Info("poll age", h.Hostname, virtpb.FormatDuration(time.Since(tmp)))
|
||||||
|
return time.Now()
|
||||||
|
})
|
||||||
|
t.AddStringFunc("droplets", func(h *virtpb.Hypervisor) string {
|
||||||
|
var totalDroplets int
|
||||||
|
var totalUnknownDroplets int
|
||||||
|
// dur := time.Since(h.lastpoll)
|
||||||
|
// tmp := virtpb.FormatDuration(dur)
|
||||||
|
// fmt.Fprintln(w, h.pb.Hostname, "killcount =", h.killcount, "lastpoll:", tmp)
|
||||||
|
hm := me.hmap[h]
|
||||||
|
for name, _ := range hm.lastDroplets {
|
||||||
|
totalDroplets += 1
|
||||||
|
d := me.cluster.FindDropletByName(name)
|
||||||
|
if d == nil {
|
||||||
|
totalUnknownDroplets += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log.Printf("Total Droplets %d total libvirt only droplets = %d\n", totalDroplets, totalUnknownDroplets)
|
||||||
|
return fmt.Sprintf("%d", totalDroplets)
|
||||||
|
})
|
||||||
|
|
||||||
|
// display the protobuf
|
||||||
|
dw.TB = t
|
||||||
|
f := func(e *virtpb.Hypervisor) {
|
||||||
|
log.Info("std HypervisorWindow() something here", e.Hostname)
|
||||||
|
// m.Enabled = true
|
||||||
|
}
|
||||||
|
dw.TB.Custom(f)
|
||||||
|
|
||||||
|
dw.TB.ShowTable()
|
||||||
|
}
|
Loading…
Reference in New Issue