Merge changes from topic 'account-index-gh16'

* changes:
  Encapsulate per-index utilities in a pair of generic classes
  Generify OnlineReindexer and friends
  Move Document generation to AbstractLuceneIndex
  Parameterize Lucene's QueryBuilder
  Extract SubIndex as an abstract Index implementation
  LuceneVersionManager: Pass "changes_" prefix as argument
  Extract a method for in-memory testing state in Lucene
  Move BooleanQuery static call into LuceneChangeModule
  Move GerritIndexWriterConfig to its own file
  Schema for secondary index over accounts
This commit is contained in:
Dave Borowitz
2016-03-18 11:02:37 +00:00
committed by Gerrit Code Review
26 changed files with 1374 additions and 748 deletions

View File

@@ -26,7 +26,6 @@ import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init; import com.google.gerrit.pgm.Init;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.AsyncReceiveCommits; import com.google.gerrit.server.git.AsyncReceiveCommits;
import com.google.gerrit.server.index.change.ChangeSchemas;
import com.google.gerrit.server.ssh.NoSshModule; import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.util.SocketUtil; import com.google.gerrit.server.util.SocketUtil;
import com.google.gerrit.server.util.SystemLog; import com.google.gerrit.server.util.SystemLog;
@@ -142,8 +141,7 @@ public class GerritServer {
cfg.setBoolean("index", "lucene", "testInmemory", true); cfg.setBoolean("index", "lucene", "testInmemory", true);
cfg.setString("gitweb", null, "cgi", ""); cfg.setString("gitweb", null, "cgi", "");
daemon.setEnableHttpd(desc.httpd()); daemon.setEnableHttpd(desc.httpd());
daemon.setLuceneModule(new LuceneIndexModule( daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0));
ChangeSchemas.getLatest().getVersion(), 0));
daemon.setDatabaseForTesting(ImmutableList.<Module>of( daemon.setDatabaseForTesting(ImmutableList.<Module>of(
new InMemoryTestingDatabaseModule(cfg))); new InMemoryTestingDatabaseModule(cfg)));
daemon.start(); daemon.start();

View File

@@ -20,9 +20,22 @@ import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AbstractFuture; import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.lucene.LuceneChangeIndex.GerritIndexWriterConfig; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.Schema.Values;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.index.TrackingIndexWriter; import org.apache.lucene.index.TrackingIndexWriter;
@@ -33,12 +46,13 @@ import org.apache.lucene.search.ReferenceManager.RefreshListener;
import org.apache.lucene.search.SearcherFactory; import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory; import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.sql.Timestamp;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -46,25 +60,45 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
/** Piece of the change index that is implemented as a separate Lucene index. */ /** Basic Lucene index implementation. */
public class SubIndex { public abstract class AbstractLuceneIndex<K, V> implements Index<K, V> {
private static final Logger log = LoggerFactory.getLogger(SubIndex.class); private static final Logger log =
LoggerFactory.getLogger(AbstractLuceneIndex.class);
static String sortFieldName(FieldDef<?, ?> f) {
return f.getName() + "_SORT";
}
public static void setReady(SitePaths sitePaths, int version, boolean ready)
throws IOException {
try {
// TODO(dborowitz): Totally broken for non-change indexes.
FileBasedConfig cfg =
LuceneVersionManager.loadGerritIndexConfig(sitePaths);
LuceneVersionManager.setReady(cfg, version, ready);
cfg.save();
} catch (ConfigInvalidException e) {
throw new IOException(e);
}
}
private final Schema<V> schema;
private final SitePaths sitePaths;
private final Directory dir; private final Directory dir;
private final TrackingIndexWriter writer; private final TrackingIndexWriter writer;
private final ReferenceManager<IndexSearcher> searcherManager; private final ReferenceManager<IndexSearcher> searcherManager;
private final ControlledRealTimeReopenThread<IndexSearcher> reopenThread; private final ControlledRealTimeReopenThread<IndexSearcher> reopenThread;
private final Set<NrtFuture> notDoneNrtFutures; private final Set<NrtFuture> notDoneNrtFutures;
SubIndex(Path path, GerritIndexWriterConfig writerConfig, AbstractLuceneIndex(
SearcherFactory searcherFactory) throws IOException { Schema<V> schema,
this(FSDirectory.open(path), path.getFileName().toString(), writerConfig, SitePaths sitePaths,
searcherFactory); Directory dir,
} final String name,
SubIndex(Directory dir, final String dirName,
GerritIndexWriterConfig writerConfig, GerritIndexWriterConfig writerConfig,
SearcherFactory searcherFactory) throws IOException { SearcherFactory searcherFactory) throws IOException {
this.schema = schema;
this.sitePaths = sitePaths;
this.dir = dir; this.dir = dir;
IndexWriter delegateWriter; IndexWriter delegateWriter;
long commitPeriod = writerConfig.getCommitWithinMs(); long commitPeriod = writerConfig.getCommitWithinMs();
@@ -80,7 +114,7 @@ public class SubIndex {
delegateWriter = autoCommitWriter; delegateWriter = autoCommitWriter;
new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder() new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
.setNameFormat("Commit-%d " + dirName) .setNameFormat("Commit-%d " + name)
.setDaemon(true) .setDaemon(true)
.build()) .build())
.scheduleAtFixedRate(new Runnable() { .scheduleAtFixedRate(new Runnable() {
@@ -92,14 +126,14 @@ public class SubIndex {
autoCommitWriter.commit(); autoCommitWriter.commit();
} }
} catch (IOException e) { } catch (IOException e) {
log.error("Error committing Lucene index " + dirName, e); log.error("Error committing " + name + " Lucene index", e);
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
log.error("Error committing Lucene index " + dirName, e); log.error("Error committing " + name + " Lucene index", e);
try { try {
autoCommitWriter.close(); autoCommitWriter.close();
} catch (IOException e2) { } catch (IOException e2) {
log.error("SEVERE: Error closing Lucene index " + dirName log.error("SEVERE: Error closing " + name
+ " after OOM; index may be corrupted.", e); + " Lucene index after OOM; index may be corrupted.", e);
} }
} }
} }
@@ -115,7 +149,7 @@ public class SubIndex {
writer, searcherManager, writer, searcherManager,
0.500 /* maximum stale age (seconds) */, 0.500 /* maximum stale age (seconds) */,
0.010 /* minimum stale age (seconds) */); 0.010 /* minimum stale age (seconds) */);
reopenThread.setName("NRT " + dirName); reopenThread.setName("NRT " + name);
reopenThread.setPriority(Math.min( reopenThread.setPriority(Math.min(
Thread.currentThread().getPriority() + 2, Thread.currentThread().getPriority() + 2,
Thread.MAX_PRIORITY)); Thread.MAX_PRIORITY));
@@ -145,7 +179,13 @@ public class SubIndex {
reopenThread.start(); reopenThread.start();
} }
void close() { @Override
public void markReady(boolean ready) throws IOException {
setReady(sitePaths, schema.getVersion(), ready);
}
@Override
public void close() {
reopenThread.close(); reopenThread.close();
// Closing the reopen thread sets its generation to Long.MAX_VALUE, but we // Closing the reopen thread sets its generation to Long.MAX_VALUE, but we
@@ -187,7 +227,8 @@ public class SubIndex {
return new NrtFuture(writer.deleteDocuments(term)); return new NrtFuture(writer.deleteDocuments(term));
} }
void deleteAll() throws IOException { @Override
public void deleteAll() throws IOException {
writer.deleteAll(); writer.deleteAll();
} }
@@ -203,6 +244,55 @@ public class SubIndex {
searcherManager.release(searcher); searcherManager.release(searcher);
} }
Document toDocument(V obj, FillArgs fillArgs) {
Document result = new Document();
for (Values<V> vs : schema.buildFields(obj, fillArgs)) {
if (vs.getValues() != null) {
add(result, vs);
}
}
return result;
}
void add(Document doc, Values<V> values) {
String name = values.getField().getName();
FieldType<?> type = values.getField().getType();
Store store = store(values.getField());
if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
for (Object value : values.getValues()) {
doc.add(new IntField(name, (Integer) value, store));
}
} else if (type == FieldType.LONG) {
for (Object value : values.getValues()) {
doc.add(new LongField(name, (Long) value, store));
}
} else if (type == FieldType.TIMESTAMP) {
for (Object value : values.getValues()) {
doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
}
} else if (type == FieldType.EXACT
|| type == FieldType.PREFIX) {
for (Object value : values.getValues()) {
doc.add(new StringField(name, (String) value, store));
}
} else if (type == FieldType.FULL_TEXT) {
for (Object value : values.getValues()) {
doc.add(new TextField(name, (String) value, store));
}
} else if (type == FieldType.STORED_ONLY) {
for (Object value : values.getValues()) {
doc.add(new StoredField(name, (byte[]) value));
}
} else {
throw FieldType.badFieldType(type);
}
}
private static Field.Store store(FieldDef<?, ?> f) {
return f.isStored() ? Field.Store.YES : Field.Store.NO;
}
private final class NrtFuture extends AbstractFuture<Void> { private final class NrtFuture extends AbstractFuture<Void> {
private final long gen; private final long gen;
@@ -287,4 +377,9 @@ public class SubIndex {
} }
} }
} }
@Override
public Schema<V> getSchema() {
return schema;
}
} }

View File

