Propagate instanceId in Events

Add instanceId in the Events payload when defined.
The value won't be present in the JSON payload
if the value is not set in the Gerrit config

Reference design: https://gerrit-review.googlesource.com/c/homepage/+/263710

Feature: Issue 12685
Change-Id: I507db3f0efba5649ad519361f88ee3f60ab27205
This commit is contained in:
Fabio Ponciroli
2020-05-07 13:22:00 +02:00
parent ba19b98081
commit 214b8963e1
5 changed files with 149 additions and 2 deletions

View File

@@ -1281,6 +1281,39 @@ public class MyListener implements GitReferenceUpdatedListener {
}
----
== Trace Event origin
When plugins are installed in a multi-master setups it can be useful to know
the Gerrit `instanceId` of the server that has generated an Event.
E.g. A plugin that sends an instance message for every comment on a change may
want to react only if the event is generated on the local Gerrit master, for
avoiding duplicating the notifications.
If link:config-gerrit.html[instanceId] is set, each Event will contain its
origin in the `instanceId` field.
Here and example of ref-updated JSON event payload with `instanceId`:
[source,json]
---
{
"submitter": {
"name": "Administrator",
"email": "admin@example.com",
"username": "admin"
},
"refUpdate": {
"oldRev": "a69fc95c7aad5ad41c618d31548b8af835d2959a",
"newRev": "31da6556d638a74e5370b62f83e8007f94abb7c6",
"refName": "refs/changes/01/1/meta",
"project": "test"
},
"type": "ref-updated",
"eventCreatedOn": 1588849085,
"instanceId": "instance1"
}
---
[[capabilities]]
== Plugin Owned Capabilities

View File

@@ -40,7 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
public class EventRecorder {
private final RegistrationHandle eventListenerRegistration;
private final ListMultimap<String, RefEvent> recordedEvents;
private final ListMultimap<String, Event> recordedEvents;
@Singleton
public static class Factory {
@@ -79,6 +79,8 @@ public class EventRecorder {
refEventKey(
event.getType(), event.getProjectNameKey().get(), event.getRefName());
recordedEvents.put(key, event);
} else {
recordedEvents.put(e.type, e);
}
}
@@ -158,6 +160,17 @@ public class EventRecorder {
return events;
}
public ImmutableList<Event> getGenericEvents(String type, int expectedSize) {
if (expectedSize == 0) {
assertThat(recordedEvents).doesNotContainKey(type);
return ImmutableList.of();
}
assertThat(recordedEvents).containsKey(type);
ImmutableList<Event> events = FluentIterable.from(recordedEvents.get(type)).toList();
assertThat(events).hasSize(expectedSize);
return events;
}
public void assertNoRefUpdatedEvents(String project, String branch) throws Exception {
getRefUpdatedEvents(project, branch, 0);
}

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.server.util.time.TimeUtil;
public abstract class Event {
public final String type;
public long eventCreatedOn = TimeUtil.nowMs() / 1000L;
public String instanceId;
protected Event(String type) {
this.type = type;

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
@@ -23,6 +24,7 @@ import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritInstanceId;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -65,18 +67,22 @@ public class EventBroker implements EventDispatcher {
protected final ChangeNotes.Factory notesFactory;
protected final String gerritInstanceId;
@Inject
public EventBroker(
PluginSetContext<UserScopedEventListener> listeners,
PluginSetContext<EventListener> unrestrictedListeners,
PermissionBackend permissionBackend,
ProjectCache projectCache,
ChangeNotes.Factory notesFactory) {
ChangeNotes.Factory notesFactory,
@Nullable @GerritInstanceId String gerritInstanceId) {
this.listeners = listeners;
this.unrestrictedListeners = unrestrictedListeners;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
this.notesFactory = notesFactory;
this.gerritInstanceId = gerritInstanceId;
}
@Override
@@ -105,6 +111,7 @@ public class EventBroker implements EventDispatcher {
}
protected void fireEvent(Change change, ChangeEvent event) throws PermissionBackendException {
setInstanceId(event);
for (PluginSetEntryContext<UserScopedEventListener> c : listeners) {
CurrentUser user = c.call(UserScopedEventListener::getUser);
if (isVisibleTo(change, user)) {
@@ -115,7 +122,9 @@ public class EventBroker implements EventDispatcher {
}
protected void fireEvent(Project.NameKey project, ProjectEvent event) {
setInstanceId(event);
for (PluginSetEntryContext<UserScopedEventListener> c : listeners) {
CurrentUser user = c.call(UserScopedEventListener::getUser);
if (isVisibleTo(project, user)) {
c.run(l -> l.onEvent(event));
@@ -126,6 +135,7 @@ public class EventBroker implements EventDispatcher {
protected void fireEvent(BranchNameKey branchName, RefEvent event)
throws PermissionBackendException {
setInstanceId(event);
for (PluginSetEntryContext<UserScopedEventListener> c : listeners) {
CurrentUser user = c.call(UserScopedEventListener::getUser);
if (isVisibleTo(branchName, user)) {
@@ -136,6 +146,7 @@ public class EventBroker implements EventDispatcher {
}
protected void fireEvent(Event event) throws PermissionBackendException {
setInstanceId(event);
for (PluginSetEntryContext<UserScopedEventListener> c : listeners) {
CurrentUser user = c.call(UserScopedEventListener::getUser);
if (isVisibleTo(event, user)) {
@@ -145,6 +156,10 @@ public class EventBroker implements EventDispatcher {
fireEventForUnrestrictedListeners(event);
}
protected void setInstanceId(Event event) {
event.instanceId = gerritInstanceId;
}
protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
try {
Optional<ProjectState> state = projectCache.get(project);

View File

@@ -0,0 +1,85 @@
// Copyright (C) 2020 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.server.event;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.EventDispatcher;
import com.google.gerrit.server.events.EventTypes;
import com.google.inject.Inject;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class InstanceIdInEventIT extends AbstractDaemonTest {
public static class TestDispatcher {
private final DynamicItem<EventDispatcher> eventDispatcher;
@Inject
TestDispatcher(DynamicItem<EventDispatcher> eventDispatcher) {
this.eventDispatcher = eventDispatcher;
}
public void postEvent(TestEvent event) {
try {
eventDispatcher.get().postEvent(event);
} catch (Exception e) {
fail("Exception raised when posting Event " + e.getCause());
}
}
}
public static class TestEvent extends Event {
private static final String TYPE = "test-event-instance-id";
public TestEvent() {
super(TYPE);
}
}
@Inject private DynamicItem<EventDispatcher> eventDispatcher;
TestDispatcher testDispatcher;
@Before
public void setUp() throws Exception {
testDispatcher = new TestDispatcher(eventDispatcher);
EventTypes.register(TestEvent.TYPE, TestEvent.class);
}
@Test
@GerritConfig(name = "gerrit.instanceId", value = "testInstanceId")
public void shouldSetInstanceIdWhenDefined() {
testDispatcher.postEvent(new TestEvent());
ImmutableList<Event> events = eventRecorder.getGenericEvents(TestEvent.TYPE, 1);
assertThat(events.get(0).instanceId).isEqualTo("testInstanceId");
}
@Test
public void shouldNotSetInstanceIdWhenNotDefined() {
testDispatcher.postEvent(new TestEvent());
ImmutableList<Event> events = eventRecorder.getGenericEvents(TestEvent.TYPE, 1);
assertThat(events.get(0).instanceId).isNull();
}
}