| package rpc |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "sort" |
| "sync" |
| "time" |
| |
| "github.com/twitchtv/twirp" |
| "go.skia.org/infra/autoroll/go/config" |
| "go.skia.org/infra/autoroll/go/config/db" |
| "go.skia.org/infra/autoroll/go/manual" |
| "go.skia.org/infra/autoroll/go/modes" |
| "go.skia.org/infra/autoroll/go/recent_rolls" |
| "go.skia.org/infra/autoroll/go/revision" |
| "go.skia.org/infra/autoroll/go/roller_cleanup" |
| "go.skia.org/infra/autoroll/go/status" |
| "go.skia.org/infra/autoroll/go/strategy" |
| "go.skia.org/infra/autoroll/go/unthrottle" |
| "go.skia.org/infra/go/alogin" |
| "go.skia.org/infra/go/autoroll" |
| "go.skia.org/infra/go/firestore" |
| "go.skia.org/infra/go/now" |
| "go.skia.org/infra/go/skerr" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/go/twirp_auth2" |
| "go.skia.org/infra/go/util" |
| "google.golang.org/grpc/codes" |
| grpcstatus "google.golang.org/grpc/status" |
| "google.golang.org/protobuf/types/known/timestamppb" |
| ) |
| |
| // Generate the go code from the protocol buffer definitions. |
| //go:generate bazelisk run --config=mayberemote //:protoc -- --go_opt=paths=source_relative --twirp_out=. --go_out=. ./rpc.proto |
| //go:generate mv ./go.skia.org/infra/autoroll/go/rpc/rpc.twirp.go ./rpc.twirp.go |
| //go:generate rm -rf ./go.skia.org |
| //go:generate bazelisk run --config=mayberemote //:goimports "--run_under=cd $PWD &&" -- -w rpc.pb.go |
| //go:generate bazelisk run --config=mayberemote //:goimports "--run_under=cd $PWD &&" -- -w rpc.twirp.go |
| //go:generate bazelisk run --config=mayberemote //:protoc -- --twirp_typescript_out=../../modules/rpc ./rpc.proto |
| |
| // timeNowFunc allows tests to mock out time.Now() for testing. |
| var timeNowFunc = time.Now |
| |
| // loadRollersFunc allows tests to mock out loadRollers() for testing. |
| var loadRollersFunc = loadRollers |
| |
| // AutoRollServer implements AutoRollRPCs. |
| type AutoRollServer struct { |
| *twirp_auth2.AuthHelper |
| cancelPolling context.CancelFunc |
| cleanupDB roller_cleanup.DB |
| handler http.Handler |
| manualRollDB manual.DB |
| throttle unthrottle.Throttle |
| rollers map[string]*AutoRoller |
| rollersMtx sync.RWMutex |
| rollsDB recent_rolls.DB |
| } |
| |
| // GetHandler returns the http.Handler for this AutoRollServer. |
| func (s *AutoRollServer) GetHandler() http.Handler { |
| return s.handler |
| } |
| |
| // NewAutoRollServer returns an AutoRollServer instance. |
| // If configRefreshInterval is zero, the configs are not refreshed. |
| func NewAutoRollServer(ctx context.Context, statusDB status.DB, configDB db.DB, rollsDB recent_rolls.DB, manualRollDB manual.DB, cleanupDB roller_cleanup.DB, throttle unthrottle.Throttle, configRefreshInterval time.Duration, plogin alogin.Login) (*AutoRollServer, error) { |
| rollers, cancelPolling, err := loadRollersFunc(ctx, statusDB, configDB) |
| if err != nil { |
| return nil, skerr.Wrapf(err, "failed to load roller configs from DB") |
| } |
| srv := &AutoRollServer{ |
| AuthHelper: twirp_auth2.New(), |
| cancelPolling: cancelPolling, |
| cleanupDB: cleanupDB, |
| manualRollDB: manualRollDB, |
| throttle: throttle, |
| rollers: rollers, |
| rollsDB: rollsDB, |
| } |
| |
| srv.handler = alogin.StatusMiddleware(plogin)(NewAutoRollServiceServer(srv, nil)) |
| if configRefreshInterval != time.Duration(0) { |
| go util.RepeatCtx(ctx, configRefreshInterval, func(ctx context.Context) { |
| rollers, cancelPolling, err = loadRollersFunc(ctx, statusDB, configDB) |
| if err != nil { |
| sklog.Errorf("Failed to refresh rollers: %s", err) |
| return |
| } |
| srv.rollersMtx.Lock() |
| defer srv.rollersMtx.Unlock() |
| srv.cancelPolling() |
| srv.rollers = rollers |
| srv.cancelPolling = cancelPolling |
| }) |
| } |
| return srv, nil |
| } |
| |
| // oldRollerConfigDeletionThreshold indicates that we'll delete a roller config |
| // from the DB if we fail to decode it and it's been two weeks since it last |
| // reported in. We assume that if the roller hasn't reported in for two weeks it |
| // has been removed. |
| const oldRollerConfigDeletionThreshold = 2 * 7 * 24 * time.Hour |
| |
| // loadRollerConfigs loads the roller configs from the config DB. If it |
| // encounters errors for rollers which haven't reported their status for two |
| // weeks, it may delete their configs from the DB. |
| func loadRollerConfigs(ctx context.Context, statusDB status.DB, configDB db.DB) ([]*config.Config, error) { |
| var configs []*config.Config |
| var err error |
| for { |
| configs, err = configDB.GetAll(ctx) |
| if err == nil { |
| return configs, nil |
| } else { |
| // The config format might have changed, causing a failure to decode |
| // configs for defunct rollers. Determine whether the roller is likely |
| // turned down and delete the config from the DB if so. |
| if failedDecodeRoller, ok := db.IsFailedDecode(err); ok { |
| st, getStatusErr := statusDB.Get(ctx, failedDecodeRoller) |
| if getStatusErr != nil { |
| // Treat rollers which never checked in as defunct. |
| if grpcstatus.Code(skerr.Unwrap(getStatusErr)) == codes.NotFound { |
| st = &status.AutoRollStatus{ |
| AutoRollMiniStatus: status.AutoRollMiniStatus{ |
| Timestamp: time.Time{}, |
| }, |
| } |
| } else { |
| return nil, skerr.Wrapf(err, "failed to decode roller config %s and failed to retrieve status: %s", failedDecodeRoller, getStatusErr) |
| } |
| } |
| lastCheckin := now.Now(ctx).Sub(st.Timestamp) |
| if lastCheckin > oldRollerConfigDeletionThreshold { |
| sklog.Errorf("Failed to decode config for %s; last checked in %s ago; deleting config...", failedDecodeRoller, lastCheckin) |
| if err := configDB.Delete(ctx, failedDecodeRoller); err != nil { |
| return nil, skerr.Wrapf(err, "failed to decode config for defunct roller %s and failed to delete config from the DB", failedDecodeRoller) |
| } |
| } else { |
| return nil, skerr.Wrap(err) |
| } |
| } else { |
| return nil, skerr.Wrap(err) |
| } |
| } |
| } |
| } |
| |
| // loadRollers loads the roller configs from the config DB and creates the |
| // various databases used for each roller. Returns a map containing the rollers |
| // themselves and a context.CancelFunc which can be used to stop the polling |
| // loops for the rollers, eg. when loadRollers is to be called again. |
| func loadRollers(ctx context.Context, statusDB status.DB, configDB db.DB) (rv map[string]*AutoRoller, rvCancel context.CancelFunc, rvErr error) { |
| configs, err := loadRollerConfigs(ctx, statusDB, configDB) |
| if err != nil { |
| return nil, nil, skerr.Wrap(err) |
| } |
| |
| rollers := make(map[string]*AutoRoller, len(configs)) |
| // Use a cancellable context so that we can restart the polling loops when |
| // we reload the rollers next time. |
| cancellableCtx, cancel := context.WithCancel(ctx) |
| defer func() { |
| // If something went wrong, cancel the polling loops to avoid a |
| // goroutine leak. |
| if rvErr != nil { |
| cancel() |
| } |
| }() |
| for _, cfg := range configs { |
| // Set up DBs for the roller. |
| cfg := cfg // Capture loop variable for use by goroutines. |
| arbMode, err := modes.NewDatastoreModeHistory(ctx, cfg.RollerName) |
| if err != nil { |
| return nil, nil, skerr.Wrap(err) |
| } |
| go util.RepeatCtx(cancellableCtx, 10*time.Second, func(_ context.Context) { |
| if err := arbMode.Update(ctx); err != nil { |
| sklog.Errorf("Failed to retrieve mode history for %s: %s", cfg.RollerName, err) |
| } |
| }) |
| arbStatus, err := status.NewCache(ctx, statusDB, cfg.RollerName) |
| if err != nil { |
| return nil, nil, skerr.Wrap(err) |
| } |
| go util.RepeatCtx(cancellableCtx, 10*time.Second, func(_ context.Context) { |
| if err := arbStatus.Update(ctx); err != nil { |
| sklog.Errorf("Failed to retrieve status for %s: %s", cfg.RollerName, err) |
| } |
| }) |
| arbStrategy, err := strategy.NewDatastoreStrategyHistory(ctx, cfg.RollerName, cfg.ValidStrategies()) |
| if err != nil { |
| return nil, nil, skerr.Wrap(err) |
| } |
| go util.RepeatCtx(cancellableCtx, 10*time.Second, func(_ context.Context) { |
| if err := arbStrategy.Update(ctx); err != nil { |
| sklog.Errorf("Failed to retrieve strategy history for %s: %s", cfg.RollerName, err) |
| } |
| }) |
| rollers[cfg.RollerName] = &AutoRoller{ |
| Cfg: cfg, |
| Mode: arbMode, |
| Status: arbStatus, |
| Strategy: arbStrategy, |
| } |
| } |
| return rollers, cancel, nil |
| } |
| |
| // GetRoller retrieves the given roller. |
| func (s *AutoRollServer) GetRoller(roller string) (*AutoRoller, error) { |
| s.rollersMtx.RLock() |
| defer s.rollersMtx.RUnlock() |
| rv, ok := s.rollers[roller] |
| if !ok { |
| return nil, twirp.NewError(twirp.NotFound, "Unknown roller") |
| } |
| return rv, nil |
| } |
| |
| // Helper for sorting AutoRollMiniStatuses. |
| type autoRollMiniStatusSlice []*AutoRollMiniStatus |
| |
| func (s autoRollMiniStatusSlice) Len() int { |
| return len(s) |
| } |
| |
| func (s autoRollMiniStatusSlice) Less(a, b int) bool { |
| return s[a].RollerId < s[b].RollerId |
| } |
| |
| func (s autoRollMiniStatusSlice) Swap(a, b int) { |
| s[a], s[b] = s[b], s[a] |
| } |
| |
| // GetRollers implements AutoRollRPCs. |
| func (s *AutoRollServer) GetRollers(ctx context.Context, req *GetRollersRequest) (*GetRollersResponse, error) { |
| s.rollersMtx.RLock() |
| defer s.rollersMtx.RUnlock() |
| statuses := make([]*AutoRollMiniStatus, 0, len(s.rollers)) |
| for name, roller := range s.rollers { |
| mc := roller.Mode.CurrentMode() |
| mode := modes.ModeRunning |
| if mc != nil { |
| mode = mc.Mode |
| } |
| st, err := convertMiniStatus(roller.Status.GetMini(), name, mode, roller.Cfg.ChildDisplayName, roller.Cfg.ParentDisplayName) |
| if err != nil { |
| return nil, err |
| } |
| statuses = append(statuses, st) |
| } |
| // Sort for testing. |
| sort.Sort(autoRollMiniStatusSlice(statuses)) |
| return &GetRollersResponse{ |
| Rollers: statuses, |
| }, nil |
| } |
| |
| // GetRolls implements AutoRollRPCs. |
| func (s *AutoRollServer) GetRolls(ctx context.Context, req *GetRollsRequest) (*GetRollsResponse, error) { |
| if _, err := s.GetRoller(req.RollerId); err != nil { |
| return nil, err |
| } |
| rolls, cursor, err := s.rollsDB.GetRolls(ctx, req.RollerId, req.Cursor) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| rollsConv, err := convertRollCLs(rolls) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return &GetRollsResponse{ |
| Rolls: rollsConv, |
| Cursor: cursor, |
| }, nil |
| } |
| |
| // GetMiniStatus implements AutoRollRPCs. |
| func (s *AutoRollServer) GetMiniStatus(ctx context.Context, req *GetMiniStatusRequest) (*GetMiniStatusResponse, error) { |
| roller, err := s.GetRoller(req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| ms, err := convertMiniStatus(roller.Status.GetMini(), req.RollerId, roller.Mode.CurrentMode().Mode, roller.Cfg.ChildDisplayName, roller.Cfg.ParentDisplayName) |
| if err != nil { |
| return nil, err |
| } |
| return &GetMiniStatusResponse{ |
| Status: ms, |
| }, nil |
| } |
| |
| // getStatus retrieves the status for the given roller. |
| func (s *AutoRollServer) getStatus(ctx context.Context, rollerID string) (*AutoRollStatus, error) { |
| roller, err := s.GetRoller(rollerID) |
| if err != nil { |
| return nil, err |
| } |
| st := roller.Status.Get() |
| var manualReqs []*manual.ManualRollRequest |
| if roller.Cfg.SupportsManualRolls { |
| manualReqs, err = s.manualRollDB.GetRecent(roller.Cfg.RollerName, recent_rolls.RecentRollsLength) |
| if err != nil { |
| return nil, err |
| } |
| } |
| cleanup, err := s.cleanupDB.History(ctx, rollerID, 1) |
| if err != nil { |
| return nil, err |
| } |
| return convertStatus(st, roller.Cfg, roller.Mode.CurrentMode(), roller.Strategy.CurrentStrategy(), manualReqs, cleanup) |
| } |
| |
| // GetStatus implements AutoRollRPCs. |
| func (s *AutoRollServer) GetStatus(ctx context.Context, req *GetStatusRequest) (*GetStatusResponse, error) { |
| st, err := s.getStatus(ctx, req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| return &GetStatusResponse{ |
| Status: st, |
| }, nil |
| } |
| |
| // SetMode implements AutoRollRPCs. |
| func (s *AutoRollServer) SetMode(ctx context.Context, req *SetModeRequest) (*SetModeResponse, error) { |
| // Verify that the user has edit access. |
| user, err := s.GetEditor(ctx) |
| if err != nil { |
| return nil, err |
| } |
| roller, err := s.GetRoller(req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| var mode string |
| switch req.Mode { |
| case Mode_RUNNING: |
| mode = modes.ModeRunning |
| case Mode_STOPPED: |
| mode = modes.ModeStopped |
| case Mode_DRY_RUN: |
| mode = modes.ModeDryRun |
| case Mode_OFFLINE: |
| mode = modes.ModeOffline |
| default: |
| return nil, twirp.InvalidArgumentError("mode", "invalid mode") |
| } |
| if len(roller.Cfg.ValidModes) > 0 { |
| // Note: this assumes that the Mode enums in rpc.proto and config.proto |
| // are in sync. |
| modeConv := config.Mode(req.Mode) |
| isValidMode := false |
| validModeStrs := make([]string, 0, len(roller.Cfg.ValidModes)) |
| for _, validMode := range roller.Cfg.ValidModes { |
| validModeStrs = append(validModeStrs, validMode.String()) |
| if modeConv == validMode { |
| isValidMode = true |
| } |
| } |
| if !isValidMode { |
| return nil, twirp.InvalidArgumentError("mode", fmt.Sprintf("requested mode is not allowed for this roller; valid modes: %v", validModeStrs)) |
| } |
| } |
| if err := roller.Mode.Add(ctx, mode, user, req.Message); err != nil { |
| return nil, err |
| } |
| st, err := s.getStatus(ctx, req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| return &SetModeResponse{ |
| Status: st, |
| }, nil |
| } |
| |
| // GetModeHistory implements AutoRollRPCs. |
| func (s *AutoRollServer) GetModeHistory(ctx context.Context, req *GetModeHistoryRequest) (*GetModeHistoryResponse, error) { |
| roller, err := s.GetRoller(req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| history, nextOffset, err := roller.Mode.GetHistory(ctx, int(req.Offset)) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| historyConv := make([]*ModeChange, 0, len(history)) |
| for _, entry := range history { |
| mc, err := convertModeChange(entry) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| historyConv = append(historyConv, mc) |
| } |
| return &GetModeHistoryResponse{ |
| History: historyConv, |
| NextOffset: int32(nextOffset), |
| }, nil |
| } |
| |
| // SetStrategy implements AutoRollRPCs. |
| func (s *AutoRollServer) SetStrategy(ctx context.Context, req *SetStrategyRequest) (*SetStrategyResponse, error) { |
| // Verify that the user has edit access. |
| user, err := s.GetEditor(ctx) |
| if err != nil { |
| return nil, err |
| } |
| roller, err := s.GetRoller(req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| var strat string |
| switch req.Strategy { |
| case Strategy_BATCH: |
| strat = strategy.ROLL_STRATEGY_BATCH |
| case Strategy_N_BATCH: |
| strat = strategy.ROLL_STRATEGY_N_BATCH |
| case Strategy_SINGLE: |
| strat = strategy.ROLL_STRATEGY_SINGLE |
| default: |
| return nil, twirp.InvalidArgumentError("strategy", "invalid strategy") |
| } |
| if err := roller.Strategy.Add(ctx, strat, user, req.Message); err != nil { |
| return nil, err |
| } |
| st, err := s.getStatus(ctx, req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| return &SetStrategyResponse{ |
| Status: st, |
| }, nil |
| } |
| |
| // GetStrategyHistory implements AutoRollRPCs. |
| func (s *AutoRollServer) GetStrategyHistory(ctx context.Context, req *GetStrategyHistoryRequest) (*GetStrategyHistoryResponse, error) { |
| roller, err := s.GetRoller(req.RollerId) |
| if err != nil { |
| return nil, err |
| } |
| history, nextOffset, err := roller.Strategy.GetHistory(ctx, int(req.Offset)) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| historyConv := make([]*StrategyChange, 0, len(history)) |
| for _, entry := range history { |
| mc, err := convertStrategyChange(entry) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| historyConv = append(historyConv, mc) |
| } |
| return &GetStrategyHistoryResponse{ |
| History: historyConv, |
| NextOffset: int32(nextOffset), |
| }, nil |
| } |
| |
| // CreateManualRoll implements AutoRollRPCs. |
| func (s *AutoRollServer) CreateManualRoll(ctx context.Context, req *CreateManualRollRequest) (*CreateManualRollResponse, error) { |
| // Verify that the user has edit access. |
| user, err := s.GetEditor(ctx) |
| if err != nil { |
| return nil, err |
| } |
| // Check that the roller exists. |
| if _, err := s.GetRoller(req.RollerId); err != nil { |
| return nil, err |
| } |
| m := &manual.ManualRollRequest{ |
| RollerName: req.RollerId, |
| Revision: req.Revision, |
| Requester: user, |
| DryRun: req.DryRun, |
| } |
| m.Status = manual.STATUS_PENDING |
| m.Timestamp = firestore.FixTimestamp(timeNowFunc()) |
| if err := s.manualRollDB.Put(m); err != nil { |
| return nil, err |
| } |
| resp, err := convertManualRollRequest(m) |
| if err != nil { |
| return nil, err |
| } |
| return &CreateManualRollResponse{ |
| Roll: resp, |
| }, nil |
| } |
| |
| // Unthrottle implements AutoRollRPCs. |
| func (s *AutoRollServer) Unthrottle(ctx context.Context, req *UnthrottleRequest) (*UnthrottleResponse, error) { |
| // Verify that the user has edit access. |
| if _, err := s.GetEditor(ctx); err != nil { |
| return nil, err |
| } |
| // Check that the roller exists. |
| if _, err := s.GetRoller(req.RollerId); err != nil { |
| return nil, err |
| } |
| if err := s.throttle.Unthrottle(ctx, req.RollerId); err != nil { |
| return nil, err |
| } |
| return &UnthrottleResponse{}, nil |
| } |
| |
| // AddCleanupRequest implements AutoRollRPCs. |
| func (s *AutoRollServer) AddCleanupRequest(ctx context.Context, req *AddCleanupRequestRequest) (*AddCleanupRequestResponse, error) { |
| // Verify that the user has edit access. |
| user, err := s.GetEditor(ctx) |
| if err != nil { |
| return nil, err |
| } |
| // Check that the roller exists. |
| if _, err := s.GetRoller(req.RollerId); err != nil { |
| return nil, err |
| } |
| cr := &roller_cleanup.CleanupRequest{ |
| RollerID: req.RollerId, |
| NeedsCleanup: true, |
| User: user, |
| Timestamp: firestore.FixTimestamp(timeNowFunc()), |
| Justification: req.Justification, |
| } |
| if err := s.cleanupDB.RequestCleanup(ctx, cr); err != nil { |
| return nil, err |
| } |
| return &AddCleanupRequestResponse{}, nil |
| } |
| |
| // GetCleanupHistory implements AutoRollRPCs. |
| func (s *AutoRollServer) GetCleanupHistory(ctx context.Context, req *GetCleanupHistoryRequest) (*GetCleanupHistoryResponse, error) { |
| // Check that the roller exists. |
| if _, err := s.GetRoller(req.RollerId); err != nil { |
| return nil, err |
| } |
| history, err := s.cleanupDB.History(ctx, req.RollerId, int(req.Limit)) |
| if err != nil { |
| return nil, err |
| } |
| rv := make([]*CleanupRequest, 0, len(history)) |
| for _, cr := range history { |
| rv = append(rv, &CleanupRequest{ |
| NeedsCleanup: cr.NeedsCleanup, |
| User: cr.User, |
| Timestamp: timestamppb.New(cr.Timestamp), |
| Justification: cr.Justification, |
| }) |
| } |
| return &GetCleanupHistoryResponse{ |
| History: rv, |
| }, nil |
| } |
| |
| // AutoRoller provides interactions with a single roller. |
| type AutoRoller struct { |
| Cfg *config.Config |
| |
| // Interactions with the roller through the DB. |
| Mode modes.ModeHistory |
| Status *status.Cache |
| Strategy strategy.StrategyHistory |
| } |
| |
| func convertMiniStatus(inp *status.AutoRollMiniStatus, roller, mode, childName, parentName string) (*AutoRollMiniStatus, error) { |
| m, err := convertMode(mode) |
| if err != nil { |
| return nil, err |
| } |
| return &AutoRollMiniStatus{ |
| RollerId: roller, |
| Mode: m, |
| CurrentRollRev: inp.CurrentRollRev, |
| LastRollRev: inp.LastRollRev, |
| ChildName: childName, |
| ParentName: parentName, |
| NumFailed: int32(inp.NumFailedRolls), |
| NumBehind: int32(inp.NumNotRolledCommits), |
| Timestamp: timestamppb.New(inp.Timestamp), |
| LastSuccessfulRollTimestamp: timestamppb.New(inp.LastSuccessfulRollTimestamp), |
| }, nil |
| } |
| |
| func convertRollCLs(inp []*autoroll.AutoRollIssue) ([]*AutoRollCL, error) { |
| rv := make([]*AutoRollCL, 0, len(inp)) |
| for _, v := range inp { |
| cl, err := convertRollCL(v) |
| if err != nil { |
| return nil, err |
| } |
| rv = append(rv, cl) |
| } |
| return rv, nil |
| } |
| |
| func convertRollCLResult(res string) (AutoRollCL_Result, error) { |
| switch res { |
| case autoroll.ROLL_RESULT_IN_PROGRESS: |
| return AutoRollCL_IN_PROGRESS, nil |
| case autoroll.ROLL_RESULT_SUCCESS: |
| return AutoRollCL_SUCCESS, nil |
| case autoroll.ROLL_RESULT_FAILURE: |
| return AutoRollCL_FAILURE, nil |
| case autoroll.ROLL_RESULT_DRY_RUN_IN_PROGRESS: |
| return AutoRollCL_DRY_RUN_IN_PROGRESS, nil |
| case autoroll.ROLL_RESULT_DRY_RUN_SUCCESS: |
| return AutoRollCL_DRY_RUN_SUCCESS, nil |
| case autoroll.ROLL_RESULT_DRY_RUN_FAILURE: |
| return AutoRollCL_DRY_RUN_FAILURE, nil |
| default: |
| return -1, twirp.InternalError(fmt.Sprintf("invalid roll result %q", res)) |
| } |
| } |
| |
| func convertRollCL(inp *autoroll.AutoRollIssue) (*AutoRollCL, error) { |
| if inp == nil { |
| return nil, nil |
| } |
| tjs, err := convertTryJobs(inp.TryResults) |
| if err != nil { |
| return nil, err |
| } |
| res, err := convertRollCLResult(inp.Result) |
| if err != nil { |
| return nil, err |
| } |
| return &AutoRollCL{ |
| Id: fmt.Sprintf("%d", inp.Issue), |
| Result: res, |
| Subject: inp.Subject, |
| RollingTo: inp.RollingTo, |
| RollingFrom: inp.RollingFrom, |
| Created: timestamppb.New(inp.Created), |
| Modified: timestamppb.New(inp.Modified), |
| TryJobs: tjs, |
| }, nil |
| } |
| |
| func convertTryJobs(inp []*autoroll.TryResult) ([]*TryJob, error) { |
| if inp == nil { |
| return nil, nil |
| } |
| rv := make([]*TryJob, 0, len(inp)) |
| for _, v := range inp { |
| tj, err := convertTryJob(v) |
| if err != nil { |
| return nil, err |
| } |
| rv = append(rv, tj) |
| } |
| return rv, nil |
| } |
| |
| func convertTryJobStatus(st string) (TryJob_Status, error) { |
| v, ok := TryJob_Status_value[st] |
| if !ok { |
| return -1, twirp.InternalError(fmt.Sprintf("invalid tryjob status %q", st)) |
| } |
| return TryJob_Status(v), nil |
| } |
| |
| func convertTryJobResult(st string) (TryJob_Result, error) { |
| // Special case: "" -> UNKNOWN |
| if st == "" { |
| return TryJob_UNKNOWN, nil |
| } |
| v, ok := TryJob_Result_value[st] |
| if !ok { |
| return -1, twirp.InternalError(fmt.Sprintf("invalid tryjob result %q", st)) |
| } |
| return TryJob_Result(v), nil |
| } |
| |
| func convertTryJob(inp *autoroll.TryResult) (*TryJob, error) { |
| st, err := convertTryJobStatus(inp.Status) |
| if err != nil { |
| return nil, err |
| } |
| res, err := convertTryJobResult(inp.Result) |
| if err != nil { |
| return nil, err |
| } |
| return &TryJob{ |
| Name: inp.Builder, |
| Status: st, |
| Result: res, |
| Url: inp.Url, |
| Category: inp.Category, |
| }, nil |
| } |
| |
| func convertMode(m string) (Mode, error) { |
| switch m { |
| case modes.ModeRunning: |
| return Mode_RUNNING, nil |
| case modes.ModeStopped: |
| return Mode_STOPPED, nil |
| case modes.ModeDryRun: |
| return Mode_DRY_RUN, nil |
| case modes.ModeOffline: |
| return Mode_OFFLINE, nil |
| default: |
| return -1, twirp.InternalError(fmt.Sprintf("invalid mode %q", m)) |
| } |
| } |
| |
| func convertModeChange(inp *modes.ModeChange) (*ModeChange, error) { |
| mode, err := convertMode(inp.Mode) |
| if err != nil { |
| return nil, err |
| } |
| return &ModeChange{ |
| Message: inp.Message, |
| Mode: mode, |
| RollerId: inp.Roller, |
| Time: timestamppb.New(inp.Time), |
| User: inp.User, |
| }, nil |
| } |
| |
| func convertStrategy(s string) (Strategy, error) { |
| switch s { |
| case strategy.ROLL_STRATEGY_BATCH: |
| return Strategy_BATCH, nil |
| case strategy.ROLL_STRATEGY_N_BATCH: |
| return Strategy_N_BATCH, nil |
| case strategy.ROLL_STRATEGY_SINGLE: |
| return Strategy_SINGLE, nil |
| default: |
| return -1, twirp.InternalError(fmt.Sprintf("invalid strategy %q", s)) |
| } |
| } |
| |
| func convertStrategyChange(inp *strategy.StrategyChange) (*StrategyChange, error) { |
| strat, err := convertStrategy(inp.Strategy) |
| if err != nil { |
| return nil, err |
| } |
| return &StrategyChange{ |
| Message: inp.Message, |
| RollerId: inp.Roller, |
| Strategy: strat, |
| Time: timestamppb.New(inp.Time), |
| User: inp.User, |
| }, nil |
| } |
| |
| func convertRevision(inp *revision.Revision) *Revision { |
| return &Revision{ |
| Description: inp.Description, |
| Display: inp.Display, |
| Id: inp.Id, |
| Time: timestamppb.New(inp.Timestamp), |
| Url: inp.URL, |
| InvalidReason: inp.InvalidReason, |
| } |
| } |
| |
| func convertRevisions(inp []*revision.Revision) []*Revision { |
| rv := make([]*Revision, 0, len(inp)) |
| for _, v := range inp { |
| rv = append(rv, convertRevision(v)) |
| } |
| return rv |
| } |
| |
| func convertConfig(inp *config.Config) *AutoRollConfig { |
| var validModes []Mode |
| if len(inp.ValidModes) > 0 { |
| validModes = make([]Mode, 0, len(inp.ValidModes)) |
| for _, m := range inp.ValidModes { |
| validModes = append(validModes, Mode(m)) |
| } |
| } |
| return &AutoRollConfig{ |
| ChildBugLink: inp.ChildBugLink, |
| ParentBugLink: inp.ParentBugLink, |
| ParentWaterfall: inp.ParentWaterfall, |
| RollerId: inp.RollerName, |
| SupportsManualRolls: inp.SupportsManualRolls, |
| TimeWindow: inp.TimeWindow, |
| ValidModes: validModes, |
| } |
| } |
| |
| func convertManualRollRequests(inp []*manual.ManualRollRequest) ([]*ManualRoll, error) { |
| rv := make([]*ManualRoll, 0, len(inp)) |
| for _, v := range inp { |
| req, err := convertManualRollRequest(v) |
| if err != nil { |
| return nil, err |
| } |
| rv = append(rv, req) |
| } |
| return rv, nil |
| } |
| |
| func convertManualRollResult(s manual.ManualRollResult) (ManualRoll_Result, error) { |
| switch manual.ManualRollResult(s) { |
| case manual.RESULT_UNKNOWN: |
| return ManualRoll_UNKNOWN, nil |
| case manual.RESULT_FAILURE: |
| return ManualRoll_FAILURE, nil |
| case manual.RESULT_SUCCESS: |
| return ManualRoll_SUCCESS, nil |
| default: |
| return -1, twirp.InternalError(fmt.Sprintf("invalid manual roll result %q", s)) |
| } |
| } |
| |
| func convertManualRollStatus(s manual.ManualRollStatus) (ManualRoll_Status, error) { |
| switch manual.ManualRollStatus(s) { |
| case manual.STATUS_PENDING: |
| return ManualRoll_PENDING, nil |
| case manual.STATUS_STARTED: |
| return ManualRoll_PENDING, nil |
| case manual.STATUS_COMPLETE: |
| return ManualRoll_COMPLETED, nil |
| default: |
| return -1, twirp.InternalError(fmt.Sprintf("invalid manual roll status %q", s)) |
| } |
| } |
| |
| func convertManualRollRequest(inp *manual.ManualRollRequest) (*ManualRoll, error) { |
| res, err := convertManualRollResult(inp.Result) |
| if err != nil { |
| return nil, err |
| } |
| st, err := convertManualRollStatus(inp.Status) |
| if err != nil { |
| return nil, err |
| } |
| return &ManualRoll{ |
| Id: inp.Id, |
| RollerId: inp.RollerName, |
| Revision: inp.Revision, |
| Requester: inp.Requester, |
| Result: res, |
| Status: st, |
| Timestamp: timestamppb.New(inp.Timestamp), |
| Url: inp.Url, |
| Canary: inp.Canary, |
| DryRun: inp.DryRun, |
| NoEmail: inp.NoEmail, |
| NoResolveRevision: inp.NoResolveRevision, |
| }, nil |
| } |
| |
| func convertStatus(st *status.AutoRollStatus, cfg *config.Config, modeChange *modes.ModeChange, strat *strategy.StrategyChange, manualReqs []*manual.ManualRollRequest, cleanupHistory []*roller_cleanup.CleanupRequest) (*AutoRollStatus, error) { |
| mode := modes.ModeRunning |
| if modeChange != nil { |
| mode = modeChange.Mode |
| } |
| ms, err := convertMiniStatus(&st.AutoRollMiniStatus, cfg.RollerName, mode, cfg.ChildDisplayName, cfg.ParentDisplayName) |
| if err != nil { |
| return nil, err |
| } |
| var mc *ModeChange |
| if modeChange != nil { |
| mc, err = convertModeChange(modeChange) |
| if err != nil { |
| return nil, err |
| } |
| } |
| var sc *StrategyChange |
| if strat != nil { |
| sc, err = convertStrategyChange(strat) |
| if err != nil { |
| return nil, err |
| } |
| } |
| lastRoll, err := convertRollCL(st.LastRoll) |
| if err != nil { |
| return nil, err |
| } |
| currentRoll, err := convertRollCL(st.CurrentRoll) |
| if err != nil { |
| return nil, err |
| } |
| recentRolls, err := convertRollCLs(st.Recent) |
| if err != nil { |
| return nil, err |
| } |
| var manualRolls []*ManualRoll |
| if manualReqs != nil { |
| manualRolls, err = convertManualRollRequests(manualReqs) |
| if err != nil { |
| return nil, err |
| } |
| } |
| var cleanupReq *CleanupRequest |
| if len(cleanupHistory) != 0 && cleanupHistory[0].NeedsCleanup { |
| cleanupReq = &CleanupRequest{ |
| NeedsCleanup: cleanupHistory[0].NeedsCleanup, |
| User: cleanupHistory[0].User, |
| Timestamp: timestamppb.New(cleanupHistory[0].Timestamp), |
| Justification: cleanupHistory[0].Justification, |
| } |
| } |
| rv := &AutoRollStatus{ |
| CleanupRequested: cleanupReq, |
| MiniStatus: ms, |
| Status: st.Status, |
| Config: convertConfig(cfg), |
| FullHistoryUrl: st.FullHistoryUrl, |
| IssueUrlBase: st.IssueUrlBase, |
| Mode: mc, |
| Strategy: sc, |
| NotRolledRevisions: convertRevisions(st.NotRolledRevisions), |
| CurrentRoll: currentRoll, |
| LastRoll: lastRoll, |
| RecentRolls: recentRolls, |
| ManualRolls: manualRolls, |
| Error: st.Error, |
| ThrottledUntil: timestamppb.New(time.Unix(st.ThrottledUntil, 0)), |
| } |
| return rv, nil |
| } |