From 932d49edb1d2537e1069b510ea2884a8a39dfe2f Mon Sep 17 00:00:00 2001 From: tursom Date: Tue, 4 Jul 2023 16:36:42 +0800 Subject: [PATCH] implement function watch dog --- .gitignore | 2 + cmd/watchdog/config.go | 35 ++++++++++++++++ cmd/watchdog/main.go | 92 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 15 ++++++- go.sum | 21 ++++++++++ vmomi/connector.go | 23 +++++++++++ vmomi/utils.go | 36 +++++++++++++++++ vmomi/vm.go | 76 ++++++++++++++++++++++++++++++++++ 8 files changed, 299 insertions(+), 1 deletion(-) create mode 100755 cmd/watchdog/config.go create mode 100755 cmd/watchdog/main.go create mode 100755 go.sum create mode 100755 vmomi/connector.go create mode 100755 vmomi/utils.go create mode 100755 vmomi/vm.go diff --git a/.gitignore b/.gitignore index 728c95a..f8a582e 100755 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ go.work .idea + +config.yaml diff --git a/cmd/watchdog/config.go b/cmd/watchdog/config.go new file mode 100755 index 0000000..80127e4 --- /dev/null +++ b/cmd/watchdog/config.go @@ -0,0 +1,35 @@ +package main + +import ( + "io" + "os" + + "gopkg.in/yaml.v3" +) + +type ( + config struct { + User string `yaml:"user"` + Password string `yaml:"password"` + Watch []*watchVm `yaml:"watch"` + } + watchVm struct { + Name string `yaml:"name"` + Addr string `yaml:"addr"` + } +) + +func parseConfig(file string) (*config, error) { + cfgFile, err := os.Open(file) + if err != nil { + return nil, err + } + + cfgBytes, err := io.ReadAll(cfgFile) + if err != nil { + return nil, err + } + + var cfg config + return &cfg, yaml.Unmarshal(cfgBytes, &cfg) +} diff --git a/cmd/watchdog/main.go b/cmd/watchdog/main.go new file mode 100755 index 0000000..8f3db2e --- /dev/null +++ b/cmd/watchdog/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/go-ping/ping" + _ "github.com/go-ping/ping" + + "github.com/tursom/esxi-monitor/vmomi" +) + +func main() { + cfg, err := parseConfig("config.yaml") + if err != nil { + panic(err) + } + + watchTargets := make(map[string]*watchVm) + for _, watch := range cfg.Watch { + watchTargets[watch.Name] = watch + } + + client, err := vmomi.Connect("https://esxi/sdk", cfg.User, cfg.Password) + if err != nil { + panic(err) + } + + vms, err := vmomi.ListVms(client.Client) + if err != nil { + panic(err) + } + + // ip -> vm instance + watches := make(map[string]*vmomi.VirtualMachine) + for _, vm := range vms { + target, contains := watchTargets[vm.Name()] + if !contains { + continue + } + + fmt.Printf("watching virtual machine: addr=%s, name=%s\n", target.Addr, vm.Name()) + watches[target.Addr] = vm + } + + doWatch(watches) +} + +// watches map ip -> vm instance +func doWatch(watches map[string]*vmomi.VirtualMachine) { + t := time.NewTicker(time.Minute) + for { + <-t.C + + for ip, vm := range watches { + pinger, err := ping.NewPinger(ip) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "ping to target failed: %s\n", err) + + continue + } + + pinger.Count = 4 + + err = pinger.Run() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "ping to target failed: %s\n", err) + continue + } + + statistics := pinger.Statistics() + if statistics.PacketsRecv == 0 { + fmt.Printf("restarting vm %s(%s)", ip, vm.Name()) + + task, err := vm.Reset() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "restart vm failed: %s\n", err) + continue + } + + ctx, _ := context.WithTimeout(context.Background(), time.Minute) + err = task.Wait(ctx) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "restart vm failed: %s\n", err) + continue + } + } + } + } +} diff --git a/go.mod b/go.mod index ab55911..151c808 100755 --- a/go.mod +++ b/go.mod @@ -1,3 +1,16 @@ -module esxi-monitor +module github.com/tursom/esxi-monitor go 1.20 + +require ( + github.com/go-ping/ping v1.1.0 + github.com/vmware/govmomi v0.30.5 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/google/uuid v1.3.0 // indirect + golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 // indirect +) diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..f076789 --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= +github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/vmware/govmomi v0.30.5 h1:p4sFypY4AJlFRiS2OFEGh9DCzufBrexbtPL5DB9Pxw0= +github.com/vmware/govmomi v0.30.5/go.mod h1:F7adsVewLNHsW/IIm7ziFURaXDaHEwcc+ym4r3INMdY= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vmomi/connector.go b/vmomi/connector.go new file mode 100755 index 0000000..d389325 --- /dev/null +++ b/vmomi/connector.go @@ -0,0 +1,23 @@ +package vmomi + +import ( + "context" + url2 "net/url" + + "github.com/vmware/govmomi" +) + +func Connect(url, user, password string) (*govmomi.Client, error) { + esxiUri, err := url2.Parse(url) + if err != nil { + panic(err) + } + + esxiUri.User = url2.UserPassword(user, password) + + return govmomi.NewClient( + context.Background(), + esxiUri, + true, + ) +} diff --git a/vmomi/utils.go b/vmomi/utils.go new file mode 100755 index 0000000..d6e4cb8 --- /dev/null +++ b/vmomi/utils.go @@ -0,0 +1,36 @@ +package vmomi + +import ( + "context" + + "github.com/vmware/govmomi/view" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/mo" +) + +func ListVms(client *vim25.Client) ([]*VirtualMachine, error) { + manager := view.NewManager(client) + + v, err := manager.CreateContainerView( + context.Background(), + client.ServiceContent.RootFolder, + []string{"VirtualMachine"}, + true, + ) + if err != nil { + return nil, err + } + + var vms []mo.VirtualMachine + err = v.Retrieve(context.Background(), []string{"VirtualMachine"}, []string{}, &vms) + if err != nil { + return nil, err + } + + var dist []*VirtualMachine + for i := range vms { + dist = append(dist, makeVirtualMachine(client, (*MoVirtualMachine)(&vms[i]))) + } + + return dist, nil +} diff --git a/vmomi/vm.go b/vmomi/vm.go new file mode 100755 index 0000000..031c498 --- /dev/null +++ b/vmomi/vm.go @@ -0,0 +1,76 @@ +package vmomi + +import ( + "context" + + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/mo" +) + +type ( + MoVirtualMachine mo.VirtualMachine + ObjectVirtualMachine object.VirtualMachine + + VirtualMachine struct { + c *vim25.Client + mvm *MoVirtualMachine + ovm *ObjectVirtualMachine + } +) + +func makeVirtualMachine(c *vim25.Client, vm *MoVirtualMachine) *VirtualMachine { + return &VirtualMachine{ + c: c, + mvm: vm, + ovm: vm.GetOvm(c), + } +} + +func (v *VirtualMachine) Ovm() *ObjectVirtualMachine { + return v.ovm +} + +func (v *VirtualMachine) Mvm() *MoVirtualMachine { + return v.mvm +} + +func (v *VirtualMachine) Name() string { + return v.mvm.Summary.Config.Name +} + +func (v *VirtualMachine) Uuid() string { + return v.mvm.Summary.Config.Uuid +} + +func (v *VirtualMachine) PowerOn() (*object.Task, error) { + return v.ovm.GetOvm().PowerOn(context.Background()) +} + +func (v *VirtualMachine) PowerOff() (*object.Task, error) { + return v.ovm.GetOvm().PowerOff(context.Background()) +} + +func (v *VirtualMachine) Reset() (*object.Task, error) { + return v.ovm.GetOvm().Reset(context.Background()) +} + +func (vm *MoVirtualMachine) GetMvm() *mo.VirtualMachine { + return (*mo.VirtualMachine)(vm) +} + +func (vm *MoVirtualMachine) GetOvm(c *vim25.Client) *ObjectVirtualMachine { + return (*ObjectVirtualMachine)(object.NewVirtualMachine(c, vm.Reference())) +} + +func (ovm *ObjectVirtualMachine) GetOvm() *object.VirtualMachine { + return (*object.VirtualMachine)(ovm) +} + +func (ovm *ObjectVirtualMachine) GetMvm(c *vim25.Client) (*MoVirtualMachine, error) { + var mvm mo.VirtualMachine + + pc := property.DefaultCollector(c) + return (*MoVirtualMachine)(&mvm), pc.RetrieveOne(context.Background(), ovm.Reference(), []string{}, &mvm) +}