/* * Copyright 2014 WANdisco * * WANdisco licenses this file to you 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 c5db; import c5db.interfaces.replication.QuorumConfiguration; import c5db.replication.generated.LogEntry; import c5db.replication.rpc.RpcMessage; import c5db.replication.rpc.RpcRequest; import c5db.replication.rpc.RpcWireReply; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.jetlang.channels.Request; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; /** * Matchers for RPC requests and/or replies. */ public class RpcMatchers { public static class RequestMatcher extends TypeSafeMatcher<Request<RpcRequest, RpcWireReply>> { private final List<Predicate<Request<RpcRequest, RpcWireReply>>> predicates = new ArrayList<>(); private final List<Consumer<Description>> describers = new ArrayList<>(); @Override protected boolean matchesSafely(Request<RpcRequest, RpcWireReply> item) { return predicates.stream().allMatch((predicate) -> predicate.test(item)); } @Override public void describeTo(Description description) { describers.forEach((describer) -> describer.accept(description)); } public static RequestMatcher anAppendRequest() { return new RequestMatcher().addCriterion( RpcMatchers::isAnAppendEntriesRequest, (description) -> description .appendText("an AppendEntries request")); } public static RequestMatcher aPreElectionPoll() { return new RequestMatcher().addCriterion( (request) -> request.getRequest().isPreElectionPollMessage(), (description) -> description.appendText("a PreElectionPoll")); } public static RequestMatcher aRequestVote() { return new RequestMatcher().addCriterion( (request) -> request.getRequest().isRequestVoteMessage(), (description) -> description.appendText("a RequestVote")); } public RequestMatcher from(long peerId) { return addCriterion( (request) -> request.getRequest().from == peerId, (description) -> description .appendText(" from ").appendValue(peerId)); } public RequestMatcher to(long peerId) { return addCriterion( (request) -> request.getRequest().to == peerId, (description) -> description .appendText(" to ").appendValue(peerId)); } public RequestMatcher containingEntryIndex(long index) { return addCriterion( (request) -> entryList(request).stream().anyMatch((entry) -> entry.getIndex() == index), (description) -> description.appendText(" containing a log entry with index ").appendValue(index)); } public RequestMatcher withCommitIndex(Matcher<Long> indexMatcher) { return addCriterion( (request) -> indexMatcher.matches(request.getRequest().getAppendMessage().getCommitIndex()), (description) -> description.appendText(" with commitIndex ").appendDescriptionOf(indexMatcher)); } public RequestMatcher withPrevLogIndex(Matcher<Long> indexMatcher) { return addCriterion( (request) -> indexMatcher.matches(request.getRequest().getAppendMessage().getPrevLogIndex()), (description) -> description.appendText(" with prevLogIndex ").appendDescriptionOf(indexMatcher)); } public RequestMatcher containingQuorumConfig(QuorumConfiguration quorumConfig) { return addCriterion( (request) -> containsQuorumConfiguration(entryList(request), quorumConfig), (description) -> description.appendText(" with an entry containing the quorum configuration ") .appendValue(quorumConfig)); } private RequestMatcher addCriterion(Predicate<Request<RpcRequest, RpcWireReply>> predicate, Consumer<Description> describer) { RequestMatcher copy = new RequestMatcher(); copy.predicates.addAll(this.predicates); copy.predicates.add(predicate); copy.describers.addAll(this.describers); copy.describers.add(describer); return copy; } } public static class ReplyMatcher extends TypeSafeMatcher<RpcMessage> { private final List<Predicate<RpcMessage>> predicates = new ArrayList<>(); private final List<Consumer<Description>> describers = new ArrayList<>(); @Override protected boolean matchesSafely(RpcMessage item) { return predicates.stream().allMatch((predicate) -> predicate.test(item)); } @Override public void describeTo(Description description) { describers.forEach((describer) -> describer.accept(description)); } public static ReplyMatcher anAppendReply() { return new ReplyMatcher().addCriterion( RpcMessage::isAppendReplyMessage, (description) -> description .appendText("an AppendEntries reply")); } public static ReplyMatcher aPreElectionReply() { return new ReplyMatcher().addCriterion( RpcMessage::isPreElectionReplyMessage, (description) -> description.appendText("a PreElectionReply") ); } public ReplyMatcher withTerm(Matcher<Long> termMatcher) { return addCriterion( (reply) -> termMatcher.matches(reply.getAppendReplyMessage().getTerm()), (description) -> description.appendText(" with term ").appendDescriptionOf(termMatcher) ); } public ReplyMatcher withResult(boolean success) { return addCriterion( (reply) -> reply.getAppendReplyMessage().getSuccess() == success, (description) -> description.appendText(" with result ").appendValue(success)); } public ReplyMatcher withNextLogIndex(Matcher<Long> indexMatcher) { return addCriterion( (reply) -> indexMatcher.matches(reply.getAppendReplyMessage().getMyNextLogEntry()), (description) -> description.appendText(" with 'myNextLogEntry' ").appendDescriptionOf(indexMatcher)); } public ReplyMatcher withPollResult(boolean wouldVote) { return addCriterion( (reply) -> reply.getPreElectionReplyMessage().getWouldVote() == wouldVote, (description) -> description.appendText(" with poll result ").appendValue(wouldVote)); } private ReplyMatcher addCriterion(Predicate<RpcMessage> predicate, Consumer<Description> describer) { ReplyMatcher copy = new ReplyMatcher(); copy.predicates.addAll(this.predicates); copy.predicates.add(predicate); copy.describers.addAll(this.describers); copy.describers.add(describer); return copy; } } public static boolean containsQuorumConfiguration(List<LogEntry> entryList, QuorumConfiguration configuration) { return entryList.stream().anyMatch( (entry) -> QuorumConfiguration.fromProtostuff(entry.getQuorumConfiguration()) .equals(configuration)); } private static boolean isAnAppendEntriesRequest(Request<RpcRequest, RpcWireReply> request) { return request.getRequest().getAppendMessage() != null; } private static List<LogEntry> entryList(Request<RpcRequest, RpcWireReply> request) { return request.getRequest().getAppendMessage().getEntriesList(); } }