Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --wait, -w flag to run-task #1861

Merged
merged 1 commit into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions actor/actionerror/task_failed_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package actionerror

type TaskFailedError struct{}

func (TaskFailedError) Error() string {
return "Task failed to complete successfully"
}
1 change: 1 addition & 0 deletions actor/v7action/cloud_controller_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ type CloudControllerClient interface {
GetAppFeature(appGUID string, featureName string) (resources.ApplicationFeature, ccv3.Warnings, error)
GetStacks(query ...ccv3.Query) ([]resources.Stack, ccv3.Warnings, error)
GetStagingSecurityGroups(spaceGUID string, queries ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error)
GetTask(guid string) (resources.Task, ccv3.Warnings, error)
GetUser(userGUID string) (resources.User, ccv3.Warnings, error)
GetUsers(query ...ccv3.Query) ([]resources.User, ccv3.Warnings, error)
MapRoute(routeGUID string, appGUID string) (ccv3.Warnings, error)
Expand Down
32 changes: 32 additions & 0 deletions actor/v7action/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package v7action

import (
"strconv"
"time"

"sort"

"code.cloudfoundry.org/cli/actor/actionerror"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
"code.cloudfoundry.org/cli/resources"
log "github.com/sirupsen/logrus"
)

// Run resources.Task runs the provided command in the application environment associated
Expand Down Expand Up @@ -67,3 +70,32 @@ func (actor Actor) TerminateTask(taskGUID string) (resources.Task, Warnings, err
task, warnings, err := actor.CloudControllerClient.UpdateTaskCancel(taskGUID)
return resources.Task(task), Warnings(warnings), err
}

func (actor Actor) PollTask(task resources.Task) (resources.Task, Warnings, error) {
var allWarnings Warnings

for task.State != constant.TaskSucceeded && task.State != constant.TaskFailed {

time.Sleep(actor.Config.PollingInterval())

ccTask, warnings, err := actor.CloudControllerClient.GetTask(task.GUID)
log.WithFields(log.Fields{
"task_guid": task.GUID,
"state": task.State,
}).Debug("polling task state")

allWarnings = append(allWarnings, warnings...)

if err != nil {
return resources.Task{}, allWarnings, err
}

task = resources.Task(ccTask)
}

if task.State == constant.TaskFailed {
return task, allWarnings, actionerror.TaskFailedError{}
}

return task, allWarnings, nil
}
63 changes: 62 additions & 1 deletion actor/v7action/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ var _ = Describe("Task Actions", func() {
var (
actor *Actor
fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient
fakeConfig *v7actionfakes.FakeConfig
)

BeforeEach(func() {
fakeCloudControllerClient = new(v7actionfakes.FakeCloudControllerClient)
actor = NewActor(fakeCloudControllerClient, nil, nil, nil, nil, nil)
fakeConfig = new(v7actionfakes.FakeConfig)
actor = NewActor(fakeCloudControllerClient, fakeConfig, nil, nil, nil, nil)
})

Describe("RunTask", func() {
Expand Down Expand Up @@ -305,4 +307,63 @@ var _ = Describe("Task Actions", func() {
})
})
})

Describe("PollTask", func() {

It("polls for SUCCEDED state", func() {
firstTaskResponse := resources.Task{State: constant.TaskRunning}
secondTaskResponse := resources.Task{State: constant.TaskSucceeded}

fakeCloudControllerClient.GetTaskReturnsOnCall(0, firstTaskResponse, nil, nil)
fakeCloudControllerClient.GetTaskReturnsOnCall(1, secondTaskResponse, nil, nil)

task, _, _ := actor.PollTask(resources.Task{})

Expect(task.State).To(Equal(constant.TaskSucceeded))
})

It("polls for FAILED state", func() {
firstTaskResponse := resources.Task{State: constant.TaskRunning}
secondTaskResponse := resources.Task{State: constant.TaskFailed}

fakeCloudControllerClient.GetTaskReturnsOnCall(0, firstTaskResponse, nil, nil)
fakeCloudControllerClient.GetTaskReturnsOnCall(1, secondTaskResponse, nil, nil)

task, _, _ := actor.PollTask(resources.Task{})

Expect(task.State).To(Equal(constant.TaskFailed))
})

It("aggregates warnings from all requests made while polling", func() {
firstTaskResponse := resources.Task{State: constant.TaskRunning}
secondTaskResponse := resources.Task{State: constant.TaskSucceeded}

fakeCloudControllerClient.GetTaskReturnsOnCall(0, firstTaskResponse, ccv3.Warnings{"warning-1"}, nil)
fakeCloudControllerClient.GetTaskReturnsOnCall(1, secondTaskResponse, ccv3.Warnings{"warning-2"}, nil)

_, warnings, _ := actor.PollTask(resources.Task{})

Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
})

It("handles errors from requests to cc", func() {
firstTaskResponse := resources.Task{State: constant.TaskSucceeded}

fakeCloudControllerClient.GetTaskReturnsOnCall(0, firstTaskResponse, nil, errors.New("request-error"))

_, _, err := actor.PollTask(resources.Task{})

Expect(err).To(MatchError("request-error"))
})

It("returns an error if the task failed", func() {
firstTaskResponse := resources.Task{State: constant.TaskFailed}

fakeCloudControllerClient.GetTaskReturnsOnCall(0, firstTaskResponse, nil, nil)

_, _, err := actor.PollTask(resources.Task{})

Expect(err).To(MatchError("Task failed to complete successfully"))
})
})
})
83 changes: 83 additions & 0 deletions actor/v7action/v7actionfakes/fake_cloud_controller_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/cloudcontroller/ccv3/internal/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const (
GetSpaceStagingSecurityGroupsRequest = "GetSpaceStagingSecurityGroups"
GetSSHEnabled = "GetSSHEnabled"
GetStacksRequest = "GetStacks"
GetTaskRequest = "GetTask"
GetUserRequest = "GetUser"
GetUsersRequest = "GetUsers"
MapRouteRequest = "MapRoute"
Expand Down Expand Up @@ -303,6 +304,7 @@ var APIRoutes = []Route{
{Resource: StacksResource, Path: "/", Method: http.MethodGet, Name: GetStacksRequest},
{Resource: StacksResource, Path: "/:stack_guid", Method: http.MethodPatch, Name: PatchStackRequest},
{Resource: TasksResource, Path: "/:task_guid/cancel", Method: http.MethodPut, Name: PutTaskCancelRequest},
{Resource: TasksResource, Path: "/:task_guid", Method: http.MethodGet, Name: GetTaskRequest},
{Resource: UsersResource, Path: "/", Method: http.MethodGet, Name: GetUsersRequest},
{Resource: UsersResource, Path: "/:user_guid", Method: http.MethodGet, Name: GetUserRequest},
{Resource: UsersResource, Path: "/", Method: http.MethodPost, Name: PostUserRequest},
Expand Down
14 changes: 14 additions & 0 deletions api/cloudcontroller/ccv3/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ func (client *Client) UpdateTaskCancel(taskGUID string) (resources.Task, Warning

return responseBody, warnings, err
}

func (client *Client) GetTask(guid string) (resources.Task, Warnings, error) {
var responseBody resources.Task

_, warnings, err := client.MakeRequest(RequestParams{
RequestName: internal.GetTaskRequest,
URIParams: internal.Params{
"task_guid": guid,
},
ResponseBody: &responseBody,
})

return responseBody, warnings, err
}
85 changes: 85 additions & 0 deletions api/cloudcontroller/ccv3/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,89 @@ var _ = Describe("Task", func() {
})
})
})

