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

Workflows steps replays on error #754

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## UNRELEASED

### FEATURES

* Workflows steps replays on error ([GH-753](https://github.com/ystia/yorc/issues/753))

### ENHANCEMENTS

* Add basic support for ssh on Windows ([GH-751](https://github.com/ystia/yorc/issues/751))
Expand Down
39 changes: 4 additions & 35 deletions commands/deployments/tasks/dep_fix_task_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,9 @@
package tasks

import (
"bytes"
"encoding/json"
"log"
"net/http"
"path"
"strings"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/ystia/yorc/v4/commands/deployments"
"github.com/ystia/yorc/v4/commands/httputil"
"github.com/ystia/yorc/v4/tasks"
)

Expand All @@ -35,40 +26,18 @@ func init() {
}

var updateTaskStepCmd = &cobra.Command{
Use: "fix <DeploymentId> <TaskId> <StepName>",
Short: "Fix a deployment task step on error",
Deprecated: "use 'yorc deployments tasks update-step-state' instead.",
Use: "fix <DeploymentId> <TaskId> <StepName>",
Short: "Fix a deployment task step on error",
Long: `Fix a task step specifying the deployment id, the task id and the step name.
The task step must be on error to be fixed.`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 3 {
return errors.Errorf("Expecting a deployment id, a task id and a step name(got %d parameters)", len(args))
}
client, err := httputil.GetClient(deployments.ClientConfig)
if err != nil {
httputil.ErrExit(err)
}

// The task step status is set to "done"
step := &tasks.TaskStep{Name: args[2], Status: strings.ToLower(tasks.TaskStepStatusDONE.String())}
body, err := json.Marshal(step)
if err != nil {
log.Panic(err)
}

url := path.Join("/deployments", args[0], "tasks", args[1], "steps", args[2])
request, err := client.NewRequest("PUT", url, bytes.NewBuffer(body))
if err != nil {
httputil.ErrExit(err)
}
updateTaskStepState(args[0], args[1], args[2], tasks.TaskStepStatusDONE.String())

request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
httputil.ErrExit(err)
}
defer response.Body.Close()
ids := args[0] + "/" + args[1]
httputil.HandleHTTPStatusCode(response, ids, "deployment/task/step", http.StatusOK)
return nil
},
}
84 changes: 84 additions & 0 deletions commands/deployments/tasks/dep_update_task_step_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2018 Bull S.A.S. Atos Technologies - Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois, France.
loicalbertin marked this conversation as resolved.
Show resolved Hide resolved
//
// 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 tasks

import (
"bytes"
"encoding/json"
"net/http"
"path"
"strings"

"github.com/spf13/cobra"

"github.com/ystia/yorc/v4/commands/deployments"
"github.com/ystia/yorc/v4/commands/httputil"
"github.com/ystia/yorc/v4/tasks"
)

func init() {
tasksCmd.AddCommand(updateTaskStepStateCmd)
}

func updateTaskStepState(deploymentID, taskID, stepName, statusStr string) {
client, err := httputil.GetClient(deployments.ClientConfig)
if err != nil {
httputil.ErrExit(err)
}

statusStr = strings.ToLower(statusStr)

// Safety check
_, err = tasks.ParseTaskStepStatus(statusStr)
if err != nil {
httputil.ErrExit(err)
}

// The task step status is set to "done"
loicalbertin marked this conversation as resolved.
Show resolved Hide resolved
loicalbertin marked this conversation as resolved.
Show resolved Hide resolved
step := &tasks.TaskStep{Status: statusStr}
body, err := json.Marshal(step)
if err != nil {
httputil.ErrExit(err)
}

url := path.Join("/deployments", deploymentID, "tasks", taskID, "steps", stepName)
request, err := client.NewRequest("PUT", url, bytes.NewBuffer(body))
if err != nil {
httputil.ErrExit(err)
}

request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
httputil.ErrExit(err)
}
defer response.Body.Close()
ids := deploymentID + "/" + taskID
httputil.HandleHTTPStatusCode(response, ids, "deployment/task/step", http.StatusOK)
}

var updateTaskStepStateCmd = &cobra.Command{
Use: "update-step-state <DeploymentId> <TaskId> <StepName> <state>",
Aliases: []string{"update-state", "update-step", "uss", "us"},
Short: "Update a deployment task step on error",
Args: cobra.ExactArgs(4),
Long: `Update a task step specifying the deployment id, the task id, the step name and a valid state for the step.
The task step must be on error to be update.
This allows to bypass or retry errors happening in a workflow. So accepted state are either DONE or INITIAL.
Use 'yorc deployment tasks resume' command to restart a workflow after having updated it's errors`,
Run: func(cmd *cobra.Command, args []string) {
updateTaskStepState(args[0], args[1], args[2], args[3])
},
}
60 changes: 60 additions & 0 deletions commands/deployments/tasks/dep_update_task_step_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2018 Bull S.A.S. Atos Technologies - Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois, France.
loicalbertin marked this conversation as resolved.
Show resolved Hide resolved
//
// 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 tasks

import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/ystia/yorc/v4/commands/deployments"
"github.com/ystia/yorc/v4/config"
)

