Merge "Allow admins to index a change even if the branch is not Readable for them" into stable-2.16
This commit is contained in:
		| @@ -1376,6 +1376,28 @@ EditPreferencesInfo] is returned. | |||||||
|   } |   } | ||||||
| ---- | ---- | ||||||
|  |  | ||||||
|  | [[index.changes]] | ||||||
|  | === Index a set of changes | ||||||
|  |  | ||||||
|  | This endpoint allows Gerrit admins to index a set of changes with one request | ||||||
|  | by providing a link:#index-changes-input[IndexChangesInput] entity. | ||||||
|  |  | ||||||
|  | Using this endpoint Gerrit admins can also index change(s) which are not visible to them. | ||||||
|  |  | ||||||
|  | .Request | ||||||
|  | ---- | ||||||
|  |   POST /config/server/index.changes HTTP/1.0 | ||||||
|  |   Content-Type: application/json; charset=UTF-8 | ||||||
|  |  | ||||||
|  |   {changes: ["foo~101", "bar~202"]} | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | .Response | ||||||
|  | ---- | ||||||
|  |   HTTP/1.1 200 OK | ||||||
|  |   Content-Disposition: attachment | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  |  | ||||||
| [[ids]] | [[ids]] | ||||||
| == IDs | == IDs | ||||||
| @@ -1825,6 +1847,17 @@ Hit ratio for cache entries that are held on disk (0 \<= value \<= 100). | |||||||
| Only set for disk caches. | Only set for disk caches. | ||||||
| |================================== | |================================== | ||||||
|  |  | ||||||
|  | [[index-changes-input]] | ||||||
|  | === IndexChangesInput | ||||||
|  | The `IndexChangesInput` contains a list of numerical changes IDs to index. | ||||||
|  |  | ||||||
|  | [options="header",cols="1,^2,4"] | ||||||
|  | |================================ | ||||||
|  | |Field Name         ||Description | ||||||
|  | |`changes`   || | ||||||
|  | List of link:rest-api-changes.html#change-id[change-ids] | ||||||
|  | |================================ | ||||||
|  |  | ||||||
| [[jvm-summary-info]] | [[jvm-summary-info]] | ||||||
| === JvmSummaryInfo | === JvmSummaryInfo | ||||||
| The `JvmSummaryInfo` entity contains information about the JVM. | The `JvmSummaryInfo` entity contains information about the JVM. | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ public class ChangeIndexedCounter implements ChangeIndexedListener { | |||||||
|     countsByChange.clear(); |     countsByChange.clear(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   long getCount(ChangeInfo info) { |   public long getCount(ChangeInfo info) { | ||||||
|     return countsByChange.get(info._number); |     return countsByChange.get(info._number); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,83 @@ | |||||||
|  | // Copyright (C) 2019 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.restapi.config; | ||||||
|  |  | ||||||
|  | import com.google.common.flogger.FluentLogger; | ||||||
|  | import com.google.gerrit.common.data.GlobalCapability; | ||||||
|  | import com.google.gerrit.extensions.annotations.RequiresCapability; | ||||||
|  | import com.google.gerrit.extensions.restapi.Response; | ||||||
|  | import com.google.gerrit.extensions.restapi.RestModifyView; | ||||||
|  | import com.google.gerrit.reviewdb.server.ReviewDb; | ||||||
|  | import com.google.gerrit.server.change.ChangeFinder; | ||||||
|  | import com.google.gerrit.server.config.ConfigResource; | ||||||
|  | import com.google.gerrit.server.index.change.ChangeIndexer; | ||||||
|  | import com.google.gerrit.server.notedb.ChangeNotes; | ||||||
|  | import com.google.gerrit.server.query.change.ChangeData; | ||||||
|  | import com.google.gerrit.server.restapi.config.IndexChanges.Input; | ||||||
|  | import com.google.gwtorm.server.OrmException; | ||||||
|  | import com.google.gwtorm.server.SchemaFactory; | ||||||
|  | import com.google.inject.Inject; | ||||||
|  | import com.google.inject.Singleton; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) | ||||||
|  | @Singleton | ||||||
|  | public class IndexChanges implements RestModifyView<ConfigResource, Input> { | ||||||
|  |   private static final FluentLogger logger = FluentLogger.forEnclosingClass(); | ||||||
|  |  | ||||||
|  |   public static class Input { | ||||||
|  |     public Set<String> changes; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private final ChangeFinder changeFinder; | ||||||
|  |   private final SchemaFactory<ReviewDb> schemaFactory; | ||||||
|  |   private final ChangeData.Factory changeDataFactory; | ||||||
|  |   private final ChangeIndexer indexer; | ||||||
|  |  | ||||||
|  |   @Inject | ||||||
|  |   IndexChanges( | ||||||
|  |       ChangeFinder changeFinder, | ||||||
|  |       SchemaFactory<ReviewDb> schemaFactory, | ||||||
|  |       ChangeData.Factory changeDataFactory, | ||||||
|  |       ChangeIndexer indexer) { | ||||||
|  |     this.changeFinder = changeFinder; | ||||||
|  |     this.schemaFactory = schemaFactory; | ||||||
|  |     this.changeDataFactory = changeDataFactory; | ||||||
|  |     this.indexer = indexer; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Override | ||||||
|  |   public Object apply(ConfigResource resource, Input input) throws OrmException { | ||||||
|  |     if (input == null || input.changes == null) { | ||||||
|  |       return Response.ok("Nothing to index"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try (ReviewDb db = schemaFactory.open()) { | ||||||
|  |       for (String id : input.changes) { | ||||||
|  |         for (ChangeNotes n : changeFinder.find(id)) { | ||||||
|  |           try { | ||||||
|  |             indexer.index(changeDataFactory.create(db, n)); | ||||||
|  |             logger.atFine().log("Indexed change %s", id); | ||||||
|  |           } catch (IOException e) { | ||||||
|  |             logger.atSevere().withCause(e).log("Failed to index change %s", id); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Response.ok("Indexed changes " + input.changes); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -37,6 +37,7 @@ public class Module extends RestApiModule { | |||||||
|     get(CONFIG_KIND, "version").to(GetVersion.class); |     get(CONFIG_KIND, "version").to(GetVersion.class); | ||||||
|     get(CONFIG_KIND, "info").to(GetServerInfo.class); |     get(CONFIG_KIND, "info").to(GetServerInfo.class); | ||||||
|     post(CONFIG_KIND, "check.consistency").to(CheckConsistency.class); |     post(CONFIG_KIND, "check.consistency").to(CheckConsistency.class); | ||||||
|  |     post(CONFIG_KIND, "index.changes").to(IndexChanges.class); | ||||||
|     post(CONFIG_KIND, "reload").to(ReloadConfig.class); |     post(CONFIG_KIND, "reload").to(ReloadConfig.class); | ||||||
|     get(CONFIG_KIND, "preferences").to(GetPreferences.class); |     get(CONFIG_KIND, "preferences").to(GetPreferences.class); | ||||||
|     put(CONFIG_KIND, "preferences").to(SetPreferences.class); |     put(CONFIG_KIND, "preferences").to(SetPreferences.class); | ||||||
|   | |||||||
| @@ -57,7 +57,8 @@ public class ConfigRestApiBindingsIT extends AbstractRestApiBindingsTest { | |||||||
|           RestCall.get("/config/server/capabilities"), |           RestCall.get("/config/server/capabilities"), | ||||||
|           RestCall.get("/config/server/caches"), |           RestCall.get("/config/server/caches"), | ||||||
|           RestCall.post("/config/server/caches"), |           RestCall.post("/config/server/caches"), | ||||||
|           RestCall.get("/config/server/tasks")); |           RestCall.get("/config/server/tasks"), | ||||||
|  |           RestCall.post("/config/server/index.changes")); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Cache REST endpoints to be tested, the URLs contain a placeholder for the cache identifier. |    * Cache REST endpoints to be tested, the URLs contain a placeholder for the cache identifier. | ||||||
|   | |||||||
| @@ -0,0 +1,98 @@ | |||||||
|  | // Copyright (C) 2019 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.acceptance.rest.config; | ||||||
|  |  | ||||||
|  | import static com.google.common.truth.Truth.assertThat; | ||||||
|  |  | ||||||
|  | import com.google.common.collect.ImmutableSet; | ||||||
|  | import com.google.gerrit.acceptance.AbstractDaemonTest; | ||||||
|  | import com.google.gerrit.acceptance.ChangeIndexedCounter; | ||||||
|  | import com.google.gerrit.extensions.common.ChangeInfo; | ||||||
|  | import com.google.gerrit.extensions.events.ChangeIndexedListener; | ||||||
|  | import com.google.gerrit.extensions.registration.DynamicSet; | ||||||
|  | import com.google.gerrit.extensions.registration.RegistrationHandle; | ||||||
|  | import com.google.gerrit.server.restapi.config.IndexChanges; | ||||||
|  | import com.google.inject.Inject; | ||||||
|  | import org.junit.After; | ||||||
|  | import org.junit.Before; | ||||||
|  | import org.junit.Test; | ||||||
|  |  | ||||||
|  | public class IndexChangesIT extends AbstractDaemonTest { | ||||||
|  |  | ||||||
|  |   @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners; | ||||||
|  |  | ||||||
|  |   private ChangeIndexedCounter changeIndexedCounter; | ||||||
|  |   private RegistrationHandle changeIndexedCounterHandle; | ||||||
|  |  | ||||||
|  |   @Before | ||||||
|  |   public void addChangeIndexedCounter() { | ||||||
|  |     changeIndexedCounter = new ChangeIndexedCounter(); | ||||||
|  |     changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @After | ||||||
|  |   public void removeChangeIndexedCounter() { | ||||||
|  |     if (changeIndexedCounterHandle != null) { | ||||||
|  |       changeIndexedCounterHandle.remove(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Test | ||||||
|  |   public void indexRequestFromNonAdminRejected() throws Exception { | ||||||
|  |     String changeId = createChange().getChangeId(); | ||||||
|  |     IndexChanges.Input in = new IndexChanges.Input(); | ||||||
|  |     in.changes = ImmutableSet.of(changeId); | ||||||
|  |     changeIndexedCounter.clear(); | ||||||
|  |     userRestSession.post("/config/server/index.changes", in).assertForbidden(); | ||||||
|  |     assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Test | ||||||
|  |   public void indexVisibleChange() throws Exception { | ||||||
|  |     String changeId = createChange().getChangeId(); | ||||||
|  |     IndexChanges.Input in = new IndexChanges.Input(); | ||||||
|  |     in.changes = ImmutableSet.of(changeId); | ||||||
|  |     changeIndexedCounter.clear(); | ||||||
|  |     adminRestSession.post("/config/server/index.changes", in).assertOK(); | ||||||
|  |     assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Test | ||||||
|  |   public void indexNonVisibleChange() throws Exception { | ||||||
|  |     String changeId = createChange().getChangeId(); | ||||||
|  |     ChangeInfo changeInfo = info(changeId); | ||||||
|  |     blockRead("refs/heads/master"); | ||||||
|  |     IndexChanges.Input in = new IndexChanges.Input(); | ||||||
|  |     changeIndexedCounter.clear(); | ||||||
|  |     in.changes = ImmutableSet.of(changeId); | ||||||
|  |     adminRestSession.post("/config/server/index.changes", in).assertOK(); | ||||||
|  |     assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Test | ||||||
|  |   public void indexMultipleChanges() throws Exception { | ||||||
|  |     ImmutableSet.Builder<String> changeIds = ImmutableSet.builder(); | ||||||
|  |     for (int i = 0; i < 10; i++) { | ||||||
|  |       changeIds.add(createChange().getChangeId()); | ||||||
|  |     } | ||||||
|  |     IndexChanges.Input in = new IndexChanges.Input(); | ||||||
|  |     in.changes = changeIds.build(); | ||||||
|  |     changeIndexedCounter.clear(); | ||||||
|  |     adminRestSession.post("/config/server/index.changes", in).assertOK(); | ||||||
|  |     for (String changeId : in.changes) { | ||||||
|  |       assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Saša Živkov
					Saša Živkov