/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.nifi.cluster.coordination.flow;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.nifi.cluster.protocol.DataFlow;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.cluster.protocol.StandardDataFlow;
import org.apache.nifi.fingerprint.FingerprintFactory;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class TestPopularVoteFlowElection {
@Test
public void testOnlyEmptyFlows() throws IOException {
final FingerprintFactory fingerprintFactory = Mockito.mock(FingerprintFactory.class);
Mockito.when(fingerprintFactory.createFingerprint(Mockito.any(byte[].class))).thenReturn("fingerprint");
final PopularVoteFlowElection election = new PopularVoteFlowElection(1, TimeUnit.MINUTES, 3, fingerprintFactory);
final byte[] flow = Files.readAllBytes(Paths.get("src/test/resources/conf/empty-flow.xml"));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
assertNull(election.castVote(createDataFlow(flow), createNodeId(1)));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
assertNull(election.castVote(createDataFlow(flow), createNodeId(2)));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
final DataFlow electedDataFlow = election.castVote(createDataFlow(flow), createNodeId(3));
assertNotNull(electedDataFlow);
assertEquals(new String(flow), new String(electedDataFlow.getFlow()));
}
@Test
public void testDifferentEmptyFlows() throws IOException {
final FingerprintFactory fingerprintFactory = Mockito.mock(FingerprintFactory.class);
Mockito.when(fingerprintFactory.createFingerprint(Mockito.any(byte[].class))).thenAnswer(new Answer<String>() {
@Override
public String answer(final InvocationOnMock invocation) throws Throwable {
final byte[] flow = invocation.getArgumentAt(0, byte[].class);
final String xml = new String(flow);
// Return the ID of the root group as the fingerprint.
final String fingerprint = xml.replaceAll("(?s:(.*<id>)(.*?)(</id>.*))", "$2");
return fingerprint;
}
});
final PopularVoteFlowElection election = new PopularVoteFlowElection(1, TimeUnit.MINUTES, 3, fingerprintFactory);
final byte[] flow1 = Files.readAllBytes(Paths.get("src/test/resources/conf/empty-flow.xml"));
final byte[] flow2 = Files.readAllBytes(Paths.get("src/test/resources/conf/different-empty-flow.xml"));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
assertNull(election.castVote(createDataFlow(flow1), createNodeId(1)));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
assertNull(election.castVote(createDataFlow(flow1), createNodeId(2)));
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
final DataFlow electedDataFlow = election.castVote(createDataFlow(flow2), createNodeId(3));
assertNotNull(electedDataFlow);
final String electedFlowXml = new String(electedDataFlow.getFlow());
assertTrue(new String(flow1).equals(electedFlowXml) || new String(flow2).equals(electedFlowXml));
}
@Test
public void testEmptyFlowIgnoredIfNonEmptyFlowExists() throws IOException {
final FingerprintFactory fingerprintFactory = Mockito.mock(FingerprintFactory.class);
Mockito.when(fingerprintFactory.createFingerprint(Mockito.any(byte[].class))).thenReturn("fingerprint");
final PopularVoteFlowElection election = new PopularVoteFlowElection(1, TimeUnit.MINUTES, 8, fingerprintFactory);
final byte[] emptyFlow = Files.readAllBytes(Paths.get("src/test/resources/conf/empty-flow.xml"));
final byte[] nonEmptyFlow = Files.readAllBytes(Paths.get("src/test/resources/conf/non-empty-flow.xml"));
for (int i = 0; i < 8; i++) {
assertFalse(election.isElectionComplete());
assertNull(election.getElectedDataFlow());
final DataFlow dataFlow;
if (i % 4 == 0) {
dataFlow = createDataFlow(nonEmptyFlow);
} else {
dataFlow = createDataFlow(emptyFlow);
}
final DataFlow electedDataFlow = election.castVote(dataFlow, createNodeId(i));
if (i == 7) {
assertNotNull(electedDataFlow);
assertEquals(new String(nonEmptyFlow), new String(electedDataFlow.getFlow()));
} else {
assertNull(electedDataFlow);
}
}
}
private NodeIdentifier createNodeId(final int index) {
return new NodeIdentifier(UUID.randomUUID().toString(), "localhost", 9000 + index, "localhost", 9000 + index, "localhost", 9000 + index, 9000 + index, true);
}
private DataFlow createDataFlow(final byte[] flow) {
return new StandardDataFlow(flow, new byte[0], new byte[0], new HashSet<>());
}
}