Skip to content

Commit

Permalink
vcsim: add TaskHistoryCollector support
Browse files Browse the repository at this point in the history
TaskHistoryCollector provides a way to collect Task history beyond the 10 minute max of the 'RecentTask' property.
And unlike 'RecentTask', can filter based on Task state and collect tasks of child entities.

refactor portions of 'EventHistoryCollector' to 'HistoryCollector' for use with 'TaskHistoryCollector'

govc: add history flags to 'tasks' command

Closes vmware#2567
  • Loading branch information
dougm committed Mar 25, 2024
1 parent b310f54 commit eb71e5e
Show file tree
Hide file tree
Showing 11 changed files with 847 additions and 195 deletions.
8 changes: 7 additions & 1 deletion govc/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5594,14 +5594,20 @@ By default, all recent tasks are included (via TaskManager), but can be limited
to a specific inventory object.
Examples:
govc tasks
govc tasks # tasks completed within the past 10 minutes
govc tasks -b 24h # tasks completed within the past 24 hours
govc tasks -s queued -s running # incomplete tasks
govc tasks -s error -s success # completed tasks
govc tasks -f
govc tasks -f /dc1/host/cluster1
Options:
-b=0s Begin time of task history
-e=0s End time of task history
-f=false Follow recent task updates
-l=false Use long task description
-n=25 Output the last N tasks
-s=[] Task states
```

## tree
Expand Down
139 changes: 126 additions & 13 deletions govc/task/recent.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -22,11 +22,15 @@ import (
"fmt"
"io"
"strings"
"time"

"github.com/vmware/govmomi/govc/cli"
"github.com/vmware/govmomi/govc/flags"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/task"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
Expand All @@ -37,6 +41,12 @@ type recent struct {
max int
follow bool
long bool

state flags.StringList
begin time.Duration
end time.Duration

plain bool
}

func init() {
Expand All @@ -50,6 +60,9 @@ func (cmd *recent) Register(ctx context.Context, f *flag.FlagSet) {
f.IntVar(&cmd.max, "n", 25, "Output the last N tasks")
f.BoolVar(&cmd.follow, "f", false, "Follow recent task updates")
f.BoolVar(&cmd.long, "l", false, "Use long task description")
f.Var(&cmd.state, "s", "Task states")
f.DurationVar(&cmd.begin, "b", 0, "Begin time of task history")
f.DurationVar(&cmd.end, "e", 0, "End time of task history")
}

func (cmd *recent) Description() string {
Expand All @@ -64,7 +77,10 @@ By default, all recent tasks are included (via TaskManager), but can be limited
to a specific inventory object.
Examples:
govc tasks
govc tasks # tasks completed within the past 10 minutes
govc tasks -b 24h # tasks completed within the past 24 hours
govc tasks -s queued -s running # incomplete tasks
govc tasks -s error -s success # completed tasks
govc tasks -f
govc tasks -f /dc1/host/cluster1`
}
Expand Down Expand Up @@ -101,7 +117,101 @@ func taskName(info *types.TaskInfo) string {
}
}

type history struct {
*task.HistoryCollector

cmd *recent
}

func (h *history) Collect(ctx context.Context, f func([]types.TaskInfo)) error {
for {
tasks, err := h.ReadNextTasks(ctx, 10)
if err != nil {
return err
}

if len(tasks) == 0 {
if h.cmd.follow {
// TODO: this only follows new events.
// need to watch TaskHistoryCollector.LatestPage for updates to existing Tasks
time.Sleep(time.Second)
continue
}
break
}

f(tasks)
}
return nil
}

type collector interface {
Collect(context.Context, func([]types.TaskInfo)) error
Destroy(context.Context) error
}

func (cmd *recent) newCollector(ctx context.Context, c *vim25.Client, ref *types.ManagedObjectReference) (collector, error) {
if cmd.begin == 0 && cmd.end == 0 {
if ref == nil {
ref = c.ServiceContent.TaskManager
}

v, err := view.NewManager(c).CreateTaskView(ctx, ref)
if err != nil {
return nil, err
}

v.Follow = cmd.follow && cmd.plain
return v, nil
}

m := task.NewManager(c)

if ref == nil {
ref = &c.ServiceContent.RootFolder
}

now, err := methods.GetCurrentTime(ctx, c) // vCenter server time (UTC)
if err != nil {
return nil, err
}

if cmd.begin == 0 {
cmd.begin = 10 * time.Minute
}

filter := types.TaskFilterSpec{
Entity: &types.TaskFilterSpecByEntity{
Entity: *ref,
Recursion: types.TaskFilterSpecRecursionOptionAll,
},
Time: &types.TaskFilterSpecByTime{
TimeType: types.TaskFilterSpecTimeOptionStartedTime,
BeginTime: types.NewTime(now.Add(cmd.begin * -1)),
},
}

for _, state := range cmd.state {
filter.State = append(filter.State, types.TaskInfoState(state))
}

if cmd.end != 0 {
filter.Time.EndTime = types.NewTime(now.Add(cmd.end * -1))
}

collector, err := m.CreateCollectorForTasks(ctx, filter)
if err != nil {
return nil, err
}

return &history{collector, cmd}, nil
}

