blob: 3c1955977acb8279d954c2a8a3e9c052b94bf79d [file] [log] [blame]
// Copyright 2021 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 admin
import (
"context"
"fmt"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/emptypb"
"go.chromium.org/luci/common/tsmon/field"
"go.chromium.org/luci/common/tsmon/metric"
"go.chromium.org/luci/grpc/appstatus"
"go.chromium.org/luci/server/dsmapper"
adminpb "go.chromium.org/luci/cv/internal/rpc/admin/api"
)
var (
metricUpgraded = metric.NewCounter(
"cv/internal/dsmapper/upgraded",
"Number of Datastore entities upgraded with dsmapper",
nil,
field.String("name"), // dsmapper.ID
field.Int("id"), // dsmapper.JobID
field.String("kind"), // entity Kind
)
)
type dsMapper struct {
ctrl *dsmapper.Controller
cfgs map[dsmapper.ID]*dsmapper.JobConfig
final bool
}
func newDSMapper(ctrl *dsmapper.Controller) *dsMapper {
s := &dsMapper{ctrl: ctrl}
// Add your dsmapper factory here,
// e.g. s.register(&yourConfig, yourFactory)
// TODO(crbug/1260615): remove descriptions once CQDaemon is gone.
s.register(&removeCLDescriptionsCOnfig, removeCLDescriptionsFactory)
s.final = true
return s
}
func (d *dsMapper) register(cfg *dsmapper.JobConfig, f dsmapper.Factory) {
if d.final {
panic("register must be called in New()")
}
if err := cfg.Validate(); err != nil {
panic(err)
}
if f == nil {
panic("dsmapper.Factory must not be nil")
}
d.ctrl.RegisterFactory(cfg.Mapper, f) // panics if id not unique
if d.cfgs == nil {
d.cfgs = make(map[dsmapper.ID]*dsmapper.JobConfig, 1)
}
d.cfgs[cfg.Mapper] = cfg
}
func (a *AdminServer) DSMLaunchJob(ctx context.Context, req *adminpb.DSMLaunchJobRequest) (resp *adminpb.DSMJobID, err error) {
defer func() { err = appstatus.GRPCifyAndLog(ctx, err) }()
if err = checkAllowed(ctx, "DSMLaunchJob"); err != nil {
return
}
cfg, exists := a.dsmapper.cfgs[dsmapper.ID(req.GetName())]
if !exists {
var sb strings.Builder
_, _ = fmt.Fprintf(&sb, "Name %q is not pregistered; %d known names are: ", req.GetName(), len(a.dsmapper.cfgs))
for name := range a.dsmapper.cfgs {
sb.WriteString(string(name))
sb.WriteRune(',')
}
return nil, appstatus.Error(codes.NotFound, strings.TrimRight(sb.String(), ","))
}
jobID, err := a.dsmapper.ctrl.LaunchJob(ctx, cfg)
if err != nil {
return nil, err
}
return &adminpb.DSMJobID{Id: int64(jobID)}, nil
}
func (a *AdminServer) DSMGetJob(ctx context.Context, req *adminpb.DSMJobID) (_ *adminpb.DSMJob, err error) {
defer func() { err = appstatus.GRPCifyAndLog(ctx, err) }()
if err = checkAllowed(ctx, "DSMGetJob"); err != nil {
return
}
id := dsmapper.JobID(req.GetId())
job, err := a.dsmapper.ctrl.GetJob(ctx, id)
switch {
case err == dsmapper.ErrNoSuchJob:
return nil, appstatus.Error(codes.NotFound, "not found")
case err != nil:
return nil, err
}
info, err := job.FetchInfo(ctx)
if err != nil {
return nil, err
}
return &adminpb.DSMJob{
Name: string(job.Config.Mapper),
Info: info,
}, nil
}
func (a *AdminServer) DSMAbortJob(ctx context.Context, req *adminpb.DSMJobID) (_ *emptypb.Empty, err error) {
defer func() { err = appstatus.GRPCifyAndLog(ctx, err) }()
if err = checkAllowed(ctx, "DSMAbortJob"); err != nil {
return
}
id := dsmapper.JobID(req.GetId())
switch _, err := a.dsmapper.ctrl.AbortJob(ctx, id); {
case err == dsmapper.ErrNoSuchJob:
return nil, appstatus.Error(codes.NotFound, "not found")
case err != nil:
return nil, err
default:
return &emptypb.Empty{}, nil
}
}