diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/records/MembershipState.java b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/records/MembershipState.java index 80889b3d4aa4a..b05afd07f6ce0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/records/MembershipState.java +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/records/MembershipState.java @@ -358,4 +358,23 @@ public long getDeletionMs() { public static void setDeletionMs(long time) { MembershipState.deletionMs = time; } + + /** + * First use the comparator of the BaseRecord to compare the date modified. + * If they are equal, compare their primary keys to ensure that MembershipStates + * with the same date modified but reported by different routers will not be judged as equal. + * + * @param record the MembershipState object to be compared. + * @return a negative integer, zero, or a positive integer as this object + * is less than, equal to, or greater than the specified object. + */ + @Override + public int compareTo(BaseRecord record) { + int order = super.compareTo(record); + if (order == 0) { + MembershipState other = (MembershipState) record; + return this.getPrimaryKey().compareTo(other.getPrimaryKey()); + } + return order; + } } diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/store/TestStateStoreMembershipState.java b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/store/TestStateStoreMembershipState.java index 533a1d97daf7f..f7f0970bd364e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/store/TestStateStoreMembershipState.java +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/store/TestStateStoreMembershipState.java @@ -223,6 +223,58 @@ public void testRegistrationMajorityQuorum() assertEquals(quorumEntry.getRouterId(), ROUTERS[3]); } + /** + * Fix getRepresentativeQuorum when records have same date modified time. + */ + @Test + public void testRegistrationMajorityQuorumEqDateModified() + throws IOException { + + // Populate the state store with a set of non-matching elements + // 1) ns0:nn0 - Standby (newest) + // 2) ns0:nn0 - Active + // 3) ns0:nn0 - Active + // 4) ns0:nn0 - Active + // (2), (3), (4) have the same date modified time + // Verify the selected entry is the newest majority opinion (4) + String ns = "ns0"; + String nn = "nn0"; + + long dateModified = Time.now(); + // Active - oldest + MembershipState report = createRegistration( + ns, nn, ROUTERS[1], FederationNamenodeServiceState.ACTIVE); + report.setDateModified(dateModified); + assertTrue(namenodeHeartbeat(report)); + + // Active - 2nd oldest + report = createRegistration( + ns, nn, ROUTERS[2], FederationNamenodeServiceState.ACTIVE); + report.setDateModified(dateModified); + assertTrue(namenodeHeartbeat(report)); + + // Active - 3rd oldest + report = createRegistration( + ns, nn, ROUTERS[3], FederationNamenodeServiceState.ACTIVE); + report.setDateModified(dateModified); + assertTrue(namenodeHeartbeat(report)); + + // standby - newest overall + report = createRegistration( + ns, nn, ROUTERS[0], FederationNamenodeServiceState.STANDBY); + assertTrue(namenodeHeartbeat(report)); + + // Load and calculate quorum + assertTrue(getStateStore().loadCache(MembershipStore.class, true)); + + // Verify quorum entry + MembershipState quorumEntry = getNamenodeRegistration( + report.getNameserviceId(), report.getNamenodeId()); + assertNotNull(quorumEntry); + // The name node status should be active + assertEquals(FederationNamenodeServiceState.ACTIVE, quorumEntry.getState()); + } + @Test public void testRegistrationQuorumExcludesExpired() throws InterruptedException, IOException {