Compare commits

..

19 Commits

Author SHA1 Message Date
Jeff Carr 39cbc15468 start moving away from old cluster code 2025-03-25 20:00:13 -05:00
Jeff Carr 498bfe82f0 switch from virtbuf to virtpb 2025-02-22 17:48:10 -06:00
Jeff Carr 6ee6fd8a96 improvements from autogenpb 2024-12-04 02:24:56 -06:00
Jeff Carr f45dacfcaf create works from a filename
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-16 05:22:41 -06:00
Jeff Carr 6944de6d85 fix build 2024-11-16 00:08:43 -06:00
Jeff Carr db5a32cda8 rename
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-13 18:53:56 -06:00
Jeff Carr 3e894b2f0c rename Cluster
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-07 05:03:56 -06:00
Jeff Carr 4fbd3ed518 things work again
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 12:50:03 -05:00
Jeff Carr fb9f42285d sprintf changes
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 00:42:02 -05:00
Jeff Carr a1feb086a6 runs again
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-31 14:16:07 -05:00
Jeff Carr ec9d464997 compiles after protobuf changes
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-31 13:41:02 -05:00
Jeff Carr 748f9b4608 compiles. maybe works on some stuff still
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-31 05:02:20 -05:00
Jeff Carr 123f64f56c events are in cluster.E
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 18:10:55 -05:00
Jeff Carr 97e8fb3fed create sends a droplet protobuf
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 11:01:03 -05:00
Jeff Carr 38bddac541 passes droplet protobuf
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 09:30:05 -05:00
Jeff Carr 020de631c8 try to send a create
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 09:14:20 -05:00
Jeff Carr 7685f0b5eb go-arg options
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 07:52:44 -05:00
Jeff Carr b129e1b1de attempt bash completion, but it doesn't work
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 03:24:40 -05:00
Jeff Carr 910d3070f3 maybe
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-30 03:15:56 -05:00
9 changed files with 347 additions and 118 deletions

View File

@ -1,20 +1,22 @@
VERSION = $(shell git describe --tags) VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d)
# create the go.mod and go.sum if this is a brand new repo
# REDOMOD = $(shell if [ -e go.mod ]; then echo go.mod; else echo no go mod; fi)
REDOMOD = $(shell if [ -e go.sum ]; then echo go.sum exists; else GO111MODULE= go mod init; GO111MODULE= go mod tidy; fi)
all: build all: build
./virtigoctl --version ./virtigoctl --version
./virtigoctl --help ./virtigoctl --help
make dump-droplets
build: build:
GO111MODULE=off go build -v -ldflags "-X main.Version=${VERSION} -X gui.GUIVERSION=${VERSION}" GO111MODULE=off go build \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
# this is for release builds using the go.mod files verbose:
release-build: GO111MODULE=off go build -v -x \
@echo ${REDOMOD} -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
go build -v -ldflags "-X main.Version=${VERSION} -X gui.GUIVERSION=${VERSION}"
install:
GO111MODULE=off go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
# autofixes your import headers in your golang files # autofixes your import headers in your golang files
goimports: goimports:
@ -30,10 +32,20 @@ clean:
rm -f go.* rm -f go.*
rm -f virtigo* rm -f virtigo*
# git clone the sources and all the golang dependancies into ~/go/src dump-uptime:
# if you don't have go-clone, you can get it from http://go.wit.com/ ./virtigoctl dump --uptime=true
git-clone:
go-clone --recursive --go-src --no-work go.wit.com/apps/go-clone dump-droplets:
go-clone --recursive --go-src --no-work go.wit.com/apps/virtigo ./virtigoctl dump --droplets=true
go-clone --recursive --go-src --no-work go.wit.com/apps/gowebd
go-clone --recursive --go-src --no-work go.wit.com/lib/daemons/virtigod dump-droplets-full:
./virtigoctl dump --droplets-full=true
dump-hypervisors:
./virtigoctl dump --hypervisors=true
start:
./virtigoctl --start www.wit.com go.wit.com
create-from-nagios-filename:
./virtigoctl create --filename=/home/nfs1/node004/kvm/nagios.lab.wit.org.qcow2

49
argv.go
View File

