blob: daa5a0378e49f624338af544cb8bee5521eed8da [file] [log] [blame]
package service
import (
"context"
"fmt"
"slices"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/perf/go/anomalygroup"
v1 "go.skia.org/infra/perf/go/anomalygroup/proto/v1"
"go.skia.org/infra/perf/go/backend/shared"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/culprit"
"go.skia.org/infra/perf/go/culprit/notify"
pb "go.skia.org/infra/perf/go/culprit/proto/v1"
"go.skia.org/infra/perf/go/subscription"
sub_pb "go.skia.org/infra/perf/go/subscription/proto/v1"
"google.golang.org/grpc"
)
// culpritService implements CulpritService
type culpritService struct {
pb.UnimplementedCulpritServiceServer
anomalygroupStore anomalygroup.Store
culpritStore culprit.Store
subscriptionStore subscription.Store
notifier notify.CulpritNotifier
config *config.InstanceConfig
}
// New returns a new instance of culpritService.
func New(anomalygroupStore anomalygroup.Store, culpritStore culprit.Store, subscriptionStore subscription.Store,
notifier notify.CulpritNotifier, cfg *config.InstanceConfig) *culpritService {
return &culpritService{
anomalygroupStore: anomalygroupStore,
culpritStore: culpritStore,
subscriptionStore: subscriptionStore,
notifier: notifier,
config: cfg,
}
}
// RegisterGrpc implements backend.BackendService
func (s *culpritService) RegisterGrpc(server *grpc.Server) {
pb.RegisterCulpritServiceServer(server, s)
}
// GetAuthorizationPolicy implements backend.BackendService
func (s *culpritService) GetAuthorizationPolicy() shared.AuthorizationPolicy {
// TODO(pasthana): Add proper authorization policy
return shared.AuthorizationPolicy{
AllowUnauthenticated: true,
}
}
// GetServiceDescriptor implements backend.BackendService
func (s *culpritService) GetServiceDescriptor() grpc.ServiceDesc {
return pb.CulpritService_ServiceDesc
}
func (s *culpritService) PersistCulprit(ctx context.Context, req *pb.PersistCulpritRequest) (*pb.PersistCulpritResponse, error) {
ids, err := s.culpritStore.Upsert(ctx, req.AnomalyGroupId, req.Commits)
if err != nil {
return nil, err
}
err = s.anomalygroupStore.AddCulpritIDs(ctx, req.AnomalyGroupId, ids)
if err != nil {
return nil, err
}
return &pb.PersistCulpritResponse{CulpritIds: ids}, nil
}
func (s *culpritService) GetCulprit(context context.Context, req *pb.GetCulpritRequest) (*pb.GetCulpritResponse, error) {
culprits, err := s.culpritStore.Get(context, req.CulpritIds)
if err != nil {
return nil, err
}
return &pb.GetCulpritResponse{
Culprits: culprits,
}, nil
}
// File bugs per culprit for the anomaly group (from a bisect)
func (s *culpritService) NotifyUserOfCulprit(ctx context.Context, req *pb.NotifyUserOfCulpritRequest) (*pb.NotifyUserOfCulpritResponse, error) {
var err error
culprits, err := s.culpritStore.Get(ctx, req.CulpritIds)
sklog.Debugf("[CP] %d culprits loaded by %s.", len(culprits), req.CulpritIds)
if err != nil {
return nil, err
}
anomalygroup, err := s.anomalygroupStore.LoadById(ctx, req.AnomalyGroupId)
if err != nil {
return nil, err
}
subscription, err := s.subscriptionStore.GetSubscription(ctx, anomalygroup.SubsciptionName, anomalygroup.SubscriptionRevision)
if err != nil {
return nil, err
}
// TODO(wenbinzhang): clean up mocks
// mock subscription before the sheriff config is ready for production.
subscription = PrepareSubscription(subscription, anomalygroup, s.config, "Culprit")
issueIds := make([]string, 0)
for _, culprit := range culprits {
sklog.Debugf("[CP] Processing culprit %s.", culprit.Id)
issueId, err := s.notifier.NotifyCulpritFound(ctx, culprit, subscription)
if err != nil {
return nil, err
}
err = s.culpritStore.AddIssueId(ctx, culprit.Id, issueId, req.AnomalyGroupId)
if err != nil {
return nil, err
}
issueIds = append(issueIds, issueId)
}
return &pb.NotifyUserOfCulpritResponse{IssueIds: issueIds}, nil
}
// File a bug to report a list of anomalies.
func (s *culpritService) NotifyUserOfAnomaly(ctx context.Context, req *pb.NotifyUserOfAnomalyRequest) (*pb.NotifyUserOfAnomalyResponse, error) {
sklog.Debug("Notifying user of anomaly group: %s.", req.AnomalyGroupId)
var err error
anomalygroup, err := s.anomalygroupStore.LoadById(ctx, req.AnomalyGroupId)
if err != nil {
return nil, err
}
subscription, err := s.subscriptionStore.GetSubscription(ctx, anomalygroup.SubsciptionName, anomalygroup.SubscriptionRevision)
if err != nil {
return nil, err
}
// TODO(wenbinzhang): clean up mocks
// mock subscription before the sheriff config is ready for production.
subscription = PrepareSubscription(subscription, anomalygroup, s.config, "Report")
issueId, err := s.notifier.NotifyAnomaliesFound(ctx, anomalygroup, subscription, req.Anomaly)
if err != nil {
return nil, err
}
return &pb.NotifyUserOfAnomalyResponse{IssueId: issueId}, nil
}
// Temporary helper to make up a subscription or certain fields for testing purposes.
func PrepareSubscription(sub *sub_pb.Subscription, ag *v1.AnomalyGroup, config *config.InstanceConfig, suffix string) *sub_pb.Subscription {
if sub == nil {
// If no subscription is loaded, use a fake subscirption.
sklog.Debugf("Cannot load subscription. Using mock. Name: %s, Revision: %s", ag.SubsciptionName, ag.SubscriptionRevision)
sub = &sub_pb.Subscription{
Name: fmt.Sprintf("Mocked Sub For Anomaly - %s", suffix),
Revision: fmt.Sprintf("Mocked Revision - %s", suffix),
BugLabels: []string{"Mocked Sub Label"},
Hotlists: []string{"5141966"},
BugComponent: "1325852",
BugPriority: 2,
BugSeverity: 3,
BugCcEmails: []string{"wenbinzhang@google.com"},
ContactEmail: "wenbinzhang@google.com",
}
} else if config != nil && !slices.Contains(config.SheriffConfigsToNotify, sub.Name) {
// If a subscription is loaded, but it is not in the allowlist, update the fields to avoid notifing end users.
sklog.Debugf("Loaded subscription. Overwriting it. Name: %s, Revision: %s", sub.Name, sub.Revision)
sub.BugLabels = []string{"Mocked Sub Label - overwrite"}
sub.Hotlists = []string{"5141966"}
sub.BugComponent = "1325852"
sub.BugCcEmails = []string{"wenbinzhang@google.com"}
sub.ContactEmail = "wenbinzhang@google.com"
}
return sub
}