This commit is contained in:
2025-03-10 14:10:39 +11:00
commit df047ffd71
3 changed files with 321 additions and 0 deletions

311
main.go Normal file
View File

@@ -0,0 +1,311 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/url"
"os"
"runtime"
"strings"
"time"
_ "time/tzdata"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25/mo"
)
type HostTimeErrors struct {
HostName string
HostTime time.Time
Cluster string
Vcenter string
}
var (
c *govmomi.Client
ctx context.Context
cancel context.CancelFunc
location *time.Location
sha1ver string // sha1 revision used to build the program
buildTime string // when the executable was built
hostTimeErrors []HostTimeErrors
)
/*
type dateInfo struct {
types.HostDateTimeInfo
Service *types.HostService `json:"service"`
Current *time.Time `json:"current"`
}
*/
func prettyPrint(args ...interface{}) {
var caller string
timeNow := time.Now().Format("01-02-2006 15:04:05")
prefix := fmt.Sprintf("[%s] %s -- ", "PrettyPrint", timeNow)
_, fileName, fileLine, ok := runtime.Caller(1)
if ok {
caller = fmt.Sprintf("%s:%d", fileName, fileLine)
} else {
caller = ""
}
fmt.Printf("\n%s%s\n", prefix, caller)
if len(args) == 2 {
label := args[0]
value := args[1]
s, _ := json.MarshalIndent(value, "", "\t")
fmt.Printf("%s%s: %s\n", prefix, label, string(s))
} else {
s, _ := json.MarshalIndent(args, "", "\t")
fmt.Printf("%s%s\n", prefix, string(s))
}
}
func main() {
// Command line flags for the vCenter connection
vURL := flag.String("url", "", "The URL of a vCenter server, eg https://server.domain.example/sdk")
vUser := flag.String("user", "", "The username to use when connecting to vCenter")
vPass := flag.String("password", "", "The password to use when connecting to vCenter")
vTZ := flag.String("tz", "Australia/Sydney", "The timezone to use when converting vCenter UTC times")
vInsecure := flag.Bool("insecure", true, "Allow insecure connections to vCenter")
vAllowedDiff := flag.Int("diff", 300, "Permitted time difference in seconds")
flag.Parse()
// Print logs to file
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
defer f.Close()
log.SetOutput(f)
log.Printf("Starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver)
// So we can convert vCenter UTC to our local timezone
log.Printf("Setting timezone to '%s'\n", *vTZ)
location, err = time.LoadLocation(*vTZ)
if err != nil {
fmt.Fprintf(os.Stderr, "Error setting timezone to %s : %s\n", *vTZ, err)
os.Exit(1)
}
u, err := url.Parse(*vURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing url %s : %s\n", *vURL, err)
os.Exit(1)
} else {
if !strings.HasSuffix(u.Path, "/sdk") {
u.Path, _ = url.JoinPath(u.Path, "/sdk")
log.Printf("Updated vCenter URL to '%v'\n", u)
}
}
log.Printf("Connecting to vCenter %s\n", u)
u.User = url.UserPassword(*vUser, *vPass)
ctx, cancel = context.WithCancel(context.Background())
defer cancel()
// Login to vcenter
c, err = govmomi.NewClient(ctx, u, *vInsecure)
if err != nil {
fmt.Fprintf(os.Stderr, "Logging in error: %s\n", err)
os.Exit(1)
}
defer c.Logout(ctx)
// Create a view manager
m := view.NewManager(c.Client)
// Create a container view for all Datacenters
dcView, err := m.CreateContainerView(ctx, c.Client.ServiceContent.RootFolder, []string{"Datacenter"}, true)
if err != nil {
log.Fatalf("Failed to create container view: %v", err)
}
defer dcView.Destroy(ctx)
// Retrieve all Datacenters
var dcs []mo.Datacenter
err = dcView.Retrieve(ctx, []string{"Datacenter"}, []string{"name", "hostFolder"}, &dcs)
if err != nil {
log.Fatalf("Failed to retrieve datacenters: %v", err)
}
// Iterate through each datacenter and find hosts
for _, dc := range dcs {
//fmt.Printf("Datacenter: %s\n", dc.Name)
// Create a finder for the datacenter
finder := find.NewFinder(c.Client, false)
datacenter, err := finder.Datacenter(ctx, dc.Name)
if err != nil {
log.Printf("Failed to get datacenter object: %v", err)
continue
}
finder.SetDatacenter(datacenter)
// Find all ESXi hosts in this datacenter
hosts, err := finder.HostSystemList(ctx, "*")
if err != nil {
log.Printf("Failed to get hosts in datacenter %s: %v", dc.Name, err)
continue
}
// Retrieve host properties
var hostProperties []mo.HostSystem
err = dcView.Retrieve(ctx, []string{"HostSystem"}, []string{"name", "hardware.systemInfo", "configManager"}, &hostProperties)
if err != nil {
fmt.Printf("Failed to retrieve host properties: %v", err)
continue
}
// Print hosts
for _, host := range hosts {
clusterName := ""
var hs mo.HostSystem
err = host.Properties(ctx, host.Reference(), []string{"name", "Parent"}, &hs)
if err != nil {
log.Printf("Failed to retrieve host properties: %v", err)
continue
}
dts, err := host.ConfigManager().DateTimeSystem(ctx)
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
hostTime, err := dts.Query(ctx)
//fmt.Printf(" - Host: %s; Time: %v\n", hs.Name, hostTime)
// Convert ESXi UTC time to local time
esxiTimeLocal := hostTime.Local()
localTime := time.Now()
diff := localTime.Sub(esxiTimeLocal)
maxDiff := time.Duration(*vAllowedDiff) * time.Second
if diff > maxDiff || diff < -maxDiff {
fmt.Printf("ESXi %s time differs from local time by more than %d seconds (ESXi: %v, Local: %v)", hs.Name, *vAllowedDiff, esxiTimeLocal, localTime)
// Get the cluster name
if hs.Parent.Type == "ClusterComputeResource" {
// Retrieve properties of the compute resource
var moCompute mo.ComputeResource
err = c.RetrieveOne(ctx, *hs.Parent, nil, &moCompute)
if err == nil {
clusterName = moCompute.Name
}
}
thisResult := HostTimeErrors{
HostName: hs.Name,
Cluster: clusterName,
Vcenter: u.Host,
HostTime: esxiTimeLocal,
}
hostTimeErrors = append(hostTimeErrors, thisResult)
}
/*
var hostDts mo.HostDateTimeSystem
if err = dts.Properties(ctx, dts.Reference(), nil, &hostDts); err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
ss, err := host.ConfigManager().ServiceSystem(ctx)
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
services, err := ss.Service(ctx)
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
res := &dateInfo{HostDateTimeInfo: hostDts.DateTimeInfo}
for i, service := range services {
if service.Key == "ntpd" {
res.Service = &services[i]
break
}
}
res.Current, err = dts.Query(ctx)
if err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
prettyPrint(res)
*/
}
}
/*
// Create a new result
result := OutageResults{
VM: event.Vm.Name,
OutageDuration: out.Format("15:04:05"),
OutageStart: outageStart,
RestartTime: restartTime,
Cluster: event.ComputeResource.Name,
FailedHost: failedHost,
NewHost: event.Host.Name,
GuestOS: vmOS,
CurrentPowerState: vmPowerState,
Description: event.FullFormattedMessage,
}
// Append to list of all results
results = append(results, result)
}
for _, hostEvent := range hostFailures {
hostResults = append(hostResults, HostFailureResults{
HostName: hostEvent.Host.Name,
FailureTime: hostEvent.CreatedTime.In(location),
Cluster: hostEvent.ComputeResource.Name,
Vcenter: u.Host,
})
}
} else {
log.Printf("Found %d hostfailure messages in last %.1f hour(s)", len(hostFailures), begin.Abs().Hours())
}
*/
// Combine details of host outages and VM outages into one interface
/*
var combined []interface{}
for _, h := range hostResults {
combined = append(combined, h)
}
for _, v := range results {
combined = append(combined, v)
}
*/
// Output final results in JSON
if len(hostTimeErrors) > 0 {
j, _ := json.Marshal(hostTimeErrors)
fmt.Println(string(j))
} else {
fmt.Println("{}")
}
}