blob: a6e89afb8aee3f8a898ec153910d1b4f5bc273f5 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"strconv"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
ds "go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/grpc/grpcutil"
tricium "infra/tricium/api/v1"
"infra/tricium/appengine/common/gerrit"
"infra/tricium/appengine/common/track"
)
// Progress implements Tricium.Progress.
func (r *TriciumServer) Progress(c context.Context, req *tricium.ProgressRequest) (res *tricium.ProgressResponse, err error) {
defer func() {
err = grpcutil.GRPCifyAndLogErr(c, err)
}()
runID, err := validateProgressRequest(c, req)
logging.Fields{
"runID": runID,
}.Infof(c, "Request received.")
if err != nil {
return nil, err
}
// There is no run (yet) for the requested change.
if runID == 0 {
return &tricium.ProgressResponse{}, nil
}
runState, functionProgress, err := progress(c, runID)
if err != nil {
return nil, err
}
return &tricium.ProgressResponse{
RunId: strconv.FormatInt(runID, 10),
State: runState,
FunctionProgress: functionProgress,
}, nil
}
// validateProgressRequest both validates the request and parses out the run ID.
//
// If the request is invalid, an error is returned. If the request is valid, it
// also returns a run ID, or zero if there is no run.
func validateProgressRequest(c context.Context, req *tricium.ProgressRequest) (int64, error) {
switch source := req.Source.(type) {
case *tricium.ProgressRequest_RunId:
runID, err := strconv.ParseInt(source.RunId, 10, 64)
if err != nil {
return 0, errors.Annotate(err, "invalid run ID").
Tag(grpcutil.InvalidArgumentTag).Err()
}
return runID, nil
case *tricium.ProgressRequest_GerritRevision:
gr := source.GerritRevision
if gr.Host == "" {
return 0, errors.Reason("missing Gerrit host").
Tag(grpcutil.InvalidArgumentTag).Err()
}
if gr.Project == "" {
return 0, errors.Reason("missing Gerrit project").
Tag(grpcutil.InvalidArgumentTag).Err()
}
if gr.Change == "" {
return 0, errors.Reason("missing Gerrit change ID").
Tag(grpcutil.InvalidArgumentTag).Err()
}
if !changeIDPattern.MatchString(gr.Change) {
return 0, errors.Reason("invalid Gerrit change ID: " + gr.Change).
Tag(grpcutil.InvalidArgumentTag).Err()
}
if gr.GitRef == "" {
return 0, errors.Reason("missing Gerrit git ref").
Tag(grpcutil.InvalidArgumentTag).Err()
}
// Check if there is an existing run for the provided Gerrit change
// details. If so, return the run ID, otherwise return zero.
// No run ID is a very common case; it may mean that Tricium isn't
// enabled for this particular repo, or that the owner is not
// whitelisted, or just that the run hasn't started yet.
g := &GerritChangeToRunID{
ID: gerritMappingID(gr.Host, gr.Project, gr.Change, gr.GitRef),
}
if err := ds.Get(c, g); err != nil {
if err == ds.ErrNoSuchEntity {
logging.Fields{
"url": gerrit.CreateURL(gr.Host, gr.GitRef),
}.Infof(c, "No run found.")
return 0, nil
}
return 0, errors.Annotate(err, "failed to fetch run ID").
Tag(grpcutil.InternalTag).Err()
}
return g.RunID, nil
case nil:
return 0, errors.Reason("missing source").
Tag(grpcutil.InvalidArgumentTag).Err()
default:
return 0, errors.Reason("unexpected source").
Tag(grpcutil.InvalidArgumentTag).Err()
}
}
func progress(c context.Context, runID int64) (tricium.State, []*tricium.FunctionProgress, error) {
requestKey := ds.NewKey(c, "AnalyzeRequest", "", runID, nil)
requestRes := &track.AnalyzeRequestResult{ID: 1, Parent: requestKey}
if err := ds.Get(c, requestRes); err != nil {
return tricium.State_PENDING, nil,
errors.Annotate(err, "failed to get AnalyzeRequestResult for run %d", runID).
Tag(grpcutil.InvalidArgumentTag).Err()
}
workflowRun := &track.WorkflowRun{ID: 1, Parent: requestKey}
if err := ds.Get(c, workflowRun); err != nil {
return tricium.State_PENDING, nil, errors.Annotate(err, "failed to get WorkflowRun").
Tag(grpcutil.InternalTag).Err()
}
functions, err := track.FetchFunctionRuns(c, runID)
if err != nil {
return tricium.State_PENDING, nil, errors.Annotate(err, "failed to get FunctionRuns").
Tag(grpcutil.InternalTag).Err()
}
var workerResults []*track.WorkerRunResult
for _, function := range functions {
functionKey := ds.KeyForObj(c, function)
for _, workerName := range function.Workers {
workerKey := ds.NewKey(c, "WorkerRun", workerName, 0, functionKey)
workerResults = append(workerResults, &track.WorkerRunResult{ID: 1, Parent: workerKey})
}
}
if err := ds.Get(c, workerResults); err != nil && err != ds.ErrNoSuchEntity {
return tricium.State_PENDING, nil, errors.Annotate(err, "failed to get WorkerRunResults").
Tag(grpcutil.InternalTag).Err()
}
res := []*tricium.FunctionProgress{}
for _, wr := range workerResults {
p := &tricium.FunctionProgress{
Name: wr.Function,
Platform: wr.Platform,
State: wr.State,
NumComments: int32(wr.NumComments),
}
p.BuildbucketHost = workflowRun.BuildbucketServerHost
p.BuildbucketBuildId = wr.BuildbucketBuildID
res = append(res, p)
}
return requestRes.State, res, nil
}