| 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 |
| } |