All checks were successful
continuous-integration/drone/push Build is passing
346 lines
9.2 KiB
Go
346 lines
9.2 KiB
Go
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/methods"
|
|
"github.com/vmware/govmomi/vim25/mo"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
)
|
|
|
|
type HostTimeErrors struct {
|
|
HostName string
|
|
HostTime time.Time
|
|
HostTimeDrift string
|
|
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 {
|
|
log.Fatalf("Logging in error: %s\n", err)
|
|
}
|
|
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 {
|
|
log.Printf("Failed to retrieve host properties in datacenter %s: %v", dc.Name, err)
|
|
continue
|
|
}
|
|
*/
|
|
log.Printf("DC %s contains %d hosts\n", dc.Name, len(hosts))
|
|
|
|
// 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 %s parent properties: %v", hs.Name, err)
|
|
continue
|
|
}
|
|
|
|
dts, err := host.ConfigManager().DateTimeSystem(ctx)
|
|
if err != nil {
|
|
log.Printf("Failed to get datetime system for host %s: %v", hs.Name, err)
|
|
continue
|
|
}
|
|
|
|
// test triggering update
|
|
// Call RefreshDateTimeSystem method using methods.RefreshDateTimeSystem
|
|
refreshReq := &types.RefreshDateTimeSystem{
|
|
This: dts.Reference(),
|
|
}
|
|
_, err = methods.RefreshDateTimeSystem(ctx, c.Client, refreshReq)
|
|
if err != nil {
|
|
log.Printf("Failed to refresh datetime system for host %s: %v", hs.Name, err)
|
|
} else {
|
|
//prettyPrint(refreshResp)
|
|
}
|
|
|
|
/*
|
|
// Call UpdateDateTime method using methods.UpdateDateTime - this will manually set the ESXi clock
|
|
updateReq := &types.UpdateDateTime{
|
|
This: dts.Reference(),
|
|
DateTime: time.Now().UTC(),
|
|
}
|
|
_, err = methods.UpdateDateTime(ctx, c.Client, updateReq)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
*/
|
|
|
|
// Get the current ESXi time which is UTC
|
|
hostTime, err := dts.Query(ctx)
|
|
|
|
// 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 {
|
|
log.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,
|
|
HostTimeDrift: diff.String(),
|
|
}
|
|
|
|
hostTimeErrors = append(hostTimeErrors, thisResult)
|
|
} else {
|
|
log.Printf("[%s] Host: %s; Time: %v; Drift: %s\n", dc.Name, hs.Name, hostTime, diff)
|
|
}
|
|
|
|
/*
|
|
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)
|
|
}
|
|
*/
|
|
|
|
log.Printf("Finished checking vCenter %s\n", u.Host)
|
|
|
|
// Output final results in JSON
|
|
if len(hostTimeErrors) > 0 {
|
|
j, _ := json.Marshal(hostTimeErrors)
|
|
fmt.Println(string(j))
|
|
} else {
|
|
fmt.Println("{}")
|
|
}
|
|
}
|