@@ -0,0 +1,98 @@
// Copyright (C) 2016 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.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.gerrit.lucene.LuceneChangeIndex.ID_SORT_FIELD;
import static com.google.gerrit.lucene.LuceneChangeIndex.UPDATED_SORT_FIELD;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.QueryOptions;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.Schema.Values;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.query.DataSource;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.io.IOException;
import java.nio.file.Path;
import java.sql.Timestamp;
public class ChangeSubIndex extends AbstractLuceneIndex<Change.Id, ChangeData>
implements ChangeIndex {
ChangeSubIndex(
Schema<ChangeData> schema,
SitePaths sitePaths,
Path path,
GerritIndexWriterConfig writerConfig,
SearcherFactory searcherFactory) throws IOException {
this(schema, sitePaths, FSDirectory.open(path),
path.getFileName().toString(), writerConfig, searcherFactory);
}
ChangeSubIndex(
Schema<ChangeData> schema,
SitePaths sitePaths,
Directory dir,
String name,
GerritIndexWriterConfig writerConfig,
SearcherFactory searcherFactory) throws IOException {
super(schema, sitePaths, dir, name, writerConfig, searcherFactory);
}
@Override
public void replace(ChangeData obj) throws IOException {
throw new UnsupportedOperationException(
"don't use ChangeSubIndex directly");
}
@Override
public void delete(Change.Id key) throws IOException {
throw new UnsupportedOperationException(
"don't use ChangeSubIndex directly");
}
@Override
public DataSource<ChangeData> getSource(Predicate<ChangeData> p,
QueryOptions opts) throws QueryParseException {
throw new UnsupportedOperationException(
"don't use ChangeSubIndex directly");
}
@Override
void add(Document doc, Values<ChangeData> values) {
// Add separate DocValues fields for those fields needed for sorting.
FieldDef<ChangeData, ?> f = values.getField();
if (f == ChangeField.LEGACY_ID) {
int v = (Integer) getOnlyElement(values.getValues());
doc.add(new NumericDocValuesField(ID_SORT_FIELD, v));
} else if (f == ChangeField.UPDATED) {
long t = ((Timestamp) getOnlyElement(values.getValues())).getTime();
doc.add(new NumericDocValuesField(UPDATED_SORT_FIELD, t));
}
super.add(doc, values);
}
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2016 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.lucene;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.server.config.ConfigUtil;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.eclipse.jgit.lib.Config;
import java.util.Map;
/**
* Combination of Lucene {@link IndexWriterConfig} with additional
* Gerrit-specific options.
*/
class GerritIndexWriterConfig {
private static final Map<String, String> CUSTOM_CHAR_MAPPING =
ImmutableMap.of("_", " ", ".", " ");
private final IndexWriterConfig luceneConfig;
private long commitWithinMs;
private final CustomMappingAnalyzer analyzer;
GerritIndexWriterConfig(Config cfg, String name) {
analyzer =
new CustomMappingAnalyzer(new StandardAnalyzer(
CharArraySet.EMPTY_SET), CUSTOM_CHAR_MAPPING);
luceneConfig = new IndexWriterConfig(analyzer)
.setOpenMode(OpenMode.CREATE_OR_APPEND)
.setCommitOnClose(true);
double m = 1 << 20;
luceneConfig.setRAMBufferSizeMB(cfg.getLong(
"index", name, "ramBufferSize",
(long) (IndexWriterConfig.DEFAULT_RAM_BUFFER_SIZE_MB * m)) / m);
luceneConfig.setMaxBufferedDocs(cfg.getInt(
"index", name, "maxBufferedDocs",
IndexWriterConfig.DEFAULT_MAX_BUFFERED_DOCS));
try {
commitWithinMs =
ConfigUtil.getTimeUnit(cfg, "index", name, "commitWithin",
MILLISECONDS.convert(5, MINUTES), MILLISECONDS);
} catch (IllegalArgumentException e) {
commitWithinMs = cfg.getLong("index", name, "commitWithin", 0);
}
}
CustomMappingAnalyzer getAnalyzer() {
return analyzer;
}
IndexWriterConfig getLuceneConfig() {
return luceneConfig;
}
long getCommitWithinMs() {
return commitWithinMs;
}
}

View File

@@ -15,17 +15,15 @@
package com.google.gerrit.lucene; package com.google.gerrit.lucene;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
import static com.google.gerrit.lucene.LuceneVersionManager.CHANGES_PREFIX;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE; import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE; import static com.google.gerrit.server.index.change.ChangeField.CHANGE;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID; import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT; import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import static com.google.gerrit.server.index.change.IndexRewriter.CLOSED_STATUSES; import static com.google.gerrit.server.index.change.IndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.IndexRewriter.OPEN_STATUSES; import static com.google.gerrit.server.index.change.IndexRewriter.OPEN_STATUSES;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -37,16 +35,12 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldDef.FillArgs; import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.IndexExecutor; import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.QueryOptions; import com.google.gerrit.server.index.QueryOptions;
import com.google.gerrit.server.index.Schema; import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.Schema.Values;
import com.google.gerrit.server.index.change.ChangeField; import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeField.ChangeProtoField; import com.google.gerrit.server.index.change.ChangeField.ChangeProtoField;
import com.google.gerrit.server.index.change.ChangeField.PatchSetApprovalProtoField; import com.google.gerrit.server.index.change.ChangeField.PatchSetApprovalProtoField;
@@ -64,23 +58,10 @@ import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject; import com.google.inject.assistedinject.AssistedInject;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.ScoreDoc;
@@ -92,20 +73,16 @@ import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs; import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -124,6 +101,11 @@ public class LuceneChangeIndex implements ChangeIndex {
public static final String CHANGES_OPEN = "open"; public static final String CHANGES_OPEN = "open";
public static final String CHANGES_CLOSED = "closed"; public static final String CHANGES_CLOSED = "closed";
static final String UPDATED_SORT_FIELD =
sortFieldName(ChangeField.UPDATED);
static final String ID_SORT_FIELD =
sortFieldName(ChangeField.LEGACY_ID);
private static final String ADDED_FIELD = ChangeField.ADDED.getName(); private static final String ADDED_FIELD = ChangeField.ADDED.getName();
private static final String APPROVAL_FIELD = ChangeField.APPROVAL.getName(); private static final String APPROVAL_FIELD = ChangeField.APPROVAL.getName();
private static final String CHANGE_FIELD = ChangeField.CHANGE.getName(); private static final String CHANGE_FIELD = ChangeField.CHANGE.getName();
@@ -132,68 +114,13 @@ public class LuceneChangeIndex implements ChangeIndex {
private static final String PATCH_SET_FIELD = ChangeField.PATCH_SET.getName(); private static final String PATCH_SET_FIELD = ChangeField.PATCH_SET.getName();
private static final String REVIEWEDBY_FIELD = private static final String REVIEWEDBY_FIELD =
ChangeField.REVIEWEDBY.getName(); ChangeField.REVIEWEDBY.getName();
private static final String UPDATED_SORT_FIELD =
sortFieldName(ChangeField.UPDATED);
private static final String ID_SORT_FIELD =
sortFieldName(ChangeField.LEGACY_ID);
private static final Map<String, String> CUSTOM_CHAR_MAPPING = ImmutableMap.of( static Term idTerm(ChangeData cd) {
"_", " ", ".", " "); return QueryBuilder.intTerm(LEGACY_ID.getName(), cd.getId().get());
public static void setReady(SitePaths sitePaths, int version, boolean ready)
throws IOException {
try {
FileBasedConfig cfg =
LuceneVersionManager.loadGerritIndexConfig(sitePaths);
LuceneVersionManager.setReady(cfg, version, ready);
cfg.save();
} catch (ConfigInvalidException e) {
throw new IOException(e);
}
} }
private static String sortFieldName(FieldDef<?, ?> f) { static Term idTerm(Change.Id id) {
return f.getName() + "_SORT"; return QueryBuilder.intTerm(LEGACY_ID.getName(), id.get());
}
static interface Factory {
LuceneChangeIndex create(Schema<ChangeData> schema);
}
static class GerritIndexWriterConfig {
private final IndexWriterConfig luceneConfig;
private long commitWithinMs;
private GerritIndexWriterConfig(Config cfg, String name) {
CustomMappingAnalyzer analyzer =
new CustomMappingAnalyzer(new StandardAnalyzer(
CharArraySet.EMPTY_SET), CUSTOM_CHAR_MAPPING);
luceneConfig = new IndexWriterConfig(analyzer)
.setOpenMode(OpenMode.CREATE_OR_APPEND)
.setCommitOnClose(true);
double m = 1 << 20;
luceneConfig.setRAMBufferSizeMB(cfg.getLong(
"index", name, "ramBufferSize",
(long) (IndexWriterConfig.DEFAULT_RAM_BUFFER_SIZE_MB * m)) / m);
luceneConfig.setMaxBufferedDocs(cfg.getInt(
"index", name, "maxBufferedDocs",
IndexWriterConfig.DEFAULT_MAX_BUFFERED_DOCS));
try {
commitWithinMs =
ConfigUtil.getTimeUnit(cfg, "index", name, "commitWithin",
MILLISECONDS.convert(5, MINUTES), MILLISECONDS);
} catch (IllegalArgumentException e) {
commitWithinMs = cfg.getLong("index", name, "commitWithin", 0);
}
}
IndexWriterConfig getLuceneConfig() {
return luceneConfig;
}
long getCommitWithinMs() {
return commitWithinMs;
}
} }
private final SitePaths sitePaths; private final SitePaths sitePaths;
@@ -202,9 +129,9 @@ public class LuceneChangeIndex implements ChangeIndex {
private final Provider<ReviewDb> db; private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory; private final ChangeData.Factory changeDataFactory;
private final Schema<ChangeData> schema; private final Schema<ChangeData> schema;
private final QueryBuilder queryBuilder; private final QueryBuilder<ChangeData> queryBuilder;
private final SubIndex openIndex; private final ChangeSubIndex openIndex;
private final SubIndex closedIndex; private final ChangeSubIndex closedIndex;
@AssistedInject @AssistedInject
LuceneChangeIndex( LuceneChangeIndex(
@@ -222,31 +149,25 @@ public class LuceneChangeIndex implements ChangeIndex {
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
this.schema = schema; this.schema = schema;
CustomMappingAnalyzer analyzer =
new CustomMappingAnalyzer(new StandardAnalyzer(CharArraySet.EMPTY_SET),
CUSTOM_CHAR_MAPPING);
queryBuilder = new QueryBuilder(analyzer);
BooleanQuery.setMaxClauseCount(cfg.getInt("index", "maxTerms",
BooleanQuery.getMaxClauseCount()));
GerritIndexWriterConfig openConfig = GerritIndexWriterConfig openConfig =
new GerritIndexWriterConfig(cfg, "changes_open"); new GerritIndexWriterConfig(cfg, "changes_open");
GerritIndexWriterConfig closedConfig = GerritIndexWriterConfig closedConfig =
new GerritIndexWriterConfig(cfg, "changes_closed"); new GerritIndexWriterConfig(cfg, "changes_closed");
queryBuilder = new QueryBuilder<>(schema, openConfig.getAnalyzer());
SearcherFactory searcherFactory = new SearcherFactory(); SearcherFactory searcherFactory = new SearcherFactory();
if (cfg.getBoolean("index", "lucene", "testInmemory", false)) { if (LuceneIndexModule.isInMemoryTest(cfg)) {
openIndex = new SubIndex(new RAMDirectory(), "ramOpen", openConfig, openIndex = new ChangeSubIndex(schema, sitePaths, new RAMDirectory(),
searcherFactory); "ramOpen", openConfig, searcherFactory);
closedIndex = new SubIndex(new RAMDirectory(), "ramClosed", closedConfig, closedIndex = new ChangeSubIndex(schema, sitePaths, new RAMDirectory(),
searcherFactory); "ramClosed", closedConfig, searcherFactory);
} else { } else {
Path dir = LuceneVersionManager.getDir(sitePaths, schema); Path dir = LuceneVersionManager.getDir(sitePaths, CHANGES_PREFIX, schema);
openIndex = new SubIndex(dir.resolve(CHANGES_OPEN), openConfig, openIndex = new ChangeSubIndex(schema, sitePaths,
searcherFactory); dir.resolve(CHANGES_OPEN), openConfig, searcherFactory);
closedIndex = new SubIndex(dir.resolve(CHANGES_CLOSED), closedConfig, closedIndex = new ChangeSubIndex(schema, sitePaths,
searcherFactory); dir.resolve(CHANGES_CLOSED), closedConfig, searcherFactory);
} }
} }
@@ -275,8 +196,10 @@ public class LuceneChangeIndex implements ChangeIndex {
@Override @Override
public void replace(ChangeData cd) throws IOException { public void replace(ChangeData cd) throws IOException {
Term id = QueryBuilder.idTerm(cd); Term id = LuceneChangeIndex.idTerm(cd);
Document doc = toDocument(cd); // toDocument is essentially static and doesn't depend on the specific
// sub-index, so just pick one.
Document doc = openIndex.toDocument(cd, fillArgs);
try { try {
if (cd.change().getStatus().isOpen()) { if (cd.change().getStatus().isOpen()) {
Futures.allAsList( Futures.allAsList(
@@ -294,7 +217,7 @@ public class LuceneChangeIndex implements ChangeIndex {
@Override @Override
public void delete(Change.Id id) throws IOException { public void delete(Change.Id id) throws IOException {
Term idTerm = QueryBuilder.idTerm(id); Term idTerm = LuceneChangeIndex.idTerm(id);
try { try {
Futures.allAsList( Futures.allAsList(
openIndex.delete(idTerm), openIndex.delete(idTerm),
@@ -314,7 +237,7 @@ public class LuceneChangeIndex implements ChangeIndex {
public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts) public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts)
throws QueryParseException { throws QueryParseException {
Set<Change.Status> statuses = IndexRewriter.getPossibleStatus(p); Set<Change.Status> statuses = IndexRewriter.getPossibleStatus(p);
List<SubIndex> indexes = Lists.newArrayListWithCapacity(2); List<ChangeSubIndex> indexes = Lists.newArrayListWithCapacity(2);
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) { if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
indexes.add(openIndex); indexes.add(openIndex);
} }
@@ -326,7 +249,9 @@ public class LuceneChangeIndex implements ChangeIndex {
@Override @Override
public void markReady(boolean ready) throws IOException { public void markReady(boolean ready) throws IOException {
setReady(sitePaths, schema.getVersion(), ready); // Do not delegate to ChangeSubIndex#markReady, since changes have an
// additional level of directory nesting.
AbstractLuceneIndex.setReady(sitePaths, schema.getVersion(), ready);
} }
private Sort getSort() { private Sort getSort() {
@@ -335,21 +260,17 @@ public class LuceneChangeIndex implements ChangeIndex {
new SortField(ID_SORT_FIELD, SortField.Type.LONG, true)); new SortField(ID_SORT_FIELD, SortField.Type.LONG, true));
} }
public SubIndex getOpenChangesIndex() { public ChangeSubIndex getClosedChangesIndex() {
return openIndex;
}
public SubIndex getClosedChangesIndex() {
return closedIndex; return closedIndex;
} }
private class QuerySource implements ChangeDataSource { private class QuerySource implements ChangeDataSource {
private final List<SubIndex> indexes; private final List<ChangeSubIndex> indexes;
private final Query query; private final Query query;
private final QueryOptions opts; private final QueryOptions opts;
private final Sort sort; private final Sort sort;
private QuerySource(List<SubIndex> indexes, Query query, QueryOptions opts, private QuerySource(List<ChangeSubIndex> indexes, Query query, QueryOptions opts,
Sort sort) { Sort sort) {
this.indexes = indexes; this.indexes = indexes;
this.query = checkNotNull(query, "null query from Lucene"); this.query = checkNotNull(query, "null query from Lucene");
@@ -557,64 +478,4 @@ public class LuceneChangeIndex implements ChangeIndex {
} }
return result; return result;
} }
private Document toDocument(ChangeData cd) {
Document result = new Document();
for (Values<ChangeData> vs : schema.buildFields(cd, fillArgs)) {
if (vs.getValues() != null) {
add(result, vs);
}
}
return result;
}
private void add(Document doc, Values<ChangeData> values) {
String name = values.getField().getName();
FieldType<?> type = values.getField().getType();
Store store = store(values.getField());
FieldDef<ChangeData, ?> f = values.getField();
// Add separate DocValues fields for those fields needed for sorting.
if (f == ChangeField.LEGACY_ID) {
int v = (Integer) getOnlyElement(values.getValues());
doc.add(new NumericDocValuesField(sortFieldName(f), v));
} else if (f == ChangeField.UPDATED) {
long t = ((Timestamp) getOnlyElement(values.getValues())).getTime();
doc.add(new NumericDocValuesField(UPDATED_SORT_FIELD, t));
}
if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
for (Object value : values.getValues()) {
doc.add(new IntField(name, (Integer) value, store));
}
} else if (type == FieldType.LONG) {
for (Object value : values.getValues()) {
doc.add(new LongField(name, (Long) value, store));
}
} else if (type == FieldType.TIMESTAMP) {
for (Object value : values.getValues()) {
doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
}
} else if (type == FieldType.EXACT
|| type == FieldType.PREFIX) {
for (Object value : values.getValues()) {
doc.add(new StringField(name, (String) value, store));
}
} else if (type == FieldType.FULL_TEXT) {
for (Object value : values.getValues()) {
doc.add(new TextField(name, (String) value, store));
}
} else if (type == FieldType.STORED_ONLY) {
for (Object value : values.getValues()) {
doc.add(new StoredField(name, (byte[]) value));
}
} else {
throw FieldType.badFieldType(type);
}
}
private static Field.Store store(FieldDef<?, ?> f) {
return f.isStored() ? Field.Store.YES : Field.Store.NO;
}
} }

View File

@@ -14,40 +14,69 @@
package com.google.gerrit.lucene; package com.google.gerrit.lucene;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexConfig; import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.index.IndexModule; import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.Schema; import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeSchemas;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import org.apache.lucene.search.BooleanQuery;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
public class LuceneIndexModule extends LifecycleModule { import java.util.Collection;
private final Integer singleVersion; import java.util.Map;
private final int threads;
public LuceneIndexModule() { public class LuceneIndexModule extends LifecycleModule {
this(null, 0); private static final String SINGLE_VERSIONS =
"LuceneIndexModule/SingleVersions";
public static LuceneIndexModule singleVersionAllLatest(int threads) {
return new LuceneIndexModule(ImmutableMap.<String, Integer> of(), threads);
} }
public LuceneIndexModule(Integer singleVersion, int threads) { public static LuceneIndexModule singleVersionWithExplicitVersions(
this.singleVersion = singleVersion; Map<String, Integer> versions, int threads) {
return new LuceneIndexModule(versions, threads);
}
public static LuceneIndexModule latestVersionWithOnlineUpgrade() {
return new LuceneIndexModule(null, 0);
}
static boolean isInMemoryTest(Config cfg) {
return cfg.getBoolean("index", "lucene", "testInmemory", false);
}
private final int threads;
private final Map<String, Integer> singleVersions;
private LuceneIndexModule(Map<String, Integer> singleVersions, int threads) {
this.singleVersions = singleVersions;
this.threads = threads; this.threads = threads;
} }
@Override @Override
protected void configure() { protected void configure() {
factory(LuceneChangeIndex.Factory.class); install(
factory(OnlineReindexer.Factory.class); new FactoryModuleBuilder()
.implement(ChangeIndex.class, LuceneChangeIndex.class)
.build(ChangeIndex.Factory.class));
install(new IndexModule(threads)); install(new IndexModule(threads));
if (singleVersion == null) { if (singleVersions == null) {
install(new MultiVersionModule()); install(new MultiVersionModule());
} else { } else {
install(new SingleVersionModule()); install(new SingleVersionModule());
@@ -57,6 +86,8 @@ public class LuceneIndexModule extends LifecycleModule {
@Provides @Provides
@Singleton @Singleton
IndexConfig getIndexConfig(@GerritServerConfig Config cfg) { IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
BooleanQuery.setMaxClauseCount(cfg.getInt("index", "maxTerms",
BooleanQuery.getMaxClauseCount()));
return IndexConfig.fromConfig(cfg); return IndexConfig.fromConfig(cfg);
} }
@@ -71,34 +102,48 @@ public class LuceneIndexModule extends LifecycleModule {
@Override @Override
public void configure() { public void configure() {
listener().to(SingleVersionListener.class); listener().to(SingleVersionListener.class);
} bind(new TypeLiteral<Map<String, Integer>>() {})
.annotatedWith(Names.named(SINGLE_VERSIONS))
@Provides .toInstance(singleVersions);
@Singleton
LuceneChangeIndex getIndex(LuceneChangeIndex.Factory factory) {
Schema<ChangeData> schema = singleVersion != null
? ChangeSchemas.get(singleVersion)
: ChangeSchemas.getLatest();
return factory.create(schema);
} }
} }
@Singleton @Singleton
static class SingleVersionListener implements LifecycleListener { static class SingleVersionListener implements LifecycleListener {
private final ChangeIndexCollection indexes; private final Collection<IndexDefinition<?, ?, ?>> defs;
private final LuceneChangeIndex index; private final Map<String, Integer> singleVersions;
@Inject @Inject
SingleVersionListener(ChangeIndexCollection indexes, SingleVersionListener(
LuceneChangeIndex index) { Collection<IndexDefinition<?, ?, ?>> defs,
this.indexes = indexes; @Named(SINGLE_VERSIONS) Map<String, Integer> singleVersions) {
this.index = index; this.defs = defs;
this.singleVersions = singleVersions;
} }
@Override @Override
public void start() { public void start() {
indexes.setSearchIndex(index); for (IndexDefinition<?, ?, ?> def : defs) {
indexes.addWriteIndex(index); start(def);
}
}
private <K, V, I extends Index<K, V>> void start(
IndexDefinition<K, V, I> def) {
Schema<V> schema;
Integer v = singleVersions.get(def.getName());
if (v == null) {
schema = def.getLatest();
} else {
schema = def.getSchemas().get(v);
if (schema == null) {
throw new ProvisionException(String.format(
"Unrecognized %s schema version: %s", def.getName(), v));
}
}
I index = def.getIndexFactory().create(schema);
def.getIndexCollection().setSearchIndex(index);
def.getIndexCollection().addWriteIndex(index);
} }
@Override @Override

View File

@@ -20,11 +20,14 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.OnlineReindexer;
import com.google.gerrit.server.index.Schema; import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeSchemas; import com.google.gerrit.server.index.change.ChangeIndexDefintion;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.ProvisionException; import com.google.inject.ProvisionException;
@@ -50,7 +53,7 @@ public class LuceneVersionManager implements LifecycleListener {
private static final Logger log = LoggerFactory private static final Logger log = LoggerFactory
.getLogger(LuceneVersionManager.class); .getLogger(LuceneVersionManager.class);
private static final String CHANGES_PREFIX = "changes_"; static final String CHANGES_PREFIX = "changes_";
private static class Version { private static class Version {
private final Schema<ChangeData> schema; private final Schema<ChangeData> schema;
@@ -68,9 +71,9 @@ public class LuceneVersionManager implements LifecycleListener {
} }
} }
static Path getDir(SitePaths sitePaths, Schema<ChangeData> schema) { static Path getDir(SitePaths sitePaths, String prefix, Schema<?> schema) {
return sitePaths.index_dir.resolve(String.format("%s%04d", return sitePaths.index_dir.resolve(String.format("%s%04d",
CHANGES_PREFIX, schema.getVersion())); prefix, schema.getVersion()));
} }
static FileBasedConfig loadGerritIndexConfig(SitePaths sitePaths) static FileBasedConfig loadGerritIndexConfig(SitePaths sitePaths)
@@ -93,9 +96,9 @@ public class LuceneVersionManager implements LifecycleListener {
private final SitePaths sitePaths; private final SitePaths sitePaths;
private final LuceneChangeIndex.Factory indexFactory; private final LuceneChangeIndex.Factory indexFactory;
private final ChangeIndexCollection indexes; private final ChangeIndexCollection indexes;
private final OnlineReindexer.Factory reindexerFactory; private final ChangeIndexDefintion changeDef;
private final boolean onlineUpgrade; private final boolean onlineUpgrade;
private OnlineReindexer reindexer; private OnlineReindexer<Change.Id, ChangeData, ChangeIndex> reindexer;
@Inject @Inject
LuceneVersionManager( LuceneVersionManager(
@@ -103,11 +106,11 @@ public class LuceneVersionManager implements LifecycleListener {
SitePaths sitePaths, SitePaths sitePaths,
LuceneChangeIndex.Factory indexFactory, LuceneChangeIndex.Factory indexFactory,
ChangeIndexCollection indexes, ChangeIndexCollection indexes,
OnlineReindexer.Factory reindexerFactory) { ChangeIndexDefintion changeDef) {
this.sitePaths = sitePaths; this.sitePaths = sitePaths;
this.indexFactory = indexFactory; this.indexFactory = indexFactory;
this.indexes = indexes; this.indexes = indexes;
this.reindexerFactory = reindexerFactory; this.changeDef = changeDef;
this.onlineUpgrade = cfg.getBoolean("index", null, "onlineUpgrade", true); this.onlineUpgrade = cfg.getBoolean("index", null, "onlineUpgrade", true);
} }
@@ -157,7 +160,8 @@ public class LuceneVersionManager implements LifecycleListener {
} }
markNotReady(cfg, versions.values(), write); markNotReady(cfg, versions.values(), write);
LuceneChangeIndex searchIndex = indexFactory.create(search.schema); LuceneChangeIndex searchIndex =
(LuceneChangeIndex) indexFactory.create(search.schema);
indexes.setSearchIndex(searchIndex); indexes.setSearchIndex(searchIndex);
for (Version v : write) { for (Version v : write) {
if (v.schema != null) { if (v.schema != null) {
@@ -171,7 +175,7 @@ public class LuceneVersionManager implements LifecycleListener {
int latest = write.get(0).version; int latest = write.get(0).version;
if (onlineUpgrade && latest != search.version) { if (onlineUpgrade && latest != search.version) {
reindexer = reindexerFactory.create(latest); reindexer = new OnlineReindexer<>(changeDef, latest);
reindexer.start(); reindexer.start();
} }
} }
@@ -223,8 +227,8 @@ public class LuceneVersionManager implements LifecycleListener {
private TreeMap<Integer, Version> scanVersions(Config cfg) { private TreeMap<Integer, Version> scanVersions(Config cfg) {
TreeMap<Integer, Version> versions = Maps.newTreeMap(); TreeMap<Integer, Version> versions = Maps.newTreeMap();
for (Schema<ChangeData> schema : ChangeSchemas.ALL.values()) { for (Schema<ChangeData> schema : changeDef.getSchemas().values()) {
Path p = getDir(sitePaths, schema); Path p = getDir(sitePaths, CHANGES_PREFIX, schema);
boolean isDir = Files.isDirectory(p); boolean isDir = Files.isDirectory(p);
if (Files.exists(p) && !isDir) { if (Files.exists(p) && !isDir) {
log.warn("Not a directory: %s", p.toAbsolutePath()); log.warn("Not a directory: %s", p.toAbsolutePath());

View File

@@ -14,24 +14,23 @@
package com.google.gerrit.lucene; package com.google.gerrit.lucene;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID; import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.lucene.search.BooleanClause.Occur.MUST; import static org.apache.lucene.search.BooleanClause.Occur.MUST;
import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT; import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD; import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.FieldType; import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.IndexPredicate; import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.index.IntegerRangePredicate; import com.google.gerrit.server.index.IntegerRangePredicate;
import com.google.gerrit.server.index.RegexPredicate; import com.google.gerrit.server.index.RegexPredicate;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.TimestampRangePredicate; import com.google.gerrit.server.index.TimestampRangePredicate;
import com.google.gerrit.server.query.AndPredicate; import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.NotPredicate; import com.google.gerrit.server.query.NotPredicate;
import com.google.gerrit.server.query.OrPredicate; import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException; import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
@@ -48,23 +47,22 @@ import org.apache.lucene.util.NumericUtils;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
public class QueryBuilder { public class QueryBuilder<V> {
static Term intTerm(String name, int value) {
public static Term idTerm(ChangeData cd) { BytesRefBuilder builder = new BytesRefBuilder();
return intTerm(LEGACY_ID.getName(), cd.getId().get()); NumericUtils.intToPrefixCodedBytes(value, 0, builder);
} return new Term(name, builder.get());
public static Term idTerm(Change.Id id) {
return intTerm(LEGACY_ID.getName(), id.get());
} }
private final Schema<V> schema;
private final org.apache.lucene.util.QueryBuilder queryBuilder; private final org.apache.lucene.util.QueryBuilder queryBuilder;
public QueryBuilder(Analyzer analyzer) { public QueryBuilder(Schema<V> schema, Analyzer analyzer) {
this.schema = schema;
queryBuilder = new org.apache.lucene.util.QueryBuilder(analyzer); queryBuilder = new org.apache.lucene.util.QueryBuilder(analyzer);
} }
public Query toQuery(Predicate<ChangeData> p) throws QueryParseException { public Query toQuery(Predicate<V> p) throws QueryParseException {
if (p instanceof AndPredicate) { if (p instanceof AndPredicate) {
return and(p); return and(p);
} else if (p instanceof OrPredicate) { } else if (p instanceof OrPredicate) {
@@ -72,13 +70,13 @@ public class QueryBuilder {
} else if (p instanceof NotPredicate) { } else if (p instanceof NotPredicate) {
return not(p); return not(p);
} else if (p instanceof IndexPredicate) { } else if (p instanceof IndexPredicate) {
return fieldQuery((IndexPredicate<ChangeData>) p); return fieldQuery((IndexPredicate<V>) p);
} else { } else {
throw new QueryParseException("cannot create query for index: " + p); throw new QueryParseException("cannot create query for index: " + p);
} }
} }
private Query or(Predicate<ChangeData> p) private Query or(Predicate<V> p)
throws QueryParseException { throws QueryParseException {
try { try {
BooleanQuery.Builder q = new BooleanQuery.Builder(); BooleanQuery.Builder q = new BooleanQuery.Builder();
@@ -91,17 +89,17 @@ public class QueryBuilder {
} }
} }
private Query and(Predicate<ChangeData> p) private Query and(Predicate<V> p)
throws QueryParseException { throws QueryParseException {
try { try {
BooleanQuery.Builder b = new BooleanQuery.Builder(); BooleanQuery.Builder b = new BooleanQuery.Builder();
List<Query> not = Lists.newArrayListWithCapacity(p.getChildCount()); List<Query> not = Lists.newArrayListWithCapacity(p.getChildCount());
for (int i = 0; i < p.getChildCount(); i++) { for (int i = 0; i < p.getChildCount(); i++) {
Predicate<ChangeData> c = p.getChild(i); Predicate<V> c = p.getChild(i);
if (c instanceof NotPredicate) { if (c instanceof NotPredicate) {
Predicate<ChangeData> n = c.getChild(0); Predicate<V> n = c.getChild(0);
if (n instanceof TimestampRangePredicate) { if (n instanceof TimestampRangePredicate) {
b.add(notTimestamp((TimestampRangePredicate<ChangeData>) n), MUST); b.add(notTimestamp((TimestampRangePredicate<V>) n), MUST);
} else { } else {
not.add(toQuery(n)); not.add(toQuery(n));
} }
@@ -118,11 +116,11 @@ public class QueryBuilder {
} }
} }
private Query not(Predicate<ChangeData> p) private Query not(Predicate<V> p)
throws QueryParseException { throws QueryParseException {
Predicate<ChangeData> n = p.getChild(0); Predicate<V> n = p.getChild(0);
if (n instanceof TimestampRangePredicate) { if (n instanceof TimestampRangePredicate) {
return notTimestamp((TimestampRangePredicate<ChangeData>) n); return notTimestamp((TimestampRangePredicate<V>) n);
} }
// Lucene does not support negation, start with all and subtract. // Lucene does not support negation, start with all and subtract.
@@ -132,8 +130,11 @@ public class QueryBuilder {
.build(); .build();
} }
private Query fieldQuery(IndexPredicate<ChangeData> p) private Query fieldQuery(IndexPredicate<V> p)
throws QueryParseException { throws QueryParseException {
checkArgument(schema.hasField(p.getField()),
"field not in schema v%s: %s", schema.getVersion(),
p.getField().getName());
if (p.getType() == FieldType.INTEGER) { if (p.getType() == FieldType.INTEGER) {
return intQuery(p); return intQuery(p);
} else if (p.getType() == FieldType.INTEGER_RANGE) { } else if (p.getType() == FieldType.INTEGER_RANGE) {
@@ -151,13 +152,7 @@ public class QueryBuilder {
} }
} }
private static Term intTerm(String name, int value) { private Query intQuery(IndexPredicate<V> p)
BytesRefBuilder builder = new BytesRefBuilder();
NumericUtils.intToPrefixCodedBytes(value, 0, builder);
return new Term(name, builder.get());
}
private Query intQuery(IndexPredicate<ChangeData> p)
throws QueryParseException { throws QueryParseException {
int value; int value;
try { try {
@@ -170,11 +165,11 @@ public class QueryBuilder {
return new TermQuery(intTerm(p.getField().getName(), value)); return new TermQuery(intTerm(p.getField().getName(), value));
} }
private Query intRangeQuery(IndexPredicate<ChangeData> p) private Query intRangeQuery(IndexPredicate<V> p)
throws QueryParseException { throws QueryParseException {
if (p instanceof IntegerRangePredicate) { if (p instanceof IntegerRangePredicate) {
IntegerRangePredicate<ChangeData> r = IntegerRangePredicate<V> r =
(IntegerRangePredicate<ChangeData>) p; (IntegerRangePredicate<V>) p;
int minimum = r.getMinimumValue(); int minimum = r.getMinimumValue();
int maximum = r.getMaximumValue(); int maximum = r.getMaximumValue();
if (minimum == maximum) { if (minimum == maximum) {
@@ -192,11 +187,11 @@ public class QueryBuilder {
throw new QueryParseException("not an integer range: " + p); throw new QueryParseException("not an integer range: " + p);
} }
private Query timestampQuery(IndexPredicate<ChangeData> p) private Query timestampQuery(IndexPredicate<V> p)
throws QueryParseException { throws QueryParseException {
if (p instanceof TimestampRangePredicate) { if (p instanceof TimestampRangePredicate) {
TimestampRangePredicate<ChangeData> r = TimestampRangePredicate<V> r =
(TimestampRangePredicate<ChangeData>) p; (TimestampRangePredicate<V>) p;
return NumericRangeQuery.newLongRange( return NumericRangeQuery.newLongRange(
r.getField().getName(), r.getField().getName(),
r.getMinTimestamp().getTime(), r.getMinTimestamp().getTime(),
@@ -206,7 +201,7 @@ public class QueryBuilder {
throw new QueryParseException("not a timestamp: " + p); throw new QueryParseException("not a timestamp: " + p);
} }
private Query notTimestamp(TimestampRangePredicate<ChangeData> r) private Query notTimestamp(TimestampRangePredicate<V> r)
throws QueryParseException { throws QueryParseException {
if (r.getMinTimestamp().getTime() == 0) { if (r.getMinTimestamp().getTime() == 0) {
return NumericRangeQuery.newLongRange( return NumericRangeQuery.newLongRange(
@@ -218,7 +213,7 @@ public class QueryBuilder {
throw new QueryParseException("cannot negate: " + r); throw new QueryParseException("cannot negate: " + r);
} }
private Query exactQuery(IndexPredicate<ChangeData> p) { private Query exactQuery(IndexPredicate<V> p) {
if (p instanceof RegexPredicate<?>) { if (p instanceof RegexPredicate<?>) {
return regexQuery(p); return regexQuery(p);
} else { } else {
@@ -226,7 +221,7 @@ public class QueryBuilder {
} }
} }
private Query regexQuery(IndexPredicate<ChangeData> p) { private Query regexQuery(IndexPredicate<V> p) {
String re = p.getValue(); String re = p.getValue();
if (re.startsWith("^")) { if (re.startsWith("^")) {
re = re.substring(1); re = re.substring(1);
@@ -237,11 +232,11 @@ public class QueryBuilder {
return new RegexpQuery(new Term(p.getField().getName(), re)); return new RegexpQuery(new Term(p.getField().getName(), re));
} }
private Query prefixQuery(IndexPredicate<ChangeData> p) { private Query prefixQuery(IndexPredicate<V> p) {
return new PrefixQuery(new Term(p.getField().getName(), p.getValue())); return new PrefixQuery(new Term(p.getField().getName(), p.getValue()));
} }
private Query fullTextQuery(IndexPredicate<ChangeData> p) private Query fullTextQuery(IndexPredicate<V> p)
throws QueryParseException { throws QueryParseException {
String value = p.getValue(); String value = p.getValue();
if (value == null) { if (value == null) {

View File

@@ -403,7 +403,9 @@ public class Daemon extends SiteProgram {
} }
switch (indexType) { switch (indexType) {
case LUCENE: case LUCENE:
return luceneModule != null ? luceneModule : new LuceneIndexModule(); return luceneModule != null
? luceneModule
: LuceneIndexModule.latestVersionWithOnlineUpgrade();
default: default:
throw new IllegalStateException("unsupported index.type = " + indexType); throw new IllegalStateException("unsupported index.type = " + indexType);
} }

View File

@@ -31,9 +31,10 @@ import com.google.gerrit.server.git.ScanningChangeCacheImpl;
import com.google.gerrit.server.index.IndexModule; import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.IndexModule.IndexType; import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.SiteIndexer; import com.google.gerrit.server.index.SiteIndexer;
import com.google.gerrit.server.index.change.AllChangesIndexer;
import com.google.gerrit.server.index.change.ChangeIndex; import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeSchemas; import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Injector; import com.google.inject.Injector;
@@ -47,7 +48,9 @@ import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.util.io.NullOutputStream; import org.eclipse.jgit.util.io.NullOutputStream;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -55,9 +58,9 @@ public class Reindex extends SiteProgram {
@Option(name = "--threads", usage = "Number of threads to use for indexing") @Option(name = "--threads", usage = "Number of threads to use for indexing")
private int threads = Runtime.getRuntime().availableProcessors(); private int threads = Runtime.getRuntime().availableProcessors();
@Option(name = "--schema-version", @Option(name = "--changes-schema-version",
usage = "Schema version to reindex; default is most recent version") usage = "Schema version to reindex, for changes; default is most recent version")
private Integer version; private Integer changesVersion;
@Option(name = "--verbose", usage = "Output debug information for each change") @Option(name = "--verbose", usage = "Output debug information for each change")
private boolean verbose; private boolean verbose;
@@ -82,9 +85,6 @@ public class Reindex extends SiteProgram {
checkNotSlaveMode(); checkNotSlaveMode();
disableLuceneAutomaticCommit(); disableLuceneAutomaticCommit();
disableChangeCache(); disableChangeCache();
if (version == null) {
version = ChangeSchemas.getLatest().getVersion();
}
LifecycleManager dbManager = new LifecycleManager(); LifecycleManager dbManager = new LifecycleManager();
dbManager.add(dbInjector); dbManager.add(dbInjector);
dbManager.start(); dbManager.start();
@@ -120,11 +120,16 @@ public class Reindex extends SiteProgram {
} }
private Injector createSysInjector() { private Injector createSysInjector() {
Map<String, Integer> versions = new HashMap<>();
if (changesVersion != null) {
versions.put(ChangeSchemaDefinitions.INSTANCE.getName(), changesVersion);
}
List<Module> modules = Lists.newArrayList(); List<Module> modules = Lists.newArrayList();
Module changeIndexModule; Module changeIndexModule;
switch (IndexModule.getIndexType(dbInjector)) { switch (IndexModule.getIndexType(dbInjector)) {
case LUCENE: case LUCENE:
changeIndexModule = new LuceneIndexModule(version, threads); changeIndexModule = LuceneIndexModule.singleVersionWithExplicitVersions(
versions, threads);
break; break;
default: default:
throw new IllegalStateException("unsupported index.type"); throw new IllegalStateException("unsupported index.type");
@@ -164,9 +169,9 @@ public class Reindex extends SiteProgram {
} }
pm.endTask(); pm.endTask();
SiteIndexer batchIndexer = AllChangesIndexer batchIndexer =
sysInjector.getInstance(SiteIndexer.class); sysInjector.getInstance(AllChangesIndexer.class);
SiteIndexer.Result result = batchIndexer.setNumChanges(changeCount) SiteIndexer.Result result = batchIndexer.setTotalWork(changeCount)
.setProgressOut(System.err) .setProgressOut(System.err)
.setVerboseOut(verbose ? System.out : NullOutputStream.INSTANCE) .setVerboseOut(verbose ? System.out : NullOutputStream.INSTANCE)
.indexAll(index, projects); .indexAll(index, projects);

View File

@@ -15,14 +15,15 @@
package com.google.gerrit.pgm.init; package com.google.gerrit.pgm.init;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.gerrit.lucene.LuceneChangeIndex; import com.google.gerrit.lucene.AbstractLuceneIndex;
import com.google.gerrit.pgm.init.api.ConsoleUI; import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags; import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep; import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section; import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.IndexModule.IndexType; import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.change.ChangeSchemas; import com.google.gerrit.server.index.SchemaDefinitions;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -57,8 +58,11 @@ class InitIndex implements InitStep {
ui.header("Index"); ui.header("Index");
IndexType type = index.select("Type", "type", IndexType.LUCENE); IndexType type = index.select("Type", "type", IndexType.LUCENE);
LuceneChangeIndex.setReady( for (SchemaDefinitions<?> def : IndexModule.ALL_SCHEMA_DEFS) {
site, ChangeSchemas.getLatest().getVersion(), true); // TODO(dborowitz): Totally broken for non-change indexes.
AbstractLuceneIndex.setReady(
site, def.getLatest().getVersion(), true);
}
if ((site.isNew || isEmptySite()) && type == IndexType.LUCENE) { if ((site.isNew || isEmptySite()) && type == IndexType.LUCENE) {
} else { } else {
final String message = String.format( final String message = String.format(

View File

@@ -14,15 +14,24 @@
package com.google.gerrit.server.index; package com.google.gerrit.server.index;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.DummyChangeIndex; import com.google.gerrit.server.index.change.DummyChangeIndex;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
public class DummyIndexModule extends AbstractModule { public class DummyIndexModule extends AbstractModule {
private static class DummyChangeIndexFactory implements ChangeIndex.Factory {
@Override
public ChangeIndex create(Schema<ChangeData> schema) {
throw new UnsupportedOperationException();
}
}
@Override @Override
protected void configure() { protected void configure() {
install(new IndexModule(1)); install(new IndexModule(1));
bind(IndexConfig.class).toInstance(IndexConfig.createDefault()); bind(IndexConfig.class).toInstance(IndexConfig.createDefault());
bind(Index.class).toInstance(new DummyChangeIndex()); bind(Index.class).toInstance(new DummyChangeIndex());
bind(ChangeIndex.Factory.class).toInstance(new DummyChangeIndexFactory());
} }
} }

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2016 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.server.index;
import com.google.common.collect.ImmutableSortedMap;
/**
* Definition of an index over a Gerrit data type.
* <p>
* An <em>index</em> includes a set of schema definitions along with the
* specific implementations used to query the secondary index implementation in
* a running server. If you are just interested in the static definition of one
* or more schemas, see the implementations of {@link SchemaDefinitions}.
*/
public abstract class IndexDefinition<K, V, I extends Index<K, V>> {
public interface IndexFactory<K, V, I extends Index<K, V>> {
I create(Schema<V> schema);
}
private final SchemaDefinitions<V> schemaDefs;
private final IndexCollection<K, V, I> indexCollection;
private final IndexFactory<K, V, I> indexFactory;
private final SiteIndexer<K, V, I> siteIndexer;
protected IndexDefinition(
SchemaDefinitions<V> schemaDefs,
IndexCollection<K, V, I> indexCollection,
IndexFactory<K, V, I> indexFactory,
SiteIndexer<K, V, I> siteIndexer) {
this.schemaDefs = schemaDefs;
this.indexCollection = indexCollection;
this.indexFactory = indexFactory;
this.siteIndexer = siteIndexer;
}
public final String getName() {
return schemaDefs.getName();
}
public final ImmutableSortedMap<Integer, Schema<V>> getSchemas() {
return schemaDefs.getSchemas();
}
public final Schema<V> getLatest() {
return schemaDefs.getLatest();
}
public final IndexCollection<K, V, I> getIndexCollection() {
return indexCollection;
}
public final IndexFactory<K, V, I> getIndexFactory() {
return indexFactory;
}
public final SiteIndexer<K, V, I> getSiteIndexer() {
return siteIndexer;
}
}

View File

@@ -17,21 +17,31 @@ package com.google.gerrit.server.index;
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH; import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE; import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeIndexDefintion;
import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.change.IndexRewriter; import com.google.gerrit.server.index.change.IndexRewriter;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import java.util.Collection;
import java.util.Set;
/** /**
* Module for non-indexer-specific secondary index setup. * Module for non-indexer-specific secondary index setup.
* <p> * <p>
@@ -43,6 +53,10 @@ public class IndexModule extends LifecycleModule {
LUCENE LUCENE
} }
public static final ImmutableCollection<SchemaDefinitions<?>> ALL_SCHEMA_DEFS =
ImmutableList.<SchemaDefinitions<?>> of(
ChangeSchemaDefinitions.INSTANCE);
/** Type of secondary index. */ /** Type of secondary index. */
public static IndexType getIndexType(Injector injector) { public static IndexType getIndexType(Injector injector) {
Config cfg = injector.getInstance( Config cfg = injector.getInstance(
@@ -74,6 +88,33 @@ public class IndexModule extends LifecycleModule {
factory(ChangeIndexer.Factory.class); factory(ChangeIndexer.Factory.class);
} }
@Provides
Collection<IndexDefinition<?, ?, ?>> getIndexDefinitions(
ChangeIndexDefintion changes) {
Collection<IndexDefinition<?, ?, ?>> result =
ImmutableList.<IndexDefinition<?, ?, ?>> of(changes);
Set<String> expected = FluentIterable.from(ALL_SCHEMA_DEFS)
.transform(new Function<SchemaDefinitions<?>, String>() {
@Override
public String apply(SchemaDefinitions<?> in) {
return in.getName();
}
}).toSet();
Set<String> actual = FluentIterable.from(result)
.transform(new Function<IndexDefinition<?, ?, ?>, String>() {
@Override
public String apply(IndexDefinition<?, ?, ?> in) {
return in.getName();
}
}).toSet();
if (!expected.equals(actual)) {
throw new ProvisionException(
"need index definitions for all schemas: "
+ expected + " != " + actual);
}
return result;
}
@Provides @Provides
@Singleton @Singleton
ChangeIndexer getChangeIndexer( ChangeIndexer getChangeIndexer(

View File

@@ -12,18 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package com.google.gerrit.lucene; package com.google.gerrit.server.index;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.SiteIndexer;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -32,30 +25,21 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class OnlineReindexer { public class OnlineReindexer<K, V, I extends Index<K, V>> {
private static final Logger log = LoggerFactory private static final Logger log = LoggerFactory
.getLogger(OnlineReindexer.class); .getLogger(OnlineReindexer.class);
public interface Factory { private final IndexCollection<K, V, I> indexes;
OnlineReindexer create(int version); private final SiteIndexer<K, V, I> batchIndexer;
}
private final ChangeIndexCollection indexes;
private final SiteIndexer batchIndexer;
private final ProjectCache projectCache;
private final int version; private final int version;
private ChangeIndex index; private I index;
private final AtomicBoolean running = new AtomicBoolean(); private final AtomicBoolean running = new AtomicBoolean();
@Inject public OnlineReindexer(
OnlineReindexer( IndexDefinition<K, V, I> def,
ChangeIndexCollection indexes, int version) {
SiteIndexer batchIndexer, this.indexes = def.getIndexCollection();
ProjectCache projectCache, this.batchIndexer = def.getSiteIndexer();
@Assisted int version) {
this.indexes = indexes;
this.batchIndexer = batchIndexer;
this.projectCache = projectCache;
this.version = version; this.version = version;
} }
@@ -94,8 +78,7 @@ public class OnlineReindexer {
"not an active write schema version: %s", version); "not an active write schema version: %s", version);
log.info("Starting online reindex from schema version {} to {}", log.info("Starting online reindex from schema version {} to {}",
version(indexes.getSearchIndex()), version(index)); version(indexes.getSearchIndex()), version(index));
SiteIndexer.Result result = SiteIndexer.Result result = batchIndexer.indexAll(index);
batchIndexer.indexAll(index, projectCache.all());
if (!result.success()) { if (!result.success()) {
log.error("Online reindex of schema version {} failed. Successfully" log.error("Online reindex of schema version {} failed. Successfully"
+ " indexed {} changes, failed to index {} changes", + " indexed {} changes, failed to index {} changes",
@@ -106,7 +89,7 @@ public class OnlineReindexer {
activateIndex(); activateIndex();
} }
void activateIndex() { public void activateIndex() {
indexes.setSearchIndex(index); indexes.setSearchIndex(index);
log.info("Using schema version {}", version(index)); log.info("Using schema version {}", version(index));
try { try {
@@ -115,13 +98,13 @@ public class OnlineReindexer {
log.warn("Error activating new schema version {}", version(index)); log.warn("Error activating new schema version {}", version(index));
} }
List<ChangeIndex> toRemove = Lists.newArrayListWithExpectedSize(1); List<I> toRemove = Lists.newArrayListWithExpectedSize(1);
for (ChangeIndex i : indexes.getWriteIndexes()) { for (I i : indexes.getWriteIndexes()) {
if (version(i) != version(index)) { if (version(i) != version(index)) {
toRemove.add(i); toRemove.add(i);
} }
} }
for (ChangeIndex i : toRemove) { for (I i : toRemove) {
try { try {
i.markReady(false); i.markReady(false);
indexes.removeWriteIndex(version(i)); indexes.removeWriteIndex(version(i));

View File

@@ -0,0 +1,58 @@
// Copyright (C) 2016 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.server.index;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableSortedMap;
/**
* Definitions of the various schema versions over a given Gerrit data type.
* <p>
* A <em>schema</em> is a description of the fields that are indexed over the
* given data type. This class contains all the versions of a schema defined
* over its data type, exposed as a map of version number to schema definition.
* If you are interested in the classes responsible for backend-specific runtime
* implementations, see the implementations of {@link IndexDefinition}.
*/
public abstract class SchemaDefinitions<V> {
private final String name;
private final ImmutableSortedMap<Integer, Schema<V>> schemas;
protected SchemaDefinitions(String name, Class<V> valueClass) {
this.name = checkNotNull(name);
this.schemas = SchemaUtil.schemasFromClass(getClass(), valueClass);
}
public final String getName() {
return name;
}
public final ImmutableSortedMap<Integer, Schema<V>> getSchemas() {
return schemas;
}
public final Schema<V> get(int version) {
Schema<V> schema = schemas.get(version);
checkArgument(schema != null,
"Unrecognized %s schema version: %s", name, version);
return schema;
}
public final Schema<V> getLatest() {
return schemas.lastEntry().getValue();
}
}

View File

@@ -19,8 +19,8 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.CharMatcher; import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -30,16 +30,16 @@ import org.eclipse.jgit.lib.PersonIdent;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class SchemaUtil { public class SchemaUtil {
public static <V> ImmutableMap<Integer, Schema<V>> schemasFromClass( public static <V> ImmutableSortedMap<Integer, Schema<V>> schemasFromClass(
Class<?> schemasClass, Class<V> valueClass) { Class<?> schemasClass, Class<V> valueClass) {
Map<Integer, Schema<V>> schemas = Maps.newTreeMap(); Map<Integer, Schema<V>> schemas = Maps.newHashMap();
for (Field f : schemasClass.getDeclaredFields()) { for (Field f : schemasClass.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()) if (Modifier.isStatic(f.getModifiers())
&& Modifier.isFinal(f.getModifiers()) && Modifier.isFinal(f.getModifiers())
@@ -65,7 +65,7 @@ public class SchemaUtil {
if (schemas.isEmpty()) { if (schemas.isEmpty()) {
throw new ExceptionInInitializerError("no ChangeSchemas found"); throw new ExceptionInInitializerError("no ChangeSchemas found");
} }
return ImmutableMap.copyOf(schemas); return ImmutableSortedMap.copyOf(schemas);
} }
public static <V> Schema<V> schema(Collection<FieldDef<V, ?>> fields) { public static <V> Schema<V> schema(Collection<FieldDef<V, ?>> fields) {
@@ -81,13 +81,28 @@ public class SchemaUtil {
if (person == null) { if (person == null) {
return ImmutableSet.of(); return ImmutableSet.of();
} }
HashSet<String> parts = Sets.newHashSet(); return getPersonParts(
String email = person.getEmailAddress().toLowerCase(); person.getName(),
parts.add(email); Collections.singleton(person.getEmailAddress()));
parts.addAll(Arrays.asList(email.split("@"))); }
public static Set<String> getPersonParts(String name,
Iterable<String> emails) {
Splitter at = Splitter.on('@');
Splitter s = Splitter.on(CharMatcher.anyOf("@.- ")).omitEmptyStrings(); Splitter s = Splitter.on(CharMatcher.anyOf("@.- ")).omitEmptyStrings();
Iterables.addAll(parts, s.split(email)); HashSet<String> parts = Sets.newHashSet();
Iterables.addAll(parts, s.split(person.getName().toLowerCase())); for (String email : emails) {
if (email == null) {
continue;
}
String lowerEmail = email.toLowerCase();
parts.add(lowerEmail);
Iterables.addAll(parts, at.split(lowerEmail));
Iterables.addAll(parts, s.split(lowerEmail));
}
if (name != null) {
Iterables.addAll(parts, s.split(name.toLowerCase()));
}
return parts; return parts;
} }

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2013 The Android Open Source Project // Copyright (C) 2016 The Android Open Source Project
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -14,81 +14,18 @@
package com.google.gerrit.server.index; package com.google.gerrit.server.index;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AsyncFunction;
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.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListLoader;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class SiteIndexer { public interface SiteIndexer<K, V, I extends Index<K, V>> {
private static final Logger log = public class Result {
LoggerFactory.getLogger(SiteIndexer.class);
public static class Result {
private final long elapsedNanos; private final long elapsedNanos;
private final boolean success; private final boolean success;
private final int done; private final int done;
private final int failed; private final int failed;
private Result(Stopwatch sw, boolean success, int done, int failed) { public Result(Stopwatch sw, boolean success, int done, int failed) {
this.elapsedNanos = sw.elapsed(TimeUnit.NANOSECONDS); this.elapsedNanos = sw.elapsed(TimeUnit.NANOSECONDS);
this.success = success; this.success = success;
this.done = done; this.done = done;
@@ -112,313 +49,5 @@ public class SiteIndexer {
} }
} }
private final SchemaFactory<ReviewDb> schemaFactory; Result indexAll(I index);
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
private final ListeningExecutorService executor;
private final ChangeIndexer.Factory indexerFactory;
private final ChangeNotes.Factory notesFactory;
private final ThreeWayMergeStrategy mergeStrategy;
private int numChanges = -1;
private OutputStream progressOut = NullOutputStream.INSTANCE;
private PrintWriter verboseWriter =
new PrintWriter(NullOutputStream.INSTANCE);
@Inject
SiteIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager,
@IndexExecutor(BATCH) ListeningExecutorService executor,
ChangeIndexer.Factory indexerFactory,
ChangeNotes.Factory notesFactory,
@GerritServerConfig Config config) {
this.schemaFactory = schemaFactory;
this.changeDataFactory = changeDataFactory;
this.repoManager = repoManager;
this.executor = executor;
this.indexerFactory = indexerFactory;
this.notesFactory = notesFactory;
this.mergeStrategy = MergeUtil.getMergeStrategy(config);
}
public SiteIndexer setNumChanges(int num) {
numChanges = num;
return this;
}
public SiteIndexer setProgressOut(OutputStream out) {
progressOut = checkNotNull(out);
return this;
}
public SiteIndexer setVerboseOut(OutputStream out) {
verboseWriter = new PrintWriter(checkNotNull(out));
return this;
}
public Result indexAll(ChangeIndex index,
Iterable<Project.NameKey> projects) {
Stopwatch sw = Stopwatch.createStarted();
final MultiProgressMonitor mpm =
new MultiProgressMonitor(progressOut, "Reindexing changes");
final Task projTask = mpm.beginSubTask("projects",
(projects instanceof Collection)
? ((Collection<?>) projects).size()
: MultiProgressMonitor.UNKNOWN);
final Task doneTask = mpm.beginSubTask(null,
numChanges >= 0 ? numChanges : MultiProgressMonitor.UNKNOWN);
final Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
final List<ListenableFuture<?>> futures = Lists.newArrayList();
final AtomicBoolean ok = new AtomicBoolean(true);
for (final Project.NameKey project : projects) {
final ListenableFuture<?> future = executor.submit(reindexProject(
indexerFactory.create(executor, index), project, doneTask, failedTask,
verboseWriter));
futures.add(future);
future.addListener(new Runnable() {
@Override
public void run() {
try {
future.get();
} catch (ExecutionException | InterruptedException e) {
fail(project, e);
} catch (RuntimeException e) {
failAndThrow(project, e);
} catch (Error e) {
// Can't join with RuntimeException because "RuntimeException |
// Error" becomes Throwable, which messes with signatures.
failAndThrow(project, e);
} finally {
projTask.update(1);
}
}
private void fail(Project.NameKey project, Throwable t) {
log.error("Failed to index project " + project, t);
ok.set(false);
}
private void failAndThrow(Project.NameKey project, RuntimeException e) {
fail(project, e);
throw e;
}
private void failAndThrow(Project.NameKey project, Error e) {
fail(project, e);
throw e;
}
}, MoreExecutors.directExecutor());
}
try {
mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
new AsyncFunction<List<?>, Void>() {
@Override
public ListenableFuture<Void> apply(List<?> input) {
mpm.end();
return Futures.immediateFuture(null);
}
}));
} catch (ExecutionException e) {
log.error("Error in batch indexer", e);
ok.set(false);
}
// If too many changes failed, maybe there was a bug in the indexer. Don't
// trust the results. This is not an exact percentage since we bump the same
// failure counter if a project can't be read, but close enough.
int nFailed = failedTask.getCount();
int nTotal = nFailed + doneTask.getCount();
double pctFailed = ((double) nFailed) / nTotal * 100;
if (pctFailed > 10) {
log.error("Failed {}/{} changes ({}%); not marking new index as ready",
nFailed, nTotal, Math.round(pctFailed));
ok.set(false);
}
return new Result(sw, ok.get(), doneTask.getCount(), failedTask.getCount());
}
private Callable<Void> reindexProject(final ChangeIndexer indexer,
final Project.NameKey project, final Task done, final Task failed,
final PrintWriter verboseWriter) {
return new Callable<Void>() {
@Override
public Void call() throws Exception {
Multimap<ObjectId, ChangeData> byId = ArrayListMultimap.create();
// TODO(dborowitz): Opening all repositories in a live server may be
// wasteful; see if we can determine which ones it is safe to close
// with RepositoryCache.close(repo).
try (Repository repo = repoManager.openRepository(project);
ReviewDb db = schemaFactory.open()) {
Map<String, Ref> refs = repo.getRefDatabase().getRefs(ALL);
for (ChangeNotes cn : notesFactory.scan(repo, db, project)) {
Ref r = refs.get(cn.getChange().currentPatchSetId().toRefName());
if (r != null) {
byId.put(r.getObjectId(), changeDataFactory.create(db, cn));
}
}
new ProjectIndexer(indexer,
mergeStrategy,
byId,
repo,
done,
failed,
verboseWriter).call();
} catch (RepositoryNotFoundException rnfe) {
log.error(rnfe.getMessage());
}
return null;
}
@Override
public String toString() {
return "Index all changes of project " + project.get();
}
};
}
private static class ProjectIndexer implements Callable<Void> {
private final ChangeIndexer indexer;
private final ThreeWayMergeStrategy mergeStrategy;
private final Multimap<ObjectId, ChangeData> byId;
private final ProgressMonitor done;
private final ProgressMonitor failed;
private final PrintWriter verboseWriter;
private final Repository repo;
private RevWalk walk;
private ProjectIndexer(ChangeIndexer indexer,
ThreeWayMergeStrategy mergeStrategy,
Multimap<ObjectId, ChangeData> changesByCommitId,
Repository repo,
ProgressMonitor done,
ProgressMonitor failed,
PrintWriter verboseWriter) {
this.indexer = indexer;
this.mergeStrategy = mergeStrategy;
this.byId = changesByCommitId;
this.repo = repo;
this.done = done;
this.failed = failed;
this.verboseWriter = verboseWriter;
}
@Override
public Void call() throws Exception {
walk = new RevWalk(repo);
try {
// Walk only refs first to cover as many changes as we can without having
// to mark every single change.
for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_HEADS).values()) {
RevObject o = walk.parseAny(ref.getObjectId());
if (o instanceof RevCommit) {
walk.markStart((RevCommit) o);
}
}
RevCommit bCommit;
while ((bCommit = walk.next()) != null && !byId.isEmpty()) {
if (byId.containsKey(bCommit)) {
getPathsAndIndex(bCommit);
byId.removeAll(bCommit);
}
}
for (ObjectId id : byId.keySet()) {
getPathsAndIndex(id);
}
} finally {
walk.close();
}
return null;
}
private void getPathsAndIndex(ObjectId b) throws Exception {
List<ChangeData> cds = Lists.newArrayList(byId.get(b));
try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
RevCommit bCommit = walk.parseCommit(b);
RevTree bTree = bCommit.getTree();
RevTree aTree = aFor(bCommit, walk);
df.setRepository(repo);
if (!cds.isEmpty()) {
List<String> paths = (aTree != null)
? getPaths(df.scan(aTree, bTree))
: Collections.<String>emptyList();
Iterator<ChangeData> cdit = cds.iterator();
for (ChangeData cd ; cdit.hasNext(); cdit.remove()) {
cd = cdit.next();
try {
cd.setCurrentFilePaths(paths);
indexer.index(cd);
done.update(1);
if (verboseWriter != null) {
verboseWriter.println("Reindexed change " + cd.getId());
}
} catch (Exception e) {
fail("Failed to index change " + cd.getId(), true, e);
}
}
}
} catch (Exception e) {
fail("Failed to index commit " + b.name(), false, e);
for (ChangeData cd : cds) {
fail("Failed to index change " + cd.getId(), true, null);
}
}
}
private List<String> getPaths(List<DiffEntry> filenames) {
Set<String> paths = Sets.newTreeSet();
for (DiffEntry e : filenames) {
if (e.getOldPath() != null) {
paths.add(e.getOldPath());
}
if (e.getNewPath() != null) {
paths.add(e.getNewPath());
}
}
return ImmutableList.copyOf(paths);
}
private RevTree aFor(RevCommit b, RevWalk walk) throws IOException {
switch (b.getParentCount()) {
case 0:
return walk.parseTree(emptyTree());
case 1:
RevCommit a = b.getParent(0);
walk.parseBody(a);
return walk.parseTree(a.getTree());
case 2:
return PatchListLoader.automerge(repo, walk, b, mergeStrategy);
default:
return null;
}
}
private ObjectId emptyTree() throws IOException {
try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
oi.flush();
return id;
}
}
private void fail(String error, boolean failed, Exception e) {
if (failed) {
this.failed.update(1);
}
if (e != null) {
log.warn(error, e);
} else {
log.warn(error);
}
if (verboseWriter != null) {
verboseWriter.println(error);
}
}
}
} }

View File

@@ -0,0 +1,140 @@
// Copyright (C) 2016 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.server.index.account;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.SchemaUtil;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.Set;
/** Secondary index schemas for accounts. */
public class AccountField {
public static final FieldDef<AccountState, Integer> ID =
new FieldDef.Single<AccountState, Integer>(
"id", FieldType.INTEGER, true) {
@Override
public Integer get(AccountState input, FillArgs args) {
return input.getAccount().getId().get();
}
};
public static final FieldDef<AccountState, Iterable<String>> EXTERNAL_ID =
new FieldDef.Repeatable<AccountState, String>(
"external_id", FieldType.EXACT, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
return Iterables.transform(
input.getExternalIds(),
new Function<AccountExternalId, String>() {
@Override
public String apply(AccountExternalId in) {
return in.getKey().get();
}
});
}
};
/** Fuzzy prefix match on name and email parts. */
public static final FieldDef<AccountState, Iterable<String>> NAME_PART =
new FieldDef.Repeatable<AccountState, String>(
"name", FieldType.PREFIX, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
String fullName = input.getAccount().getFullName();
Set<String> parts = SchemaUtil.getPersonParts(
fullName,
Iterables.transform(
input.getExternalIds(),
new Function<AccountExternalId, String>() {
@Override
public String apply(AccountExternalId in) {
return in.getEmailAddress();
}
}));
// Additional values not currently added by getPersonParts.
// TODO(dborowitz): Move to getPersonParts and remove this hack.
if (fullName != null) {
parts.add(fullName);
}
return parts;
}
};
public static final FieldDef<AccountState, String> ACTIVE =
new FieldDef.Single<AccountState, String>(
"inactive", FieldType.EXACT, false) {
@Override
public String get(AccountState input, FillArgs args) {
return input.getAccount().isActive() ? "1" : "0";
}
};
public static final FieldDef<AccountState, Iterable<String>> EMAIL =
new FieldDef.Repeatable<AccountState, String>(
"email", FieldType.PREFIX, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
return FluentIterable.from(input.getExternalIds())
.transform(
new Function<AccountExternalId, String>() {
@Override
public String apply(AccountExternalId in) {
return in.getEmailAddress();
}
})
.append(
Collections.singleton(input.getAccount().getPreferredEmail()))
.filter(Predicates.notNull())
.transform(
new Function<String, String>() {
@Override
public String apply(String in) {
return in.toLowerCase();
}
});
}
};
public static final FieldDef<AccountState, Timestamp> REGISTERED =
new FieldDef.Single<AccountState, Timestamp>(
"registered", FieldType.TIMESTAMP, false) {
@Override
public Timestamp get(AccountState input, FillArgs args) {
return input.getAccount().getRegisteredOn();
}
};
public static final FieldDef<AccountState, String> USERNAME =
new FieldDef.Single<AccountState, String>(
"username", null, false) {
@Override
public String get(AccountState input, FillArgs args) {
return input.getUserName().toLowerCase();
}
};
private AccountField() {
}
}

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2016 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.server.index.account;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.SchemaUtil;
import java.util.Collection;
public class AccountSchemas {
static final Schema<AccountState> V1 = schema(
AccountField.ID,
AccountField.ACTIVE,
AccountField.EMAIL,
AccountField.EXTERNAL_ID,
AccountField.NAME_PART,
AccountField.REGISTERED,
AccountField.USERNAME);
private static Schema<AccountState> schema(
Collection<FieldDef<AccountState, ?>> fields) {
return new Schema<>(ImmutableList.copyOf(fields));
}
@SafeVarargs
private static Schema<AccountState> schema(
FieldDef<AccountState, ?>... fields) {
return schema(ImmutableList.copyOf(fields));
}
public static final ImmutableMap<Integer, Schema<AccountState>> ALL =
SchemaUtil.schemasFromClass(AccountSchemas.class, AccountState.class);
public static Schema<AccountState> get(int version) {
Schema<AccountState> schema = ALL.get(version);
checkArgument(schema != null, "Unrecognized schema version: %s", version);
return schema;
}
public static Schema<AccountState> getLatest() {
return Iterables.getLast(ALL.values());
}
}

View File

@@ -0,0 +1,404 @@
// Copyright (C) 2013 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.server.index.change;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AsyncFunction;
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.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.SiteIndexer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListLoader;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
public class AllChangesIndexer
implements SiteIndexer<Change.Id, ChangeData, ChangeIndex> {
private static final Logger log =
LoggerFactory.getLogger(AllChangesIndexer.class);
private final SchemaFactory<ReviewDb> schemaFactory;
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
private final ListeningExecutorService executor;
private final ChangeIndexer.Factory indexerFactory;
private final ChangeNotes.Factory notesFactory;
private final ProjectCache projectCache;
private final ThreeWayMergeStrategy mergeStrategy;
private int totalWork = -1;
private OutputStream progressOut = NullOutputStream.INSTANCE;
private PrintWriter verboseWriter =
new PrintWriter(NullOutputStream.INSTANCE);
@Inject
AllChangesIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager,
@IndexExecutor(BATCH) ListeningExecutorService executor,
ChangeIndexer.Factory indexerFactory,
ChangeNotes.Factory notesFactory,
@GerritServerConfig Config config,
ProjectCache projectCache) {
this.schemaFactory = schemaFactory;
this.changeDataFactory = changeDataFactory;
this.repoManager = repoManager;
this.executor = executor;
this.indexerFactory = indexerFactory;
this.notesFactory = notesFactory;
this.projectCache = projectCache;
this.mergeStrategy = MergeUtil.getMergeStrategy(config);
}
public AllChangesIndexer setTotalWork(int num) {
totalWork = num;
return this;
}
public AllChangesIndexer setProgressOut(OutputStream out) {
progressOut = checkNotNull(out);
return this;
}
public AllChangesIndexer setVerboseOut(OutputStream out) {
verboseWriter = new PrintWriter(checkNotNull(out));
return this;
}
@Override
public Result indexAll(ChangeIndex index) {
return indexAll(index, projectCache.all());
}
public SiteIndexer.Result indexAll(ChangeIndex index,
Iterable<Project.NameKey> projects) {
Stopwatch sw = Stopwatch.createStarted();
final MultiProgressMonitor mpm =
new MultiProgressMonitor(progressOut, "Reindexing changes");
final Task projTask = mpm.beginSubTask("projects",
(projects instanceof Collection)
? ((Collection<?>) projects).size()
: MultiProgressMonitor.UNKNOWN);
final Task doneTask = mpm.beginSubTask(null,
totalWork >= 0 ? totalWork : MultiProgressMonitor.UNKNOWN);
final Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
final List<ListenableFuture<?>> futures = Lists.newArrayList();
final AtomicBoolean ok = new AtomicBoolean(true);
for (final Project.NameKey project : projects) {
final ListenableFuture<?> future = executor.submit(reindexProject(
indexerFactory.create(executor, index), project, doneTask, failedTask,
verboseWriter));
futures.add(future);
future.addListener(new Runnable() {
@Override
public void run() {
try {
future.get();
} catch (ExecutionException | InterruptedException e) {
fail(project, e);
} catch (RuntimeException e) {
failAndThrow(project, e);
} catch (Error e) {
// Can't join with RuntimeException because "RuntimeException |
// Error" becomes Throwable, which messes with signatures.
failAndThrow(project, e);
} finally {
projTask.update(1);
}
}
private void fail(Project.NameKey project, Throwable t) {
log.error("Failed to index project " + project, t);
ok.set(false);
}
private void failAndThrow(Project.NameKey project, RuntimeException e) {
fail(project, e);
throw e;
}
private void failAndThrow(Project.NameKey project, Error e) {
fail(project, e);
throw e;
}
}, MoreExecutors.directExecutor());
}
try {
mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
new AsyncFunction<List<?>, Void>() {
@Override
public ListenableFuture<Void> apply(List<?> input) {
mpm.end();
return Futures.immediateFuture(null);
}
}));
} catch (ExecutionException e) {
log.error("Error in batch indexer", e);
ok.set(false);
}
// If too many changes failed, maybe there was a bug in the indexer. Don't
// trust the results. This is not an exact percentage since we bump the same
// failure counter if a project can't be read, but close enough.
int nFailed = failedTask.getCount();
int nTotal = nFailed + doneTask.getCount();
double pctFailed = ((double) nFailed) / nTotal * 100;
if (pctFailed > 10) {
log.error("Failed {}/{} changes ({}%); not marking new index as ready",
nFailed, nTotal, Math.round(pctFailed));
ok.set(false);
}
return new Result(sw, ok.get(), doneTask.getCount(), failedTask.getCount());
}
private Callable<Void> reindexProject(final ChangeIndexer indexer,
final Project.NameKey project, final Task done, final Task failed,
final PrintWriter verboseWriter) {
return new Callable<Void>() {
@Override
public Void call() throws Exception {
Multimap<ObjectId, ChangeData> byId = ArrayListMultimap.create();
// TODO(dborowitz): Opening all repositories in a live server may be
// wasteful; see if we can determine which ones it is safe to close
// with RepositoryCache.close(repo).
try (Repository repo = repoManager.openRepository(project);
ReviewDb db = schemaFactory.open()) {
Map<String, Ref> refs = repo.getRefDatabase().getRefs(ALL);
for (ChangeNotes cn : notesFactory.scan(repo, db, project)) {
Ref r = refs.get(cn.getChange().currentPatchSetId().toRefName());
if (r != null) {
byId.put(r.getObjectId(), changeDataFactory.create(db, cn));
}
}
new ProjectIndexer(indexer,
mergeStrategy,
byId,
repo,
done,
failed,
verboseWriter).call();
} catch (RepositoryNotFoundException rnfe) {
log.error(rnfe.getMessage());
}
return null;
}
@Override
public String toString() {
return "Index all changes of project " + project.get();
}
};
}
private static class ProjectIndexer implements Callable<Void> {
private final ChangeIndexer indexer;
private final ThreeWayMergeStrategy mergeStrategy;
private final Multimap<ObjectId, ChangeData> byId;
private final ProgressMonitor done;
private final ProgressMonitor failed;
private final PrintWriter verboseWriter;
private final Repository repo;
private RevWalk walk;
private ProjectIndexer(ChangeIndexer indexer,
ThreeWayMergeStrategy mergeStrategy,
Multimap<ObjectId, ChangeData> changesByCommitId,
Repository repo,
ProgressMonitor done,
ProgressMonitor failed,
PrintWriter verboseWriter) {
this.indexer = indexer;
this.mergeStrategy = mergeStrategy;
this.byId = changesByCommitId;
this.repo = repo;
this.done = done;
this.failed = failed;
this.verboseWriter = verboseWriter;
}
@Override
public Void call() throws Exception {
walk = new RevWalk(repo);
try {
// Walk only refs first to cover as many changes as we can without having
// to mark every single change.
for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_HEADS).values()) {
RevObject o = walk.parseAny(ref.getObjectId());
if (o instanceof RevCommit) {
walk.markStart((RevCommit) o);
}
}
RevCommit bCommit;
while ((bCommit = walk.next()) != null && !byId.isEmpty()) {
if (byId.containsKey(bCommit)) {
getPathsAndIndex(bCommit);
byId.removeAll(bCommit);
}
}
for (ObjectId id : byId.keySet()) {
getPathsAndIndex(id);
}
} finally {
walk.close();
}
return null;
}
private void getPathsAndIndex(ObjectId b) throws Exception {
List<ChangeData> cds = Lists.newArrayList(byId.get(b));
try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
RevCommit bCommit = walk.parseCommit(b);
RevTree bTree = bCommit.getTree();
RevTree aTree = aFor(bCommit, walk);
df.setRepository(repo);
if (!cds.isEmpty()) {
List<String> paths = (aTree != null)
? getPaths(df.scan(aTree, bTree))
: Collections.<String>emptyList();
Iterator<ChangeData> cdit = cds.iterator();
for (ChangeData cd ; cdit.hasNext(); cdit.remove()) {
cd = cdit.next();
try {
cd.setCurrentFilePaths(paths);
indexer.index(cd);
done.update(1);
if (verboseWriter != null) {
verboseWriter.println("Reindexed change " + cd.getId());
}
} catch (Exception e) {
fail("Failed to index change " + cd.getId(), true, e);
}
}
}
} catch (Exception e) {
fail("Failed to index commit " + b.name(), false, e);
for (ChangeData cd : cds) {
fail("Failed to index change " + cd.getId(), true, null);
}
}
}
private List<String> getPaths(List<DiffEntry> filenames) {
Set<String> paths = Sets.newTreeSet();
for (DiffEntry e : filenames) {
if (e.getOldPath() != null) {
paths.add(e.getOldPath());
}
if (e.getNewPath() != null) {
paths.add(e.getNewPath());
}
}
return ImmutableList.copyOf(paths);
}
private RevTree aFor(RevCommit b, RevWalk walk) throws IOException {
switch (b.getParentCount()) {
case 0:
return walk.parseTree(emptyTree());
case 1:
RevCommit a = b.getParent(0);
walk.parseBody(a);
return walk.parseTree(a.getTree());
case 2:
return PatchListLoader.automerge(repo, walk, b, mergeStrategy);
default:
return null;
}
}
private ObjectId emptyTree() throws IOException {
try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
oi.flush();
return id;
}
}
private void fail(String error, boolean failed, Exception e) {
if (failed) {
this.failed.update(1);
}
if (e != null) {
log.warn(error, e);
} else {
log.warn(error);
}
if (verboseWriter != null) {
verboseWriter.println(error);
}
}
}
}

View File

@@ -16,7 +16,11 @@ package com.google.gerrit.server.index.change;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.Index; import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
public interface ChangeIndex extends Index<Change.Id, ChangeData> { public interface ChangeIndex extends Index<Change.Id, ChangeData> {
public interface Factory extends
IndexDefinition.IndexFactory<Change.Id, ChangeData, ChangeIndex> {
}
} }

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2013 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.server.index.change;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
public class ChangeIndexDefintion
extends IndexDefinition<Change.Id, ChangeData, ChangeIndex> {
@Inject
ChangeIndexDefintion(
ChangeIndexCollection indexCollection,
ChangeIndex.Factory indexFactory,
AllChangesIndexer allChangesIndexer) {
super(ChangeSchemaDefinitions.INSTANCE, indexCollection, indexFactory,
allChangesIndexer);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2013 The Android Open Source Project // Copyright (C) 2016 The Android Open Source Project
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -14,17 +14,13 @@
package com.google.gerrit.server.index.change; package com.google.gerrit.server.index.change;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.index.SchemaUtil.schema; import static com.google.gerrit.server.index.SchemaUtil.schema;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.server.index.Schema; import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.SchemaUtil; import com.google.gerrit.server.index.SchemaDefinitions;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
/** Secondary index schemas for changes. */ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
public class ChangeSchemas {
@Deprecated @Deprecated
static final Schema<ChangeData> V25 = schema( static final Schema<ChangeData> V25 = schema(
ChangeField.LEGACY_ID, ChangeField.LEGACY_ID,
@@ -102,16 +98,10 @@ public class ChangeSchemas {
static final Schema<ChangeData> V27 = schema(V26.getFields().values()); static final Schema<ChangeData> V27 = schema(V26.getFields().values());
public static final ImmutableMap<Integer, Schema<ChangeData>> ALL = public static final ChangeSchemaDefinitions INSTANCE =
SchemaUtil.schemasFromClass(ChangeSchemas.class, ChangeData.class); new ChangeSchemaDefinitions();
public static Schema<ChangeData> get(int version) { private ChangeSchemaDefinitions() {
Schema<ChangeData> schema = ALL.get(version); super("changes", ChangeData.class);
checkArgument(schema != null, "Unrecognized schema version: %s", version);
return schema;
}
public static Schema<ChangeData> getLatest() {
return Iterables.getLast(ALL.values());
} }
} }

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.testutil; package com.google.gerrit.testutil;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.Scopes.SINGLETON;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@@ -49,7 +48,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope; import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.git.SendEmailExecutor; import com.google.gerrit.server.git.SendEmailExecutor;
import com.google.gerrit.server.index.IndexModule.IndexType; import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.change.ChangeSchemas; import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier; import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.DiffExecutor; import com.google.gerrit.server.patch.DiffExecutor;
@@ -74,10 +73,12 @@ import com.google.inject.servlet.RequestScoped;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
public class InMemoryModule extends FactoryModule { public class InMemoryModule extends FactoryModule {
@@ -97,8 +98,6 @@ public class InMemoryModule extends FactoryModule {
cfg.unset("cache", null, "directory"); cfg.unset("cache", null, "directory");
cfg.setString("index", null, "type", "lucene"); cfg.setString("index", null, "type", "lucene");
cfg.setBoolean("index", "lucene", "testInmemory", true); cfg.setBoolean("index", "lucene", "testInmemory", true);
cfg.setInt("index", "lucene", "testVersion",
ChangeSchemas.getLatest().getVersion());
cfg.setInt("sendemail", null, "threadPoolSize", 0); cfg.setInt("sendemail", null, "threadPoolSize", 0);
cfg.setBoolean("receive", null, "enableSignedPush", false); cfg.setBoolean("receive", null, "enableSignedPush", false);
cfg.setString("receive", null, "certNonceSeed", "sekret"); cfg.setString("receive", null, "certNonceSeed", "sekret");
@@ -234,16 +233,20 @@ public class InMemoryModule extends FactoryModule {
private Module luceneIndexModule() { private Module luceneIndexModule() {
try { try {
Map<String, Integer> singleVersions = new HashMap<>();
int version = cfg.getInt("index", "lucene", "testVersion", -1); int version = cfg.getInt("index", "lucene", "testVersion", -1);
checkState(ChangeSchemas.ALL.containsKey(version), if (version > 0) {
"invalid index.lucene.testVersion %s", version); singleVersions.put(ChangeSchemaDefinitions.INSTANCE.getName(), version);
}
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.gerrit.lucene.LuceneIndexModule"); Class.forName("com.google.gerrit.lucene.LuceneIndexModule");
Constructor<?> c = clazz.getConstructor(Integer.class, int.class); Method m = clazz.getMethod(
return (Module) c.newInstance(version, 0); "singleVersionWithExplicitVersions", Map.class, int.class);
return (Module) m.invoke(null, singleVersions, 0);
} catch (ClassNotFoundException | SecurityException | NoSuchMethodException } catch (ClassNotFoundException | SecurityException | NoSuchMethodException
| IllegalArgumentException | InstantiationException | IllegalArgumentException | IllegalAccessException
| IllegalAccessException | InvocationTargetException e) { | InvocationTargetException e) {
e.printStackTrace();
ProvisionException pe = new ProvisionException(e.getMessage()); ProvisionException pe = new ProvisionException(e.getMessage());
pe.initCause(e); pe.initCause(e);
throw pe; throw pe;

View File

@@ -337,7 +337,7 @@ public class WebAppInitializer extends GuiceServletContextListener
private Module createIndexModule() { private Module createIndexModule() {
switch (indexType) { switch (indexType) {
case LUCENE: case LUCENE:
return new LuceneIndexModule(); return LuceneIndexModule.latestVersionWithOnlineUpgrade();
default: default:
throw new IllegalStateException("unsupported index.type = " + indexType); throw new IllegalStateException("unsupported index.type = " + indexType);
} }