Skip to content

Commit

Permalink
Encapsulate the code of passedDataWhenCreate,add PipelineTaskExecutor…
Browse files Browse the repository at this point in the history
…Name type and unit test (erda-project#978)
  • Loading branch information
kakj-go authored Jul 14, 2021
1 parent 9069799 commit 364c3bb
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 56 deletions.
2 changes: 1 addition & 1 deletion modules/pipeline/dbclient/op_pipeline_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var defaultAPITestActionExecutor = spec.PipelineConfig{
Type: spec.PipelineConfigTypeActionExecutor,
Value: spec.ActionExecutorConfig{
Kind: string(spec.PipelineTaskExecutorKindAPITest),
Name: spec.PipelineTaskExecutorNameAPITestDefault,
Name: spec.PipelineTaskExecutorNameAPITestDefault.String(),
Options: nil,
},
}
Expand Down
2 changes: 1 addition & 1 deletion modules/pipeline/pipengine/reconciler/gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func (r *Reconciler) gcNamespace(namespace string, subKeys ...string) error {
}

// group tasks by executorName
groupedTasks := make(map[string][]*spec.PipelineTask) // key: executorName
groupedTasks := make(map[spec.PipelineTaskExecutorName][]*spec.PipelineTask) // key: executorName
for _, affectedPipelineID := range affectedPipelineIDs {
dbTasks, _, err := r.dbClient.GetPipelineTasksIncludeArchive(affectedPipelineID)
if err != nil {
Expand Down
42 changes: 6 additions & 36 deletions modules/pipeline/services/pipelinesvc/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ import (
"github.com/erda-project/erda/modules/pipeline/dbclient"
"github.com/erda-project/erda/modules/pipeline/events"
"github.com/erda-project/erda/modules/pipeline/services/apierrors"
"github.com/erda-project/erda/modules/pipeline/services/extmarketsvc"
"github.com/erda-project/erda/modules/pipeline/spec"
"github.com/erda-project/erda/pkg/discover"
"github.com/erda-project/erda/pkg/parser/diceyml"
"github.com/erda-project/erda/pkg/parser/pipelineyml"
"github.com/erda-project/erda/pkg/strutil"
)
Expand Down Expand Up @@ -189,21 +187,14 @@ func (s *PipelineSvc) makePipelineFromRequest(req *apistructs.PipelineCreateRequ
return p, nil
}

// passedDataWhenCreate stores data passed recursively when create graph.
type passedDataWhenCreate struct {
actionJobDefines map[string]*diceyml.Job
}

// createPipelineGraph recursively create pipeline graph.
// passedData stores data passed recursively.
func (s *PipelineSvc) createPipelineGraph(p *spec.Pipeline, passedDataOpt ...passedDataWhenCreate) (err error) {
var passedData passedDataWhenCreate
if len(passedDataOpt) > 0 {
passedData = passedDataOpt[0]
}
if passedData.actionJobDefines == nil {
passedData.actionJobDefines = make(map[string]*diceyml.Job)
}
passedData.initData(s.extMarketSvc)

// tx
txSession := s.dbClient.NewSession()
Expand Down Expand Up @@ -238,36 +229,15 @@ func (s *PipelineSvc) createPipelineGraph(p *spec.Pipeline, passedDataOpt ...pas
return apierrors.ErrParsePipelineYml.InternalError(err)
}

lastSuccessTaskMap, _, err := s.dbClient.ParseRerunFailedDetail(p.Extra.RerunFailedDetail)
if err != nil {
return apierrors.ErrCreatePipelineGraph.InternalError(err)
// search and cache action define and spec
if err := passedData.putPassedDataByPipelineYml(pipelineYml); err != nil {
return err
}

// batch search extensions
var extItems []string
for _, stage := range pipelineYml.Spec().Stages {
for _, typedAction := range stage.Actions {
for _, action := range typedAction {
if action.Type.IsSnippet() {
continue
}
extItem := extmarketsvc.MakeActionTypeVersion(action)
// extension already searched, skip
if _, ok := passedData.actionJobDefines[extItem]; ok {
continue
}
extItems = append(extItems, extmarketsvc.MakeActionTypeVersion(action))
}
}
}
extItems = strutil.DedupSlice(extItems, true)
actionJobDefines, _, err := s.extMarketSvc.SearchActions(extItems)
lastSuccessTaskMap, _, err := s.dbClient.ParseRerunFailedDetail(p.Extra.RerunFailedDetail)
if err != nil {
return apierrors.ErrCreatePipelineGraph.InternalError(err)
}
for extItem, actionJobDefine := range actionJobDefines {
passedData.actionJobDefines[extItem] = actionJobDefine
}

var snippetTasks []*spec.PipelineTask
var allStagedTasks [][]*spec.PipelineTask
Expand Down Expand Up @@ -303,7 +273,7 @@ func (s *PipelineSvc) createPipelineGraph(p *spec.Pipeline, passedDataOpt ...pas
}
snippetTasks = append(snippetTasks, pt)
default: // 生成普通任务
pt, err = s.makeNormalPipelineTask(p, ps, action, passedData.actionJobDefines[extmarketsvc.MakeActionTypeVersion(action)])
pt, err = s.makeNormalPipelineTask(p, ps, action, passedData)
if err != nil {
return apierrors.ErrCreatePipelineTask.InternalError(err)
}
Expand Down
7 changes: 5 additions & 2 deletions modules/pipeline/services/pipelinesvc/create_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ import (
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/modules/pipeline/conf"
"github.com/erda-project/erda/modules/pipeline/services/apierrors"
"github.com/erda-project/erda/modules/pipeline/services/extmarketsvc"
"github.com/erda-project/erda/modules/pipeline/spec"
"github.com/erda-project/erda/pkg/numeral"
"github.com/erda-project/erda/pkg/parser/diceyml"
"github.com/erda-project/erda/pkg/parser/pipelineyml"
)

// makeNormalPipelineTask 生成普通流水线任务
func (s *PipelineSvc) makeNormalPipelineTask(p *spec.Pipeline, ps *spec.PipelineStage, action *pipelineyml.Action, actionJobDefine *diceyml.Job) (*spec.PipelineTask, error) {
func (s *PipelineSvc) makeNormalPipelineTask(p *spec.Pipeline, ps *spec.PipelineStage, action *pipelineyml.Action, passedData passedDataWhenCreate) (*spec.PipelineTask, error) {
var actionJobDefine = passedData.getActionJobDefine(extmarketsvc.MakeActionTypeVersion(action))

task := &spec.PipelineTask{}
task.PipelineID = p.ID
task.StageID = ps.ID
Expand Down Expand Up @@ -175,7 +178,7 @@ func (s *PipelineSvc) calculateTaskRunAfter(action *pipelineyml.Action) []string
}

// judgeTaskExecutor judge task executor by action info
func (s *PipelineSvc) judgeTaskExecutor(action *pipelineyml.Action) (spec.PipelineTaskExecutorKind, string, error) {
func (s *PipelineSvc) judgeTaskExecutor(action *pipelineyml.Action) (spec.PipelineTaskExecutorKind, spec.PipelineTaskExecutorName, error) {
if action.Type == apistructs.ActionTypeAPITest {
return spec.PipelineTaskExecutorKindAPITest, spec.PipelineTaskExecutorNameAPITestDefault, nil
}
Expand Down
100 changes: 100 additions & 0 deletions modules/pipeline/services/pipelinesvc/passedDataWhenCreate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package pipelinesvc

import (
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/modules/pipeline/services/apierrors"
"github.com/erda-project/erda/modules/pipeline/services/extmarketsvc"
"github.com/erda-project/erda/pkg/parser/diceyml"
"github.com/erda-project/erda/pkg/parser/pipelineyml"
"github.com/erda-project/erda/pkg/strutil"
)

// passedDataWhenCreate stores data passed recursively when create graph.
type passedDataWhenCreate struct {
extMarketSvc *extmarketsvc.ExtMarketSvc
actionJobDefines map[string]*diceyml.Job
actionJobSpecs map[string]*apistructs.ActionSpec
}

func (that *passedDataWhenCreate) getActionJobDefine(actionTypeVersion string) *diceyml.Job {
if that == nil {
return nil
}
if that.actionJobDefines == nil {
return nil
}
return that.actionJobDefines[actionTypeVersion]
}

func (that *passedDataWhenCreate) getActionJobSpecs(actionTypeVersion string) *apistructs.ActionSpec {
if that == nil {
return nil
}
if that.actionJobDefines == nil {
return nil
}
return that.actionJobSpecs[actionTypeVersion]
}

func (that *passedDataWhenCreate) initData(extMarketSvc *extmarketsvc.ExtMarketSvc) {
if that == nil {
return
}

if that.actionJobDefines == nil {
that.actionJobDefines = make(map[string]*diceyml.Job)
}
if that.actionJobSpecs == nil {
that.actionJobSpecs = make(map[string]*apistructs.ActionSpec)
}
that.extMarketSvc = extMarketSvc
}

func (that *passedDataWhenCreate) putPassedDataByPipelineYml(pipelineYml *pipelineyml.PipelineYml) error {
if that == nil {
return nil
}
// batch search extensions
var extItems []string
for _, stage := range pipelineYml.Spec().Stages {
for _, typedAction := range stage.Actions {
for _, action := range typedAction {
if action.Type.IsSnippet() {
continue
}
extItem := extmarketsvc.MakeActionTypeVersion(action)
// extension already searched, skip
if _, ok := that.actionJobDefines[extItem]; ok {
continue
}
extItems = append(extItems, extmarketsvc.MakeActionTypeVersion(action))
}
}
}

extItems = strutil.DedupSlice(extItems, true)
actionJobDefines, actionJobSpecs, err := that.extMarketSvc.SearchActions(extItems)
if err != nil {
return apierrors.ErrCreatePipelineGraph.InternalError(err)
}
for extItem, actionJobDefine := range actionJobDefines {
that.actionJobDefines[extItem] = actionJobDefine
}
for extItem, actionJobSpec := range actionJobSpecs {
that.actionJobSpecs[extItem] = actionJobSpec
}
return nil
}
151 changes: 151 additions & 0 deletions modules/pipeline/services/pipelinesvc/passedDataWhenCreate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package pipelinesvc

import (
"fmt"
"reflect"
"testing"

"bou.ke/monkey"
"github.com/alecthomas/assert"

"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/modules/pipeline/services/extmarketsvc"
"github.com/erda-project/erda/pkg/parser/diceyml"
"github.com/erda-project/erda/pkg/parser/pipelineyml"
)

func Test_passedDataWhenCreate_putPassedDataByPipelineYml(t *testing.T) {
type fields struct {
extMarketSvc *extmarketsvc.ExtMarketSvc
actionJobDefines map[string]*diceyml.Job
actionJobSpecs map[string]*apistructs.ActionSpec
}
type args struct {
pipelineYml string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
wantActionJobDefines []string
wantActionJobSpecs []string
}{
{
name: "normal",
fields: fields{
actionJobSpecs: map[string]*apistructs.ActionSpec{},
actionJobDefines: map[string]*diceyml.Job{},
},
args: args{
pipelineYml: "version: \"1.1\"\nstages:\n - stage:\n - git-checkout:\n alias: git-checkout\n description: 代码仓库克隆\n - stage:\n - java:\n alias: java-demo\n description: 针对 java 工程的编译打包任务,产出可运行镜像\n version: \"1.0\"\n params:\n build_type: maven\n container_type: spring-boot\n jdk_version: \"11\"\n target: ./target/docker-java-app-example.jar\n workdir: ${git-checkout}\n - stage:\n - release:\n alias: release\n description: 用于打包完成时,向dicehub 提交完整可部署的dice.yml。用户若没在pipeline.yml里定义该action,CI会自动在pipeline.yml里插入该action\n params:\n dice_yml: ${git-checkout}/dice.yml\n image:\n java-demo: ${java-demo:OUTPUT:image}\n - stage:\n - dice:\n alias: dice\n description: 用于 dice 平台部署应用服务\n params:\n release_id: ${release:OUTPUT:releaseID}\n",
},
wantActionJobDefines: []string{"[email protected]", "[email protected]", "[email protected]", "[email protected]"},
wantActionJobSpecs: []string{"[email protected]", "[email protected]", "[email protected]", "[email protected]"},
},
{
name: "want_error",
fields: fields{
actionJobSpecs: map[string]*apistructs.ActionSpec{},
actionJobDefines: map[string]*diceyml.Job{},
},
args: args{
pipelineYml: "version: \"1.1\"\nstages:\n - stage:\n - git-checkout:\n alias: git-checkout\n description: 代码仓库克隆\n - stage:\n - java:\n alias: java-demo\n description: 针对 java 工程的编译打包任务,产出可运行镜像\n version: \"1.0\"\n params:\n build_type: maven\n container_type: spring-boot\n jdk_version: \"11\"\n target: ./target/docker-java-app-example.jar\n workdir: ${git-checkout}\n - stage:\n - release:\n alias: release\n description: 用于打包完成时,向dicehub 提交完整可部署的dice.yml。用户若没在pipeline.yml里定义该action,CI会自动在pipeline.yml里插入该action\n params:\n dice_yml: ${git-checkout}/dice.yml\n image:\n java-demo: ${java-demo:OUTPUT:image}\n - stage:\n - dice:\n alias: dice\n description: 用于 dice 平台部署应用服务\n params:\n release_id: ${release:OUTPUT:releaseID}\n",
},
wantActionJobDefines: []string{},
wantActionJobSpecs: []string{},
wantErr: true,
},
{
name: "api-test_1.0",
fields: fields{
actionJobSpecs: map[string]*apistructs.ActionSpec{},
actionJobDefines: map[string]*diceyml.Job{},
},
args: args{
pipelineYml: "version: '1.1'\nstages:\n - - alias: api-test\n type: api-test\n description: 执行单个接口测试。上层可以通过 pipeline.yml 编排一组接口测试的执行顺序。\n version: '1.0'\n params:\n body:\n type: none\n method: GET\n url: /api/user\n resources: {}\n displayName: 接口测试\n logoUrl: >-\n //terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/10/24195384-07b7-4203-93e1-666373639af4.png\n - - alias: api-test1\n type: api-test\n description: 执行单个接口测试。上层可以通过 pipeline.yml 编排一组接口测试的执行顺序。\n version: '1.0'\n params:\n body:\n type: none\n method: GET\n url: /api/user\n resources: {}\n displayName: 接口测试\n logoUrl: >-\n //terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/10/24195384-07b7-4203-93e1-666373639af4.png\nflatActions: null\nlifecycle: null\n",
},
wantActionJobDefines: []string{"[email protected]"},
wantActionJobSpecs: []string{"[email protected]"},
},
{
name: "api-test_1.0_with_2.0",
fields: fields{
actionJobSpecs: map[string]*apistructs.ActionSpec{},
actionJobDefines: map[string]*diceyml.Job{},
},
args: args{
pipelineYml: "version: '1.1'\nstages:\n - - alias: api-test\n type: api-test\n description: 执行单个接口测试。上层可以通过 pipeline.yml 编排一组接口测试的执行顺序。\n version: '2.0'\n params:\n body:\n type: none\n method: GET\n url: /api/user\n resources: {}\n displayName: 接口测试\n logoUrl: >-\n //terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/10/24195384-07b7-4203-93e1-666373639af4.png\n - - alias: api-test1\n type: api-test\n description: 执行单个接口测试。上层可以通过 pipeline.yml 编排一组接口测试的执行顺序。\n version: '1.0'\n params:\n body:\n type: none\n method: GET\n url: /api/user\n resources: {}\n displayName: 接口测试\n logoUrl: >-\n //terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/10/24195384-07b7-4203-93e1-666373639af4.png\nflatActions: null\nlifecycle: null\n",
},
wantActionJobDefines: []string{"[email protected]", "[email protected]"},
wantActionJobSpecs: []string{"[email protected]", "[email protected]"},
},
{
name: "snippet",
fields: fields{
actionJobSpecs: map[string]*apistructs.ActionSpec{},
actionJobDefines: map[string]*diceyml.Job{},
},
args: args{
pipelineYml: "version: '1.1'\nstages:\n - - alias: api-test\n type: api-test\n description: 执行单个接口测试。上层可以通过 pipeline.yml 编排一组接口测试的执行顺序。\n version: '1.0'\n params:\n body:\n type: none\n method: GET\n url: /api/user\n resources: {}\n displayName: 接口测试\n logoUrl: >-\n //terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/10/24195384-07b7-4203-93e1-666373639af4.png\n - - alias: snippet\n type: snippet\n description: 嵌套流水线可以声明嵌套的其他 pipeline.yml\n resources: {}\n displayName: 嵌套流水线\n logoUrl: >-\n http://terminus-paas.oss-cn-hangzhou.aliyuncs.com/paas-doc/2020/10/22/410935c6-e399-463a-b87b-0b774240d12e.png\nflatActions: null\nlifecycle: null\n",
},
wantActionJobDefines: []string{"[email protected]"},
wantActionJobSpecs: []string{"[email protected]"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
that := &passedDataWhenCreate{
actionJobDefines: tt.fields.actionJobDefines,
actionJobSpecs: tt.fields.actionJobSpecs,
}
yml, err := pipelineyml.New([]byte(tt.args.pipelineYml))
assert.NoError(t, err)

var svc *extmarketsvc.ExtMarketSvc
var guard *monkey.PatchGuard
guard = monkey.PatchInstanceMethod(reflect.TypeOf(svc), "SearchActions", func(svc *extmarketsvc.ExtMarketSvc, items []string, ops ...extmarketsvc.OpOption) (map[string]*diceyml.Job, map[string]*apistructs.ActionSpec, error) {
guard.Unpatch()
defer guard.Restore()
actionJobMap := make(map[string]*diceyml.Job)
actionSpecMap := make(map[string]*apistructs.ActionSpec)
for _, item := range items {
actionJobMap[item] = &diceyml.Job{}
actionSpecMap[item] = &apistructs.ActionSpec{}
}
if tt.wantErr {
return nil, nil, fmt.Errorf("want error")
}
return actionJobMap, actionSpecMap, nil
})
that.extMarketSvc = svc

if err := that.putPassedDataByPipelineYml(yml); (err != nil) != tt.wantErr {
t.Errorf("putPassedDataByPipelineYml() error = %v, wantErr %v", err, tt.wantErr)
}

assert.Equal(t, len(that.actionJobDefines), len(tt.wantActionJobDefines), "wantActionJobDefines")
assert.Equal(t, len(that.actionJobSpecs), len(tt.wantActionJobSpecs), "wantActionJobSpecs")

for _, v := range tt.wantActionJobSpecs {
assert.NotEmpty(t, that.actionJobSpecs[v])
}
for _, v := range tt.wantActionJobDefines {
assert.NotEmpty(t, that.actionJobDefines[v])
}
})
}
}
Loading

0 comments on commit 364c3bb

Please sign in to comment.