@ -10,18 +10,38 @@ import "go.wit.com/log"
var argv args var argv args
type args struct { type DumpCmd struct {
Droplets bool `arg:"--droplets" help:"show the running droplets"`
DropletsFull bool `arg:"--droplets-full" help:"show all the known droplets"`
Hypervisors bool `arg:"--hypervisors" help:"show the hypervisors"`
Uptime bool `arg:"--uptime" help:"show the hypervisors"`
}
type CreateCmd struct {
Filename string `arg:"--filename" help:"start a vm based off the qcow2 filename"`
Memory int `arg:"--memory" help:"set the memory in MB"`
Cpus int `arg:"--cpus" help:"set the cpus"`
}
type ImportXml struct {
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"`
IgnoreCpu bool `arg:"--xml-ignore-cpu" default:"true" help:"ignore non-standard libvirt xml cpus"` 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"` 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"` IgnDisk bool `arg:"--xml-ignore-disk" default:"false" help:"ignore duplicate disk names"`
Port int `arg:"--port" default:"8080" help:"allow droplet events via http"` DomainName string `arg:"--domain" help:"the virsh domain name"`
Hosts []string `arg:"--hosts" help:"hosts to connect to"` Host string `arg:"--host" help:"the hypervisor hostname (optional)"`
Delete bool `arg:"--delete" default:"false" help:"delete the old xml after import"`
Force bool `arg:"--force" default:"false" help:"attempts to merge xml with existing protobuf"`
} }
// Save bool `arg:"--save" default:"false" help:"save protobuf config after import"` type args struct {
// Start string `arg:"--start" help:"start a droplet"` Start []string `arg:"--start" help:"droplets to start"`
// Uptime bool `arg:"--uptime" default:"true" help:"allow uptime checks for things like Kuma"` Port int `arg:"--port" default:"8080" help:"allow droplet events via http"`
Create *CreateCmd `arg:"subcommand:create" help:"create a new droplet"`
Dump *DumpCmd `arg:"subcommand:dump" help:"show the state of the cluster"`
Import *ImportXml `arg:"subcommand:import" help:"import libvirt XML"`
}
func (a args) Description() string { func (a args) Description() string {
return ` return `
@ -29,6 +49,23 @@ func (a args) Description() string {
` `
} }
func (args) Epilogue() string {
return `
This will start three already defined servers:
virtigoctl --start www.wit.com ftp.wit.com wiki.wit.com
This will make a new vm called "foo.wit.com" with the default
virtigo values for memory, cpus, network settings, spice, etc.
virtigoctl create --filename /home/nfs/foo.wit.com.qcow2
create "bar.wit.com" with defaults except 1GB of RAM:
virtigoctl create --filename /home/nfs/bar.wit.com.qcow2 --memory 1024
`
}
func (args) Version() string { func (args) Version() string {
return "virtigoctl " + Version return "virtigoctl " + Version
} }

44
bash-completion Normal file
View File

@ -0,0 +1,44 @@
#/usr/bin/env bash
VIRTIGOCTL1="
git
uptime
dc
"
declare -A VIRTIGOCTL_COMP
VIRTIGOCTL_COMP[git]="
push
log
"
VIRTIGOCTL_COMP[start]="foo.wit.com boo.wit.com"
_git_cc() { COMPREPLY=(-a -b); }
_git_wit() { COMPREPLY=(dump-droplets start filename); }
# complete -F _wit_complete wit
_virtigoctl_complete()
{
local cur prev
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
if [ $COMP_CWORD -eq 1 ]; then
COMPREPLY=( $(compgen -W "$VIRTIGOCTL1" -- $cur) )
elif [ $COMP_CWORD -eq 2 ]; then
case "$prev" in
"screen")
COMPREPLY=( $(compgen -W "reattach" -- $cur) )
;;
*)
COMPREPLY=( $(compgen -W "${VIRTIGOCTL_COMP[$prev]}" -- $cur) )
;;
esac
fi
return 0
}
complete -F _virtigoctl_complete wit

94
create.go Normal file
View File

@ -0,0 +1,94 @@
// Copyright 2024 WIT.COM Inc Licensed GPL 3.0
package main
import (
"errors"
"fmt"
"path/filepath"
"strings"
"go.wit.com/lib/protobuf/virtpb"
"go.wit.com/log"
"google.golang.org/protobuf/proto"
)
func createFilename(dir string, filename string) error {
log.Info("dir ==", dir)
log.Info("filename ==", filename)
filetype := filepath.Ext(filename)
if filetype != ".qcow2" {
log.Info("file type", filetype, "not supported")
return errors.New("only supporting qcow2 images for now")
}
hostname := strings.TrimSuffix(filename, filetype)
log.Info("hostname ==", hostname)
newDroplet := virtpb.NewDefaultDroplet(hostname)
newDisk := new(virtpb.Disk)
newDisk.Filename = filename
newDisk.Filepath = dir
newDroplet.Disks = append(newDroplet.Disks, newDisk)
var eth *virtpb.Network
eth = new(virtpb.Network)
eth.Name = "worldbr"
newDroplet.Networks = append(newDroplet.Networks, eth)
url := urlbase + "/create"
// body, err := postDropletJSON(url, newDroplet)
// if err != nil {
// log.Info("postDropletJSON() failed:", err)
// return err
// }
body, err := postDropletWIRE(url, newDroplet)
if err != nil {
log.Info("postDropletJSON() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
// log.Info("virtigo returned body:", test)
for _, line := range strings.Split(test, "\n") {
log.Info("GOT:", line)
}
return nil
}
// send protobuf as wire data
func postDropletWIRE(url string, d *virtpb.Droplet) (string, error) {
var bytes []byte
var err error
// Automatically marshal to protobuf binary format
bytes, err = proto.Marshal(d)
if err != nil {
log.Fatalf("Failed to marshal: %v", err)
return "", err
}
// fmt.Printf("Encoded data: %x\n", bytes)
fmt.Printf("Encoded len(data): %d\n", len(bytes))
return post(url, bytes)
}
// send protobuf in json format
func postDropletJSON(url string, d *virtpb.Droplet) (string, error) {
// send protobuf as JSON
bytes, err := d.MarshalJSON()
if err != nil {
log.Info("virtpb.MarshalJson() failed:", err)
return "", err
}
return post(url, bytes)
}
func post(url string, bytes []byte) (string, error) {
body, err := httpPost(url, bytes)
if err != nil {
log.Info("httpPost() failed:", err)
return "", err
}
return string(body), nil
}

View File

@ -5,7 +5,7 @@ import (
"time" "time"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
pb "go.wit.com/lib/protobuf/virtbuf" "go.wit.com/lib/protobuf/virtpb"
"go.wit.com/log" "go.wit.com/log"
) )
@ -26,6 +26,9 @@ func (h *HyperT) RestartVirtigod() {
} }
// checks if the cluster is ready and stable // checks if the cluster is ready and stable
// func (c *Cluster) DropletReady(d *Droplet) (bool, string) {
/*
func clusterReady() (bool, string) { func clusterReady() (bool, string) {
last := time.Since(me.unstable) last := time.Since(me.unstable)
if last > me.unstableTimeout { if last > me.unstableTimeout {
@ -36,30 +39,24 @@ func clusterReady() (bool, string) {
log.Warn("clusterReady() is unstable for", shell.FormatDuration(last)) log.Warn("clusterReady() is unstable for", shell.FormatDuration(last))
return false, "clusterReady() is unstable for " + shell.FormatDuration(last) return false, "clusterReady() is unstable for " + shell.FormatDuration(last)
} }
*/
/*
func dropletReady(d *pb.Droplet) (bool, string) { func dropletReady(d *pb.Droplet) (bool, string) {
if d.CurrentState == pb.DropletState_ON { if d.Current.State == pb.DropletState_ON {
return false, "EVENT start droplet is already ON" return false, "EVENT start droplet is already ON"
} }
if d.Starts > 2 { if d.Current.StartAttempts > 2 {
// reason := "EVENT start droplet has already been started " + d.starts + " times" // reason := "EVENT start droplet has already been started " + d.starts + " times"
return false, fmt.Sprintln("EVENT start droplet has already been started ", d.Starts, " times") return false, fmt.Sprintln("EVENT start droplet has already been started ", d.Starts, " times")
} }
return true, "" return true, ""
} }
*/
// this must be bool in string because accumulated output is sometimes // this must be bool in string because accumulated output is sometimes
// written to STDOUT, sometimes to http // written to STDOUT, sometimes to http
func (h *HyperT) start(d *pb.Droplet) (bool, string) { func (h *HyperT) start(d *virtpb.Droplet) (bool, string) {
ready, result := clusterReady()
if !ready {
return false, result
}
ready, result = dropletReady(d)
if !ready {
return false, result
}
url := "http://" + h.pb.Hostname + ":2520/start?start=" + d.Hostname url := "http://" + h.pb.Hostname + ":2520/start?start=" + d.Hostname
var msg string var msg string
var data []byte var data []byte
@ -72,20 +69,16 @@ func (h *HyperT) start(d *pb.Droplet) (bool, string) {
log.Info("http post url:", url) log.Info("http post url:", url)
log.Info("http post data:", msg) log.Info("http post data:", msg)
result = "EVENT start droplet url: " + url + "\n" result := "EVENT start droplet url: " + url + "\n"
result += "EVENT start droplet response: " + string(req) result += "EVENT start droplet response: " + string(req)
// increment the counter for a start attempt working
d.Starts += 1
// mark the cluster as unstable so droplet starts can be throttled
me.unstable = time.Now()
return true, result return true, result
} }
func findDroplet(name string) *pb.Droplet { func findDroplet(name string) *virtpb.Droplet {
for _, d := range me.cluster.Droplets { loop := me.cluster.DropletsAll() // get the list of droplets
for loop.Scan() {
d := loop.Next()
if d.Hostname == name { if d.Hostname == name {
return d return d
} }

101
main.go
View File

@ -5,8 +5,11 @@ package main
import ( import (
"embed" "embed"
"os" "os"
"path/filepath"
"strings"
"go.wit.com/dev/alexflint/arg" "go.wit.com/dev/alexflint/arg"
"go.wit.com/log"
) )
var Version string var Version string
@ -14,6 +17,8 @@ var Version string
//go:embed resources/* //go:embed resources/*
var resources embed.FS var resources embed.FS
var urlbase string = "http://localhost:8080"
func main() { func main() {
var pp *arg.Parser var pp *arg.Parser
pp = arg.MustParse(&argv) pp = arg.MustParse(&argv)
@ -22,4 +27,100 @@ func main() {
pp.WriteHelp(os.Stdout) pp.WriteHelp(os.Stdout)
os.Exit(0) os.Exit(0)
} }
if argv.Dump != nil {
if argv.Dump.Droplets {
dumpDroplets(false)
os.Exit(0)
}
if argv.Dump.DropletsFull {
dumpStdout("/dumpdropletsfull")
dumpDroplets(true)
os.Exit(0)
}
if argv.Dump.Uptime {
dumpStdout("/uptime")
os.Exit(0)
}
if argv.Dump.Hypervisors {
dumpStdout("/dumphypervisors")
os.Exit(0)
}
log.Info("dump something here")
os.Exit(0)
}
if argv.Create != nil {
dir := filepath.Dir(argv.Create.Filename)
filename := filepath.Base(argv.Create.Filename)
if err := createFilename(dir, filename); err != nil {
log.Info("create failed", err)
os.Exit(-1)
}
log.Info("virtigoctl create end")
os.Exit(0)
}
if argv.Start != nil {
log.Info("start the vm's here:", argv.Start)
os.Exit(0)
}
if argv.Import != nil {
if argv.Import.DomainName == "" {
pp.WriteHelp(os.Stdout)
log.DaemonMode(true)
log.Info("Example:")
log.Info("")
log.Info(" virtigoctl import --domain grafana # will look for an existing libvirt domain")
log.Info("")
os.Exit(0)
}
log.Info("\n\nshould import here", argv.Start, "\n")
log.Info("import", argv.Import.Host, argv.Import.DomainName)
url := "/import?domainName=" + argv.Import.DomainName
if argv.Import.Force {
url += "&force=true"
}
dumpStdout(url)
}
}
func dumpDroplets(full bool) error {
log.DaemonMode(true)
log.Info("dump droplets here ==", argv.Dump.Droplets)
var url string
if full {
url = urlbase + "/dumpdropletsfull"
} else {
url = urlbase + "/dumpdroplets"
}
body, err := httpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
// log.Info("virtigo returned body:", test)
for _, line := range strings.Split(test, "\n") {
log.Info("GOT:", line)
}
return nil
}
func dumpStdout(route string) error {
log.DaemonMode(true)
url := urlbase + route
log.Info("dump url here ==", url)
body, err := httpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
for _, line := range strings.Split(test, "\n") {
log.Info("GOT:", line)
}
return nil
} }

13
post.go
View File

@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"os" "os"
"os/user" "os/user"
"strings"
"go.wit.com/log" "go.wit.com/log"
) )
@ -29,7 +28,7 @@ func httpPost(url string, data []byte) ([]byte, error) {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return nil, err return []byte("client.Do(req) error"), err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -39,11 +38,11 @@ func httpPost(url string, data []byte) ([]byte, error) {
return body, err return body, err
} }
test := strings.TrimSpace(string(body)) // test := strings.TrimSpace(string(body))
log.Info("go.wit.com returned body:", test) // log.Info("go.wit.com returned body:", test)
if test == "OK" { // if test == "OK" {
return body, nil // return body, nil
} // }
return body, nil return body, nil
} }

View File

@ -3,7 +3,7 @@ package main
import ( import (
"time" "time"
pb "go.wit.com/lib/protobuf/virtbuf" "go.wit.com/lib/protobuf/virtpb"
) )
var me virtigoT var me virtigoT
@ -20,22 +20,22 @@ func (b *virtigoT) Enable() {
// this app's variables // this app's variables
type virtigoT struct { type virtigoT struct {
cluster *pb.Cluster // basic cluster settings cluster *virtpb.OldCluster // basic cluster settings
hmap map[*pb.Hypervisor]*HyperT // map to the local struct hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct
names []string names []string
hypers []*HyperT hypers []*HyperT
killcount int killcount int
unstable time.Time // the last time the cluster was incorrect unstable time.Time // the last time the cluster was incorrect
changed bool changed bool
hyperPollDelay time.Duration // how often to poll the hypervisors // hyperPollDelay time.Duration // how often to poll the hypervisors
unstableTimeout time.Duration // how long a droplet can be unstable until it's declared dead // unstableTimeout time.Duration // how long a droplet can be unstable until it's declared dead
clusterStableDuration time.Duration // how long the cluster must be stable before new droplets can be started clusterStableDuration time.Duration // how long the cluster must be stable before new droplets can be started
missingDropletTimeout time.Duration // how long a droplet can be missing for missingDropletTimeout time.Duration // how long a droplet can be missing for
} }
// the stuff that is needed for a hypervisor // the stuff that is needed for a hypervisor
type HyperT struct { type HyperT struct {
pb *pb.Hypervisor // the Hypervisor protobuf pb *virtpb.Hypervisor // the Hypervisor protobuf
dog *time.Ticker // the watchdog timer itself dog *time.Ticker // the watchdog timer itself
lastpoll time.Time // the last time the hypervisor polled lastpoll time.Time // the last time the hypervisor polled
lastDroplets map[string]time.Time // the vm's in the last poll lastDroplets map[string]time.Time // the vm's in the last poll

View File

@ -1,11 +1,7 @@
package main package main
import ( import (
"fmt"
"time" "time"
pb "go.wit.com/lib/protobuf/virtbuf"
"go.wit.com/log"
) )
// timeFunction takes a function as an argument and returns the execution time. // timeFunction takes a function as an argument and returns the execution time.
@ -14,50 +10,3 @@ func TimeFunction(f func()) time.Duration {
f() // Execute the function f() // Execute the function
return time.Since(startTime) // Calculate the elapsed time return time.Since(startTime) // Calculate the elapsed time
} }
func (h *HyperT) sendDirs() {
url := "http://" + h.pb.Hostname + ":2520/cluster"
var msg string
var data []byte
var c *pb.Cluster
c = new(pb.Cluster)
for _, dir := range me.cluster.Dirs {
c.Dirs = append(c.Dirs, dir)
}
msg = c.FormatJSON()
data = []byte(msg) // Convert the string to []byte
req, err := httpPost(url, data)
if err != nil {
log.Info("error:", err)
return
}
// log.Info("http post url:", url)
// log.Info("http post data:", msg)
log.Info("EVENT start droplet response: " + string(req))
}
func (h *HyperT) NewWatchdog() {
h.dog = time.NewTicker(me.hyperPollDelay)
defer h.dog.Stop()
done := make(chan bool)
/*
// this example would exit/destroy the ticker in 10 seconds
go func() {
time.Sleep(10 * time.Second)
done <- true
}()
*/
for {
select {
case <-done:
fmt.Println("Done!")
return
case t := <-h.dog.C:
log.Log(POLL, "Watchdog() ticked", h.pb.Hostname, "Current time: ", t)
// h.pollHypervisor()
// h.Scan()
}
}
}