Skip to content

Commit

Permalink
Merge pull request #8 from embano1/issue-7
Browse files Browse the repository at this point in the history
  • Loading branch information
embano1 committed Jan 11, 2022
2 parents 21cc64a + 4e995d2 commit d7fd7f6
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 0 deletions.
73 changes: 73 additions & 0 deletions event/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package event

import (
"context"
"fmt"
"reflect"

"github.com/vmware/govmomi/event"
"github.com/vmware/govmomi/vim25/types"
)

// NewHistoryCollector creates a new event collector for the specified entity.
// By default, events for the entity and all (indirect) children (if any) are
// retrieved and event collection starts at "now".
func NewHistoryCollector(ctx context.Context, mgr *event.Manager, entity types.ManagedObjectReference, filters ...Filter) (*event.HistoryCollector, error) {
f := defaultFilters
f = append(f, filters...)
spec, err := createSpec(entity, f)
if err != nil {
return nil, fmt.Errorf("create filter spec: %w", err)
}
return mgr.CreateCollectorForEvents(ctx, *spec)
}

func createSpec(entity types.ManagedObjectReference, filters []Filter) (*types.EventFilterSpec, error) {
spec := types.EventFilterSpec{
Entity: &types.EventFilterSpecByEntity{
Entity: entity,
},
}

for _, f := range filters {
if err := f(&spec); err != nil {
return nil, fmt.Errorf("filter spec invalid: %w", err)
}
}

return &spec, nil
}

// Details contains the type and class of an event received from vCenter
// supported event classes: event, eventex, extendedevent.
//
// Class to type mapping:
// event: retrieved from event Class, e.g.VmPoweredOnEvent
// eventex: retrieved from EventTypeId
// extendedevent: retrieved from EventTypeId
type Details struct {
Class string
Type string
}

// GetDetails retrieves the underlying vSphere event class and name for the
// given BaseEvent, e.g. VmPoweredOnEvent (event) or
// com.vmware.applmgmt.backup.job.failed.event (extendedevent)
func GetDetails(event types.BaseEvent) Details {
var details Details

switch e := event.(type) {
case *types.EventEx:
details.Class = "eventex"
details.Type = e.EventTypeId
case *types.ExtendedEvent:
details.Class = "extendedevent"
details.Type = e.EventTypeId
default:
t := reflect.TypeOf(event).Elem().Name()
details.Class = "event"
details.Type = t
}

return details
}
188 changes: 188 additions & 0 deletions event/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package event

import (
"context"
"testing"
"time"

"github.com/vmware/govmomi/event"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/types"
"gotest.tools/v3/assert"
)

func Test_NewHistoryCollector(t *testing.T) {
simulator.Run(func(ctx context.Context, client *vim25.Client) error {
f := WithTime(&types.EventFilterSpecByTime{
BeginTime: types.NewTime(time.Now().UTC().Add(-5 * time.Minute)), // since start
})

mgr := event.NewManager(client)
collector, err := NewHistoryCollector(ctx, mgr, client.ServiceContent.RootFolder, f)
assert.NilError(t, err)

events, err := collector.ReadNextEvents(ctx, 100)
assert.NilError(t, err)
assert.Assert(t, len(events) > 0)

return nil
})
}

func Test_createSpec(t *testing.T) {
const (
notNilErr = "must not be nil"
)

entity := types.ManagedObjectReference{
Type: "host-1",
Value: "Host",
}

t.Run("fails when fs input is invalid", func(t *testing.T) {
testCases := []struct {
name string
f Filter
wantErr string
}{
{
name: "EventTypeID is nil",
f: WithEventTypeID(nil),
wantErr: notNilErr,
},
{
name: "Time is nil",
f: WithTime(nil),
wantErr: notNilErr,
},
{
name: "Username is nil",
f: WithUsername(nil),
wantErr: notNilErr,
},
{
name: "MaxCount is 0",
f: WithMaxCount(0),
wantErr: "be greater than 0",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := createSpec(entity, []Filter{tc.f})
assert.ErrorContains(t, err, tc.wantErr)
})
}
})

t.Run("creates spec with defaults", func(t *testing.T) {
spec, err := createSpec(entity, defaultFilters)
assert.NilError(t, err)
assert.Assert(t, spec.Entity != nil)
assert.Assert(t, spec.Time != nil)
assert.Assert(t, spec.UserName == nil)
assert.Assert(t, spec.EventTypeId == nil)
assert.Assert(t, spec.MaxCount == 0)
})