Describe("GetTask", func() {
When("the task exists", func() {
BeforeEach(func() {
response := `{
"guid": "the-task-guid",
"sequence_id": 1,
"name": "task-1",
"command": "some-command",
"state": "SUCCEEDED",
"created_at": "2016-11-07T05:59:01Z"
}`

server.AppendHandlers(
CombineHandlers(
VerifyRequest(http.MethodGet, "/v3/tasks/the-task-guid"),
RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning"}}),
),
)
})

It("returns the task and all warnings", func() {
task, warnings, err := client.GetTask("the-task-guid")
Expect(err).ToNot(HaveOccurred())

expectedTask := resources.Task{
GUID: "the-task-guid",
SequenceID: 1,
Name: "task-1",
State: constant.TaskSucceeded,
CreatedAt: "2016-11-07T05:59:01Z",
Command: "some-command",
}

Expect(task).To(Equal(expectedTask))
Expect(warnings).To(ConsistOf("warning"))
})
})

When("the cloud controller returns errors and warnings", func() {
BeforeEach(func() {
response := `{
"errors": [
{
"code": 10008,
"detail": "The request is semantically invalid: command presence",
"title": "CF-UnprocessableEntity"
},
{
"code": 10010,
"detail": "Task not found",
"title": "CF-ResourceNotFound"
}
]
}`
server.AppendHandlers(
CombineHandlers(
VerifyRequest(http.MethodGet, "/v3/tasks/the-task-guid"),
RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"warning"}}),
),
)
})

It("returns the errors and all warnings", func() {
_, warnings, err := client.GetTask("the-task-guid")

Expect(err).To(MatchError(ccerror.MultiError{
ResponseCode: http.StatusTeapot,
Errors: []ccerror.V3Error{
{
Code: 10008,
Detail: "The request is semantically invalid: command presence",
Title: "CF-UnprocessableEntity",
},
{
Code: 10010,
Detail: "Task not found",
Title: "CF-ResourceNotFound",
},
},
}))
Expect(warnings).To(ConsistOf("warning"))
})
})
})
})
1 change: 1 addition & 0 deletions command/v7/actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type Actor interface {
PollPackage(pkg resources.Package) (resources.Package, v7action.Warnings, error)
PollStart(app resources.Application, noWait bool, handleProcessStats func(string)) (v7action.Warnings, error)
PollStartForRolling(app resources.Application, deploymentGUID string, noWait bool, handleProcessStats func(string)) (v7action.Warnings, error)
PollTask(task resources.Task) (resources.Task, v7action.Warnings, error)
PollUploadBuildpackJob(jobURL ccv3.JobURL) (v7action.Warnings, error)
PrepareBuildpackBits(inputPath string, tmpDirPath string, downloader v7action.Downloader) (string, error)
PurgeServiceOfferingByNameAndBroker(serviceOfferingName, serviceBrokerName string) (v7action.Warnings, error)
Expand Down
Loading