blob: 011b99da72b77303af232e079a162f493c2d5be0 [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"context"
"regexp"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/resultdb/rdbperms"
"go.chromium.org/luci/server/span"
"go.chromium.org/luci/analysis/internal/config"
"go.chromium.org/luci/analysis/internal/perms"
"go.chromium.org/luci/analysis/internal/testresults"
"go.chromium.org/luci/analysis/pbutil"
pb "go.chromium.org/luci/analysis/proto/v1"
)
var variantHashRe = regexp.MustCompile("^[0-9a-f]{16}$")
// testVariantsServer implements pb.TestVariantServer.
type testVariantsServer struct {
}
// NewTestVariantsServer returns a new pb.TestVariantServer.
func NewTestVariantsServer() pb.TestVariantsServer {
return &pb.DecoratedTestVariants{
Prelude: checkAllowedPrelude,
Service: &testVariantsServer{},
Postlude: gRPCifyAndLogPostlude,
}
}
// QueryFailureRate queries the failure rate of specified test variants,
// returning signals indicating if the test variant is flaky and/or
// deterministically failing.
func (*testVariantsServer) QueryFailureRate(ctx context.Context, req *pb.QueryTestVariantFailureRateRequest) (*pb.QueryTestVariantFailureRateResponse, error) {
now := clock.Now(ctx)
if err := validateQueryTestVariantFailureRateRequest(req); err != nil {
return nil, invalidArgumentError(err)
}
opts := testresults.QueryFailureRateOptions{
Project: req.Project,
TestVariants: req.TestVariants,
AsAtTime: now,
}
var err error
// Query all subrealms the caller can see test results in.
const subRealm = ""
opts.SubRealms, err = perms.QuerySubRealmsNonEmpty(ctx, req.Project, subRealm, nil, rdbperms.PermListTestResults)
if err != nil {
return nil, err
}
ctx, cancel := span.ReadOnlyTransaction(ctx)
defer cancel()
response, err := testresults.QueryFailureRate(ctx, opts)
if err != nil {
return nil, err
}
return response, nil
}
func validateQueryTestVariantFailureRateRequest(req *pb.QueryTestVariantFailureRateRequest) error {
// MaxTestVariants is the maximum number of test variants to be queried in one request.
const MaxTestVariants = 100
if req.Project == "" {
return errors.Reason("project missing").Err()
}
if !config.ProjectRe.MatchString(req.Project) {
return errors.Reason("project is invalid, expected %s", config.ProjectRePattern).Err()
}
if len(req.TestVariants) == 0 {
return errors.Reason("test_variants missing").Err()
}
if len(req.TestVariants) > MaxTestVariants {
return errors.Reason("test_variants: no more than %v may be queried at a time", MaxTestVariants).Err()
}
type testVariant struct {
testID string
variantHash string
}
uniqueTestVariants := make(map[testVariant]struct{})
for i, tv := range req.TestVariants {
if tv.GetTestId() == "" {
return errors.Reason("test_variants[%v]: test_id missing", i).Err()
}
var variantHash string
if tv.VariantHash != "" {
if !variantHashRe.MatchString(tv.VariantHash) {
return errors.Reason("test_variants[%v]: variant_hash is not valid", i).Err()
}
variantHash = tv.VariantHash
}
// Variant may be nil as not all tests have variants.
if tv.Variant != nil || tv.VariantHash == "" {
calculatedHash := pbutil.VariantHash(tv.Variant)
if tv.VariantHash != "" && calculatedHash != tv.VariantHash {
return errors.Reason("test_variants[%v]: variant and variant_hash mismatch, variant hashed to %s, expected %s", i, calculatedHash, tv.VariantHash).Err()
}
variantHash = calculatedHash
}
key := testVariant{testID: tv.TestId, variantHash: variantHash}
if _, ok := uniqueTestVariants[key]; ok {
return errors.Reason("test_variants[%v]: already requested in the same request", i).Err()
}
uniqueTestVariants[key] = struct{}{}
}
return nil
}