package main import ( "context" "flag" "fmt" "net/url" "os" "sort" "strings" "time" "github.com/vmware/govmomi" "github.com/vmware/govmomi/event" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) 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 ) func getEvents(eventTypes []string, entities []types.ManagedObjectReference, begin time.Duration, end time.Duration) []types.Event { var returnEvents []types.Event // Refer https://github.com/vmware/govmomi/blob/main/examples/events/main.go now, err := methods.GetCurrentTime(ctx, c) // vCenter server time (UTC) if err != nil { fmt.Fprintf(os.Stderr, "Error getting vCenter time: %s\n", err) os.Exit(1) } else { fmt.Printf("vCenter time is '%v'\n", now) } m := event.NewManager(c.Client) root := c.ServiceContent.RootFolder // configure the event stream filter (begin of stream) filter := types.EventFilterSpec{ EventTypeId: eventTypes, // only stream specific types, e.g. VmEvent Entity: &types.EventFilterSpecByEntity{ Entity: root, Recursion: types.EventFilterSpecRecursionOptionAll, }, Time: &types.EventFilterSpecByTime{ BeginTime: types.NewTime(now.Add(begin * -1)), }, } if len(entities) > 0 { fmt.Printf("getEvents setting entity '%v' to filter\n", entities[0]) filter.Entity = &types.EventFilterSpecByEntity{ Entity: entities[0], Recursion: types.EventFilterSpecRecursionOptionAll, } } if end != 0 { filter.Time.EndTime = types.NewTime(now.Add(end * -1)) } collector, err := m.CreateCollectorForEvents(ctx, filter) if err != nil { fmt.Fprintf(os.Stderr, "Error creating event collector: %s\n", err) os.Exit(1) } defer collector.Destroy(ctx) for { events, err := collector.ReadNextEvents(ctx, 100) if err != nil { fmt.Fprintf(os.Stderr, "Error reading events: %s\n", err) } if len(events) == 0 { break } for i := range events { event := events[i].GetEvent() returnEvents = append(returnEvents, *event) //kind := reflect.TypeOf(events[i]).Elem().Name() //fmt.Printf("%d [%s] [%s] %s\n", event.Key, event.CreatedTime.In(location).Format(time.ANSIC), kind, event.FullFormattedMessage) fmt.Printf("%d [%s] %s\n", event.Key, event.CreatedTime.In(location).Format(time.ANSIC), event.FullFormattedMessage) } } return returnEvents } func getVM(name string) mo.VirtualMachine { // Create a container view so that we can search vCenter for a VM if we found any failure events m := view.NewManager(c.Client) cv, _ := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, []string{"VirtualMachine"}, true) var vms []mo.VirtualMachine fmt.Printf("Searching for VM\n") err := cv.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary", "name"}, &vms) if err != nil { fmt.Printf("Failed searching for VM %s : %s\n", name, err) return mo.VirtualMachine{} } else { for _, vm := range vms { if vm.Name == name { fmt.Printf("Found corresponding VM with MoRef '%s'\n", vm.Reference()) return vm } } } // If we reached here then we didn't find a VM return mo.VirtualMachine{} } 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") begin := flag.Duration("b", time.Hour, "Begin time") // default BeginTime is 1h ago end := flag.Duration("e", 0, "End time") flag.Parse() // So we can convert vCenter UTC to our local timezone location, _ = time.LoadLocation(*vTZ) fmt.Printf("Starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver) 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") fmt.Printf("Updated vCenter URL to '%v'\n", u) } } fmt.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) //finder := find.NewFinder(c.Client) fmt.Printf("Searching for hostfailure events\n") hostFailures := getEvents([]string{"com.vmware.vc.HA.DasHostFailedEvent"}, []types.ManagedObjectReference{}, *begin, *end) if len(hostFailures) > 0 { fmt.Printf("Found at least one host failure, proceeding with VM restart search\n") vmFailures := getEvents([]string{"com.vmware.vc.ha.VmRestartedByHAEvent"}, []types.ManagedObjectReference{}, *begin, *end) for i := range vmFailures { var outageStart time.Time event := vmFailures[i] fmt.Printf("Failure event for VM '%s' restarted in cluster '%s'\n", event.Vm.Name, event.ComputeResource.Name) vm := getVM(event.Vm.Name) // Use VmDisconnectedEvent to see which host this VM was on disconnectedEvents := getEvents([]string{"VmDisconnectedEvent"}, []types.ManagedObjectReference{vm.Reference()}, *begin, *end) fmt.Printf("Retrieved '%d' corresponding events.\n", len(disconnectedEvents)) // Calculate the VM outage duration based on the previous host if len(disconnectedEvents) > 0 { // Sort the disconnected events by time sort.Slice(disconnectedEvents[:], func(i, j int) bool { return disconnectedEvents[i].CreatedTime.Before(disconnectedEvents[j].CreatedTime) }) // Use the earliest event as the outage start disconnectedHost := disconnectedEvents[0] outageStart = disconnectedHost.CreatedTime.In(location) fmt.Printf("VM was running on host '%s' previously, setting outage start to '%s'\n", disconnectedHost.Host.Name, outageStart) } else { fmt.Printf("could not determine previous host for this VM. Filtering all host failures for events prior to VM restart time '%s'\n", event.CreatedTime.In(location)) // Search for host failures } /* // Get a reference to the cluster mentioned in the event cluster, err := finder.ClusterComputeResource(ctx, vmFailures[i].ComputeResource.Name) view, _ := m.CreateContainerView(ctx, cluster, []string{"VirtualMachine"}, true) var vms []mo.VirtualMachine err = view.Retrieve(ctx, []string{"VirtualMachine"}, []string{}, &vms) // Specify what properties we want to retrieve err = v.Retrieve(ctx, []string{"HostSystem"}, []string{"summary", "runtime"}, &hs) if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(1) } // Run the actual esxcli command on each host finder := find.NewFinder(c.Client) for _, host := range hs { //fmt.Printf("Host name %s is %s\n", host.Summary.Config.Name, host.Runtime.ConnectionState) // Make sure that the host is connected before we try running an esxcli command if host.Runtime.ConnectionState == "connected" { // Get a reference to the host object so that we can check the ramdisk values objRef, err := finder.ObjectReference(ctx, host.Reference()) */ } } // Selecting default datacenter /* finder := find.NewFinder(c.Client, true) dc, err := finder.DefaultDatacenter(ctx) if err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) } //refs := []types.ManagedObjectReference{dc.Reference()} */ /* // Refer https://github.com/vmware/govmomi/blob/main/examples/events/main.go now, err := methods.GetCurrentTime(ctx, c) // vCenter server time (UTC) if err != nil { fmt.Fprintf(os.Stderr, "Error getting vCenter time: %s\n", err) os.Exit(1) } m := event.NewManager(c.Client) root := c.ServiceContent.RootFolder // configure the event stream filter (begin of stream) filter := types.EventFilterSpec{ EventTypeId: []string{"com.vmware.vc.HA.DasHostFailedEvent"}, // only stream specific types, e.g. VmEvent Entity: &types.EventFilterSpecByEntity{ Entity: root, Recursion: types.EventFilterSpecRecursionOptionAll, }, Time: &types.EventFilterSpecByTime{ BeginTime: types.NewTime(now.Add(*begin * -1)), }, } if *end != 0 { filter.Time.EndTime = types.NewTime(now.Add(*end * -1)) } collector, err := m.CreateCollectorForEvents(ctx, filter) if err != nil { fmt.Fprintf(os.Stderr, "Error creating event collector: %s\n", err) os.Exit(1) } defer collector.Destroy(ctx) for { events, err := collector.ReadNextEvents(ctx, 100) if err != nil { fmt.Fprintf(os.Stderr, "Error reading events: %s\n", err) } if len(events) == 0 { break } for i := range events { event := events[i].GetEvent() kind := reflect.TypeOf(events[i]).Elem().Name() fmt.Printf("%d [%s] [%s] %s\n", event.Key, event.CreatedTime.Format(time.ANSIC), kind, event.FullFormattedMessage) } } */ // ************** // previous code here /* // Do a recursive search for all the ESXi hosts (which have a type of HostSystem) m := view.NewManager(c.Client) v, _ := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, []string{"HostSystem"}, true) // Specify what properties we want to retrieve var hs []mo.HostSystem err = v.Retrieve(ctx, []string{"HostSystem"}, []string{"summary", "runtime"}, &hs) if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(1) } // Run the actual esxcli command on each host finder := find.NewFinder(c.Client) for _, host := range hs { //fmt.Printf("Host name %s is %s\n", host.Summary.Config.Name, host.Runtime.ConnectionState) // Make sure that the host is connected before we try running an esxcli command if host.Runtime.ConnectionState == "connected" { // Get a reference to the host object so that we can check the ramdisk values objRef, err := finder.ObjectReference(ctx, host.Reference()) if err != nil { fmt.Fprintf(os.Stderr, "Error getting reference to host : %s\n", err) continue } // Get the HostSystem object hs, ok := objRef.(*object.HostSystem) if !ok { fmt.Fprintf(os.Stderr, "Couldn't find Hostsytem : %s\n", host.Reference()) continue } // Get a reference to the esxcli executor // https://gowalker.org/github.com/dhawalseth/govc-autocomplete/govc/host/esxcli // https://pkg.go.dev/github.com/vmware/govmomi/govc/host/esxcli // https://github.com/vmware/govmomi/blob/f4c5c4a58e445ba31393795c7e678fa05fb3452a/govc/host/esxcli/esxcli.go // https://golang.hotexamples.com/examples/github.com.vmware.govmomi.object/HostSystem/-/golang-hostsystem-class-examples.html // https://golang.hotexamples.com/examples/github.com.vmware.govmomi.govc.host.esxcli/-/GetFirewallInfo/golang-getfirewallinfo-function-examples.html e, err := esxcli.NewExecutor(c.Client, hs) if err != nil { fmt.Fprintf(os.Stderr, "Error getting esxcli executor: %s\n", err) continue } // Run the desired esxcli command result, err := e.Run([]string{"system", "visorfs", "ramdisk", "list"}) if err != nil { fmt.Fprintf(os.Stderr, "Error running esxcli command: %s\n", err) continue } // Print out all the values for _, values := range result.Values { fmt.Printf("%s;%s;%s\n", host.Summary.Config.Name, values["RamdiskName"][0], values["Free"][0]) } } } */ }