t.Run("creates custom spec", func(t *testing.T) {
now := time.Now().UTC()
testCases := []struct {
name string
fs []Filter
want *types.EventFilterSpec
}{
{
name: "begins -10m ago",
fs: []Filter{
WithTime(&types.EventFilterSpecByTime{
BeginTime: types.NewTime(now),
EndTime: nil,
}),
},
want: &types.EventFilterSpec{
Entity: &types.EventFilterSpecByEntity{
Entity: entity,
Recursion: types.EventFilterSpecRecursionOptionAll,
},
Time: &types.EventFilterSpecByTime{
BeginTime: types.NewTime(now),
},
},
}, {
name: "begins -1h ago, no recursion",
fs: []Filter{
WithTime(&types.EventFilterSpecByTime{
BeginTime: types.NewTime(now.Add(-1 * time.Hour)),
EndTime: nil,
}),
WithRecursion(types.EventFilterSpecRecursionOptionSelf),
},
want: &types.EventFilterSpec{
Entity: &types.EventFilterSpecByEntity{
Entity: entity,
Recursion: types.EventFilterSpecRecursionOptionSelf,
},
Time: &types.EventFilterSpecByTime{
BeginTime: types.NewTime(now.Add(-1 * time.Hour)),
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fs := defaultFilters
fs = append(fs, tc.fs...)
spec, err := createSpec(entity, fs)
assert.NilError(t, err)
assert.DeepEqual(t, spec, tc.want)
})
}
})
}

func Test_GetDetails(t *testing.T) {
testCases := []struct {
name string
event types.BaseEvent
want Details
}{
{
name: "VmPoweredOnEvent",
event: &types.VmPoweredOnEvent{},
want: Details{
Class: "event",
Type: "VmPoweredOnEvent",
},
},
{
name: "Extended Event",
event: &types.ExtendedEvent{
EventTypeId: "com.backup.job.succeeded",
},
want: Details{
Class: "extendedevent",
Type: "com.backup.job.succeeded",
},
},
{
name: "EventEx",
event: &types.EventEx{
EventTypeId: "com.appliance.shutdown.succeeded",
},
want: Details{
Class: "eventex",
Type: "com.appliance.shutdown.succeeded",
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := GetDetails(tc.event)
assert.DeepEqual(t, d, tc.want)
})
}
}
71 changes: 71 additions & 0 deletions event/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package event

import (
"fmt"
"time"

"github.com/vmware/govmomi/vim25/types"
)

// Filter is a filter applied to the event filter spec. See vSphere API
// documentation for Details on the specific fields.
type Filter func(f *types.EventFilterSpec) error

// WithEventTypeID limits the set of collected events to those specified types
func WithEventTypeID(ids []string) Filter {
return func(f *types.EventFilterSpec) error {
if ids == nil {
return fmt.Errorf("types filter must not be nil")
}
f.EventTypeId = ids
return nil
}
}

// WithMaxCount specifies the maximum number of returned events
func WithMaxCount(count uint32) Filter {
return func(f *types.EventFilterSpec) error {
if count == 0 {
return fmt.Errorf("count must be greater than 0")
}
f.MaxCount = int32(count)
return nil
}
}

// WithRecursion specifies whether events should be received only for the
// specified object, including its direct children or all children
func WithRecursion(r types.EventFilterSpecRecursionOption) Filter {
return func(f *types.EventFilterSpec) error {
f.Entity.Recursion = r
return nil
}
}

// WithTime filters events based on time
func WithTime(time *types.EventFilterSpecByTime) Filter {
return func(f *types.EventFilterSpec) error {
if time == nil {
return fmt.Errorf("time filter must not be nil")
}
f.Time = time
return nil
}
}

// WithUsername filters events based on username
func WithUsername(u *types.EventFilterSpecByUsername) Filter {
return func(f *types.EventFilterSpec) error {
if u == nil {
return fmt.Errorf("username filter must not be nil")
}
f.UserName = u
return nil
}
}

var defaultFilters = []Filter{
WithRecursion(types.EventFilterSpecRecursionOptionAll),
// explicitly start at "now"
WithTime(&types.EventFilterSpecByTime{BeginTime: types.NewTime(time.Now().UTC())}),
}

0 comments on commit d7fd7f6

Please sign in to comment.