-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from embano1/issue-7
- Loading branch information
Showing
3 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())}), | ||
} |