Rename EiffelEventParser and EiffelEventParserIf When an interface was extracted from EiffelEventParser in a previous commit the implementing class kept the name EiffelEventParser to make the diff more reviewable. In this change we rename the interface `EiffelEventParser` and the implementation `EiffelEventParserImpl` to align with the inofficial code-standard in Gerrit. Solves: Jira GER-1715 Change-Id: I2ad5a139f0e6b983b615414450a9bc1a75a1128f
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java index 9f53bee..9bdc0c4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java
@@ -40,7 +40,7 @@ import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; import com.googlesource.gerrit.plugins.eventseiffel.mq.RabbitMqPublisher; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserImpl; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingExecutor; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue; import com.googlesource.gerrit.plugins.eventseiffel.rest.RestModule; @@ -100,7 +100,7 @@ .in(Scopes.SINGLETON); bind(EiffelEventParsingExecutor.Scheduled.class).in(Scopes.SINGLETON); bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Scheduled.class); - bind(EiffelEventParserIf.class).to(EiffelEventParser.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); bind(EiffelEventParsingQueue.class).in(Scopes.SINGLETON); bind(EiffelEventHubImpl.class).in(Scopes.SINGLETON); bind(EiffelEventHub.class).to(EiffelEventHubImpl.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java index df82291..6c0ad07 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java
@@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Android Open Source Project +// Copyright (C) 2022 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. @@ -11,419 +11,64 @@ // 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.googlesource.gerrit.plugins.eventseiffel.parsing; -import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; - -import com.github.rholder.retry.RetryException; -import com.github.rholder.retry.Retryer; -import com.github.rholder.retry.RetryerBuilder; -import com.github.rholder.retry.StopStrategies; -import com.github.rholder.retry.WaitStrategies; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import com.google.common.flogger.FluentLogger; -import com.google.gerrit.entities.Project; -import com.google.gerrit.entities.RefNames; -import com.google.gerrit.exceptions.NoSuchEntityException; import com.google.gerrit.extensions.common.AccountInfo; -import com.google.gerrit.extensions.common.CommitInfo; import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.inject.Inject; -import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; -import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEvent; -import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.EventCreate; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.ScsWalker; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.RepositoryNotFoundException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -/** Creates and pushes missing Eiffel events to the Eiffel event queue. */ -public class EiffelEventParser implements EiffelEventParserIf { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); +public interface EiffelEventParser { - private static final int NBR_RETRIES = 3; + void createAndScheduleSccFromEvent(Event event); - private final EiffelEventHub eventHub; - private final GitRepositoryManager repoManager; - private final UnprocessedCommitsWalker.Factory walkerFactory; - private final EiffelEventMapper mapper; - - @Inject - public EiffelEventParser( - EiffelEventHub eventQueue, - GitRepositoryManager repoManager, - EiffelEventMapper mapper, - UnprocessedCommitsWalker.Factory walkerFactory) { - this.eventHub = eventQueue; - this.repoManager = repoManager; - this.walkerFactory = walkerFactory; - this.mapper = mapper; - } - - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleSccFromEvent(com.google.gerrit.extensions.events.RevisionCreatedListener.Event) + /** + * Creates SCC events for all commits reachable from branchRef. I.e. create SCCs for already + * merged commit that are missing SCCs. + * + * @param repoName + * @param branchRef */ - @Override - public void createAndScheduleSccFromEvent(Event event) { - CommitInfo commit = event.getRevision().commit; - SourceChangeEventKey scc = - SourceChangeEventKey.sccKey( - event.getChange().project, event.getChange().branch, commit.commit); - try { - if (eventHub.getExistingId(scc).isPresent()) { - logger.atWarning().log( - "Event %s already pushed for %d/%d", - scc, event.getChange()._number, event.getRevision()._number); - return; - } - List<UUID> parentUuids = Lists.newArrayList(); - for (CommitInfo parent : commit.parents) { - Optional<UUID> parentUuid = eventHub.getExistingId(scc.copy(parent.commit)); - if (parentUuid.isPresent()) { - parentUuids.add(parentUuid.get()); - } - } + void createAndScheduleSccFromBranch(String repoName, String branchRef); - /* Eiffel events have been scheduled or published for all parents. */ - if (parentUuids.size() == commit.parents.size()) { - pushToHub(mapper.toScc(event, parentUuids)); - } else { - createAndScheduleMissingSccs(scc); - } - } catch (IOException - | ConfigInvalidException - | NoSuchEntityException - | EiffelEventIdLookupException - | InterruptedException e) { - logger.atSevere().withCause(e).log( - "Event creation failed for: %s, %s, %s to SCC.", - event.getChange().project, event.getChange().branch, event.getRevision().commit.commit); - } - } - - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleSccFromBranch(java.lang.String, java.lang.String) + /** + * Creates SCC events for all commits reachable from commit. + * + * @param repoName + * @param targetBranch + * @param commit */ - @Override - public void createAndScheduleSccFromBranch(String repoName, String branchRef) { - ObjectId tip = getTipOf(repoName, branchRef); - if (tip == null) { - return; - } - createAndScheduleSccFromCommit(repoName, branchRef, tip.getName()); - } + void createAndScheduleSccFromCommit(String repoName, String targetBranch, String commit); - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleSccFromCommit(java.lang.String, java.lang.String, java.lang.String) + /** + * Creates missing SCS events for repoName, branch. + * + * @param repoName + * @param branch */ - @Override - public void createAndScheduleSccFromCommit(String repoName, String branchRef, String commit) { + void createAndScheduleMissingScssFromBranch(String repoName, String branch); - SourceChangeEventKey scc = SourceChangeEventKey.sccKey(repoName, branchRef, commit); - try { - createAndScheduleMissingSccs(scc); - } catch (IOException - | EiffelEventIdLookupException - | NoSuchEntityException - | ConfigInvalidException - | InterruptedException e) { - logger.atSevere().withCause(e).log("Event creation failed for: %s", scc); - } - } - - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleMissingScssFromBranch(java.lang.String, java.lang.String) + /** + * Creates missing SCS events for a submit transaction. submitter and submittedAt is set for all + * events created within the submit transaction, i.e. not reachable from commitSha1TransactionEnd. + * + * @param scs + * @param commitSha1TransactionEnd + * @param submitter + * @param submittedAt */ - @Override - public void createAndScheduleMissingScssFromBranch(String repoName, String branchRef) { - ObjectId tip = getTipOf(repoName, branchRef); - if (tip == null) { - return; - } - SourceChangeEventKey scs = SourceChangeEventKey.scsKey(repoName, branchRef, tip.getName()); - createAndScheduleMissingScss(scs, null, null, null); - } - - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleMissingScss(com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey, java.lang.String, com.google.gerrit.extensions.common.AccountInfo, java.lang.Long) - */ - @Override - public void createAndScheduleMissingScss( + void createAndScheduleMissingScss( SourceChangeEventKey scs, String commitSha1TransactionEnd, AccountInfo submitter, - Long submittedAt) { - SourceChangeEventKey currentScs = scs; - SourceChangeEventKey scc = scs.copy(SCC); - try { - try (ScsWalker scsFinder = - walkerFactory.scsWalker(scs, commitSha1TransactionEnd, submitter, submittedAt)) { + Long submittedAt); - if (eventHub.getExistingId(scc).isEmpty()) { - /* One or several SCC events are missing, create them first */ - try (UnprocessedCommitsWalker sccFinder = scsFinder.sccWalker()) { - createAndScheduleMissingSccs(scc, sccFinder); - } - } - - if (!scsFinder.hasNext()) { - logger.atInfo().log("All events were already published for %s", scs); - } else { - logger.atFine().log("Start publishing events for: %s", scs); - } - while (scsFinder.hasNext()) { - EventCreate create = scsFinder.next(); - currentScs = create.key; - Optional<UUID> sccId = eventHub.getExistingId(create.key.copy(SCC)); - if (sccId.isEmpty()) { - throw new EiffelEventIdLookupException( - "Unable to find SCC event id: %s", create.key.copy(SCC)); - } - List<UUID> scsParentEventIds = Lists.newArrayList(); - for (RevCommit parent : create.commit.getParents()) { - Optional<UUID> parentUuid = eventHub.getExistingId(create.key.copy(parent.getName())); - if (parentUuid.isPresent()) { - scsParentEventIds.add(parentUuid.get()); - } else { - exceptionForMissingParent(create, parent); - } - } - pushToHub( - mapper.toScs( - create.commit, - create.key.repo(), - create.key.branch(), - create.submitter, - create.submittedAt, - scsParentEventIds, - sccId.get())); - } - } - logger.atFine().log("Done publishing events for: %s", scs); - } catch (IOException - | EiffelEventIdLookupException - | InterruptedException - | ConfigInvalidException - | NoSuchEntityException e) { - logger.atSevere().withCause(e).log("Failed to create Eiffel event(s) for %s.", currentScs); - } - } - - /* (non-Javadoc) - * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf#createAndScheduleArtc(java.lang.String, java.lang.String, java.lang.Long, boolean) + /** + * Creates missing ARTC for a tag, together with CD and (if missing) SCS for referenced commit.7 + * + * @param projectName + * @param tagName + * @param creationTime + * @param force */ - @Override - public void createAndScheduleArtc( - String repoName, String tagName, Long creationTime, boolean force) { - try { - CompositionDefinedEventKey cd = - CompositionDefinedEventKey.create(mapper.tagCompositionName(repoName), tagName); - Optional<UUID> oldCdId = eventHub.getExistingId(cd); - if (oldCdId.isEmpty() || force) { - createAndScheduleCd(repoName, tagName, creationTime, force); - Optional<UUID> cdId = eventHub.getExistingId(cd); - if (cdId.isPresent() && !cdId.equals(oldCdId)) { - pushToHub(mapper.toArtc(repoName, tagName, creationTime, cdId.get()), force); - if (oldCdId.isPresent()) { - logger.atInfo().log( - "Event Artc has been forcibly created for: %s, %s", repoName, tagName); - } else { - logger.atFine().log("Event Artc has been created for: %s, %s", repoName, tagName); - } - } - } else { - /* Artc event has already been created */ - logger.atWarning().log( - "Event Artc has already been created for: %s, %s", repoName, tagName); - } - } catch (EiffelEventIdLookupException | InterruptedException e) { - logger.atSevere().withCause(e).log( - "Event creation failed for: %s, %s to Artc", repoName, tagName); - } - } - - private void createAndScheduleCd( - String projectName, String tagName, Long creationTime, boolean force) { - SourceChangeEventKey scs = null; - Optional<UUID> scsId = Optional.empty(); - - try { - String commitId = peelTag(projectName, tagName); - if (commitId == null) { - return; - } - scs = SourceChangeEventKey.scsKey(projectName, RefNames.REFS_HEADS + "master", commitId); - scsId = eventHub.getExistingId(scs); - - if (scsId.isEmpty()) { - Retryer<Optional<UUID>> retryer = - RetryerBuilder.<Optional<UUID>>newBuilder() - .retryIfResult(Optional::isEmpty) - .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS)) - .withStopStrategy(StopStrategies.stopAfterAttempt(2)) - .build(); - try { - scsId = retryer.call(() -> findSourceChangeEventKey(projectName, commitId)); - } catch (RetryException | ExecutionException e) { - logger.atSevere().withCause(e).log( - "Failed to find SCS for %s in %s", commitId, projectName); - return; - } - } - if (scsId.isEmpty()) { - logger.atSevere().log("Could not find SCS for: %s in %s", commitId, projectName); - return; - } - pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force); - } catch (EiffelEventIdLookupException | InterruptedException e) { - logger.atSevere().withCause(e).log( - "Event creation failed for: %s", - CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName)); - } - } - - private String peelTag(String projectName, String tagName) { - try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { - Ref tagRef = repo.getRefDatabase().exactRef(Constants.R_TAGS + tagName); - if (tagRef != null) { - ObjectId peeled = repo.getRefDatabase().peel(tagRef).getPeeledObjectId(); - return peeled != null ? peeled.getName() : tagRef.getObjectId().getName(); - } - logger.atSevere().log("Cannot find tag: %s:%s", projectName, tagName); - } catch (IOException e) { - logger.atSevere().withCause(e).log("Unable to peel tag: %s:%s", projectName, tagName); - } - return null; - } - - private Optional<UUID> findSourceChangeEventKey(String projectName, String commitId) - throws EiffelEventIdLookupException { - List<String> branches; - try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { - branches = - repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream() - .map(Ref::getName) - .collect(Collectors.toList()); - } catch (IOException ioe) { - logger.atSevere().withCause(ioe).log("Unable to find branches of %s.", projectName); - return Optional.empty(); - } - return eventHub.getScsForCommit(projectName, commitId, branches); - } - - private void pushToHub(EiffelEvent toPush) throws InterruptedException { - pushToHub(toPush, false); - } - - private void pushToHub(EiffelEvent toPush, boolean force) throws InterruptedException { - int failureCount = 0; - EventKey key = EventKey.fromEvent(toPush); - while (true) { - try { - eventHub.push(toPush, force); - logger.atFine().log("Successfully pushed %s to EventHub", key); - return; - } catch (InterruptedException e) { - if (!eventHub.isOpen()) { - logger.atInfo().log("EventHub is closed, aborting."); - throw e; - } - failureCount++; - if (failureCount < NBR_RETRIES) { - logger.atWarning().withCause(e).log( - "Interrupted while pushing %s to EventHub, attempt %d/%d", - key, failureCount, NBR_RETRIES); - } else { - throw e; - } - } - } - } - - @VisibleForTesting - void createAndScheduleMissingSccs(SourceChangeEventKey scc) - throws MissingObjectException, IncorrectObjectTypeException, IOException, - EiffelEventIdLookupException, RepositoryNotFoundException, NoSuchEntityException, - ConfigInvalidException, InterruptedException { - logger.atFine().log("Start publishing events for: %s", scc); - try (UnprocessedCommitsWalker commitFinder = walkerFactory.sccWalker(scc)) { - createAndScheduleMissingSccs(scc, commitFinder); - } - logger.atFine().log("Done publishing events for: %s", scc); - } - - /* Callers are responsible for closing commitFinder. */ - private void createAndScheduleMissingSccs( - SourceChangeEventKey scc, UnprocessedCommitsWalker commitFinder) - throws MissingObjectException, EiffelEventIdLookupException, IOException, - NoSuchEntityException, ConfigInvalidException, InterruptedException { - if (!commitFinder.hasNext()) { - logger.atInfo().log("All events were already published for %s", scc); - } else { - logger.atFine().log("Start publishing events for: %s", scc); - } - while (commitFinder.hasNext()) { - EventCreate job = commitFinder.next(); - logger.atFine().log("Processing event-creation for: %s", job.key); - List<UUID> parentIds = Lists.newArrayList(); - for (RevCommit parent : job.commit.getParents()) { - SourceChangeEventKey parentKey = scc.copy(parent.getName()); - Optional<UUID> parentId = eventHub.getExistingId(parentKey); - if (parentId.isPresent()) { - parentIds.add(parentId.get()); - } else { - exceptionForMissingParent(job, parent); - } - } - try { - pushToHub(mapper.toScc(job.commit, job.key.repo(), job.key.branch(), parentIds)); - } catch (InterruptedException e) { - logger.atSevere().log("Interrupted while pushing %s to EventHub.", job.key); - throw e; - } - } - } - - private ObjectId getTipOf(String repoName, String branch) { - try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) { - Ref branchRef = repo.exactRef(branch); - if (branchRef == null) { - logger.atWarning().log("Could not find ref: %s in project: %s", branch, repoName); - return null; - } - return branchRef.getTarget().getObjectId(); - } catch (IOException ioe) { - logger.atSevere().withCause(ioe).log("Unable to identify tip of (%s:%s).", repoName, branch); - return null; - } - } - - private void exceptionForMissingParent(EventCreate create, RevCommit parent) - throws NoSuchEntityException { - throw new NoSuchEntityException( - String.format( - "Unable to lookup parent (%s) event UUID for %s even though it should exist.", - parent.abbreviate(7).name(), create.key)); - } + void createAndScheduleArtc(String projectName, String tagName, Long creationTime, boolean force); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIf.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIf.java deleted file mode 100644 index 94aa921..0000000 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIf.java +++ /dev/null
@@ -1,74 +0,0 @@ -// Copyright (C) 2022 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.googlesource.gerrit.plugins.eventseiffel.parsing; - -import com.google.gerrit.extensions.common.AccountInfo; -import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; - -public interface EiffelEventParserIf { - - void createAndScheduleSccFromEvent(Event event); - - /** - * Creates SCC events for all commits reachable from branchRef. I.e. create SCCs for already - * merged commit that are missing SCCs. - * - * @param repoName - Name of the repository where the branch exists. - * @param branchRef - Creates missing events for all commits reachable from branch. - */ - void createAndScheduleSccFromBranch(String repoName, String branchRef); - - /** - * Creates SCC events for all commits reachable from commit. - * - * @param repoName - Name of the repository were the commits exists. - * @param branchRef - Ref of the branch to create events for. - * @param commit - Creates missing events for all commits reachable from commit. - */ - void createAndScheduleSccFromCommit(String repoName, String branchRef, String commit); - - /** - * Creates missing SCS events for repoName, branch. - * - * @param repoName - Name of the repository where the branch exists. - * @param branchRef - Creates missing events for all commits reachable from branchRef. - */ - void createAndScheduleMissingScssFromBranch(String repoName, String branchRef); - - /** - * Creates missing SCS events for a submit transaction. submitter and submittedAt is set for all - * events created within the submit transaction, i.e. not reachable from commitSha1TransactionEnd. - * - * @param scs - key for the event that shuold be created, together with parents. - * @param commitSha1TransactionEnd - tip before submit transaction. - * @param submitter - The submitter - * @param submittedAt - When the commit was submitted. - */ - void createAndScheduleMissingScss( - SourceChangeEventKey scs, - String commitSha1TransactionEnd, - AccountInfo submitter, - Long submittedAt); - - /** - * Creates missing ARTC for a tag, together with CD and (if missing) SCS for referenced commit. - * - * @param repoName - Name of the repository were the tag exists. - * @param tagName - The name of the tag. - * @param creationTime - The time at which the tag was created. - * @param force - Whether existing events should be replaced or not. - */ - void createAndScheduleArtc(String repoName, String tagName, Long creationTime, boolean force); -}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java new file mode 100644 index 0000000..5d54053 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java
@@ -0,0 +1,429 @@ +// Copyright (C) 2021 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; + +import com.github.rholder.retry.RetryException; +import com.github.rholder.retry.Retryer; +import com.github.rholder.retry.RetryerBuilder; +import com.github.rholder.retry.StopStrategies; +import com.github.rholder.retry.WaitStrategies; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.exceptions.NoSuchEntityException; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.common.CommitInfo; +import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEvent; +import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.EventCreate; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.ScsWalker; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; + +/** Creates and pushes missing Eiffel events to the Eiffel event queue. */ +public class EiffelEventParserImpl implements EiffelEventParser { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private static final int NBR_RETRIES = 3; + + private final EiffelEventHub eventHub; + private final GitRepositoryManager repoManager; + private final UnprocessedCommitsWalker.Factory walkerFactory; + private final EiffelEventMapper mapper; + + @Inject + public EiffelEventParserImpl( + EiffelEventHub eventQueue, + GitRepositoryManager repoManager, + EiffelEventMapper mapper, + UnprocessedCommitsWalker.Factory walkerFactory) { + this.eventHub = eventQueue; + this.repoManager = repoManager; + this.walkerFactory = walkerFactory; + this.mapper = mapper; + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromEvent(com.google.gerrit.extensions.events.RevisionCreatedListener.Event) + */ + @Override + public void createAndScheduleSccFromEvent(Event event) { + CommitInfo commit = event.getRevision().commit; + SourceChangeEventKey scc = + SourceChangeEventKey.sccKey( + event.getChange().project, event.getChange().branch, commit.commit); + try { + if (eventHub.getExistingId(scc).isPresent()) { + logger.atWarning().log( + "Event %s already pushed for %d/%d", + scc, event.getChange()._number, event.getRevision()._number); + return; + } + List<UUID> parentUuids = Lists.newArrayList(); + for (CommitInfo parent : commit.parents) { + Optional<UUID> parentUuid = eventHub.getExistingId(scc.copy(parent.commit)); + if (parentUuid.isPresent()) { + parentUuids.add(parentUuid.get()); + } + } + + /* Eiffel events have been scheduled or published for all parents. */ + if (parentUuids.size() == commit.parents.size()) { + pushToHub(mapper.toScc(event, parentUuids)); + } else { + createAndScheduleMissingSccs(scc); + } + } catch (IOException + | ConfigInvalidException + | NoSuchEntityException + | EiffelEventIdLookupException + | InterruptedException e) { + logger.atSevere().withCause(e).log( + "Event creation failed for: %s, %s, %s to SCC.", + event.getChange().project, event.getChange().branch, event.getRevision().commit.commit); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromBranch(java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleSccFromBranch(String repoName, String targetBranch) { + ObjectId tip = getTipOf(repoName, targetBranch); + if (tip == null) { + return; + } + createAndScheduleSccFromCommit(repoName, targetBranch, tip.getName()); + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromCommit(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleSccFromCommit(String repoName, String targetBranch, String commit) { + + SourceChangeEventKey scc = SourceChangeEventKey.sccKey(repoName, targetBranch, commit); + try { + createAndScheduleMissingSccs(scc); + } catch (IOException + | EiffelEventIdLookupException + | NoSuchEntityException + | ConfigInvalidException + | InterruptedException e) { + logger.atSevere().withCause(e).log("Event creation failed for: %s", scc); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleMissingScssFromBranch(java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleMissingScssFromBranch(String repoName, String branch) { + ObjectId tip = getTipOf(repoName, branch); + if (tip == null) { + return; + } + SourceChangeEventKey scs = SourceChangeEventKey.scsKey(repoName, branch, tip.getName()); + createAndScheduleMissingScss(scs, null, null, null); + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleMissingScss(com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey, java.lang.String, com.google.gerrit.extensions.common.AccountInfo, java.lang.Long) + */ + @Override + public void createAndScheduleMissingScss( + SourceChangeEventKey scs, + String commitSha1TransactionEnd, + AccountInfo submitter, + Long submittedAt) { + SourceChangeEventKey currentScs = scs; + SourceChangeEventKey scc = scs.copy(SCC); + try { + try (ScsWalker scsFinder = + walkerFactory.scsWalker(scs, commitSha1TransactionEnd, submitter, submittedAt)) { + + if (eventHub.getExistingId(scc).isEmpty()) { + /* One or several SCC events are missing, create them first */ + try (UnprocessedCommitsWalker sccFinder = scsFinder.sccWalker()) { + createAndScheduleMissingSccs(scc, sccFinder); + } + } + + if (!scsFinder.hasNext()) { + logger.atInfo().log("All events were already published for %s", scs); + } else { + logger.atFine().log("Start publishing events for: %s", scs); + } + while (scsFinder.hasNext()) { + EventCreate create = scsFinder.next(); + currentScs = create.key; + Optional<UUID> sccId = eventHub.getExistingId(create.key.copy(SCC)); + if (sccId.isEmpty()) { + throw new EiffelEventIdLookupException( + "Unable to find SCC event id: %s", create.key.copy(SCC)); + } + List<UUID> scsParentEventIds = Lists.newArrayList(); + for (RevCommit parent : create.commit.getParents()) { + Optional<UUID> parentUuid = eventHub.getExistingId(create.key.copy(parent.getName())); + if (parentUuid.isPresent()) { + scsParentEventIds.add(parentUuid.get()); + } else { + exceptionForMissingParent(create, parent); + } + } + pushToHub( + mapper.toScs( + create.commit, + create.key.repo(), + create.key.branch(), + create.submitter, + create.submittedAt, + scsParentEventIds, + sccId.get())); + } + } + logger.atFine().log("Done publishing events for: %s", scs); + } catch (IOException + | EiffelEventIdLookupException + | InterruptedException + | ConfigInvalidException + | NoSuchEntityException e) { + logger.atSevere().withCause(e).log("Failed to create Eiffel event(s) for %s.", currentScs); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleArtc(java.lang.String, java.lang.String, java.lang.Long, boolean) + */ + @Override + public void createAndScheduleArtc( + String projectName, String tagName, Long creationTime, boolean force) { + try { + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName); + Optional<UUID> oldCdId = eventHub.getExistingId(cd); + if (oldCdId.isEmpty() || force) { + createAndScheduleCd(projectName, tagName, creationTime, force); + Optional<UUID> cdId = eventHub.getExistingId(cd); + if (cdId.isPresent() && !cdId.equals(oldCdId)) { + pushToHub(mapper.toArtc(projectName, tagName, creationTime, cdId.get()), force); + if (oldCdId.isPresent()) { + logger.atInfo().log( + "Event Artc has been forcibly created for: %s, %s", projectName, tagName); + } else { + logger.atFine().log("Event Artc has been created for: %s, %s", projectName, tagName); + } + } + } else { + /* Artc event has already been created */ + logger.atWarning().log( + "Event Artc has already been created for: %s, %s", projectName, tagName); + } + } catch (EiffelEventIdLookupException | InterruptedException e) { + logger.atSevere().withCause(e).log( + "Event creation failed for: %s, %s to Artc", projectName, tagName); + } + } + + private void createAndScheduleCd( + String projectName, String tagName, Long creationTime, boolean force) { + SourceChangeEventKey scs = null; + Optional<UUID> scsId = Optional.empty(); + + try { + String commitId = peelTag(projectName, tagName); + if (commitId == null) { + return; + } + scs = SourceChangeEventKey.scsKey(projectName, RefNames.REFS_HEADS + "master", commitId); + scsId = eventHub.getExistingId(scs); + + if (scsId.isEmpty()) { + Retryer<Optional<UUID>> retryer = + RetryerBuilder.<Optional<UUID>>newBuilder() + .retryIfResult(Optional::isEmpty) + .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS)) + .withStopStrategy(StopStrategies.stopAfterAttempt(2)) + .build(); + try { + scsId = retryer.call(() -> findSourceChangeEventKey(projectName, commitId)); + } catch (RetryException | ExecutionException e) { + logger.atSevere().withCause(e).log( + "Failed to find SCS for %s in %s", commitId, projectName); + return; + } + } + if (scsId.isEmpty()) { + logger.atSevere().log("Could not find SCS for: %s in %s", commitId, projectName); + return; + } + pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force); + } catch (EiffelEventIdLookupException | InterruptedException e) { + logger.atSevere().withCause(e).log( + "Event creation failed for: %s", + CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName)); + } + } + + private String peelTag(String projectName, String tagName) { + try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { + Ref tagRef = repo.getRefDatabase().exactRef(Constants.R_TAGS + tagName); + if (tagRef != null) { + ObjectId peeled = repo.getRefDatabase().peel(tagRef).getPeeledObjectId(); + return peeled != null ? peeled.getName() : tagRef.getObjectId().getName(); + } + logger.atSevere().log("Cannot find tag: %s:%s", projectName, tagName); + } catch (IOException e) { + logger.atSevere().withCause(e).log("Unable to peel tag: %s:%s", projectName, tagName); + } + return null; + } + + private Optional<UUID> findSourceChangeEventKey(String projectName, String commitId) + throws EiffelEventIdLookupException { + List<String> branches; + try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { + branches = + repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream() + .map(Ref::getName) + .collect(Collectors.toList()); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Unable to find branches of %s.", projectName); + return Optional.empty(); + } + return eventHub.getScsForCommit(projectName, commitId, branches); + } + + private void pushToHub(EiffelEvent toPush) throws InterruptedException { + pushToHub(toPush, false); + } + + private void pushToHub(EiffelEvent toPush, boolean force) throws InterruptedException { + int failureCount = 0; + EventKey key = EventKey.fromEvent(toPush); + while (true) { + try { + eventHub.push(toPush, force); + logger.atFine().log("Successfully pushed %s to EventHub", key); + return; + } catch (InterruptedException e) { + if (!eventHub.isOpen()) { + logger.atInfo().log("EventHub is closed, aborting."); + throw e; + } + failureCount++; + if (failureCount < NBR_RETRIES) { + logger.atWarning().withCause(e).log( + "Interrupted while pushing %s to EventHub, attempt %d/%d", + key, failureCount, NBR_RETRIES); + } else { + throw e; + } + } + } + } + + @VisibleForTesting + void createAndScheduleMissingSccs(SourceChangeEventKey scc) + throws MissingObjectException, IncorrectObjectTypeException, IOException, + EiffelEventIdLookupException, RepositoryNotFoundException, NoSuchEntityException, + ConfigInvalidException, InterruptedException { + logger.atFine().log("Start publishing events for: %s", scc); + try (UnprocessedCommitsWalker commitFinder = walkerFactory.sccWalker(scc)) { + createAndScheduleMissingSccs(scc, commitFinder); + } + logger.atFine().log("Done publishing events for: %s", scc); + } + + /* Callers are responsible for closing commitFinder. */ + private void createAndScheduleMissingSccs( + SourceChangeEventKey scc, UnprocessedCommitsWalker commitFinder) + throws MissingObjectException, EiffelEventIdLookupException, IOException, + NoSuchEntityException, ConfigInvalidException, InterruptedException { + if (!commitFinder.hasNext()) { + logger.atInfo().log("All events were already published for %s", scc); + } else { + logger.atFine().log("Start publishing events for: %s", scc); + } + while (commitFinder.hasNext()) { + EventCreate job = commitFinder.next(); + logger.atFine().log("Processing event-creation for: %s", job.key); + List<UUID> parentIds = Lists.newArrayList(); + for (RevCommit parent : job.commit.getParents()) { + SourceChangeEventKey parentKey = scc.copy(parent.getName()); + Optional<UUID> parentId = eventHub.getExistingId(parentKey); + if (parentId.isPresent()) { + parentIds.add(parentId.get()); + } else { + exceptionForMissingParent(job, parent); + } + } + try { + pushToHub(mapper.toScc(job.commit, job.key.repo(), job.key.branch(), parentIds)); + } catch (InterruptedException e) { + logger.atSevere().log("Interrupted while pushing %s to EventHub.", job.key); + throw e; + } + } + } + + private ObjectId getTipOf(String repoName, String branch) { + try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) { + Ref branchRef = repo.exactRef(branch); + if (branchRef == null) { + logger.atWarning().log("Could not find ref: %s in project: %s", branch, repoName); + return null; + } + return branchRef.getTarget().getObjectId(); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Unable to identify tip of (%s:%s).", repoName, branch); + return null; + } + } + + private void exceptionForMissingParent(EventCreate create, RevCommit parent) + throws NoSuchEntityException { + throw new NoSuchEntityException( + String.format( + "Unable to lookup parent (%s) event UUID for %s even though it should exist.", + parent.abbreviate(7).name(), create.key)); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java index b457b6f..c5d10b8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java
@@ -36,12 +36,12 @@ private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private volatile EiffelEventParsingExecutor pool; - private final EiffelEventParserIf eventParser; + private final EiffelEventParser eventParser; private final ConcurrentHashMap<EventParsingWorker, ScheduledFuture<?>> pending = new ConcurrentHashMap<>(); @Inject - public EiffelEventParsingQueue(EiffelEventParsingExecutor pool, EiffelEventParserIf eventParser) { + public EiffelEventParsingQueue(EiffelEventParsingExecutor pool, EiffelEventParser eventParser) { this.pool = pool; this.eventParser = eventParser; }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java index 579f3b6..17b5acd 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java
@@ -26,7 +26,7 @@ import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorage; import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIf; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserImpl; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingExecutor; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue; import org.junit.Ignore; @@ -44,7 +44,7 @@ bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Direct.class); bind(TestEventPublisher.class).in(Scopes.SINGLETON); bind(EiffelEventHub.Consumer.class).to(TestEventPublisher.class); - bind(EiffelEventParserIf.class).to(EiffelEventParser.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); bind(EiffelEventParsingQueue.class); bind(EiffelEventFactory.class).toProvider(TestEventFactoryProvider.class);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java index d2d87eb..d20034b 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java
@@ -57,11 +57,11 @@ public class EiffelEventParserIT extends EiffelEventsTest { private static final Long EPOCH_MILLIS = 978307261000l; - private EiffelEventParser eventParser; + private EiffelEventParserImpl eventParser; @Before public void setUp() { - eventParser = plugin.getSysInjector().getInstance(EiffelEventParser.class); + eventParser = plugin.getSysInjector().getInstance(EiffelEventParserImpl.class); TestEventHub.EVENTS.clear(); } @@ -342,7 +342,7 @@ bind(EiffelEventHub.class).to(TestEventHub.class); bind(EventMappingConfig.class).toProvider(EventMappingConfig.Provider.class); bind(EiffelEventMapper.class); - bind(EiffelEventParserIf.class).to(EiffelEventParser.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); bind(EiffelConfig.class).toProvider(EiffelConfig.Provider.class).in(Scopes.SINGLETON); bind(EiffelEventFactory.class) .toProvider(EiffelEventFactory.Provider.class)