func (cmd *recent) Run(ctx context.Context, f *flag.FlagSet) error {
if f.NArg() > 1 {
return flag.ErrHelp
}

c, err := cmd.Client()
if err != nil {
return err
Expand Down Expand Up @@ -134,31 +244,33 @@ func (cmd *recent) Run(ctx context.Context, f *flag.FlagSet) error {
}
}

watch := *m
var watch *types.ManagedObjectReference

if f.NArg() == 1 {
refs, merr := cmd.ManagedObjects(ctx, f.Args())
if merr != nil {
return merr
}
watch = refs[0]
if len(refs) != 1 {
return fmt.Errorf("%s matches %d objects", f.Arg(0), len(refs))
}
watch = &refs[0]
}

v, err := view.NewManager(c).CreateTaskView(ctx, &watch)
// writes dump/json/xml once even if follow is specified, otherwise syntax error occurs
cmd.plain = !(cmd.Dump || cmd.JSON || cmd.XML)

v, err := cmd.newCollector(ctx, c, watch)
if err != nil {
return nil
return err
}

defer func() {
_ = v.Destroy(context.Background())
}()

// writes dump/json/xml once even if follow is specified, otherwise syntax error occurs
writesPlain := !(cmd.Dump || cmd.JSON || cmd.XML)
v.Follow = cmd.follow && writesPlain

res := &taskResult{name: tn}
if writesPlain {
if cmd.plain {
res.WriteHeader(cmd.Out)
}

Expand Down Expand Up @@ -207,17 +319,18 @@ func (t *taskResult) Write(w io.Writer) error {
user = strings.TrimPrefix(user, "com.vmware.") // e.g. com.vmware.vsan.health
}

queued := info.QueueTime.Format(stamp)
queued := "-"
start := "-"
end := start

if info.StartTime != nil {
start = info.StartTime.Format(stamp)
queued = info.StartTime.Sub(info.QueueTime).Round(time.Millisecond).String()
}

msg := fmt.Sprintf("%2d%% %s", info.Progress, info.Task)

if info.CompleteTime != nil {
if info.CompleteTime != nil && info.StartTime != nil {
msg = info.CompleteTime.Sub(*info.StartTime).String()

if info.State == types.TaskInfoStateError {
Expand Down
2 changes: 1 addition & 1 deletion govc/test/object.bats
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ EOF

n=$(ls "$dir"/*.xml | wc -l)
rm -rf "$dir"
assert_equal 6 "$n"
assert_equal 10 "$n"
}

@test "tree" {
Expand Down
21 changes: 18 additions & 3 deletions govc/test/tasks.bats
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,30 @@ load test_helper

run govc tasks
assert_success
}

@test "tasks host" {
vcsim_env
run govc tasks vm/DC0_H0_VM0
assert_success

run govc tasks vm/DC0_H0_VM0 vm/DC0_H0_VM1
assert_failure # > 1 arg

run govc tasks 'host/*'
assert_failure # matches 2 objects

run govc tasks -b 1h
assert_success
}

@test "tasks esx" {
vcsim_env -esx

run govc tasks
assert_success

run govc tasks -b 1h
assert_failure # TaskHistoryCollector not supported on ESX
}

@test "task.create" {
vcsim_env

Expand Down
4 changes: 2 additions & 2 deletions govc/test/vcsim.bats
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ EOF

# compile + run examples against vcsim
for main in ../../examples/*/main.go ; do
# TODO: #2567 #2476
if [[ $main =~ task|alarm ]]; then
# TODO: #2476
if [[ $main =~ alarm ]]; then
continue
fi
run go run "$main" -insecure -url "$GOVC_URL"
Expand Down
Loading

0 comments on commit eb71e5e

Please sign in to comment.