func Test_updateTaskStepState(t *testing.T) {

s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "fail") {
rw.WriteHeader(http.StatusBadRequest)
rw.Write([]byte("Some user error"))
return
}
rw.WriteHeader(http.StatusOK)
}))
defer s.Close()

deployments.ClientConfig = config.Client{
YorcAPI: strings.Replace(s.URL, "http://", "", 1),
}

type args struct {
deploymentID string
taskID string
stepName string
statusStr string
}
tests := []struct {
name string
args args
}{
{"TestOK", args{"depOK", "taskID", "step", "initial"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
updateTaskStepState(tt.args.deploymentID, tt.args.taskID, tt.args.stepName, tt.args.statusStr)
})
}
}
1 change: 1 addition & 0 deletions rest/dep_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ func (s *Server) updateTaskStepStatusHandler(w http.ResponseWriter, r *http.Requ
if err != nil {
log.Panic(err)
}
step.Name = stepID

// Check taskStep status change
allowed, err := tasks.CheckTaskStepStatusChange(stepBefore.Status, step.Status)
Expand Down
12 changes: 10 additions & 2 deletions rest/http_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,11 +605,19 @@ Content-Type: application/json

### Update a task step status <a name="task-step-update"></a>

Update a task step status for given deployment and task. For the moment, only step status change from "ERROR" to "DONE" is allowed otherwise an HTTP 401
(Forbidden) error is returned.
Update a task step status for given deployment and task. For the moment, only step status change from "ERROR"
to "DONE" or "INITIAL" is allowed otherwise an HTTP 401 (Forbidden) error is returned.

`PUT /deployments/<deployment_id>/tasks/<taskId>/steps/<stepId>`

**Request body**:

```json
{
"status": "step_status"
}
```

**Response**:

```HTTP
Expand Down
8 changes: 8 additions & 0 deletions tasks/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ func (c *Collector) ResumeTask(ctx context.Context, taskID string) error {
Key: path.Join(taskPath, "errorMessage"),
Value: []byte(""),
},
&api.KVTxnOp{
Verb: api.KVDelete,
Key: path.Join(taskPath, ".errorFlag"),
},
&api.KVTxnOp{
Verb: api.KVDelete,
Key: path.Join(taskPath, ".cancelFlag"),
},
}
// Set deployment status to initial for some task types
switch taskType {
Expand Down
2 changes: 1 addition & 1 deletion tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ func CheckTaskStepStatusChange(before, after string) (bool, error) {
return false, err
}

if stBefore != TaskStepStatusERROR || stAfter != TaskStepStatusDONE {
if stBefore != TaskStepStatusERROR || (stAfter != TaskStepStatusDONE && stAfter != TaskStepStatusINITIAL) {
return false, nil
}
return true, nil
Expand Down