com.google.gerrit.pgm.RebuildNoteDb.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.pgm.RebuildNoteDb.java

Source

// Copyright (C) 2014 The Android Open Source Project
//
// 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 com.google.gerrit.pgm;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.base.Predicates;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Ordering;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gerrit.common.FormatUtil;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.util.BatchProgramModule;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.pgm.util.ThreadLimiter;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.DummyIndexModule;
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
import com.google.gerrit.server.update.ChainedReceiveCommands;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RebuildNoteDb extends SiteProgram {
    private static final Logger log = LoggerFactory.getLogger(RebuildNoteDb.class);

    @Option(name = "--threads", usage = "Number of threads to use for rebuilding NoteDb")
    private int threads = Runtime.getRuntime().availableProcessors();

    @Option(name = "--project", usage = "Projects to rebuild; recommended for debugging only")
    private List<String> projects = new ArrayList<>();

    @Option(name = "--change", usage = "Individual change numbers to rebuild; recommended for debugging only")
    private List<Integer> changes = new ArrayList<>();

    private Injector dbInjector;
    private Injector sysInjector;

    @Inject
    private AllUsersName allUsersName;

    @Inject
    private ChangeRebuilder rebuilder;

    @Inject
    @GerritServerConfig
    private Config cfg;

    @Inject
    private GitRepositoryManager repoManager;

    @Inject
    private NoteDbUpdateManager.Factory updateManagerFactory;

    @Inject
    private NotesMigration notesMigration;

    @Inject
    private SchemaFactory<ReviewDb> schemaFactory;

    @Inject
    private WorkQueue workQueue;

    @Inject
    private ChangeBundleReader bundleReader;

    @Override
    public int run() throws Exception {
        mustHaveValidSite();
        dbInjector = createDbInjector(MULTI_USER);
        threads = ThreadLimiter.limitThreads(dbInjector, threads);

        LifecycleManager dbManager = new LifecycleManager();
        dbManager.add(dbInjector);
        dbManager.start();

        sysInjector = createSysInjector();
        sysInjector.injectMembers(this);
        if (!notesMigration.enabled()) {
            throw die("NoteDb is not enabled.");
        }
        LifecycleManager sysManager = new LifecycleManager();
        sysManager.add(sysInjector);
        sysManager.start();

        ListeningExecutorService executor = newExecutor();
        System.out.println("Rebuilding the NoteDb");

        ImmutableListMultimap<Project.NameKey, Change.Id> changesByProject = getChangesByProject();
        boolean ok;
        Stopwatch sw = Stopwatch.createStarted();
        try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
            deleteRefs(RefNames.REFS_DRAFT_COMMENTS, allUsersRepo);

            List<ListenableFuture<Boolean>> futures = new ArrayList<>();
            List<Project.NameKey> projectNames = Ordering.usingToString().sortedCopy(changesByProject.keySet());
            for (Project.NameKey project : projectNames) {
                ListenableFuture<Boolean> future = executor.submit(() -> {
                    try (ReviewDb db = unwrapDb(schemaFactory.open())) {
                        return rebuildProject(db, changesByProject, project, allUsersRepo);
                    } catch (Exception e) {
                        log.error("Error rebuilding project " + project, e);
                        return false;
                    }
                });
                futures.add(future);
            }

            try {
                ok = Iterables.all(Futures.allAsList(futures).get(), Predicates.equalTo(true));
            } catch (InterruptedException | ExecutionException e) {
                log.error("Error rebuilding projects", e);
                ok = false;
            }
        }

        double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
        System.out.format("Rebuild %d changes in %.01fs (%.01f/s)\n", changesByProject.size(), t,
                changesByProject.size() / t);
        return ok ? 0 : 1;
    }

    private static void execute(BatchRefUpdate bru, Repository repo) throws IOException {
        try (RevWalk rw = new RevWalk(repo)) {
            bru.execute(rw, NullProgressMonitor.INSTANCE);
        }
        for (ReceiveCommand command : bru.getCommands()) {
            if (command.getResult() != ReceiveCommand.Result.OK) {
                throw new IOException(
                        String.format("Command %s failed: %s", command.toString(), command.getResult()));
            }
        }
    }

    private void deleteRefs(String prefix, Repository allUsersRepo) throws IOException {
        RefDatabase refDb = allUsersRepo.getRefDatabase();
        Map<String, Ref> allRefs = refDb.getRefs(prefix);
        BatchRefUpdate bru = refDb.newBatchUpdate();
        for (Map.Entry<String, Ref> ref : allRefs.entrySet()) {
            bru.addCommand(
                    new ReceiveCommand(ref.getValue().getObjectId(), ObjectId.zeroId(), prefix + ref.getKey()));
        }
        execute(bru, allUsersRepo);
    }

    private Injector createSysInjector() {
        return dbInjector.createChildInjector(new FactoryModule() {
            @Override
            public void configure() {
                install(dbInjector.getInstance(BatchProgramModule.class));
                DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ReindexAfterRefUpdate.class);
                install(new DummyIndexModule());
                factory(ChangeResource.Factory.class);
            }
        });
    }

    private ListeningExecutorService newExecutor() {
        if (threads > 0) {
            return MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "RebuildChange"));
        }
        return MoreExecutors.newDirectExecutorService();
    }

    private ImmutableListMultimap<Project.NameKey, Change.Id> getChangesByProject() throws OrmException {
        // Memorize all changes so we can close the db connection and allow
        // rebuilder threads to use the full connection pool.
        ListMultimap<Project.NameKey, Change.Id> changesByProject = MultimapBuilder.hashKeys().arrayListValues()
                .build();
        try (ReviewDb db = schemaFactory.open()) {
            if (projects.isEmpty() && !changes.isEmpty()) {
                Iterable<Change> todo = unwrapDb(db).changes().get(Iterables.transform(changes, Change.Id::new));
                for (Change c : todo) {
                    changesByProject.put(c.getProject(), c.getId());
                }
            } else {
                for (Change c : unwrapDb(db).changes().all()) {
                    boolean include = false;
                    if (projects.isEmpty() && changes.isEmpty()) {
                        include = true;
                    } else if (!projects.isEmpty() && projects.contains(c.getProject().get())) {
                        include = true;
                    } else if (!changes.isEmpty() && changes.contains(c.getId().get())) {
                        include = true;
                    }
                    if (include) {
                        changesByProject.put(c.getProject(), c.getId());
                    }
                }
            }
            return ImmutableListMultimap.copyOf(changesByProject);
        }
    }

    private boolean rebuildProject(ReviewDb db, ImmutableListMultimap<Project.NameKey, Change.Id> allChanges,
            Project.NameKey project, Repository allUsersRepo) throws IOException, OrmException {
        checkArgument(allChanges.containsKey(project));
        boolean ok = true;
        ProgressMonitor pm = new TextProgressMonitor(
                new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, UTF_8))));
        pm.beginTask(FormatUtil.elide(project.get(), 50), allChanges.get(project).size());
        try (NoteDbUpdateManager manager = updateManagerFactory.create(project);
                ObjectInserter allUsersInserter = allUsersRepo.newObjectInserter();
                ObjectReader reader = allUsersInserter.newReader();
                RevWalk allUsersRw = new RevWalk(reader)) {
            manager.setAllUsersRepo(allUsersRepo, allUsersRw, allUsersInserter,
                    new ChainedReceiveCommands(allUsersRepo));
            for (Change.Id changeId : allChanges.get(project)) {
                try {
                    rebuilder.buildUpdates(manager, bundleReader.fromReviewDb(db, changeId));
                } catch (NoPatchSetsException e) {
                    log.warn(e.getMessage());
                } catch (Throwable t) {
                    log.error("Failed to rebuild change " + changeId, t);
                    ok = false;
                }
                pm.update(1);
            }
            manager.execute();
        } finally {
            pm.endTask();
        }
        return ok;
    }
}