From 9c59e9c55da4d7bd7316a626404cc956da4f6c01 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Sun, 17 Mar 2024 22:02:55 -0700 Subject: [PATCH] vcsim: add TaskManager.CreateTask support --- govc/task/create.go | 100 +++++++++++++++++++++++++++++++++ govc/task/set.go | 114 ++++++++++++++++++++++++++++++++++++++ govc/test/tasks.bats | 33 +++++++++++ simulator/task.go | 94 ++++++++++++++++++++++++++++++- simulator/task_manager.go | 51 ++++++++++++++++- 5 files changed, 388 insertions(+), 4 deletions(-) create mode 100644 govc/task/create.go create mode 100644 govc/task/set.go diff --git a/govc/task/create.go b/govc/task/create.go new file mode 100644 index 000000000..7ddd0ca1d --- /dev/null +++ b/govc/task/create.go @@ -0,0 +1,100 @@ +/* +Copyright (c) 2024-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. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "context" + "flag" + "fmt" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/session" + "github.com/vmware/govmomi/task" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/types" +) + +type create struct { + *flags.ClientFlag + + obj string +} + +func init() { + cli.Register("task.create", &create{}, true) +} + +func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { + cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) + cmd.ClientFlag.Register(ctx, f) + + f.StringVar(&cmd.obj, "o", "", "ManagedObject with which Task will be associated") +} + +func (cmd *create) Description() string { + return `Create task of type ID. + +ID must be one of: + govc extension.info -json | jq -r '.extensions[].taskList | select(. != null) | .[].taskID' + +Examples: + govc task.create $ID` +} + +func (cmd *create) Usage() string { + return "ID" +} + +func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { + c, err := cmd.Client() + if err != nil { + return err + } + + if f.NArg() != 1 { + return flag.ErrHelp + } + + m := task.NewManager(c) + + req := types.CreateTask{ + This: m.Reference(), + Obj: c.ServiceContent.RootFolder, + TaskTypeId: f.Arg(0), + Cancelable: true, + } + + if cmd.obj != "" { + req.Obj.FromString(cmd.obj) + } + + s, err := session.NewManager(c).UserSession(ctx) + if err != nil { + return err + } + req.InitiatedBy = s.UserName + + res, err := methods.CreateTask(ctx, c, &req) + if err != nil { + return err + } + + fmt.Println(res.Returnval.Task) + + return nil +} diff --git a/govc/task/set.go b/govc/task/set.go new file mode 100644 index 000000000..358dfcbf1 --- /dev/null +++ b/govc/task/set.go @@ -0,0 +1,114 @@ +/* +Copyright (c) 2024-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. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +type set struct { + *flags.ClientFlag + + desc types.LocalizableMessage + state string + err string + progress int +} + +func init() { + cli.Register("task.set", &set{}, true) +} + +func (cmd *set) Register(ctx context.Context, f *flag.FlagSet) { + cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) + cmd.ClientFlag.Register(ctx, f) + + f.StringVar(&cmd.desc.Key, "d", "", "Task description key") + f.StringVar(&cmd.desc.Message, "m", "", "Task description message") + f.StringVar(&cmd.state, "s", "", "Task state") + f.StringVar(&cmd.err, "e", "", "Task error") + f.IntVar(&cmd.progress, "p", 0, "Task progress") +} + +func (cmd *set) Description() string { + return `Set task state. + +Examples: + id=$(govc task.create com.vmware.govmomi.simulator.test) + govc task.set $id -s error` +} + +func (cmd *set) Usage() string { + return "ID" +} + +func (cmd *set) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() != 1 { + return flag.ErrHelp + } + + c, err := cmd.Client() + if err != nil { + return err + } + + ref := types.ManagedObjectReference{Type: "Task"} + if !ref.FromString(f.Arg(0)) { + ref.Value = f.Arg(0) + } + + task := object.NewTask(c, ref) + + var fault *types.LocalizedMethodFault + + if cmd.err != "" { + fault = &types.LocalizedMethodFault{ + Fault: &types.SystemError{Reason: cmd.err}, + LocalizedMessage: cmd.err, + } + cmd.state = string(types.TaskInfoStateError) + } + + if cmd.state != "" { + err := task.SetState(ctx, types.TaskInfoState(cmd.state), nil, fault) + if err != nil { + return err + } + } + + if cmd.progress != 0 { + err := task.UpdateProgress(ctx, cmd.progress) + if err != nil { + return err + } + } + + if cmd.desc.Key != "" { + err := task.SetDescription(ctx, cmd.desc) + if err != nil { + return err + } + } + + return nil +} diff --git a/govc/test/tasks.bats b/govc/test/tasks.bats index 0ed9943a4..20b250ce7 100755 --- a/govc/test/tasks.bats +++ b/govc/test/tasks.bats @@ -15,3 +15,36 @@ load test_helper run govc tasks 'host/*' assert_success } + +@test "task.create" { + vcsim_env + + export GOVC_SHOW_UNRELEASED=true + + run govc task.create enoent + assert_failure + + id=$(govc extension.info -json | jq -r '.extensions[].taskList | select(. != null) | .[].taskID' | head -1) + assert_equal com.vmware.govmomi.simulator.test "$id" + + run govc task.create "$id" + assert_success + task="$output" + + run govc task.set -s running "$task" + assert_success + + govc tasks | grep "$id" + + run govc task.set -d "$id.init" -m "task init" "$task" + assert_success + + run govc task.set -p 42 "$task" + assert_success + + run govc task.set -s success "$task" + assert_success + + run govc task.set -s running "$task" + assert_failure +} diff --git a/simulator/task.go b/simulator/task.go index 73c52b919..cab4eaf64 100644 --- a/simulator/task.go +++ b/simulator/task.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2017 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. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -22,7 +22,9 @@ import ( "strings" "time" + "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) @@ -181,3 +183,91 @@ func (t *Task) Wait() { time.Sleep(10 * time.Millisecond) } } + +func (t *Task) isDone() bool { + return t.Info.State == types.TaskInfoStateError || t.Info.State == types.TaskInfoStateSuccess +} + +func (t *Task) SetTaskState(ctx *Context, req *types.SetTaskState) soap.HasFault { + body := new(methods.SetTaskStateBody) + + if t.isDone() { + body.Fault_ = Fault("", new(types.InvalidState)) + return body + } + + changes := []types.PropertyChange{ + {Name: "info.state", Val: req.State}, + } + + switch req.State { + case types.TaskInfoStateRunning: + changes = append(changes, types.PropertyChange{Name: "info.startTime", Val: time.Now()}) + case types.TaskInfoStateError, types.TaskInfoStateSuccess: + changes = append(changes, types.PropertyChange{Name: "info.completeTime", Val: time.Now()}) + + if req.Fault != nil { + changes = append(changes, types.PropertyChange{Name: "info.error", Val: req.Fault}) + } + if req.Result != nil { + changes = append(changes, types.PropertyChange{Name: "info.result", Val: req.Result}) + } + } + + ctx.Map.Update(t, changes) + + body.Res = new(types.SetTaskStateResponse) + return body +} + +func (t *Task) SetTaskDescription(ctx *Context, req *types.SetTaskDescription) soap.HasFault { + body := new(methods.SetTaskDescriptionBody) + + if t.isDone() { + body.Fault_ = Fault("", new(types.InvalidState)) + return body + } + + ctx.Map.Update(t, []types.PropertyChange{{Name: "info.description", Val: req.Description}}) + + body.Res = new(types.SetTaskDescriptionResponse) + return body +} + +func (t *Task) UpdateProgress(ctx *Context, req *types.UpdateProgress) soap.HasFault { + body := new(methods.UpdateProgressBody) + + if t.Info.State != types.TaskInfoStateRunning { + body.Fault_ = Fault("", new(types.InvalidState)) + return body + } + + ctx.Map.Update(t, []types.PropertyChange{{Name: "info.progress", Val: req.PercentDone}}) + + body.Res = new(types.UpdateProgressResponse) + return body +} + +func (t *Task) CancelTask(ctx *Context, req *types.CancelTask) soap.HasFault { + body := new(methods.CancelTaskBody) + + if t.isDone() { + body.Fault_ = Fault("", new(types.InvalidState)) + return body + } + + changes := []types.PropertyChange{ + {Name: "info.canceled", Val: true}, + {Name: "info.completeTime", Val: time.Now()}, + {Name: "info.state", Val: types.TaskInfoStateError}, + {Name: "info.error", Val: &types.LocalizedMethodFault{ + Fault: &types.RequestCanceled{}, + LocalizedMessage: "The task was canceled by a user", + }}, + } + + ctx.Map.Update(t, changes) + + body.Res = new(types.CancelTaskResponse) + return body +} diff --git a/simulator/task_manager.go b/simulator/task_manager.go index ae84635ea..a41132b76 100644 --- a/simulator/task_manager.go +++ b/simulator/task_manager.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2017 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. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -18,10 +18,13 @@ package simulator import ( "sync" + "time" "github.com/vmware/govmomi/simulator/esx" "github.com/vmware/govmomi/simulator/vpx" + "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) @@ -62,3 +65,47 @@ func (m *TaskManager) PutObject(obj mo.Reference) { func (*TaskManager) RemoveObject(*Context, types.ManagedObjectReference) {} func (*TaskManager) UpdateObject(mo.Reference, []types.PropertyChange) {} + +func validTaskID(ctx *Context, taskID string) bool { + m := ctx.Map.ExtensionManager() + + for _, x := range m.ExtensionList { + for _, task := range x.TaskList { + if task.TaskID == taskID { + return true + } + } + } + + return false +} + +func (m *TaskManager) CreateTask(ctx *Context, req *types.CreateTask) soap.HasFault { + body := &methods.CreateTaskBody{} + + if !validTaskID(ctx, req.TaskTypeId) { + body.Fault_ = Fault("", &types.InvalidArgument{ + InvalidProperty: "taskType", + }) + return body + } + + task := &Task{} + + task.Self = ctx.Map.newReference(task) + task.Info.Key = task.Self.Value + task.Info.Task = task.Self + task.Info.DescriptionId = req.TaskTypeId + task.Info.Cancelable = req.Cancelable + task.Info.Entity = &req.Obj + task.Info.EntityName = req.Obj.Value + task.Info.Reason = &types.TaskReasonUser{UserName: ctx.Session.UserName} + task.Info.QueueTime = time.Now() + task.Info.State = types.TaskInfoStateQueued + + body.Res = &types.CreateTaskResponse{Returnval: task.Info} + + go ctx.Map.Put(task) + + return body +}