From 3b9852c113c4044954ce89f21b6682dd8113d2db Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com> Date: Fri, 30 Nov 2018 13:03:45 +0100 Subject: [PATCH 01/17] feat(ui): display spawn info Signed-off-by: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com> --- ui/src/app/views/workflow/run/node/pipeline/pipeline.html | 4 ++-- .../run/node/pipeline/spawninfo/spawninfo.component.ts | 7 ++++--- .../workflow/run/node/pipeline/spawninfo/spawninfo.html | 4 ++-- ui/src/assets/i18n/en.json | 1 + ui/src/assets/i18n/fr.json | 1 + 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html index 43eaeda06c..32dcf253c9 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html +++ b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html @@ -55,12 +55,12 @@ <div class="log animated fadeIn" *ngIf="selectedRunJob && mapStepStatus"> <ul> <li> - <app-workflow-rin-job-spawn-info + <app-workflow-run-job-spawn-info [spawnInfos]="selectedRunJob.spawninfos" [variables]="selectedRunJob.parameters" [job]="selectedRunJob.job" [(displayServicesLogs)]="displayServiceLogs"> - </app-workflow-rin-job-spawn-info> + </app-workflow-run-job-spawn-info> </li> <ng-container *ngIf="!displayServiceLogs"> <li *ngFor="let step of selectedRunJob.job.action.actions; let i = index"> diff --git a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts index e98f5126e9..abc809f389 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts +++ b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts @@ -1,4 +1,5 @@ import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; import * as AU from 'ansi_up'; import {Job} from '../../../../../../model/job.model'; import {Parameter} from '../../../../../../model/parameter.model'; @@ -6,7 +7,7 @@ import {SpawnInfo} from '../../../../../../model/pipeline.model'; import {JobVariableComponent} from '../../../../../run/workflow/variables/job.variables.component'; @Component({ - selector: 'app-workflow-rin-job-spawn-info', + selector: 'app-workflow-run-job-spawn-info', templateUrl: './spawninfo.html', styleUrls: ['./spawninfo.scss'] }) @@ -42,7 +43,7 @@ export class WorkflowRunJobSpawnInfoComponent { _displayServiceLogs: boolean; ansi_up = new AU.default; - constructor() { } + constructor(private _translate: TranslateService) {} refreshDisplayServiceLogsLink() { if (this.job && this.job.action && Array.isArray(this.job.action.requirements)) { @@ -64,7 +65,7 @@ export class WorkflowRunJobSpawnInfoComponent { if (msg !== '') { return this.ansi_up.ansi_to_html(msg); } - return ''; + return this._translate.instant('job_spawn_no_information'); } openVariableModal(event: Event): void { diff --git a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html index 2bf700dc35..7a56027ffd 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html +++ b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html @@ -1,4 +1,4 @@ -<div class="spawn" *ngIf="spawnInfos"> +<div class="spawn"> <div class="header pointing" (click)="toggle()"> <div class="status"> <i class="icon heartbeat"></i> @@ -20,7 +20,7 @@ </a> </div> </div> - <div class="spawnInfos" *ngIf="spawnInfos" [hidden]="!show"> + <div class="spawnInfos" [hidden]="!show"> <pre [innerHTML]="getSpawnInfos()"> </pre> </div> diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index cde77a6a5d..9803ec6fdd 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -418,6 +418,7 @@ "job_delete": "Delete job", "job_save": "Save job", "job_spawn_title": "Information", + "job_spawn_no_information": "No information for now...", "monitoring": "Monitoring", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 23cba201d0..883bfbd411 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -418,6 +418,7 @@ "job_delete": "Supprimer le job", "job_save": "Sauvegarder le job", "job_spawn_title": "Informations", + "job_spawn_no_information": "Pas d'information pour l'instant...", "monitoring": "Monitoring", From fd7715aa41918a1b5f4b47aca567a507101b2e83 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 14:43:06 +0100 Subject: [PATCH 02/17] wip Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 3 +-- engine/api/workflow/dao_node_run.go | 20 ++++++++++++-------- engine/api/workflow/dao_run.go | 3 +-- engine/api/workflow/execute_node_job_run.go | 3 +-- engine/api/workflow/execute_node_run.go | 11 ++++++++++- sdk/messages.go | 1 + 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index 755f05118a..f646a8ff44 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -7,7 +7,6 @@ import ( "time" "github.com/go-gorp/gorp" - "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/sdk" "github.com/ovh/cds/sdk/log" @@ -52,6 +51,6 @@ func insertNodeRunJobInfo(db gorp.SqlExecutor, info *sdk.WorkflowNodeJobRunInfo) return fmt.Errorf("insertNodeRunJobInfo> Unable to insert into workflow_node_run_job_info id = %d", info.WorkflowNodeJobRunID) } - log.Debug("insertNodeRunJobInfo> on node run: %d (%d)", info.ID, info.WorkflowNodeJobRunID) + log.Debug("insertNodeRunJobInfo> on node run: %v (%d)", info.SpawnInfos, info.WorkflowNodeJobRunID) return nil } diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 2c735d67a5..68d117dc37 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -9,14 +9,13 @@ import ( "github.com/go-gorp/gorp" "github.com/lib/pq" - "github.com/ovh/venom" - "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/engine/api/observability" "github.com/ovh/cds/engine/api/repositoriesmanager" "github.com/ovh/cds/sdk" "github.com/ovh/cds/sdk/log" + "github.com/ovh/venom" ) const nodeRunFields string = ` @@ -81,7 +80,7 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i return nil, sdk.WrapError(err, "Unable to load workflow_node_run proj=%s, workflow=%s, num=%d, node=%d", projectkey, workflowname, number, id) } - r, err := fromDBNodeRun(rr, loadOpts) + r, err := fromDBNodeRun(db, rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -140,7 +139,7 @@ func LoadNodeRunByNodeJobID(db gorp.SqlExecutor, nodeJobRunID int64, loadOpts Lo return nil, sdk.WrapError(err, "Unable to load workflow_node_run node_job_id=%d", nodeJobRunID) } - r, err := fromDBNodeRun(rr, loadOpts) + r, err := fromDBNodeRun(db, rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -185,7 +184,7 @@ func LoadAndLockNodeRunByID(ctx context.Context, db gorp.SqlExecutor, id int64, } return nil, sdk.WrapError(err, "Unable to load workflow_node_run node=%d", id) } - return fromDBNodeRun(rr, LoadRunOptions{}) + return fromDBNodeRun(db, rr, LoadRunOptions{}) } //LoadNodeRunByID load a specific node run on a workflow @@ -205,7 +204,7 @@ func LoadNodeRunByID(db gorp.SqlExecutor, id int64, loadOpts LoadRunOptions) (*s return nil, sdk.WrapError(err, "Unable to load workflow_node_run node=%d", id) } - r, err := fromDBNodeRun(rr, loadOpts) + r, err := fromDBNodeRun(db, rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -248,7 +247,7 @@ func nodeRunExist(db gorp.SqlExecutor, nodeID, num int64, subnumber int) (bool, return nb > 0, err } -func fromDBNodeRun(rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { +func fromDBNodeRun(db gorp.SqlExecutor, rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { r := new(sdk.WorkflowNodeRun) if rr.WorkflowID.Valid { r.WorkflowID = rr.WorkflowID.Int64 @@ -300,6 +299,11 @@ func fromDBNodeRun(rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error if rj.Status == sdk.StatusWaiting.String() { rj.QueuedSeconds = time.Now().Unix() - rj.Queued.Unix() } + spawnInfos, err := loadNodeRunJobInfo(db, rj.ID) + if err != nil { + return nil, sdk.WrapError(err, "unable to load spawn infos for runJob: %d", rj.ID) + } + rj.SpawnInfos = spawnInfos } } @@ -716,7 +720,7 @@ func PreviousNodeRun(db gorp.SqlExecutor, nr sdk.WorkflowNodeRun, n sdk.Workflow if err := db.SelectOne(&rr, query, workflowID, n.Name, nr.VCSBranch, nr.VCSTag, nr.Number, nr.ID); err != nil { return nodeRun, sdk.WrapError(err, "Cannot load previous run on workflow %d node %s nr.VCSBranch:%s nr.VCSTag:%s nr.Number:%d nr.ID:%d ", workflowID, n.Name, nr.VCSBranch, nr.VCSTag, nr.Number, nr.ID) } - pNodeRun, errF := fromDBNodeRun(rr, LoadRunOptions{}) + pNodeRun, errF := fromDBNodeRun(db, rr, LoadRunOptions{}) if errF != nil { return nodeRun, sdk.WrapError(errF, "PreviousNodeRun> Cannot read node run") } diff --git a/engine/api/workflow/dao_run.go b/engine/api/workflow/dao_run.go index 8d3f2ec450..23cea95fdc 100644 --- a/engine/api/workflow/dao_run.go +++ b/engine/api/workflow/dao_run.go @@ -10,7 +10,6 @@ import ( "time" "github.com/go-gorp/gorp" - "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/engine/api/observability" "github.com/ovh/cds/sdk" @@ -724,7 +723,7 @@ func syncNodeRuns(db gorp.SqlExecutor, wr *sdk.WorkflowRun, loadOpts LoadRunOpti } for _, n := range dbNodeRuns { - wnr, err := fromDBNodeRun(n, loadOpts) + wnr, err := fromDBNodeRun(db, n, loadOpts) if err != nil { return err } diff --git a/engine/api/workflow/execute_node_job_run.go b/engine/api/workflow/execute_node_job_run.go index 0ec12ab159..29af29002d 100644 --- a/engine/api/workflow/execute_node_job_run.go +++ b/engine/api/workflow/execute_node_job_run.go @@ -11,7 +11,6 @@ import ( "github.com/go-gorp/gorp" "github.com/lib/pq" - "github.com/ovh/cds/engine/api/application" "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/environment" @@ -745,7 +744,7 @@ func RestartWorkflowNodeJob(ctx context.Context, db gorp.SqlExecutor, wNodeJob s return sdk.WrapError(errNR, "RestartWorkflowNodeJob> Cannot load node run") } - //Synchronise struct but not in db + //Synchronize struct but not in db sync, errS := SyncNodeRunRunJob(ctx, db, nodeRun, wNodeJob) if errS != nil { return sdk.WrapError(errS, "RestartWorkflowNodeJob> error on sync nodeJobRun") diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index b5fc7cae3b..67ce54940d 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -10,7 +10,6 @@ import ( "github.com/fsamin/go-dump" "github.com/go-gorp/gorp" - "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/group" "github.com/ovh/cds/engine/api/observability" @@ -426,6 +425,12 @@ func addJobsToQueue(ctx context.Context, db gorp.SqlExecutor, stage *sdk.Stage, Message: spawnInfos, RemoteTime: time.Now(), }} + } else { + wjob.SpawnInfos = []sdk.SpawnInfo{sdk.SpawnInfo{ + APITime: time.Now(), + Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoJobInQueue.ID}, + RemoteTime: time.Now(), + }} } //Insert in database @@ -436,6 +441,10 @@ func addJobsToQueue(ctx context.Context, db gorp.SqlExecutor, stage *sdk.Stage, } next() + if err := AddSpawnInfosNodeJobRun(db, wjob.ID, PrepareSpawnInfos(wjob.SpawnInfos)); err != nil { + return nil, sdk.WrapError(err, "Cannot save spawn info job %d", wjob.ID) + } + //Put the job run in database stage.RunJobs = append(stage.RunJobs, wjob) diff --git a/sdk/messages.go b/sdk/messages.go index 7e36df5f78..718678e36b 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -63,6 +63,7 @@ var ( MsgSpawnInfoHatcheryErrorSpawn = &Message{"MsgSpawnInfoHatcheryErrorSpawn", trad{FR: "Une erreur est survenue lorsque la Hatchery %s (%s) a démarré un worker avec le modèle %s après %s, err:%s", EN: "Error while Hatchery %s (%s) spawn worker with model %s after %s, err:%s"}, nil} MsgSpawnInfoHatcheryStartsSuccessfully = &Message{"MsgSpawnInfoHatcheryStartsSuccessfully", trad{FR: "La Hatchery %s (%s) a démarré le worker %s avec succès en %s", EN: "Hatchery %s (%s) spawn worker %s successfully in %s"}, nil} MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "Worker %s finished working on this job and took %s to work on the steps"}, nil} + MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "Le job a été pris mis en file d'attente", EN: "Job was taken in queue"}, nil} MsgSpawnInfoJobTaken = &Message{"MsgSpawnInfoJobTaken", trad{FR: "Le job %s a été pris par le worker %s", EN: "Job %s was taken by worker %s"}, nil} MsgSpawnInfoJobTakenWorkerVersion = &Message{"MsgSpawnInfoJobTakenWorkerVersion", trad{FR: "Worker %s version:%s os:%s arch:%s", EN: "Worker %s version:%s os:%s arch:%s"}, nil} MsgSpawnInfoWorkerForJob = &Message{"MsgSpawnInfoWorkerForJob", trad{FR: "Ce worker %s a été créé pour lancer ce job", EN: "This worker %s was created to take this action"}, nil} From 54d0112b5f3751a1057828a807f4bd87b9e5ce2d Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 14:44:23 +0100 Subject: [PATCH 03/17] wip Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/execute_node_run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 67ce54940d..9b1378c12f 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -428,7 +428,7 @@ func addJobsToQueue(ctx context.Context, db gorp.SqlExecutor, stage *sdk.Stage, } else { wjob.SpawnInfos = []sdk.SpawnInfo{sdk.SpawnInfo{ APITime: time.Now(), - Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoJobInQueue.ID}, + Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoJobInQueue.ID, Args: []interface{}{}}, RemoteTime: time.Now(), }} } From f2e9087c1ea474757a4f083262e891f868151528 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 14:45:40 +0100 Subject: [PATCH 04/17] wip Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- sdk/messages.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/messages.go b/sdk/messages.go index 718678e36b..e399805d52 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -126,6 +126,7 @@ var Messages = map[string]*Message{ MsgSpawnInfoHatcheryErrorSpawn.ID: MsgSpawnInfoHatcheryErrorSpawn, MsgSpawnInfoHatcheryStartsSuccessfully.ID: MsgSpawnInfoHatcheryStartsSuccessfully, MsgSpawnInfoWorkerEnd.ID: MsgSpawnInfoWorkerEnd, + MsgSpawnInfoJobInQueue.ID: MsgSpawnInfoJobInQueue, MsgSpawnInfoJobTaken.ID: MsgSpawnInfoJobTaken, MsgSpawnInfoJobTakenWorkerVersion.ID: MsgSpawnInfoJobTakenWorkerVersion, MsgSpawnInfoWorkerForJob.ID: MsgSpawnInfoWorkerForJob, From db7e5fd05bfffbf078c75ef1b0badf0a5993e3e3 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 14:47:32 +0100 Subject: [PATCH 05/17] wip Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- sdk/messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/messages.go b/sdk/messages.go index e399805d52..676eb823ab 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -63,7 +63,7 @@ var ( MsgSpawnInfoHatcheryErrorSpawn = &Message{"MsgSpawnInfoHatcheryErrorSpawn", trad{FR: "Une erreur est survenue lorsque la Hatchery %s (%s) a démarré un worker avec le modèle %s après %s, err:%s", EN: "Error while Hatchery %s (%s) spawn worker with model %s after %s, err:%s"}, nil} MsgSpawnInfoHatcheryStartsSuccessfully = &Message{"MsgSpawnInfoHatcheryStartsSuccessfully", trad{FR: "La Hatchery %s (%s) a démarré le worker %s avec succès en %s", EN: "Hatchery %s (%s) spawn worker %s successfully in %s"}, nil} MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "Worker %s finished working on this job and took %s to work on the steps"}, nil} - MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "Le job a été pris mis en file d'attente", EN: "Job was taken in queue"}, nil} + MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "Le job a été pris mis en file d'attente", EN: "Job was queued"}, nil} MsgSpawnInfoJobTaken = &Message{"MsgSpawnInfoJobTaken", trad{FR: "Le job %s a été pris par le worker %s", EN: "Job %s was taken by worker %s"}, nil} MsgSpawnInfoJobTakenWorkerVersion = &Message{"MsgSpawnInfoJobTakenWorkerVersion", trad{FR: "Worker %s version:%s os:%s arch:%s", EN: "Worker %s version:%s os:%s arch:%s"}, nil} MsgSpawnInfoWorkerForJob = &Message{"MsgSpawnInfoWorkerForJob", trad{FR: "Ce worker %s a été créé pour lancer ce job", EN: "This worker %s was created to take this action"}, nil} From 892bb87819b263749cc63fec04cda6ccd3836e7b Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 14:56:06 +0100 Subject: [PATCH 06/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_run.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 68d117dc37..a479cfc678 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -9,13 +9,14 @@ import ( "github.com/go-gorp/gorp" "github.com/lib/pq" + "github.com/ovh/venom" + "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/engine/api/observability" "github.com/ovh/cds/engine/api/repositoriesmanager" "github.com/ovh/cds/sdk" "github.com/ovh/cds/sdk/log" - "github.com/ovh/venom" ) const nodeRunFields string = ` From 96468f498b47a5623c63e4ee9c24e8c90b202c8b Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 15:19:30 +0100 Subject: [PATCH 07/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_run.go | 37 ++++++++++++++++++++--------- engine/api/workflow/dao_run.go | 3 ++- engine/api/workflow_run.go | 1 + 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index a479cfc678..2f85416b97 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -81,7 +81,7 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i return nil, sdk.WrapError(err, "Unable to load workflow_node_run proj=%s, workflow=%s, num=%d, node=%d", projectkey, workflowname, number, id) } - r, err := fromDBNodeRun(db, rr, loadOpts) + r, err := fromDBNodeRun(rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -107,6 +107,11 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } r.Coverage = cov } + if loadOpts.WithSpawnInfos { + if err := loadSpawnInfos(db, r); err != nil { + return nil, sdk.WrapError(err, "LoadNodeRun>Error load spawn infos %d", r.ID) + } + } if loadOpts.WithVulnerabilities { vuln, errV := loadVulnerabilityReport(db, r.ID) if errV != nil && !sdk.ErrorIs(errV, sdk.ErrNotFound) { @@ -118,6 +123,21 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } +func loadSpawnInfos(db gorp.SqlExecutor, r *sdk.WorkflowNodeRun) error { + for s := range r.Stages { + stage := r.Stages[s] + for j := range stage.RunJobs { + rj := stage.RunJobs[j] + spawnInfos, err := loadNodeRunJobInfo(db, rj.ID) + if err != nil { + return sdk.WrapError(err, "unable to load spawn infos for runJob: %d", rj.ID) + } + rj.SpawnInfos = spawnInfos + } + } + return nil +} + //LoadNodeRunByNodeJobID load a specific node run on a workflow from a node job run id func LoadNodeRunByNodeJobID(db gorp.SqlExecutor, nodeJobRunID int64, loadOpts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { var rr = NodeRun{} @@ -140,7 +160,7 @@ func LoadNodeRunByNodeJobID(db gorp.SqlExecutor, nodeJobRunID int64, loadOpts Lo return nil, sdk.WrapError(err, "Unable to load workflow_node_run node_job_id=%d", nodeJobRunID) } - r, err := fromDBNodeRun(db, rr, loadOpts) + r, err := fromDBNodeRun(rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -185,7 +205,7 @@ func LoadAndLockNodeRunByID(ctx context.Context, db gorp.SqlExecutor, id int64, } return nil, sdk.WrapError(err, "Unable to load workflow_node_run node=%d", id) } - return fromDBNodeRun(db, rr, LoadRunOptions{}) + return fromDBNodeRun(rr, LoadRunOptions{}) } //LoadNodeRunByID load a specific node run on a workflow @@ -205,7 +225,7 @@ func LoadNodeRunByID(db gorp.SqlExecutor, id int64, loadOpts LoadRunOptions) (*s return nil, sdk.WrapError(err, "Unable to load workflow_node_run node=%d", id) } - r, err := fromDBNodeRun(db, rr, loadOpts) + r, err := fromDBNodeRun(rr, loadOpts) if err != nil { return nil, sdk.WithStack(err) } @@ -248,7 +268,7 @@ func nodeRunExist(db gorp.SqlExecutor, nodeID, num int64, subnumber int) (bool, return nb > 0, err } -func fromDBNodeRun(db gorp.SqlExecutor, rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { +func fromDBNodeRun(rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { r := new(sdk.WorkflowNodeRun) if rr.WorkflowID.Valid { r.WorkflowID = rr.WorkflowID.Int64 @@ -300,11 +320,6 @@ func fromDBNodeRun(db gorp.SqlExecutor, rr NodeRun, opts LoadRunOptions) (*sdk.W if rj.Status == sdk.StatusWaiting.String() { rj.QueuedSeconds = time.Now().Unix() - rj.Queued.Unix() } - spawnInfos, err := loadNodeRunJobInfo(db, rj.ID) - if err != nil { - return nil, sdk.WrapError(err, "unable to load spawn infos for runJob: %d", rj.ID) - } - rj.SpawnInfos = spawnInfos } } @@ -721,7 +736,7 @@ func PreviousNodeRun(db gorp.SqlExecutor, nr sdk.WorkflowNodeRun, n sdk.Workflow if err := db.SelectOne(&rr, query, workflowID, n.Name, nr.VCSBranch, nr.VCSTag, nr.Number, nr.ID); err != nil { return nodeRun, sdk.WrapError(err, "Cannot load previous run on workflow %d node %s nr.VCSBranch:%s nr.VCSTag:%s nr.Number:%d nr.ID:%d ", workflowID, n.Name, nr.VCSBranch, nr.VCSTag, nr.Number, nr.ID) } - pNodeRun, errF := fromDBNodeRun(db, rr, LoadRunOptions{}) + pNodeRun, errF := fromDBNodeRun(rr, LoadRunOptions{}) if errF != nil { return nodeRun, sdk.WrapError(errF, "PreviousNodeRun> Cannot read node run") } diff --git a/engine/api/workflow/dao_run.go b/engine/api/workflow/dao_run.go index 23cea95fdc..072c7ac984 100644 --- a/engine/api/workflow/dao_run.go +++ b/engine/api/workflow/dao_run.go @@ -35,6 +35,7 @@ type LoadRunOptions struct { WithCoverage bool WithArtifacts bool WithStaticFiles bool + WithSpawnInfos bool WithTests bool WithLightTests bool WithVulnerabilities bool @@ -723,7 +724,7 @@ func syncNodeRuns(db gorp.SqlExecutor, wr *sdk.WorkflowRun, loadOpts LoadRunOpti } for _, n := range dbNodeRuns { - wnr, err := fromDBNodeRun(db, n, loadOpts) + wnr, err := fromDBNodeRun(n, loadOpts) if err != nil { return err } diff --git a/engine/api/workflow_run.go b/engine/api/workflow_run.go index 3627ed683a..24cfdbf20e 100644 --- a/engine/api/workflow_run.go +++ b/engine/api/workflow_run.go @@ -764,6 +764,7 @@ func (api *API) getWorkflowNodeRunHandler() service.Handler { WithStaticFiles: true, WithCoverage: true, WithVulnerabilities: true, + WithSpawnInfos: true, }) if err != nil { return sdk.WrapError(err, "Unable to load last workflow run") From 5c563c3b239ab9cf702365923c81c572afa15818 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 17:49:50 +0100 Subject: [PATCH 08/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 27 ++++++++++++------- engine/api/workflow/dao_node_run.go | 28 +++++++++++++------- engine/api/workflow/execute_node_run.go | 6 ++--- sdk/build.go | 7 ++--- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index f646a8ff44..5e2ff463d7 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "fmt" + "strings" "time" "github.com/go-gorp/gorp" @@ -13,12 +14,18 @@ import ( ) //loadNodeRunJobInfo load infos (workflow_node_run_job_infos) for a job (workflow_node_run_job) -func loadNodeRunJobInfo(db gorp.SqlExecutor, jobID int64) ([]sdk.SpawnInfo, error) { +func loadNodeRunJobInfo(db gorp.SqlExecutor, jobIDs []int64) ([]sdk.SpawnInfo, error) { + ids := make([]string, len(jobIDs)) + for i := range ids { + ids[i] = fmt.Sprintf("%d", jobIDs[i]) + } + idsJoined := strings.Join(ids, ",") res := []struct { - Bytes sql.NullString `db:"spawninfos"` + Bytes sql.NullString `db:"spawninfos"` + WorkflowNodeJobRunID int64 `db:"workflow_node_job_run_id"` }{} - query := "SELECT spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = $1" - if _, err := db.Select(&res, query, jobID); err != nil { + query := "SELECT workflow_node_run_job_id, spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = ANY(string_to_array($1, ',')::bigint[])" + if _, err := db.Select(&res, query, idsJoined); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -26,12 +33,14 @@ func loadNodeRunJobInfo(db gorp.SqlExecutor, jobID int64) ([]sdk.SpawnInfo, erro } spawnInfos := []sdk.SpawnInfo{} - for _, r := range res { - v := []sdk.SpawnInfo{} - gorpmapping.JSONNullString(r.Bytes, &v) - spawnInfos = append(spawnInfos, v...) + for i := range res { + spInfos := []sdk.SpawnInfo{} + gorpmapping.JSONNullString(res[i].Bytes, &spInfos) + for i := range spInfos { + spInfos[i].WorkflowNodeJobRunID = res[i].WorkflowNodeJobRunID + } + spawnInfos = append(spawnInfos, spInfos...) } - return spawnInfos, nil } diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 2f85416b97..7779f82057 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -123,16 +123,26 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } -func loadSpawnInfos(db gorp.SqlExecutor, r *sdk.WorkflowNodeRun) error { - for s := range r.Stages { - stage := r.Stages[s] - for j := range stage.RunJobs { - rj := stage.RunJobs[j] - spawnInfos, err := loadNodeRunJobInfo(db, rj.ID) - if err != nil { - return sdk.WrapError(err, "unable to load spawn infos for runJob: %d", rj.ID) +func loadSpawnInfos(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun) error { + rjIds := make([]int64, 0) + for s := range nr.Stages { + for j := range nr.Stages[s].RunJobs { + rjIds = append(rjIds, nr.Stages[s].RunJobs[j].ID) + } + } + + spawnInfos, err := loadNodeRunJobInfo(db, rjIds) + if err != nil { + return sdk.WrapError(err, "unable to load spawn infos") + } + + for s := range nr.Stages { + for j := range nr.Stages[s].RunJobs { + for sp := range spawnInfos { + if spawnInfos[sp].WorkflowNodeJobRunID == nr.Stages[s].RunJobs[j].ID { + nr.Stages[s].RunJobs[j].SpawnInfos = append(nr.Stages[s].RunJobs[j].SpawnInfos, spawnInfos[sp]) + } } - rj.SpawnInfos = spawnInfos } } return nil diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 9b1378c12f..6c690961c2 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -428,7 +428,7 @@ func addJobsToQueue(ctx context.Context, db gorp.SqlExecutor, stage *sdk.Stage, } else { wjob.SpawnInfos = []sdk.SpawnInfo{sdk.SpawnInfo{ APITime: time.Now(), - Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoJobInQueue.ID, Args: []interface{}{}}, + Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoJobInQueue.ID}, RemoteTime: time.Now(), }} } @@ -511,7 +511,7 @@ func syncStage(db gorp.SqlExecutor, store cache.Store, stage *sdk.Stage) (bool, if runJobDB.Status == sdk.StatusBuilding.String() || runJobDB.Status == sdk.StatusWaiting.String() { stageEnd = false } - spawnInfos, err := loadNodeRunJobInfo(db, runJob.ID) + spawnInfos, err := loadNodeRunJobInfo(db, []int64{runJob.ID}) if err != nil { return false, sdk.WrapError(err, "unable to load spawn infos for runJob: %d", runJob.ID) } @@ -846,7 +846,7 @@ func SyncNodeRunRunJob(ctx context.Context, db gorp.SqlExecutor, nodeRun *sdk.Wo for j := range s.RunJobs { runJob := &s.RunJobs[j] if runJob.ID == nodeJobRun.ID { - spawnInfos, err := loadNodeRunJobInfo(db, runJob.ID) + spawnInfos, err := loadNodeRunJobInfo(db, []int64{runJob.ID}) if err != nil { return false, sdk.WrapError(err, "unable to load spawn infos for runJobID: %d", runJob.ID) } diff --git a/sdk/build.go b/sdk/build.go index ee0311aea0..8b9009109a 100644 --- a/sdk/build.go +++ b/sdk/build.go @@ -24,9 +24,10 @@ type PipelineBuildJob struct { // SpawnInfo contains an information about spawning type SpawnInfo struct { - APITime time.Time `json:"api_time,omitempty" db:"-" mapstructure:"-"` - RemoteTime time.Time `json:"remote_time,omitempty" db:"-" mapstructure:"-"` - Message SpawnMsg `json:"message,omitempty" db:"-"` + WorkflowNodeJobRunID int64 `json:"WorkflowNodeJobRunID,omitempty" db:"-"` + APITime time.Time `json:"api_time,omitempty" db:"-" mapstructure:"-"` + RemoteTime time.Time `json:"remote_time,omitempty" db:"-" mapstructure:"-"` + Message SpawnMsg `json:"message,omitempty" db:"-"` // UserMessage contains msg translated for end user UserMessage string `json:"user_message,omitempty" db:"-"` } From ceeb6336e9627fc2158120e7026eda9e1959e45f Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Fri, 30 Nov 2018 17:57:18 +0100 Subject: [PATCH 09/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 6 +++++- engine/api/workflow/dao_node_run.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index 5e2ff463d7..a2da05d7ab 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -35,7 +35,11 @@ func loadNodeRunJobInfo(db gorp.SqlExecutor, jobIDs []int64) ([]sdk.SpawnInfo, e spawnInfos := []sdk.SpawnInfo{} for i := range res { spInfos := []sdk.SpawnInfo{} - gorpmapping.JSONNullString(res[i].Bytes, &spInfos) + if err := gorpmapping.JSONNullString(res[i].Bytes, &spInfos); err != nil { + // should never append, but log error + log.Warning("wrong spawnInfos format: res: %v for ids: %v", res[i].Bytes, ids[i]) + continue + } for i := range spInfos { spInfos[i].WorkflowNodeJobRunID = res[i].WorkflowNodeJobRunID } diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 7779f82057..b8e412ee04 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -124,6 +124,7 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } func loadSpawnInfos(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun) error { + // load all run job ids, used to get spawnInfos with only one SQL request rjIds := make([]int64, 0) for s := range nr.Stages { for j := range nr.Stages[s].RunJobs { @@ -131,11 +132,13 @@ func loadSpawnInfos(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun) error { } } + // load all spawnInfos for all jobs in the current node run spawnInfos, err := loadNodeRunJobInfo(db, rjIds) if err != nil { return sdk.WrapError(err, "unable to load spawn infos") } + // then reattach spawninfo to good node run job for s := range nr.Stages { for j := range nr.Stages[s].RunJobs { for sp := range spawnInfos { From f747d88178c792d98c3c251dacca7331de12d013 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sat, 1 Dec 2018 00:09:18 +0100 Subject: [PATCH 10/17] refactor, get spawninfo as logs & service logs Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/api_routes.go | 1 + engine/api/workflow/dao_node_job_run_info.go | 27 ++-- engine/api/workflow/dao_node_run.go | 33 ----- engine/api/workflow/dao_run.go | 1 - engine/api/workflow/execute_node_run.go | 4 +- engine/api/workflow_run.go | 23 +++- engine/hatchery/swarm/swarm_util_create.go | 53 ++++++++ sdk/messages.go | 8 +- .../workflow/run/node/pipeline/pipeline.html | 11 +- .../pipeline/spawninfo/spawninfo.component.ts | 123 +++++++++++++++--- .../node/pipeline/spawninfo/spawninfo.html | 15 ++- .../assets/worker/web/workflow-spawninfos.js | 35 +++++ 12 files changed, 255 insertions(+), 79 deletions(-) create mode 100644 ui/src/assets/worker/web/workflow-spawninfos.js diff --git a/engine/api/api_routes.go b/engine/api/api_routes.go index 00eaee6b1e..841c7aa26b 100644 --- a/engine/api/api_routes.go +++ b/engine/api/api_routes.go @@ -261,6 +261,7 @@ func (api *API) InitRouter() { r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/stop", r.POSTEXECUTE(api.stopWorkflowNodeRunHandler)) r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeID}/history", r.GET(api.getWorkflowNodeRunHistoryHandler)) r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/{nodeName}/commits", r.GET(api.getWorkflowCommitsHandler)) + r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/job/{runJobId}/info", r.GET(api.getWorkflowNodeRunJobSpawnInfosHandler)) r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/job/{runJobId}/log/service", r.GET(api.getWorkflowNodeRunJobServiceLogsHandler)) r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/job/{runJobId}/step/{stepOrder}", r.GET(api.getWorkflowNodeRunJobStepHandler)) r.Handle("/project/{key}/workflows/{permWorkflowName}/artifact/{artifactId}", r.GET(api.getDownloadArtifactHandler)) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index a2da05d7ab..70efd0654e 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -4,7 +4,7 @@ import ( "database/sql" "encoding/json" "fmt" - "strings" + "sort" "time" "github.com/go-gorp/gorp" @@ -13,19 +13,13 @@ import ( "github.com/ovh/cds/sdk/log" ) -//loadNodeRunJobInfo load infos (workflow_node_run_job_infos) for a job (workflow_node_run_job) -func loadNodeRunJobInfo(db gorp.SqlExecutor, jobIDs []int64) ([]sdk.SpawnInfo, error) { - ids := make([]string, len(jobIDs)) - for i := range ids { - ids[i] = fmt.Sprintf("%d", jobIDs[i]) - } - idsJoined := strings.Join(ids, ",") +// LoadNodeRunJobInfo load infos (workflow_node_run_job_infos) for a job (workflow_node_run_job) +func LoadNodeRunJobInfo(db gorp.SqlExecutor, jobID int64) ([]sdk.SpawnInfo, error) { res := []struct { - Bytes sql.NullString `db:"spawninfos"` - WorkflowNodeJobRunID int64 `db:"workflow_node_job_run_id"` + Bytes sql.NullString `db:"spawninfos"` }{} - query := "SELECT workflow_node_run_job_id, spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = ANY(string_to_array($1, ',')::bigint[])" - if _, err := db.Select(&res, query, idsJoined); err != nil { + query := "SELECT workflow_node_run_job_id, spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = $1" + if _, err := db.Select(&res, query, jobID); err != nil { if err == sql.ErrNoRows { return nil, nil } @@ -37,14 +31,15 @@ func loadNodeRunJobInfo(db gorp.SqlExecutor, jobIDs []int64) ([]sdk.SpawnInfo, e spInfos := []sdk.SpawnInfo{} if err := gorpmapping.JSONNullString(res[i].Bytes, &spInfos); err != nil { // should never append, but log error - log.Warning("wrong spawnInfos format: res: %v for ids: %v", res[i].Bytes, ids[i]) + log.Warning("wrong spawnInfos format: res: %v for id: %v", res[i].Bytes, jobID) continue } - for i := range spInfos { - spInfos[i].WorkflowNodeJobRunID = res[i].WorkflowNodeJobRunID - } spawnInfos = append(spawnInfos, spInfos...) } + // sort here and not in sql, as it's could be a json array in sql value + sort.Slice(spawnInfos, func(i, j int) bool { + return spawnInfos[i].APITime.Before(spawnInfos[j].APITime) + }) return spawnInfos, nil } diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index b8e412ee04..2c735d67a5 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -107,11 +107,6 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } r.Coverage = cov } - if loadOpts.WithSpawnInfos { - if err := loadSpawnInfos(db, r); err != nil { - return nil, sdk.WrapError(err, "LoadNodeRun>Error load spawn infos %d", r.ID) - } - } if loadOpts.WithVulnerabilities { vuln, errV := loadVulnerabilityReport(db, r.ID) if errV != nil && !sdk.ErrorIs(errV, sdk.ErrNotFound) { @@ -123,34 +118,6 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i } -func loadSpawnInfos(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun) error { - // load all run job ids, used to get spawnInfos with only one SQL request - rjIds := make([]int64, 0) - for s := range nr.Stages { - for j := range nr.Stages[s].RunJobs { - rjIds = append(rjIds, nr.Stages[s].RunJobs[j].ID) - } - } - - // load all spawnInfos for all jobs in the current node run - spawnInfos, err := loadNodeRunJobInfo(db, rjIds) - if err != nil { - return sdk.WrapError(err, "unable to load spawn infos") - } - - // then reattach spawninfo to good node run job - for s := range nr.Stages { - for j := range nr.Stages[s].RunJobs { - for sp := range spawnInfos { - if spawnInfos[sp].WorkflowNodeJobRunID == nr.Stages[s].RunJobs[j].ID { - nr.Stages[s].RunJobs[j].SpawnInfos = append(nr.Stages[s].RunJobs[j].SpawnInfos, spawnInfos[sp]) - } - } - } - } - return nil -} - //LoadNodeRunByNodeJobID load a specific node run on a workflow from a node job run id func LoadNodeRunByNodeJobID(db gorp.SqlExecutor, nodeJobRunID int64, loadOpts LoadRunOptions) (*sdk.WorkflowNodeRun, error) { var rr = NodeRun{} diff --git a/engine/api/workflow/dao_run.go b/engine/api/workflow/dao_run.go index 072c7ac984..419c2150d0 100644 --- a/engine/api/workflow/dao_run.go +++ b/engine/api/workflow/dao_run.go @@ -35,7 +35,6 @@ type LoadRunOptions struct { WithCoverage bool WithArtifacts bool WithStaticFiles bool - WithSpawnInfos bool WithTests bool WithLightTests bool WithVulnerabilities bool diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 6c690961c2..8b0512b066 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -511,7 +511,7 @@ func syncStage(db gorp.SqlExecutor, store cache.Store, stage *sdk.Stage) (bool, if runJobDB.Status == sdk.StatusBuilding.String() || runJobDB.Status == sdk.StatusWaiting.String() { stageEnd = false } - spawnInfos, err := loadNodeRunJobInfo(db, []int64{runJob.ID}) + spawnInfos, err := LoadNodeRunJobInfo(db, runJob.ID) if err != nil { return false, sdk.WrapError(err, "unable to load spawn infos for runJob: %d", runJob.ID) } @@ -846,7 +846,7 @@ func SyncNodeRunRunJob(ctx context.Context, db gorp.SqlExecutor, nodeRun *sdk.Wo for j := range s.RunJobs { runJob := &s.RunJobs[j] if runJob.ID == nodeJobRun.ID { - spawnInfos, err := loadNodeRunJobInfo(db, []int64{runJob.ID}) + spawnInfos, err := LoadNodeRunJobInfo(db, runJob.ID) if err != nil { return false, sdk.WrapError(err, "unable to load spawn infos for runJobID: %d", runJob.ID) } diff --git a/engine/api/workflow_run.go b/engine/api/workflow_run.go index 24cfdbf20e..5b7a4dcc0a 100644 --- a/engine/api/workflow_run.go +++ b/engine/api/workflow_run.go @@ -764,7 +764,6 @@ func (api *API) getWorkflowNodeRunHandler() service.Handler { WithStaticFiles: true, WithCoverage: true, WithVulnerabilities: true, - WithSpawnInfos: true, }) if err != nil { return sdk.WrapError(err, "Unable to load last workflow run") @@ -1345,6 +1344,28 @@ func (api *API) getWorkflowRunArtifactsHandler() service.Handler { } } +func (api *API) getWorkflowNodeRunJobSpawnInfosHandler() service.Handler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + runJobID, errJ := requestVarInt(r, "runJobId") + if errJ != nil { + return sdk.WrapError(errJ, "getWorkflowNodeRunJobSpawnInfosHandler> runJobId: invalid number") + } + db := api.mustDB() + + spawnInfos, err := workflow.LoadNodeRunJobInfo(db, runJobID) + if err != nil { + return sdk.WrapError(err, "cannot load spawn infos for node run job id %d", runJobID) + } + + l := r.Header.Get("Accept-Language") + for ki, info := range spawnInfos { + m := sdk.NewMessage(sdk.Messages[info.Message.ID], info.Message.Args...) + spawnInfos[ki].UserMessage = m.String(l) + } + return service.WriteJSON(w, spawnInfos, http.StatusOK) + } +} + func (api *API) getWorkflowNodeRunJobServiceLogsHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { runJobID, errJ := requestVarInt(r, "runJobId") diff --git a/engine/hatchery/swarm/swarm_util_create.go b/engine/hatchery/swarm/swarm_util_create.go index e8f5a95b26..a4cd1e1b7e 100644 --- a/engine/hatchery/swarm/swarm_util_create.go +++ b/engine/hatchery/swarm/swarm_util_create.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strings" + "time" types "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -119,12 +120,64 @@ checkImage: } if !imageFound { + infos := []sdk.SpawnInfo{ + { + RemoteTime: time.Now(), + Message: sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryStartDockerPull.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, + }, + }, + } + + ctxtJobSendSpawnInfo, cancelJobSendSpawnInfo := context.WithTimeout(ctx, 10*time.Second) + if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfo, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infos); err != nil { + next() + log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) + } + cancelJobSendSpawnInfo() + _, next := observability.Span(ctx, "swarm.dockerClient.pullImage", observability.Tag("image", cArgs.image)) if err := h.pullImage(dockerClient, cArgs.image, timeoutPullImage); err != nil { next() + + infosEnd := []sdk.SpawnInfo{ + { + RemoteTime: time.Now(), + Message: sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryEndDockerPullErr.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image, err}, + }, + }, + } + + ctxtJobSendSpawnInfoEndDockerPullErr, cancelJobSendSpawnInfoDockerPullErr := context.WithTimeout(ctx, 10*time.Second) + if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfoEndDockerPullErr, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infosEnd); err != nil { + next() + log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) + } + cancelJobSendSpawnInfoDockerPullErr() + return sdk.WrapError(err, "Unable to pull image %s on %s", cArgs.image, dockerClient.name) } next() + + infosEnd := []sdk.SpawnInfo{ + { + RemoteTime: time.Now(), + Message: sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryEndDockerPull.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, + }, + }, + } + + ctxtJobSendSpawnInfoEndDockerPull, cancelJobSendSpawnInfoDockerPull := context.WithTimeout(ctx, 10*time.Second) + if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfoEndDockerPull, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infosEnd); err != nil { + next() + log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) + } + cancelJobSendSpawnInfoDockerPull() } _, next = observability.Span(ctx, "swarm.dockerClient.ContainerCreate", observability.Tag(observability.TagWorker, cArgs.name), observability.Tag("network", fmt.Sprintf("%v", networkingConfig))) diff --git a/sdk/messages.go b/sdk/messages.go index 676eb823ab..17812890a3 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -59,7 +59,11 @@ var ( MsgPipelineJobUpdated = &Message{"MsgPipelineJobUpdated", trad{FR: "Le job %s du stage %s a été mis à jour", EN: "Job %s in stage %s updated"}, nil} MsgPipelineJobAdded = &Message{"MsgPipelineJobAdded", trad{FR: "Le job %s du stage %s a été ajouté", EN: "Job %s in stage %s added"}, nil} MsgPipelineJobDeleted = &Message{"MsgPipelineJobDeleted", trad{FR: "Le job %s du stage %s a été supprimé", EN: "Job %s in stage %s deleted"}, nil} + MsgSpawnInfoDeprecatedModel = &Message{"MsgSpawnInfoDeprecatedModel", trad{FR: "Attention vous utilisez un worker model (%s) déprécié", EN: "Pay attention you are using a deprecated worker model (%s)"}, nil} MsgSpawnInfoHatcheryStarts = &Message{"MsgSpawnInfoHatcheryStarts", trad{FR: "La Hatchery %s (%s) a démarré le lancement du worker avec le modèle %s", EN: "Hatchery %s (%s) starts spawn worker with model %s"}, nil} + MsgSpawnInfoHatcheryStartDockerPull = &Message{"MsgSpawnInfoHatcheryStartDockerPull", trad{FR: "La Hatchery %s (%s) a démarré le docker pull de l'image %s...", EN: "Hatchery %s (%s) starts docker pull %s..."}, nil} + MsgSpawnInfoHatcheryEndDockerPull = &Message{"MsgSpawnInfoHatcheryEndDockerPull", trad{FR: "La Hatchery %s (%s) a terminé le docker pull de l'image %s", EN: "Hatchery %s (%s) docker pull %s done"}, nil} + MsgSpawnInfoHatcheryEndDockerPullErr = &Message{"MsgSpawnInfoHatcheryEndDockerPullErr", trad{FR: "ATTENTION: La Hatchery %s (%s) a terminé le docker pull de l'image %s en erreur: %s", EN: "WARNING: Hatchery %s (%s) - docker pull %s done with error: %v"}, nil} MsgSpawnInfoHatcheryErrorSpawn = &Message{"MsgSpawnInfoHatcheryErrorSpawn", trad{FR: "Une erreur est survenue lorsque la Hatchery %s (%s) a démarré un worker avec le modèle %s après %s, err:%s", EN: "Error while Hatchery %s (%s) spawn worker with model %s after %s, err:%s"}, nil} MsgSpawnInfoHatcheryStartsSuccessfully = &Message{"MsgSpawnInfoHatcheryStartsSuccessfully", trad{FR: "La Hatchery %s (%s) a démarré le worker %s avec succès en %s", EN: "Hatchery %s (%s) spawn worker %s successfully in %s"}, nil} MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "Worker %s finished working on this job and took %s to work on the steps"}, nil} @@ -78,7 +82,6 @@ var ( MsgWorkflowImportedInserted = &Message{"MsgWorkflowImportedInserted", trad{FR: "Le workflow %s a été créé", EN: "Workflow %s has been created"}, nil} MsgSpawnInfoHatcheryCannotStartJob = &Message{"MsgSpawnInfoHatcheryCannotStart", trad{FR: "Aucune hatchery n'a pu démarrer de worker respectant vos pré-requis de job, merci de les vérifier.", EN: "No hatchery can spawn a worker corresponding your job's requirements. Please check your job's requirements."}, nil} MsgWorkflowRunBranchDeleted = &Message{"MsgWorkflowRunBranchDeleted", trad{FR: "La branche %s a été supprimée", EN: "Branch %s has been deleted"}, nil} - MsgSpawnInfoDeprecatedModel = &Message{"MsgSpawnInfoDeprecatedModel", trad{FR: "Attention vous utilisez un worker model (%s) déprécié", EN: "Pay attention you are using a deprecated worker model (%s)"}, nil} ) // Messages contains all sdk Messages @@ -125,6 +128,9 @@ var Messages = map[string]*Message{ MsgSpawnInfoHatcheryStarts.ID: MsgSpawnInfoHatcheryStarts, MsgSpawnInfoHatcheryErrorSpawn.ID: MsgSpawnInfoHatcheryErrorSpawn, MsgSpawnInfoHatcheryStartsSuccessfully.ID: MsgSpawnInfoHatcheryStartsSuccessfully, + MsgSpawnInfoHatcheryStartDockerPull.ID: MsgSpawnInfoHatcheryStartDockerPull, + MsgSpawnInfoHatcheryEndDockerPull.ID: MsgSpawnInfoHatcheryEndDockerPull, + MsgSpawnInfoHatcheryEndDockerPullErr.ID: MsgSpawnInfoHatcheryEndDockerPullErr, MsgSpawnInfoWorkerEnd.ID: MsgSpawnInfoWorkerEnd, MsgSpawnInfoJobInQueue.ID: MsgSpawnInfoJobInQueue, MsgSpawnInfoJobTaken.ID: MsgSpawnInfoJobTaken, diff --git a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html index 32dcf253c9..b0ed2b528f 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html +++ b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html @@ -56,10 +56,13 @@ <ul> <li> <app-workflow-run-job-spawn-info - [spawnInfos]="selectedRunJob.spawninfos" - [variables]="selectedRunJob.parameters" - [job]="selectedRunJob.job" - [(displayServicesLogs)]="displayServiceLogs"> + [project]="project" + [workflowName]="workflowName" + [nodeRun]="nodeRun" + [job]="selectedRunJob.job" + [nodeJobRun]="selectedRunJob" + [variables]="selectedRunJob.parameters" + [(displayServicesLogs)]="displayServiceLogs"> </app-workflow-run-job-spawn-info> </li> <ng-container *ngIf="!displayServiceLogs"> diff --git a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts index abc809f389..2cda394da4 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts +++ b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts @@ -1,19 +1,42 @@ -import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; -import {TranslateService} from '@ngx-translate/core'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; import * as AU from 'ansi_up'; -import {Job} from '../../../../../../model/job.model'; -import {Parameter} from '../../../../../../model/parameter.model'; -import {SpawnInfo} from '../../../../../../model/pipeline.model'; -import {JobVariableComponent} from '../../../../../run/workflow/variables/job.variables.component'; +import { CDSWebWorker } from 'app/shared/worker/web.worker'; +import { Subscription } from 'rxjs'; +import { environment } from '../../../../../../../environments/environment'; +import { Job } from '../../../../../../model/job.model'; +import { Parameter } from '../../../../../../model/parameter.model'; +import { SpawnInfo } from '../../../../../../model/pipeline.model'; +import { PipelineStatus } from '../../../../../../model/pipeline.model'; +import { Project } from '../../../../../../model/project.model'; +import { WorkflowNodeJobRun, WorkflowNodeRun } from '../../../../../../model/workflow.run.model'; +import { AuthentificationStore } from '../../../../../../service/auth/authentification.store'; +import { JobVariableComponent } from '../../../../../run/workflow/variables/job.variables.component'; @Component({ selector: 'app-workflow-run-job-spawn-info', templateUrl: './spawninfo.html', styleUrls: ['./spawninfo.scss'] }) -export class WorkflowRunJobSpawnInfoComponent { +export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { - @Input() spawnInfos: Array<SpawnInfo>; + @Input() project: Project; + @Input() workflowName: string; + @Input() nodeRun: WorkflowNodeRun; + @Input('nodeJobRun') + set nodeJobRun(data: WorkflowNodeJobRun) { + if (data) { + this._nodeJobRun = data; + if (data.status === PipelineStatus.SUCCESS || data.status === PipelineStatus.FAIL || data.status === PipelineStatus.STOPPED) { + this.stopWorker(); + } + } + } + get nodeJobRun(): WorkflowNodeJobRun { + return this._nodeJobRun; + } + + spawnInfos: String; @Input() variables: Array<Parameter>; @Input('job') set job(data: Job) { @@ -37,18 +60,34 @@ export class WorkflowRunJobSpawnInfoComponent { @ViewChild('jobVariable') jobVariable: JobVariableComponent; + _nodeJobRun: WorkflowNodeJobRun; + + worker: CDSWebWorker; + workerSubscription: Subscription; + + serviceSpawnInfos: Array<SpawnInfo>; + loading = true; + show = true; displayServiceLogsLink = false; _job: Job; _displayServiceLogs: boolean; ansi_up = new AU.default; - constructor(private _translate: TranslateService) {} + ngOnDestroy(): void { + this.stopWorker(); + } + + ngOnInit(): void { + this.initWorker(); + } + + constructor(private _authStore: AuthentificationStore, private _translate: TranslateService) { } refreshDisplayServiceLogsLink() { - if (this.job && this.job.action && Array.isArray(this.job.action.requirements)) { - this.displayServiceLogsLink = this.job.action.requirements.some((req) => req.type === 'service'); - } + if (this.job && this.job.action && Array.isArray(this.job.action.requirements)) { + this.displayServiceLogsLink = this.job.action.requirements.some((req) => req.type === 'service'); + } } toggle() { @@ -57,9 +96,9 @@ export class WorkflowRunJobSpawnInfoComponent { getSpawnInfos() { let msg = ''; - if (this.spawnInfos) { - this.spawnInfos.forEach( s => { - msg += '[' + s.api_time.toString().substr(0, 19) + '] ' + s.user_message + '\n'; + if (this.nodeJobRun.spawninfos) { + this.nodeJobRun.spawninfos.forEach(s => { + msg += '[' + s.api_time.toString().substr(0, 19) + '] ' + s.user_message + '\n'; }); } if (msg !== '') { @@ -68,10 +107,62 @@ export class WorkflowRunJobSpawnInfoComponent { return this._translate.instant('job_spawn_no_information'); } + initWorker(): void { + if (!this.serviceSpawnInfos) { + this.loading = true; + } + + if (this.nodeJobRun.status !== PipelineStatus.WAITING && this.nodeJobRun.status !== PipelineStatus.BUILDING) { + this.spawnInfos = this.getSpawnInfos(); + this.loading = false; + return; + } + + if (!this.worker) { + this.worker = new CDSWebWorker('./assets/worker/web/workflow-spawninfos.js'); + this.worker.start({ + user: this._authStore.getUser(), + session: this._authStore.getSessionToken(), + api: environment.apiURL, + key: this.project.key, + workflowName: this.workflowName, + number: this.nodeRun.num, + nodeRunId: this.nodeRun.id, + runJobId: this.nodeJobRun.id, + }); + + this.workerSubscription = this.worker.response().subscribe(msg => { + if (msg) { + let serviceSpawnInfos: Array<SpawnInfo> = JSON.parse(msg); + if (this.loading) { + this.loading = false; + } + let infos = ''; + serviceSpawnInfos.forEach(s => { + infos += '[' + s.api_time.toString().substr(0, 19) + '] ' + s.user_message + '\n'; + }); + this.spawnInfos = this.ansi_up.ansi_to_html(infos); + if (this.nodeJobRun.status === PipelineStatus.SUCCESS || this.nodeJobRun.status === PipelineStatus.FAIL || + this.nodeJobRun.status === PipelineStatus.STOPPED) { + this.stopWorker(); + this.spawnInfos = this.getSpawnInfos(); + } + } + }); + } + } + + stopWorker() { + if (this.worker) { + this.worker.stop(); + this.worker = null; + } + } + openVariableModal(event: Event): void { event.stopPropagation(); if (this.jobVariable) { - this.jobVariable.show({autofocus: false, closable: false, observeChanges: true}); + this.jobVariable.show({ autofocus: false, closable: false, observeChanges: true }); } } } diff --git a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html index 7a56027ffd..2512c051b5 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html +++ b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.html @@ -8,10 +8,10 @@ </div> <div class="variables" *ngIf="displayServiceLogsLink"> <a class="pointing" *ngIf="!displayServiceLogs" (click)="displayServiceLogs = true; $event.stopPropagation()"> - Display services logs + Display services logs </a> <a class="pointing" *ngIf="displayServiceLogs" (click)="displayServiceLogs = false; $event.stopPropagation()"> - Display job logs + Display job logs </a> </div> <div class="variables"> @@ -21,8 +21,13 @@ </div> </div> <div class="spawnInfos" [hidden]="!show"> - <pre [innerHTML]="getSpawnInfos()"> - </pre> + <div class="log" *ngIf="!loading"> + <div class="logs"> + <pre [innerHTML]="spawnInfos"> + </pre> + </div> + </div> + <div class="ui active centered inline loader" *ngIf="loading"></div> </div> </div> -<app-workflow-run-job-variable [variables]="variables" #jobVariable></app-workflow-run-job-variable> +<app-workflow-run-job-variable [variables]="variables" #jobVariable></app-workflow-run-job-variable> \ No newline at end of file diff --git a/ui/src/assets/worker/web/workflow-spawninfos.js b/ui/src/assets/worker/web/workflow-spawninfos.js new file mode 100644 index 0000000000..77ed43bb84 --- /dev/null +++ b/ui/src/assets/worker/web/workflow-spawninfos.js @@ -0,0 +1,35 @@ + +importScripts('../common.js'); + +var started = false; +var key = ''; +var workflowName = ''; +var number = 0; +var nodeRunId = 0; +var runJobId = 0; + + +onmessage = function (e) { + key = e.data.key; + workflowName = e.data.workflowName; + number = e.data.number; + nodeRunId = e.data.nodeRunId; + runJobId = e.data.runJobId; + + loadLog(e.data.user, e.data.session, e.data.api); +}; + +function loadLog (user, session, api) { + loop(4, function () { + var url = '/project/' + key + '/workflows/' + workflowName + '/runs/' + number + '/nodes/' + nodeRunId + '/job/' + runJobId + '/info'; + + var xhr = httpCall(url, api, user, session); + if (xhr.status >= 400) { + return true; + } + if (xhr.status === 200 && xhr.responseText !== null) { + postMessage(xhr.responseText); + } + return false; + }); +} From 6b0a41192402b92088c5cf23e35641696eaf0f31 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sat, 1 Dec 2018 00:11:01 +0100 Subject: [PATCH 11/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index 70efd0654e..0058263726 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -31,7 +31,7 @@ func LoadNodeRunJobInfo(db gorp.SqlExecutor, jobID int64) ([]sdk.SpawnInfo, erro spInfos := []sdk.SpawnInfo{} if err := gorpmapping.JSONNullString(res[i].Bytes, &spInfos); err != nil { // should never append, but log error - log.Warning("wrong spawnInfos format: res: %v for id: %v", res[i].Bytes, jobID) + log.Warning("wrong spawnInfos format: res: %v for id: %v err: %v", res[i].Bytes, jobID, err) continue } spawnInfos = append(spawnInfos, spInfos...) From 86b2a226a78419f3c107bd1903b810a2ea96b92e Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sat, 1 Dec 2018 00:12:20 +0100 Subject: [PATCH 12/17] import Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 1 + engine/api/workflow/execute_node_run.go | 1 + 2 files changed, 2 insertions(+) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index 0058263726..fe522eb960 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -8,6 +8,7 @@ import ( "time" "github.com/go-gorp/gorp" + "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/sdk" "github.com/ovh/cds/sdk/log" diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 8b0512b066..974c85bfc9 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -10,6 +10,7 @@ import ( "github.com/fsamin/go-dump" "github.com/go-gorp/gorp" + "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/group" "github.com/ovh/cds/engine/api/observability" From e127f66d6b2a5a07e7ebe23c8e548bbe5abcfe86 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sat, 1 Dec 2018 15:32:22 +0100 Subject: [PATCH 13/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- sdk/messages.go | 10 ++-- .../workflow/run/node/pipeline/pipeline.html | 4 +- .../pipeline/spawninfo/spawninfo.component.ts | 51 ++++++++----------- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/sdk/messages.go b/sdk/messages.go index 17812890a3..694ddf12de 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -63,18 +63,18 @@ var ( MsgSpawnInfoHatcheryStarts = &Message{"MsgSpawnInfoHatcheryStarts", trad{FR: "La Hatchery %s (%s) a démarré le lancement du worker avec le modèle %s", EN: "Hatchery %s (%s) starts spawn worker with model %s"}, nil} MsgSpawnInfoHatcheryStartDockerPull = &Message{"MsgSpawnInfoHatcheryStartDockerPull", trad{FR: "La Hatchery %s (%s) a démarré le docker pull de l'image %s...", EN: "Hatchery %s (%s) starts docker pull %s..."}, nil} MsgSpawnInfoHatcheryEndDockerPull = &Message{"MsgSpawnInfoHatcheryEndDockerPull", trad{FR: "La Hatchery %s (%s) a terminé le docker pull de l'image %s", EN: "Hatchery %s (%s) docker pull %s done"}, nil} - MsgSpawnInfoHatcheryEndDockerPullErr = &Message{"MsgSpawnInfoHatcheryEndDockerPullErr", trad{FR: "ATTENTION: La Hatchery %s (%s) a terminé le docker pull de l'image %s en erreur: %s", EN: "WARNING: Hatchery %s (%s) - docker pull %s done with error: %v"}, nil} + MsgSpawnInfoHatcheryEndDockerPullErr = &Message{"MsgSpawnInfoHatcheryEndDockerPullErr", trad{FR: "⚠ La Hatchery %s (%s) a terminé le docker pull de l'image %s en erreur: %s", EN: "⚠ Hatchery %s (%s) - docker pull %s done with error: %v"}, nil} MsgSpawnInfoHatcheryErrorSpawn = &Message{"MsgSpawnInfoHatcheryErrorSpawn", trad{FR: "Une erreur est survenue lorsque la Hatchery %s (%s) a démarré un worker avec le modèle %s après %s, err:%s", EN: "Error while Hatchery %s (%s) spawn worker with model %s after %s, err:%s"}, nil} MsgSpawnInfoHatcheryStartsSuccessfully = &Message{"MsgSpawnInfoHatcheryStartsSuccessfully", trad{FR: "La Hatchery %s (%s) a démarré le worker %s avec succès en %s", EN: "Hatchery %s (%s) spawn worker %s successfully in %s"}, nil} MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "Worker %s finished working on this job and took %s to work on the steps"}, nil} - MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "Le job a été pris mis en file d'attente", EN: "Job was queued"}, nil} + MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "✓ Le job a été pris mis en file d'attente", EN: "✓ Job was queued"}, nil} MsgSpawnInfoJobTaken = &Message{"MsgSpawnInfoJobTaken", trad{FR: "Le job %s a été pris par le worker %s", EN: "Job %s was taken by worker %s"}, nil} MsgSpawnInfoJobTakenWorkerVersion = &Message{"MsgSpawnInfoJobTakenWorkerVersion", trad{FR: "Worker %s version:%s os:%s arch:%s", EN: "Worker %s version:%s os:%s arch:%s"}, nil} MsgSpawnInfoWorkerForJob = &Message{"MsgSpawnInfoWorkerForJob", trad{FR: "Ce worker %s a été créé pour lancer ce job", EN: "This worker %s was created to take this action"}, nil} - MsgSpawnInfoWorkerForJobError = &Message{"MsgSpawnInfoWorkerForJobError", trad{FR: "Ce worker %s a été créé pour lancer ce job, mais ne possède pas tous les pré-requis. Vérifiez que les prérequis suivants:%s", EN: "This worker %s was created to take this action, but does not have all prerequisites. Please verify the following prerequisites:%s"}, nil} - MsgSpawnInfoJobError = &Message{"MsgSpawnInfoJobError", trad{FR: "Impossible de lancer ce job : %s", EN: "Unable to run this job: %s"}, nil} + MsgSpawnInfoWorkerForJobError = &Message{"MsgSpawnInfoWorkerForJobError", trad{FR: "⚠ Ce worker %s a été créé pour lancer ce job, mais ne possède pas tous les pré-requis. Vérifiez que les prérequis suivants:%s", EN: "⚠ This worker %s was created to take this action, but does not have all prerequisites. Please verify the following prerequisites:%s"}, nil} + MsgSpawnInfoJobError = &Message{"MsgSpawnInfoJobError", trad{FR: "⚠ Impossible de lancer ce job : %s", EN: "⚠ Unable to run this job: %s"}, nil} MsgWorkflowStarting = &Message{"MsgWorkflowStarting", trad{FR: "Le workflow %s#%s a été démarré", EN: "Workflow %s#%s has been started"}, nil} - MsgWorkflowError = &Message{"MsgWorkflowError", trad{FR: "Une erreur est survenue: %v", EN: "An error has occured: %v"}, nil} + MsgWorkflowError = &Message{"MsgWorkflowError", trad{FR: "⚠ Une erreur est survenue: %v", EN: "⚠ An error has occured: %v"}, nil} MsgWorkflowNodeStop = &Message{"MsgWorkflowNodeStop", trad{FR: "Le pipeline a été arrété par %s", EN: "The pipeline has been stopped by %s"}, nil} MsgWorkflowNodeMutex = &Message{"MsgWorkflowNodeMutex", trad{FR: "Le pipeline %s est mis en attente tant qu'il est en cours sur un autre run", EN: "The pipeline %s is waiting while it's running on another run"}, nil} MsgWorkflowNodeMutexRelease = &Message{"MsgWorkflowNodeMutexRelease", trad{FR: "Lancement du pipeline %s", EN: "Triggering pipeline %s"}, nil} diff --git a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html index b0ed2b528f..18356360ac 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/pipeline.html +++ b/ui/src/app/views/workflow/run/node/pipeline/pipeline.html @@ -52,14 +52,14 @@ </div> <div class="row"> <div class="column"> - <div class="log animated fadeIn" *ngIf="selectedRunJob && mapStepStatus"> + <div class="log animated fadeIn" *ngIf="selectedRunJob && mapStepStatus && mapJobStatus"> <ul> <li> <app-workflow-run-job-spawn-info [project]="project" [workflowName]="workflowName" [nodeRun]="nodeRun" - [job]="selectedRunJob.job" + [jobStatus]="mapJobStatus.get(selectedRunJob.job.pipeline_action_id).status" [nodeJobRun]="selectedRunJob" [variables]="selectedRunJob.parameters" [(displayServicesLogs)]="displayServiceLogs"> diff --git a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts index 2cda394da4..24bf3d147b 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts +++ b/ui/src/app/views/workflow/run/node/pipeline/spawninfo/spawninfo.component.ts @@ -4,7 +4,6 @@ import * as AU from 'ansi_up'; import { CDSWebWorker } from 'app/shared/worker/web.worker'; import { Subscription } from 'rxjs'; import { environment } from '../../../../../../../environments/environment'; -import { Job } from '../../../../../../model/job.model'; import { Parameter } from '../../../../../../model/parameter.model'; import { SpawnInfo } from '../../../../../../model/pipeline.model'; import { PipelineStatus } from '../../../../../../model/pipeline.model'; @@ -22,12 +21,16 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { @Input() project: Project; @Input() workflowName: string; + @Input() jobStatus: string; @Input() nodeRun: WorkflowNodeRun; @Input('nodeJobRun') set nodeJobRun(data: WorkflowNodeJobRun) { if (data) { this._nodeJobRun = data; - if (data.status === PipelineStatus.SUCCESS || data.status === PipelineStatus.FAIL || data.status === PipelineStatus.STOPPED) { + this.refreshDisplayServiceLogsLink(); + if (this.jobStatus === PipelineStatus.SUCCESS || + this.jobStatus === PipelineStatus.FAIL || + this.jobStatus === PipelineStatus.STOPPED) { this.stopWorker(); } } @@ -38,14 +41,6 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { spawnInfos: String; @Input() variables: Array<Parameter>; - @Input('job') - set job(data: Job) { - this._job = data; - this.refreshDisplayServiceLogsLink(); - } - get job(): Job { - return this._job - } @Input('displayServiceLogs') set displayServiceLogs(data: boolean) { this._displayServiceLogs = data; @@ -70,7 +65,6 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { show = true; displayServiceLogsLink = false; - _job: Job; _displayServiceLogs: boolean; ansi_up = new AU.default; @@ -85,8 +79,8 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { constructor(private _authStore: AuthentificationStore, private _translate: TranslateService) { } refreshDisplayServiceLogsLink() { - if (this.job && this.job.action && Array.isArray(this.job.action.requirements)) { - this.displayServiceLogsLink = this.job.action.requirements.some((req) => req.type === 'service'); + if (this.nodeJobRun.job && this.nodeJobRun.job.action && Array.isArray(this.nodeJobRun.job.action.requirements)) { + this.displayServiceLogsLink = this.nodeJobRun.job.action.requirements.some((req) => req.type === 'service'); } } @@ -94,10 +88,11 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { this.show = !this.show; } - getSpawnInfos() { + getSpawnInfos(spawnInfosIn: Array<SpawnInfo>) { + this.loading = false; let msg = ''; - if (this.nodeJobRun.spawninfos) { - this.nodeJobRun.spawninfos.forEach(s => { + if (spawnInfosIn) { + spawnInfosIn.forEach(s => { msg += '[' + s.api_time.toString().substr(0, 19) + '] ' + s.user_message + '\n'; }); } @@ -112,9 +107,10 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { this.loading = true; } - if (this.nodeJobRun.status !== PipelineStatus.WAITING && this.nodeJobRun.status !== PipelineStatus.BUILDING) { - this.spawnInfos = this.getSpawnInfos(); - this.loading = false; + if (this.jobStatus !== PipelineStatus.WAITING && this.jobStatus !== PipelineStatus.BUILDING) { + if (this.nodeJobRun.spawninfos && this.nodeJobRun.spawninfos.length > 0) { + this.spawnInfos = this.getSpawnInfos(this.nodeJobRun.spawninfos); + } return; } @@ -134,18 +130,15 @@ export class WorkflowRunJobSpawnInfoComponent implements OnInit, OnDestroy { this.workerSubscription = this.worker.response().subscribe(msg => { if (msg) { let serviceSpawnInfos: Array<SpawnInfo> = JSON.parse(msg); - if (this.loading) { - this.loading = false; + if (serviceSpawnInfos && serviceSpawnInfos.length > 0) { + this.spawnInfos = this.getSpawnInfos(serviceSpawnInfos); } - let infos = ''; - serviceSpawnInfos.forEach(s => { - infos += '[' + s.api_time.toString().substr(0, 19) + '] ' + s.user_message + '\n'; - }); - this.spawnInfos = this.ansi_up.ansi_to_html(infos); - if (this.nodeJobRun.status === PipelineStatus.SUCCESS || this.nodeJobRun.status === PipelineStatus.FAIL || - this.nodeJobRun.status === PipelineStatus.STOPPED) { + if (this.jobStatus === PipelineStatus.SUCCESS || this.jobStatus === PipelineStatus.FAIL || + this.jobStatus === PipelineStatus.STOPPED) { this.stopWorker(); - this.spawnInfos = this.getSpawnInfos(); + if (this.nodeJobRun.spawninfos && this.nodeJobRun.spawninfos.length > 0) { + this.spawnInfos = this.getSpawnInfos(this.nodeJobRun.spawninfos); + } } } }); From 6d5547e8deb489c9595e11ba746f2a128327a2e1 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sun, 2 Dec 2018 18:21:56 +0100 Subject: [PATCH 14/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/hatchery/swarm/swarm_util_create.go | 63 +++++----------------- sdk/hatchery/hatchery.go | 10 ++++ sdk/hatchery/starter.go | 57 +++++++------------- sdk/messages.go | 2 +- 4 files changed, 41 insertions(+), 91 deletions(-) diff --git a/engine/hatchery/swarm/swarm_util_create.go b/engine/hatchery/swarm/swarm_util_create.go index a4cd1e1b7e..f37328265c 100644 --- a/engine/hatchery/swarm/swarm_util_create.go +++ b/engine/hatchery/swarm/swarm_util_create.go @@ -4,7 +4,6 @@ import ( "fmt" "regexp" "strings" - "time" types "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -120,64 +119,26 @@ checkImage: } if !imageFound { - infos := []sdk.SpawnInfo{ - { - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ - ID: sdk.MsgSpawnInfoHatcheryStartDockerPull.ID, - Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, - }, - }, - } - - ctxtJobSendSpawnInfo, cancelJobSendSpawnInfo := context.WithTimeout(ctx, 10*time.Second) - if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfo, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infos); err != nil { - next() - log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) - } - cancelJobSendSpawnInfo() + hatchery.SendSpawnInfo(ctx, h, spawnArgs.IsWorkflowJob, spawnArgs.JobID, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryStartDockerPull.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, + }) _, next := observability.Span(ctx, "swarm.dockerClient.pullImage", observability.Tag("image", cArgs.image)) if err := h.pullImage(dockerClient, cArgs.image, timeoutPullImage); err != nil { next() - - infosEnd := []sdk.SpawnInfo{ - { - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ - ID: sdk.MsgSpawnInfoHatcheryEndDockerPullErr.ID, - Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image, err}, - }, - }, - } - - ctxtJobSendSpawnInfoEndDockerPullErr, cancelJobSendSpawnInfoDockerPullErr := context.WithTimeout(ctx, 10*time.Second) - if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfoEndDockerPullErr, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infosEnd); err != nil { - next() - log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) - } - cancelJobSendSpawnInfoDockerPullErr() - + hatchery.SendSpawnInfo(ctx, h, spawnArgs.IsWorkflowJob, spawnArgs.JobID, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryEndDockerPullErr.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image, err}, + }) return sdk.WrapError(err, "Unable to pull image %s on %s", cArgs.image, dockerClient.name) } next() - infosEnd := []sdk.SpawnInfo{ - { - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ - ID: sdk.MsgSpawnInfoHatcheryEndDockerPull.ID, - Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, - }, - }, - } - - ctxtJobSendSpawnInfoEndDockerPull, cancelJobSendSpawnInfoDockerPull := context.WithTimeout(ctx, 10*time.Second) - if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfoEndDockerPull, spawnArgs.IsWorkflowJob, spawnArgs.JobID, infosEnd); err != nil { - next() - log.Warning("spawnWorkerForJob> cannot client.QueueJobSendSpawnInfo for job %d: %s", spawnArgs.JobID, err) - } - cancelJobSendSpawnInfoDockerPull() + hatchery.SendSpawnInfo(ctx, h, spawnArgs.IsWorkflowJob, spawnArgs.JobID, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryEndDockerPull.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), cArgs.image}, + }) } _, next = observability.Span(ctx, "swarm.dockerClient.ContainerCreate", observability.Tag(observability.TagWorker, cArgs.name), observability.Tag("network", fmt.Sprintf("%v", networkingConfig))) diff --git a/sdk/hatchery/hatchery.go b/sdk/hatchery/hatchery.go index 108645643b..0b47e02545 100644 --- a/sdk/hatchery/hatchery.go +++ b/sdk/hatchery/hatchery.go @@ -485,6 +485,16 @@ func canRunJob(h Interface, j workerStarterRequest, model sdk.Model) bool { return h.CanSpawn(&model, j.id, j.requirements) } +// SendSpawnInfo sends a spawnInfo +func SendSpawnInfo(ctx context.Context, h Interface, isWorkflowJob bool, jobID int64, spawnMsg sdk.SpawnMsg) { + infos := []sdk.SpawnInfo{{RemoteTime: time.Now(), Message: spawnMsg}} + ctxc, cancel := context.WithTimeout(ctx, 10*time.Second) + if err := h.CDSClient().QueueJobSendSpawnInfo(ctxc, isWorkflowJob, jobID, infos); err != nil { + log.Warning("spawnWorkerForJob> cannot client.sendSpawnInfo for job %d: %s", jobID, err) + } + cancel() +} + func logTime(h Interface, name string, then time.Time) { d := time.Since(then) if d > time.Duration(h.Configuration().LogOptions.SpawnOptions.ThresholdCritical)*time.Second { diff --git a/sdk/hatchery/starter.go b/sdk/hatchery/starter.go index 3ba3416d7b..1483425f83 100644 --- a/sdk/hatchery/starter.go +++ b/sdk/hatchery/starter.go @@ -156,61 +156,40 @@ func spawnWorkerForJob(h Interface, j workerStarterRequest) (bool, error) { log.Debug("hatchery> spawnWorkerForJob> %d - send book job %d %s by hatchery %d isWorkflowJob:%t", j.timestamp, j.id, j.model.Name, h.ID(), j.isWorkflowJob) start := time.Now() - infos := []sdk.SpawnInfo{ - { - RemoteTime: start, - Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoHatcheryStarts.ID, Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), j.model.Name}}, - }, - } + SendSpawnInfo(ctx, h, j.isWorkflowJob, j.id, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryStarts.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), j.model.Name}, + }) + log.Info("hatchery> spawnWorkerForJob> SpawnWorker> starting model %s for job %d", j.model.Name, j.id) _, next = observability.Span(ctx, "hatchery.SpawnWorker") workerName, errSpawn := h.SpawnWorker(j.ctx, SpawnArguments{Model: j.model, IsWorkflowJob: j.isWorkflowJob, JobID: j.id, Requirements: j.requirements, LogInfo: "spawn for job"}) next() if errSpawn != nil { _, next = observability.Span(ctx, "hatchery.QueueJobSendSpawnInfo", observability.Tag("status", "errSpawn")) - log.Warning("spawnWorkerForJob> %d - cannot spawn worker %s for job %d: %s", j.timestamp, j.model.Name, j.id, errSpawn) - infos = append(infos, sdk.SpawnInfo{ - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoHatcheryErrorSpawn.ID, Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), j.model.Name, sdk.Round(time.Since(start), time.Second).String(), errSpawn.Error()}}, + SendSpawnInfo(ctx, h, j.isWorkflowJob, j.id, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryErrorSpawn.ID, + Args: []interface{}{h.Service().Name, fmt.Sprintf("%d", h.ID()), j.model.Name, sdk.Round(time.Since(start), time.Second).String(), errSpawn.Error()}, }) - ctxt, cancel := context.WithTimeout(ctx, 10*time.Second) - if err := h.CDSClient().QueueJobSendSpawnInfo(ctxt, j.isWorkflowJob, j.id, infos); err != nil { - log.Warning("spawnWorkerForJob> %d - cannot client.QueueJobSendSpawnInfo for job (err spawn)%d: %s", j.timestamp, j.id, err) - } log.Error("hatchery %s cannot spawn worker %s for job %d: %v", h.Service().Name, j.model.Name, j.id, errSpawn) next() - cancel() return false, nil } - infos = append(infos, sdk.SpawnInfo{ - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoHatcheryStartsSuccessfully.ID, - Args: []interface{}{ - h.Service().Name, - fmt.Sprintf("%d", h.ID()), - workerName, - sdk.Round(time.Since(start), time.Second).String()}, - }, + SendSpawnInfo(ctx, h, j.isWorkflowJob, j.id, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoHatcheryStartsSuccessfully.ID, + Args: []interface{}{ + h.Service().Name, + fmt.Sprintf("%d", h.ID()), + workerName, + sdk.Round(time.Since(start), time.Second).String()}, }) if j.model.IsDeprecated { - infos = append(infos, sdk.SpawnInfo{ - RemoteTime: time.Now(), - Message: sdk.SpawnMsg{ - ID: sdk.MsgSpawnInfoDeprecatedModel.ID, - Args: []interface{}{j.model.Name}, - }, + SendSpawnInfo(ctx, h, j.isWorkflowJob, j.id, sdk.SpawnMsg{ + ID: sdk.MsgSpawnInfoDeprecatedModel.ID, + Args: []interface{}{j.model.Name}, }) } - - ctxtJobSendSpawnInfo, cancelJobSendSpawnInfo := context.WithTimeout(ctx, 10*time.Second) - _, next = observability.Span(ctx, "hatchery.QueueJobSendSpawnInfo", observability.Tag("status", "spawnOK")) - if err := h.CDSClient().QueueJobSendSpawnInfo(ctxtJobSendSpawnInfo, j.isWorkflowJob, j.id, infos); err != nil { - next() - log.Warning("spawnWorkerForJob> %d - cannot client.QueueJobSendSpawnInfo for job %d: %s", j.timestamp, j.id, err) - } - next() - cancelJobSendSpawnInfo() return true, nil // ok for this job } diff --git a/sdk/messages.go b/sdk/messages.go index 694ddf12de..7b103f87cb 100644 --- a/sdk/messages.go +++ b/sdk/messages.go @@ -66,7 +66,7 @@ var ( MsgSpawnInfoHatcheryEndDockerPullErr = &Message{"MsgSpawnInfoHatcheryEndDockerPullErr", trad{FR: "⚠ La Hatchery %s (%s) a terminé le docker pull de l'image %s en erreur: %s", EN: "⚠ Hatchery %s (%s) - docker pull %s done with error: %v"}, nil} MsgSpawnInfoHatcheryErrorSpawn = &Message{"MsgSpawnInfoHatcheryErrorSpawn", trad{FR: "Une erreur est survenue lorsque la Hatchery %s (%s) a démarré un worker avec le modèle %s après %s, err:%s", EN: "Error while Hatchery %s (%s) spawn worker with model %s after %s, err:%s"}, nil} MsgSpawnInfoHatcheryStartsSuccessfully = &Message{"MsgSpawnInfoHatcheryStartsSuccessfully", trad{FR: "La Hatchery %s (%s) a démarré le worker %s avec succès en %s", EN: "Hatchery %s (%s) spawn worker %s successfully in %s"}, nil} - MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "Worker %s finished working on this job and took %s to work on the steps"}, nil} + MsgSpawnInfoWorkerEnd = &Message{"MsgSpawnInfoWorkerEnd", trad{FR: "✓ Le worker %s a terminé et a passé %s à travailler sur les étapes", EN: "✓ Worker %s finished working on this job and took %s to work on the steps"}, nil} MsgSpawnInfoJobInQueue = &Message{"MsgSpawnInfoJobInQueue", trad{FR: "✓ Le job a été pris mis en file d'attente", EN: "✓ Job was queued"}, nil} MsgSpawnInfoJobTaken = &Message{"MsgSpawnInfoJobTaken", trad{FR: "Le job %s a été pris par le worker %s", EN: "Job %s was taken by worker %s"}, nil} MsgSpawnInfoJobTakenWorkerVersion = &Message{"MsgSpawnInfoJobTakenWorkerVersion", trad{FR: "Worker %s version:%s os:%s arch:%s", EN: "Worker %s version:%s os:%s arch:%s"}, nil} From bac6ed417d0add1603a0c1dc082d4340bee3b0eb Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sun, 2 Dec 2018 18:25:13 +0100 Subject: [PATCH 15/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- sdk/build.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/build.go b/sdk/build.go index 8b9009109a..ee0311aea0 100644 --- a/sdk/build.go +++ b/sdk/build.go @@ -24,10 +24,9 @@ type PipelineBuildJob struct { // SpawnInfo contains an information about spawning type SpawnInfo struct { - WorkflowNodeJobRunID int64 `json:"WorkflowNodeJobRunID,omitempty" db:"-"` - APITime time.Time `json:"api_time,omitempty" db:"-" mapstructure:"-"` - RemoteTime time.Time `json:"remote_time,omitempty" db:"-" mapstructure:"-"` - Message SpawnMsg `json:"message,omitempty" db:"-"` + APITime time.Time `json:"api_time,omitempty" db:"-" mapstructure:"-"` + RemoteTime time.Time `json:"remote_time,omitempty" db:"-" mapstructure:"-"` + Message SpawnMsg `json:"message,omitempty" db:"-"` // UserMessage contains msg translated for end user UserMessage string `json:"user_message,omitempty" db:"-"` } From 409df42f7d4532cc59c4c42aebc80ce98ae25f9b Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sun, 2 Dec 2018 18:26:10 +0100 Subject: [PATCH 16/17] import Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_run.go | 1 + engine/api/workflow/execute_node_job_run.go | 1 + 2 files changed, 2 insertions(+) diff --git a/engine/api/workflow/dao_run.go b/engine/api/workflow/dao_run.go index 419c2150d0..8d3f2ec450 100644 --- a/engine/api/workflow/dao_run.go +++ b/engine/api/workflow/dao_run.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-gorp/gorp" + "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/engine/api/observability" "github.com/ovh/cds/sdk" diff --git a/engine/api/workflow/execute_node_job_run.go b/engine/api/workflow/execute_node_job_run.go index 29af29002d..a1615929d9 100644 --- a/engine/api/workflow/execute_node_job_run.go +++ b/engine/api/workflow/execute_node_job_run.go @@ -11,6 +11,7 @@ import ( "github.com/go-gorp/gorp" "github.com/lib/pq" + "github.com/ovh/cds/engine/api/application" "github.com/ovh/cds/engine/api/cache" "github.com/ovh/cds/engine/api/environment" From 346d3cb978b9a567dc86b07e7d4783d0fd3bb0c9 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick@esnau.lt> Date: Sun, 2 Dec 2018 18:29:18 +0100 Subject: [PATCH 17/17] cr Signed-off-by: Yvonnick Esnault <yvonnick@esnau.lt> --- engine/api/workflow/dao_node_job_run_info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index fe522eb960..c2949e2419 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -19,7 +19,7 @@ func LoadNodeRunJobInfo(db gorp.SqlExecutor, jobID int64) ([]sdk.SpawnInfo, erro res := []struct { Bytes sql.NullString `db:"spawninfos"` }{} - query := "SELECT workflow_node_run_job_id, spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = $1" + query := "SELECT spawninfos FROM workflow_node_run_job_info WHERE workflow_node_run_job_id = $1" if _, err := db.Select(&res, query, jobID); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -60,6 +60,6 @@ func insertNodeRunJobInfo(db gorp.SqlExecutor, info *sdk.WorkflowNodeJobRunInfo) return fmt.Errorf("insertNodeRunJobInfo> Unable to insert into workflow_node_run_job_info id = %d", info.WorkflowNodeJobRunID) } - log.Debug("insertNodeRunJobInfo> on node run: %v (%d)", info.SpawnInfos, info.WorkflowNodeJobRunID) + log.Debug("insertNodeRunJobInfo> on node run: %d (job run:%d)", info.WorkflowNodeRunID, info.WorkflowNodeJobRunID) return nil }