1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.je.rep.impl; 9 10 import java.io.Serializable; 11 import java.net.InetSocketAddress; 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.Iterator; 15 import java.util.Map; 16 import java.util.Set; 17 import java.util.UUID; 18 19 import com.sleepycat.bind.tuple.TupleBinding; 20 import com.sleepycat.bind.tuple.TupleInput; 21 import com.sleepycat.bind.tuple.TupleOutput; 22 import com.sleepycat.je.DatabaseException; 23 import com.sleepycat.je.EnvironmentFailureException; 24 import com.sleepycat.je.JEVersion; 25 import com.sleepycat.je.rep.MemberNotFoundException; 26 import com.sleepycat.je.rep.impl.RepGroupDB.NodeBinding; 27 import com.sleepycat.je.utilint.VLSN; 28 29 /** 30 * Represents a snapshot of the Replication Group as a whole. Note that 31 * membership associated with a group is dynamic and its constituents can 32 * change at any time. It's useful to keep in mind that due to the distributed 33 * nature of the Replication Group all the nodes in a replication group may not 34 * have the same consistent picture of the replication group at a single point 35 * in time, but will converge to become consistent eventually. 36 */ 37 public class RepGroupImpl { 38 39 /** The latest supported format version. */ 40 public static final int MAX_FORMAT_VERSION = 3; 41 42 /** 43 * Format version introduced in JE 6.0.1 that records a node's most recent 44 * JE version, and the minimum JE version required to join the group. 45 */ 46 public static final int FORMAT_VERSION_3 = 3; 47 48 /** 49 * The latest format version that is compatible with JE 6.0.0 and earlier 50 * versions. 51 */ 52 public static final int FORMAT_VERSION_2 = 2; 53 54 /** The initial format version for newly created RepGroupImpl instances. */ 55 public static final int INITIAL_FORMAT_VERSION = 3; 56 57 /** The oldest supported format version. */ 58 static final int MIN_FORMAT_VERSION = 2; 59 60 /** The first JE version that supports FORMAT_VERSION_3. */ 61 public static final JEVersion FORMAT_VERSION_3_JE_VERSION = 62 new JEVersion("6.0.1"); 63 64 /** 65 * The first JE version that supports the oldest supported format version. 66 */ 67 public static final JEVersion MIN_FORMAT_VERSION_JE_VERSION = 68 new JEVersion("5.0.0"); 69 70 /** The initial change version. */ 71 private final static int CHANGE_VERSION_START = 0; 72 73 /* 74 * The special UUID associated with a group, when the group UUID is unknown 75 * because a node is still in the process of joining the group. This value 76 * cannot be created by UUID.randomUUID 77 */ 78 private final static UUID UNKNOWN_UUID = new UUID(0, 0); 79 80 /** 81 * The maximum number of secondary nodes that can join the group at the 82 * same time time. This number of secondary node IDs will be reserved at 83 * the top of the node ID range. 84 */ 85 public static final int MAX_SECONDARY_NODES = 1024; 86 87 /** The first node ID for persistent nodes. */ 88 private static final int NODE_SEQUENCE_START = 0; 89 90 /** The maximum node ID for persistent nodes. */ 91 private static final int NODE_SEQUENCE_MAX = 92 Integer.MAX_VALUE - MAX_SECONDARY_NODES; 93 94 /** Returns true if the node is electable. */ 95 private static final Predicate ELECTABLE_PREDICATE = new Predicate() { 96 @Override 97 boolean include(final RepNodeImpl n) { 98 return n.getType().isElectable(); 99 } 100 }; 101 102 /** Returns true if the node is a monitor. */ 103 private static final Predicate MONITOR_PREDICATE = new Predicate() { 104 @Override 105 boolean include(final RepNodeImpl n) { 106 return n.getType().isMonitor(); 107 } 108 }; 109 110 /** Returns true if the node is secondary. */ 111 private static final Predicate SECONDARY_PREDICATE = new Predicate() { 112 @Override 113 boolean include(final RepNodeImpl n) { 114 return n.getType().isSecondary(); 115 } 116 }; 117 118 /* The name of the Replication Group. */ 119 private final String groupName; 120 121 /* 122 * The universally unique UUID associated with the replicated environment. 123 */ 124 private UUID uuid; 125 126 /* 127 * The version number associated with this group's format in the database. 128 */ 129 private volatile int formatVersion; 130 131 /* 132 * Tracks the change version level. It's updated with every change to the 133 * member set in the membership database. 134 */ 135 private int changeVersion = 0; 136 137 /* 138 * The most recently assigned node ID for persistent nodes. Node IDs for 139 * persistent nodes are never reused. 140 */ 141 private int nodeIdSequence; 142 143 /* 144 * The following maps represent the set of nodes in the group indexed in 145 * two different ways: by user-defined node name and by internal id. Note 146 * that both maps contain nodes that are no longer members of the group. 147 * 148 * All access to nodesById and nodesByName should be synchronized on 149 * nodesById, to avoid ConcurrentModificationException and to provide 150 * consistent results for both maps. 151 */ 152 153 /* All the nodes that form the replication group, indexed by Id. */ 154 private final Map<Integer, RepNodeImpl> nodesById = 155 new HashMap<Integer, RepNodeImpl>(); 156 157 /* 158 * All the nodes that form the replication group, indexed by node name. 159 * This map is used exclusively for efficient lookups by name. The map 160 * nodesById does all the heavy lifting. 161 */ 162 private final Map<String, RepNodeImpl> nodesByName = 163 new HashMap<String, RepNodeImpl>(); 164 165 /** The minimum JE version required for nodes to join the group. */ 166 private volatile JEVersion minJEVersion = MIN_FORMAT_VERSION_JE_VERSION; 167 168 /** 169 * Constructor to create a new empty repGroup, typically as part of 170 * environment initialization. 171 * 172 * @param groupName the group name 173 * @param currentJEVersion if non-null, override the current JE version, 174 * for testing 175 */ RepGroupImpl(String groupName, JEVersion currentJEVersion)176 public RepGroupImpl(String groupName, JEVersion currentJEVersion) { 177 this(groupName, false, currentJEVersion); 178 } 179 180 /** 181 * Constructor to create a group and specify if the group's UUID should be 182 * unknown or generated randomly. 183 */ RepGroupImpl(String groupName, boolean unknownUUID, JEVersion currentJEVersion)184 RepGroupImpl(String groupName, 185 boolean unknownUUID, 186 JEVersion currentJEVersion) { 187 this(groupName, 188 unknownUUID ? UNKNOWN_UUID : UUID.randomUUID(), 189 getCurrentFormatVersion(currentJEVersion)); 190 } 191 192 /** Get the current format version, supporting a test override. */ getCurrentFormatVersion( final JEVersion currentJEVersion)193 private static int getCurrentFormatVersion( 194 final JEVersion currentJEVersion) { 195 196 return (currentJEVersion == null) ? 197 MAX_FORMAT_VERSION : 198 getMaxFormatVersion(currentJEVersion); 199 } 200 201 /** 202 * Constructor to create a group and specify the group's UUID and format 203 * version. 204 */ RepGroupImpl(String groupName, UUID uuid, int formatVersion)205 public RepGroupImpl(String groupName, UUID uuid, int formatVersion) { 206 this(groupName, 207 uuid, 208 formatVersion, 209 CHANGE_VERSION_START, 210 NODE_SEQUENCE_START, 211 ((formatVersion < FORMAT_VERSION_3) ? 212 MIN_FORMAT_VERSION_JE_VERSION : 213 FORMAT_VERSION_3_JE_VERSION)); 214 } 215 216 /** 217 * Constructor used to recreate an existing RepGroup, typically as part of 218 * a deserialization operation. 219 * 220 * @param groupName 221 * @param uuid 222 * @param formatVersion 223 * @param changeVersion 224 * @param minJEVersion 225 */ RepGroupImpl(String groupName, UUID uuid, int formatVersion, int changeVersion, int nodeIdSequence, JEVersion minJEVersion)226 public RepGroupImpl(String groupName, 227 UUID uuid, 228 int formatVersion, 229 int changeVersion, 230 int nodeIdSequence, 231 JEVersion minJEVersion) { 232 this.groupName = groupName; 233 this.uuid = uuid; 234 this.formatVersion = formatVersion; 235 this.changeVersion = changeVersion; 236 setNodeIdSequence(nodeIdSequence); 237 this.minJEVersion = minJEVersion; 238 239 if (formatVersion < MIN_FORMAT_VERSION || 240 formatVersion > MAX_FORMAT_VERSION) { 241 throw new IllegalStateException( 242 "Expected membership database format version between: " + 243 MIN_FORMAT_VERSION + " and " + MAX_FORMAT_VERSION + 244 ", encountered unsupported version: " + formatVersion); 245 } 246 if (minJEVersion == null) { 247 throw new IllegalArgumentException( 248 "The minJEVersion must not be null"); 249 } 250 } 251 252 /* 253 * Returns true if the UUID has not as yet been established at this node. 254 * This is the case when a knew node first joins a group, and it has not 255 * as yet replicated the group database via the replication stream. 256 */ hasUnknownUUID()257 public boolean hasUnknownUUID() { 258 return UNKNOWN_UUID.equals(uuid); 259 } 260 261 /** 262 * Predicate to help determine whether the UUID is the canonical unknown 263 * UUID. 264 */ isUnknownUUID(UUID uuid)265 public static boolean isUnknownUUID(UUID uuid) { 266 return UNKNOWN_UUID.equals(uuid); 267 } 268 269 /** 270 * Sets the UUID. The UUID can only be set if it's currently unknown. 271 */ setUUID(UUID uuid)272 public void setUUID(UUID uuid) { 273 if (!hasUnknownUUID()) { 274 throw EnvironmentFailureException.unexpectedState 275 ("Expected placeholder UUID, not " + uuid); 276 } 277 this.uuid = uuid; 278 } 279 280 /** 281 * Removes a member transiently from the rep group by marking it as removed 282 * and optionally deleting it from the by-name and by-ID maps. This action 283 * is usually a precursor to making the change persistent on disk. 284 * 285 * @param nodeName identifies the node being removed 286 * 287 * @param delete whether to delete the node from the maps 288 * 289 * @return the node that was removed 290 * 291 * @throws EnvironmentFailureException if the node is not part of the group 292 * or is a secondary node 293 */ removeMember(final String nodeName, final boolean delete)294 public RepNodeImpl removeMember(final String nodeName, 295 final boolean delete) { 296 final RepNodeImpl node = getMember(nodeName); 297 if (node == null) { 298 throw EnvironmentFailureException.unexpectedState 299 ("Node:" + nodeName + " is not a member of the group."); 300 } 301 if (node.getType().isSecondary()) { 302 throw EnvironmentFailureException.unexpectedState( 303 "Cannot remove secondary node: " + nodeName); 304 } 305 if (delete) { 306 synchronized (nodesById) { 307 nodesById.remove(node.getNodeId()); 308 nodesByName.remove(nodeName); 309 } 310 } 311 node.setRemoved(true); 312 return node; 313 } 314 315 /** 316 * Checks for whether a new or changed node definition is in conflict with 317 * other members of the group. In particular, checks that the specified 318 * node does not use the same socket address as another member. 319 * <p> 320 * This check must be done when adding a new member to the group, or 321 * changing the network address of an existing member, and must be done 322 * with the rep group entry in the database locked for write to prevent 323 * race conditions. 324 * 325 * @param node the new node that is being checked for conflicts 326 * @throws NodeConflictException if there is a conflict 327 */ checkForConflicts(RepNodeImpl node)328 public void checkForConflicts(RepNodeImpl node) 329 throws DatabaseException, NodeConflictException { 330 331 for (RepNodeImpl n : getAllMembers(null)) { 332 if (n.getNameIdPair().equals(node.getNameIdPair())) { 333 continue; 334 } 335 if (n.getSocketAddress().equals(node.getSocketAddress())) { 336 throw new NodeConflictException 337 ("New or moved node:" + node.getName() + 338 ", is configured with the socket address: " + 339 node.getSocketAddress() + 340 ". It conflicts with the socket already " + 341 "used by the member: " + n.getName()); 342 } 343 } 344 } 345 346 @Override hashCode()347 public int hashCode() { 348 final int prime = 31; 349 int result = 1; 350 result = prime * result + changeVersion; 351 result = prime * result 352 + ((groupName == null) ? 0 : groupName.hashCode()); 353 synchronized (nodesById) { 354 result = prime * result + nodesById.hashCode(); 355 } 356 /* Don't bother with nodesByName */ 357 result = prime * result 358 + ((uuid == null) ? 0 : uuid.hashCode()); 359 result = prime * result + formatVersion; 360 return result; 361 } 362 363 @Override equals(Object obj)364 public boolean equals(Object obj) { 365 if (this == obj) { 366 return true; 367 } 368 if (obj == null) { 369 return false; 370 } 371 if (!(obj instanceof RepGroupImpl)) { 372 return false; 373 } 374 RepGroupImpl other = (RepGroupImpl) obj; 375 if (changeVersion != other.changeVersion) { 376 return false; 377 } 378 if (groupName == null) { 379 if (other.groupName != null) { 380 return false; 381 } 382 } else if (!groupName.equals(other.groupName)) { 383 return false; 384 } 385 /* Don't bother with nodesByName, since nodesById equality covers it */ 386 if (uuid == null) { 387 if (other.uuid != null) { 388 return false; 389 } 390 } else if (!uuid.equals(other.uuid)) { 391 return false; 392 } 393 if (formatVersion != other.formatVersion) { 394 return false; 395 } 396 if (!minJEVersion.equals(other.minJEVersion)) { 397 return false; 398 } 399 400 /* 401 * Do this last, since it is expensive because of its need to avoid 402 * concurrency conflicts. 403 */ 404 final Map<Integer, RepNodeImpl> otherNodesById; 405 synchronized (other.nodesById) { 406 otherNodesById = 407 new HashMap<Integer, RepNodeImpl>(other.nodesById); 408 } 409 synchronized (nodesById) { 410 if (!nodesById.equals(otherNodesById)) { 411 return false; 412 } 413 } 414 415 return true; 416 } 417 418 /** 419 * Sets the nodes associated with the Rep group. Note that both nodesById 420 * and nodesByIndex are initialized. 421 */ setNodes(final Map<Integer, RepNodeImpl> nodes)422 public void setNodes(final Map<Integer, RepNodeImpl> nodes) { 423 424 synchronized (nodesById) { 425 426 /* Remove non-secondary nodes */ 427 for (final Iterator<RepNodeImpl> iter = 428 nodesById.values().iterator(); 429 iter.hasNext(); ) { 430 final RepNodeImpl node = iter.next(); 431 if (!node.getType().isSecondary()) { 432 iter.remove(); 433 nodesByName.remove(node.getName()); 434 } 435 } 436 437 /* Add specified nodes */ 438 if (nodes != null) { 439 for (final RepNodeImpl node : nodes.values()) { 440 final RepNodeImpl prevById = 441 nodesById.put(node.getNodeId(), node); 442 final RepNodeImpl prevByName = 443 nodesByName.put(node.getName(), node); 444 445 /* 446 * Also remove entries for any previous nodes if the 447 * mapping between names and IDs was changed. 448 */ 449 if ((prevById != null) && 450 !node.getName().equals(prevById.getName())) { 451 nodesByName.remove(prevById.getName()); 452 } 453 if ((prevByName != null) && 454 node.getNodeId() != prevByName.getNodeId()) { 455 nodesById.remove(prevByName.getNodeId()); 456 } 457 } 458 } 459 460 assert new HashSet<RepNodeImpl>(nodesById.values()).equals( 461 new HashSet<RepNodeImpl>(nodesByName.values())) 462 : "Node maps indexed by ID and name differ: " + 463 "IDs: " + nodesById + ", Names: " + nodesByName; 464 } 465 } 466 467 /** 468 * Add a secondary node. The caller should have already have an assigned 469 * the node an ID and checked that the replication group supports secondary 470 * nodes. 471 * 472 * @param node the secondary node 473 * @throws IllegalStateException if the store does not currently support 474 * secondary nodes 475 * @throws NodeConflictException if the node conflicts with an existing 476 * persistent node 477 */ addSecondaryNode(final RepNodeImpl node)478 public void addSecondaryNode(final RepNodeImpl node) { 479 if (!node.getType().isSecondary()) { 480 throw new IllegalArgumentException( 481 "Attempt to call addSecondaryNode on a non-SECONDARY node: " + 482 node); 483 } 484 if (node.getNameIdPair().hasNullId()) { 485 throw new IllegalArgumentException( 486 "Attempt to call addSecondaryNode on node without ID: " + 487 node); 488 } 489 490 synchronized (nodesById) { 491 final RepNodeImpl prevById = nodesById.get(node.getNodeId()); 492 assert (prevById == null) || prevById.getType().isSecondary() 493 : "Same node ID for secondary and non-secondary nodes: " + 494 node + ", " + prevById; 495 final RepNodeImpl prevByName = nodesByName.get(node.getName()); 496 if ((prevByName != null) && !prevByName.getType().isSecondary()) { 497 throw new NodeConflictException( 498 "New secondary node " + node.getName() + 499 " conflicts with an existing non-secondary node with the" + 500 " same name: " + prevByName); 501 } 502 final RepNodeImpl prevById2 = 503 nodesById.put(node.getNodeId(), node); 504 assert prevById == prevById2; 505 final RepNodeImpl prevByName2 = 506 nodesByName.put(node.getName(), node); 507 assert prevByName == prevByName2; 508 if ((prevById != null) && 509 !node.getName().equals(prevById.getName())) { 510 nodesByName.remove(prevById.getName()); 511 } 512 if ((prevByName != null) && 513 (node.getNodeId() != prevByName.getNodeId())) { 514 nodesById.remove(prevByName.getNodeId()); 515 } 516 517 assert new HashSet<RepNodeImpl>(nodesById.values()).equals( 518 new HashSet<RepNodeImpl>(nodesByName.values())) 519 : "Node maps indexed by ID and name differ: " + 520 "IDs: " + nodesById + ", Names: " + nodesByName; 521 } 522 } 523 524 /** 525 * Remove a secondary node, which should have an assigned ID 526 * 527 * @param node the secondary node 528 */ removeSecondaryNode(final RepNodeImpl node)529 public void removeSecondaryNode(final RepNodeImpl node) { 530 if (!node.getType().isSecondary()) { 531 throw new IllegalArgumentException( 532 "Attempt to call removeSecondaryNode on a" + 533 " non-SECONDARY node: " + node); 534 } 535 if (node.getNameIdPair().hasNullId()) { 536 throw new IllegalArgumentException( 537 "Attempt to call removeSecondaryNode on a node with no ID: " + 538 node); 539 } 540 synchronized (nodesById) { 541 nodesById.remove(node.getNodeId()); 542 nodesByName.remove(node.getName()); 543 } 544 } 545 546 /** 547 * returns the unique UUID associated with the replicated environment. 548 * 549 * @return the UUID 550 */ getUUID()551 public UUID getUUID() { 552 return uuid; 553 } 554 555 /** 556 * Returns the version of the format (the schema) in use by this group 557 * instance in the database. 558 * 559 * @return the format version 560 */ getFormatVersion()561 public int getFormatVersion() { 562 return formatVersion; 563 } 564 565 /** 566 * Returns the highest format version supported by the specified JE 567 * version. 568 * 569 * @param jeVersion the JE version 570 * @return the highest format version supported by that JE version 571 */ getMaxFormatVersion(final JEVersion jeVersion)572 public static int getMaxFormatVersion(final JEVersion jeVersion) { 573 if (jeVersion.compareTo(FORMAT_VERSION_3_JE_VERSION) < 0) { 574 return FORMAT_VERSION_2; 575 } 576 return FORMAT_VERSION_3; 577 } 578 579 /** 580 * Returns the version of the instance as represented by changes to the 581 * members constituting the group. 582 * 583 * @return the object change version 584 */ getChangeVersion()585 public int getChangeVersion() { 586 return changeVersion; 587 } 588 589 /** 590 * Increments the object change version. It must be called with the group 591 * entry locked in the group database. 592 * 593 * @return the incremented change version 594 */ incrementChangeVersion()595 public int incrementChangeVersion() { 596 return ++changeVersion; 597 } 598 599 /** 600 * Returns the current highest node ID currently in use by the group. 601 * 602 * @return the highest node ID in use 603 */ getNodeIdSequence()604 public int getNodeIdSequence() { 605 return nodeIdSequence; 606 } 607 608 /** 609 * Set the node id sequence. This is only done in unusual circumstances, 610 * e.g. when a replication group is being reset in an existing replicated 611 * environment and we want to ensure that the internal node ids are not 612 * reused in the logs. 613 */ setNodeIdSequence(int nodeIdSequence)614 public void setNodeIdSequence(int nodeIdSequence) { 615 if (nodeIdSequence < 0 || nodeIdSequence > NODE_SEQUENCE_MAX) { 616 throw new IllegalArgumentException( 617 "Bad nodeIdSequence: " + nodeIdSequence); 618 } 619 this.nodeIdSequence = nodeIdSequence; 620 } 621 622 /** 623 * Increments the node ID sequence and returns it. 624 * 625 * @return the next node ID for use in a new node 626 */ getNextNodeId()627 public int getNextNodeId() { 628 if (nodeIdSequence >= NODE_SEQUENCE_MAX) { 629 throw new IllegalStateException("Reached maximum node ID"); 630 } 631 return ++nodeIdSequence; 632 } 633 634 /** 635 * Returns the node ID that is associated with the very first node in the 636 * replication group. 637 */ getFirstNodeId()638 public static int getFirstNodeId() { 639 return NODE_SEQUENCE_START+1; 640 } 641 642 /** 643 * Returns the minimum JE version that a node must be running in order to 644 * join the group. 645 */ getMinJEVersion()646 public JEVersion getMinJEVersion() { 647 return minJEVersion; 648 } 649 650 /** 651 * Sets the minimum JE version that a node must be running in order to join 652 * the replication group. The group object should have had its nodes 653 * fetched using the {@link RepGroupDB#fetchGroup} method and should be 654 * stored to the group database after making this change. Throws a {@link 655 * MinJEVersionUnsupportedException} if the requested version is not 656 * supported. Updates the group format version as needed to match the JE 657 * version. Has no effect if the current minimum value is already as high 658 * or higher than the requested one. 659 * 660 * @param newMinJEVersion the new minimum JE version 661 * @throws MinJEVersionUnsupportedException if the requested version is not 662 * supported by the group's electable nodes 663 */ setMinJEVersion(final JEVersion newMinJEVersion)664 public void setMinJEVersion(final JEVersion newMinJEVersion) 665 throws MinJEVersionUnsupportedException { 666 667 if (newMinJEVersion == null) { 668 throw new IllegalArgumentException( 669 "The newMinJEVersion argument must not be null"); 670 } 671 if (newMinJEVersion.compareTo(minJEVersion) <= 0) { 672 return; 673 } 674 final int newFormatVersion = getMaxFormatVersion(newMinJEVersion); 675 676 /* Minimum JE version is not stored before format version 3 */ 677 if (newFormatVersion < FORMAT_VERSION_3) { 678 return; 679 } 680 681 for (final RepNodeImpl node : getElectableMembers()) { 682 final JEVersion nodeJEVersion = node.getJEVersion(); 683 if ((nodeJEVersion != null) && 684 nodeJEVersion.compareTo(newMinJEVersion) < 0) { 685 throw new MinJEVersionUnsupportedException( 686 newMinJEVersion, node.getName(), nodeJEVersion); 687 } 688 } 689 minJEVersion = newMinJEVersion; 690 formatVersion = newFormatVersion; 691 } 692 693 /** 694 * Used to ensure that the ReplicationGroup value is consistent after it 695 * has been fetched via a readUncommitted access to the rep group database. 696 * It does so by ensuring that the summarized values match the nodes that 697 * were actually read. 698 */ makeConsistent()699 public void makeConsistent() { 700 synchronized (nodesById) { 701 if (nodesById.isEmpty()) { 702 return; 703 } 704 int computedNodeId = NODE_SEQUENCE_START-1; 705 int computedChangeVersion = -1; 706 for (RepNodeImpl mi : nodesById.values()) { 707 /* Get the highest node ID */ 708 if (computedNodeId < mi.getNodeId()) { 709 computedNodeId = mi.getNodeId(); 710 } 711 /* Get the highest change version. */ 712 if (computedChangeVersion < mi.getChangeVersion()) { 713 computedChangeVersion = mi.getChangeVersion(); 714 } 715 } 716 setNodeIdSequence(computedNodeId); 717 changeVersion = computedChangeVersion; 718 } 719 } 720 721 /* 722 * Serialization 723 */ 724 725 /** 726 * Serializes an object by converting its TupleBinding byte based 727 * representation into the hex characters denoting the bytes. 728 * 729 * @param <T> the type of the object being serialized 730 * @param binding the tuble binding used to convert it into its byte form 731 * @param object the object being serialized 732 * @return the hex string containing the serialized hex form of the object 733 */ objectToHex(TupleBinding<T> binding, T object)734 static <T> String objectToHex(TupleBinding<T> binding, T object) { 735 StringBuilder buffer = new StringBuilder(); 736 TupleOutput tuple = new TupleOutput(new byte[100]); 737 binding.objectToEntry(object, tuple); 738 byte[] bytes = tuple.getBufferBytes(); 739 int size = tuple.getBufferLength(); 740 741 for (int i=0; i < size; i++) { 742 int lowNibble = (bytes[i] & 0xf); 743 int highNibble = ((bytes[i]>>4) & 0xf); 744 buffer.append(Character.forDigit(lowNibble,16)); 745 buffer.append(Character.forDigit(highNibble,16)); 746 } 747 return buffer.toString(); 748 } 749 750 /** 751 * Returns a serialized character based form of the group suitable for use 752 * in subclasses of SimpleProtocol. The serialized form is a multi-token 753 * string. The first token represents the RepGroup object itself with each 754 * subsequent node representing a node in the group. Tokens are separated 755 * by '|', the protocol separator character. The number of tokens is thus 756 * equal to the number of nodes in the group + 1. Each token is itself a 757 * hex character based representation of the binding used to serialize a 758 * RepGroup and store it into the database. 759 * 760 * @param groupFormatVersion the group format version 761 * @return the string encoded as above 762 */ serializeHex(final int groupFormatVersion)763 public String serializeHex(final int groupFormatVersion) { 764 final RepGroupDB.GroupBinding groupBinding = 765 new RepGroupDB.GroupBinding(groupFormatVersion); 766 StringBuilder buffer = new StringBuilder(); 767 buffer.append(objectToHex(groupBinding, this)); 768 synchronized (nodesById) { 769 for (RepNodeImpl mi : nodesById.values()) { 770 771 /* 772 * Only include nodes that can be serialized with the specified 773 * format version 774 */ 775 if (NodeBinding.supportsObjectToEntry( 776 mi, groupFormatVersion)) { 777 buffer.append(TextProtocol.SEPARATOR); 778 buffer.append(serializeHex(mi, groupFormatVersion)); 779 } 780 } 781 } 782 return buffer.toString(); 783 } 784 785 /** 786 * Returns the serialized form of the node as a sequence of hex characters 787 * suitable for use by the text based protocols. 788 * 789 * @param node the node to be serialized. 790 * @param formatVersion the group format version 791 * @return the string containing the serialized form of the node 792 */ serializeHex(final RepNodeImpl node, final int formatVersion)793 public static String serializeHex(final RepNodeImpl node, 794 final int formatVersion) { 795 final NodeBinding nodeBinding = new NodeBinding(formatVersion); 796 return objectToHex(nodeBinding, node); 797 } 798 799 /** 800 * Serialize the node into its byte representation. 801 * 802 * @param node the node to be serialized 803 * @param formatVersion the group format version 804 * @return the serialized byte array 805 */ serializeBytes(final RepNodeImpl node, final int formatVersion)806 public static byte[] serializeBytes(final RepNodeImpl node, 807 final int formatVersion) { 808 809 final NodeBinding binding = new NodeBinding(formatVersion); 810 final TupleOutput tuple = 811 new TupleOutput(new byte[NodeBinding.APPROX_MAX_SIZE]); 812 binding.objectToEntry(node, tuple); 813 return tuple.getBufferBytes(); 814 } 815 816 /** 817 * Deserializes the object serialized by {@link #serializeHex} 818 * 819 * @param hex the string containing the serialized form of the node 820 * @param formatVersion the group format version 821 * 822 * @return the de-serialized object 823 */ hexDeserializeNode(final String hex, final int formatVersion)824 public static RepNodeImpl hexDeserializeNode(final String hex, 825 final int formatVersion) { 826 final NodeBinding nodeBinding = new NodeBinding(formatVersion); 827 return hexToObject(nodeBinding, hex); 828 } 829 830 /** 831 * Deserialize the mode from its byte representation. 832 * 833 * @param bytes the byte representation of the node. 834 * @param formatVersion the group format version 835 * 836 * @return the deserialized object 837 */ deserializeNode(final byte[] bytes, final int formatVersion)838 public static RepNodeImpl deserializeNode(final byte[] bytes, 839 final int formatVersion) { 840 final NodeBinding binding = new NodeBinding(formatVersion); 841 TupleInput tuple = new TupleInput(bytes); 842 return binding.entryToObject(tuple); 843 } 844 845 /** 846 * Carries out the two step de-serialization from hex string into a byte 847 * buffer and subsequently into its object representation. 848 * 849 * @return the object representation 850 */ hexToObject(TupleBinding<T> binding, String hex)851 private static <T> T hexToObject(TupleBinding<T> binding, String hex) { 852 byte buffer[] = new byte[(hex.length()/2)]; 853 for (int i=0; i < hex.length(); i+=2) { 854 int value = Character.digit(hex.charAt(i),16); 855 value |= Character.digit(hex.charAt(i+1),16) << 4; 856 buffer[i >> 1] = (byte)value; 857 } 858 TupleInput tuple = new TupleInput(buffer); 859 return binding.entryToObject(tuple); 860 } 861 862 /** 863 * De-serializes an array of tokens into a Rep group object and its nodes. 864 * the token at <code>start</code>represents the group object and each 865 * subsequent token represents a node in the group. 866 * 867 * @param tokens the array representing the group and its nodes 868 * @param start the position in the array at which to start the 869 * de-serialization. 870 * 871 * @return the de-serialized RepGroup 872 */ deserializeHex(String[] tokens, int start)873 static public RepGroupImpl deserializeHex(String[] tokens, int start) { 874 final RepGroupDB.GroupBinding groupBinding = 875 new RepGroupDB.GroupBinding(); 876 RepGroupImpl group = hexToObject(groupBinding, tokens[start++]); 877 Map<Integer,RepNodeImpl> nodeMap = 878 new HashMap<Integer,RepNodeImpl>(); 879 while (start < tokens.length) { 880 RepNodeImpl n = 881 hexDeserializeNode(tokens[start++], group.getFormatVersion()); 882 RepNodeImpl old = nodeMap.put(n.getNameIdPair().getId(), n); 883 assert(old == null); 884 } 885 group.setNodes(nodeMap); 886 return group; 887 } 888 889 /* 890 * Accessing nodes and groups of nodes 891 */ 892 893 /** 894 * Returns the node IDs for all nodes that are currently members of the 895 * group and that act as proposers, acceptors, or distinguished learners. 896 * Returns IDs for all ELECTABLE and MONITOR nodes that are not removed, 897 * even if they are not acknowledged, but not for SECONDARY nodes. 898 */ getAllElectionMemberIds()899 public Set<Integer> getAllElectionMemberIds() { 900 Set<Integer> ret = new HashSet<Integer>(); 901 synchronized (nodesById) { 902 for (RepNodeImpl mi : nodesById.values()) { 903 if (!mi.isRemoved() && !mi.getType().isSecondary()) { 904 ret.add(mi.getNodeId()); 905 } 906 } 907 } 908 return ret; 909 } 910 911 /** 912 * Returns all nodes that are currently members of the group. Returns all 913 * ELECTABLE and MONITOR nodes that are not removed, even if they are not 914 * acknowledged, and SECONDARY nodes. If the predicate is not null, only 915 * includes members that satisfy the predicate. 916 */ getAllMembers(final Predicate p)917 public Set<RepNodeImpl> getAllMembers(final Predicate p) { 918 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 919 includeAllMembers(p, result); 920 return result; 921 } 922 923 /** 924 * Adds all nodes that are currently members of the group to the specified 925 * set. Adds all ELECTABLE and MONITOR nodes that are not removed, even if 926 * they are not acknowledged, and SECONDARY nodes. If the predicate is not 927 * null, only adds members that satisfy the predicate. 928 */ includeAllMembers(final Predicate p, final Set<? super RepNodeImpl> set)929 public void includeAllMembers(final Predicate p, 930 final Set<? super RepNodeImpl> set) { 931 synchronized (nodesById) { 932 for (RepNodeImpl mi : nodesById.values()) { 933 if (!mi.isRemoved() && ((p == null) || p.include(mi))) { 934 set.add(mi); 935 } 936 } 937 } 938 } 939 940 /** 941 * Counts the number of nodes that are currently members of the group. 942 * Counts all ELECTABLE and MONITOR nodes that are not removed, even if 943 * they are not acknowledged, and SECONDARY nodes. If the predicate is not 944 * null, only counts members that satisfy the predicate. 945 */ countAllMembers(final Predicate p)946 public int countAllMembers(final Predicate p) { 947 int count = 0; 948 synchronized (nodesById) { 949 for (final RepNodeImpl mi : nodesById.values()) { 950 if (!mi.isRemoved() && ((p == null) || p.include(mi))) { 951 count++; 952 } 953 } 954 } 955 return count; 956 } 957 958 /** 959 * Adds nodes that are currently members of the group to the specified set. 960 * Adds ELECTABLE and MONITOR node that are not removed and are 961 * acknowledged, and SECONDARY nodes. If the predicate is not null, only 962 * adds members that satisfy the predicate. 963 */ includeMembers(final Predicate p, final Set<? super RepNodeImpl> set)964 public void includeMembers(final Predicate p, 965 final Set<? super RepNodeImpl> set) { 966 synchronized (nodesById) { 967 for (RepNodeImpl n : nodesById.values()) { 968 if (!n.isRemoved() && 969 n.isQuorumAck() && 970 ((p == null) || p.include(n))) { 971 set.add(n); 972 } 973 } 974 } 975 } 976 977 /** 978 * Gets the node that is currently a member of the group that has the given 979 * socket address. Returns ELECTABLE and MONITOR nodes that are not 980 * removed, even if it is not acknowledged, and SECONDARY nodes. 981 * 982 * @return the desired node, or null if there is no such node, including 983 * if it was removed 984 */ getMember(InetSocketAddress socket)985 public RepNodeImpl getMember(InetSocketAddress socket) { 986 synchronized (nodesById) { 987 for (RepNodeImpl n : nodesById.values()) { 988 if (socket.equals(n.getSocketAddress()) && !n.isRemoved()) { 989 return n; 990 } 991 } 992 } 993 return null; 994 } 995 996 /** 997 * Returns nodes that are removed from the group. Returns ELECTABLE and 998 * MONITOR nodes that are removed and are acknowledged, but not SECONDARY 999 * nodes, which are not remembered when they are removed. 1000 */ getRemovedNodes()1001 public Set<RepNodeImpl> getRemovedNodes() { 1002 Set<RepNodeImpl> ret = new HashSet<RepNodeImpl>(); 1003 synchronized (nodesById) { 1004 for (RepNodeImpl mi : nodesById.values()) { 1005 if (mi.isRemoved() && mi.isQuorumAck()) { 1006 ret.add(mi); 1007 } 1008 } 1009 } 1010 return ret; 1011 } 1012 1013 /** A predicate for specifying which replication nodes to include. */ 1014 abstract static class Predicate { include(RepNodeImpl n)1015 abstract boolean include(RepNodeImpl n); 1016 } 1017 1018 /** 1019 * Returns all electable nodes that are currently members of the group. 1020 * Returns all ELECTABLE nodes that are not removed, even if they are not 1021 * acknowledged, but not MONITOR or SECONDARY nodes. 1022 */ getAllElectableMembers()1023 public Set<RepNodeImpl> getAllElectableMembers() { 1024 return getAllMembers(ELECTABLE_PREDICATE); 1025 } 1026 1027 /** 1028 * Returns electable nodes that are currently members of the group. 1029 * Returns ELECTABLE nodes that are not removed and are acknowledged, but 1030 * not MONITOR or SECONDARY nodes. 1031 */ getElectableMembers()1032 public Set<RepNodeImpl> getElectableMembers() { 1033 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 1034 includeElectableMembers(result); 1035 return result; 1036 } 1037 1038 /** 1039 * Adds the electable nodes that are currently members of the group to the 1040 * specified set. Adds ELECTABLE nodes that are not removed and are 1041 * acknowledged, but not MONITOR or SECONDARY nodes. 1042 */ includeElectableMembers(final Set<? super RepNodeImpl> set)1043 public void includeElectableMembers(final Set<? super RepNodeImpl> set) { 1044 includeAllMembers( 1045 new Predicate() { 1046 @Override 1047 boolean include(RepNodeImpl n) { 1048 return n.getType().isElectable() && n.isQuorumAck(); 1049 } 1050 }, 1051 set); 1052 } 1053 1054 /** 1055 * Returns the nodes that are currently members of the group that store 1056 * replication data. Returns ELECTABLE nodes that are not removed and are 1057 * acknowledged, and SECONDARY nodes, but not MONITOR nodes. 1058 */ getDataMembers()1059 public Set<RepNodeImpl> getDataMembers() { 1060 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 1061 includeDataMembers(result); 1062 return result; 1063 } 1064 1065 /** 1066 * Adds the nodes that are currently members of the group that store 1067 * replication data to the specified set. Adds ELECTABLE nodes that are 1068 * not removed and are acknowledged, and SECONDARY nodes, but not MONITOR 1069 * nodes. 1070 */ includeDataMembers(final Set<? super RepNodeImpl> set)1071 public void includeDataMembers(final Set<? super RepNodeImpl> set) { 1072 includeAllMembers( 1073 new Predicate() { 1074 @Override 1075 boolean include(final RepNodeImpl n) { 1076 return n.getType().isDataNode() && n.isQuorumAck(); 1077 } 1078 }, 1079 set); 1080 } 1081 1082 /** 1083 * Returns the monitor nodes that are currently members of the group. 1084 * Returns MONITOR nodes that are not removed and are acknowledged, but not 1085 * ELECTABLE or SECONDARY nodes. 1086 * 1087 * @return the set of monitor nodes 1088 */ getMonitorMembers()1089 public Set<RepNodeImpl> getMonitorMembers() { 1090 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 1091 includeMonitorMembers(result); 1092 return result; 1093 } 1094 1095 /** 1096 * Adds the monitor nodes that are currently members of the group to the 1097 * specified set. Adds MONITOR nodes that are not removed and are 1098 * acknowledged, but not ELECTABLE or SECONDARY nodes. 1099 */ includeMonitorMembers(final Set<? super RepNodeImpl> set)1100 public void includeMonitorMembers(final Set<? super RepNodeImpl> set) { 1101 includeMembers(MONITOR_PREDICATE, set); 1102 } 1103 1104 /** 1105 * Returns all the nodes that are currently members of the group that act 1106 * as distinguished learners to receive election results. Returns all 1107 * ELECTABLE and MONITOR that are not removed, even if they are not 1108 * acknowledged, but not SECONDARY nodes. 1109 */ getAllLearnerMembers()1110 public Set<RepNodeImpl> getAllLearnerMembers() { 1111 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 1112 includeAllMembers( 1113 new Predicate() { 1114 @Override 1115 boolean include(final RepNodeImpl n) { 1116 return (n.getType().isElectable() || 1117 n.getType().isMonitor()); 1118 } 1119 }, 1120 result); 1121 return result; 1122 } 1123 1124 /** 1125 * Returns the secondary nodes that are currently members of the group. 1126 * Returns SECONDARY nodes, but not ELECTABLE or MONITOR nodes. 1127 */ getSecondaryMembers()1128 public Set<RepNodeImpl> getSecondaryMembers() { 1129 final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>(); 1130 includeSecondaryMembers(result); 1131 return result; 1132 } 1133 1134 /** 1135 * Adds the secondary nodes that are currently members of the group to the 1136 * specified set. Adds SECONDARY nodes, but not ELECTABLE or MONITOR 1137 * nodes. 1138 */ includeSecondaryMembers(final Set<? super RepNodeImpl> set)1139 public void includeSecondaryMembers(final Set<? super RepNodeImpl> set) { 1140 includeAllMembers(SECONDARY_PREDICATE, set); 1141 } 1142 1143 /** 1144 * Returns the socket addresses for all nodes that are currently members of 1145 * the group. Returns addresses for all ELECTABLE and MONITOR nodes that 1146 * are not removed, even if they are not acknowledged, and for all 1147 * SECONDARY nodes. If the predicate is not null, only returns addresses 1148 * for members that satisfy the predicate. 1149 */ getAllMemberSockets(Predicate p)1150 private Set<InetSocketAddress> getAllMemberSockets(Predicate p) { 1151 Set<InetSocketAddress> sockets = new HashSet<InetSocketAddress>(); 1152 synchronized (nodesById) { 1153 for (final RepNodeImpl mi : nodesById.values()) { 1154 if ((((mi.getType().isElectable() || 1155 mi.getType().isMonitor()) && 1156 !mi.isRemoved()) || 1157 mi.getType().isSecondary()) && 1158 ((p == null) || p.include(mi))) { 1159 sockets.add(mi.getSocketAddress()); 1160 } 1161 } 1162 } 1163 return sockets; 1164 } 1165 1166 /** 1167 * Return the socket addresses for all nodes that are currently members of 1168 * the group and act as distinguished learners to receive election results. 1169 * Returns addresses for all ELECTABLE and MONITOR nodes that are not 1170 * removed, even if they are not acknowledged, but not for SECONDARY nodes. 1171 * 1172 * @return set of learner socket addresses 1173 */ getAllLearnerSockets()1174 public Set<InetSocketAddress> getAllLearnerSockets() { 1175 1176 /* 1177 * TODO: Consider including SECONDARY nodes in this list. That change 1178 * would increase the chance that SECONDARY nodes have up-to-date 1179 * information about the master, but would need to be paired with a 1180 * change to only wait for delivery of notifications to ELECTABLE 1181 * nodes, to avoid adding sensitivity to potentially longer network 1182 * delays in communicating with secondary nodes. 1183 */ 1184 return getAllMemberSockets(new Predicate() { 1185 @Override 1186 boolean include(RepNodeImpl n) { 1187 return !n.getType().isSecondary(); 1188 } 1189 }); 1190 } 1191 1192 /** 1193 * Return the socket addresses for all nodes that are currently members of 1194 * the group and act as helpers to supply election results. Returns 1195 * addresses for all ELECTABLE and MONITOR nodes that are not removed, even 1196 * if they are not acknowledged, and SECONDARY nodes. 1197 * 1198 * @return set of helper socket addresses 1199 */ 1200 public Set<InetSocketAddress> getAllHelperSockets() { 1201 return getAllMemberSockets(null); 1202 } 1203 1204 /** 1205 * Returns the socket addresses for all monitor nodes that are currently 1206 * members of the group. Returns addresses for all MONITOR nodes that are 1207 * not removed, even if they are not acknowledged, but not for ELECTABLE or 1208 * SECONDARY nodes. 1209 * 1210 * @return the set of Monitor socket addresses 1211 */ 1212 public Set<InetSocketAddress> getAllMonitorSockets() { 1213 return getAllMemberSockets(MONITOR_PREDICATE); 1214 } 1215 1216 /** 1217 * Returns the socket addresses for all nodes that are currently members of 1218 * the group and act as acceptors for elections. Returns addresses for all 1219 * ELECTABLE nodes that are not removed, even if they are not acknowledged, 1220 * but not for MONITOR or SECONDARY nodes, which do not act as acceptors. 1221 * 1222 * @return the set of acceptor socket addresses 1223 */ 1224 public Set<InetSocketAddress> getAllAcceptorSockets() { 1225 return getAllMemberSockets(ELECTABLE_PREDICATE); 1226 } 1227 1228 /** 1229 * Returns the node with the specified ID that is currently a member of the 1230 * group, throwing an exception if the node is found but is no longer a 1231 * member. Returns ELECTABLE and MONITOR nodes that are not removed, even 1232 * if they are not acknowledged, and SECONDARY nodes. 1233 * 1234 * @param nodeId the node ID 1235 * @return the member or null 1236 * @throws EnvironmentFailureException if the node is no longer a member 1237 */ 1238 public RepNodeImpl getMember(int nodeId) { 1239 RepNodeImpl node = getNode(nodeId); 1240 if (node == null) { 1241 return null; 1242 } 1243 if (node.isRemoved()) { 1244 throw EnvironmentFailureException.unexpectedState 1245 ("No longer a member:" + nodeId); 1246 } 1247 return node; 1248 } 1249 1250 /** 1251 * Returns the node with the specified name that is currently a member of 1252 * the group, throwing an exception if the node is found but is no longer a 1253 * member. Returns ELECTABLE and MONITOR nodes that are not removed, even 1254 * if they are not acknowledged, and SECONDARY nodes. 1255 * 1256 * @param name the node name 1257 * @return the member or null 1258 * @throws MemberNotFoundException if the node is no longer a member 1259 */ 1260 public RepNodeImpl getMember(String name) 1261 throws MemberNotFoundException { 1262 1263 RepNodeImpl node = getNode(name); 1264 if (node == null) { 1265 return null; 1266 } 1267 if (node.isRemoved()) { 1268 throw new MemberNotFoundException 1269 ("Node no longer a member:" + name); 1270 } 1271 return node; 1272 } 1273 1274 /** 1275 * Returns the node with the specified ID, regardless of its membership 1276 * state. Returns all ELECTABLE and MONITOR nodes, even if they are 1277 * removed or are not acknowledged, and SECONDARY nodes. 1278 * 1279 * @return the node or null 1280 */ 1281 public RepNodeImpl getNode(int nodeId) { 1282 synchronized (nodesById) { 1283 return nodesById.get(nodeId); 1284 } 1285 } 1286 1287 /** 1288 * Returns the node with the specified name, regardless of its membership 1289 * state. Returns all ELECTABLE and MONITOR nodes, even if they are 1290 * removed or are not acknowledged, and SECONDARY nodes. 1291 * 1292 * @return the node or null 1293 */ 1294 public RepNodeImpl getNode(String name) { 1295 synchronized (nodesById) { 1296 return nodesByName.get(name); 1297 } 1298 } 1299 1300 /** 1301 * Returns the number of all electable nodes that are currently members of 1302 * the group. Includes all ELECTABLE nodes that are not removed, even if 1303 * they are not acknowledged, but not MONITOR or SECONDARY nodes. Note 1304 * that even unACKed nodes are considered part of the group for group 1305 * size/durability considerations. 1306 * 1307 * @return the size of the group for durability considerations 1308 */ 1309 public int getElectableGroupSize() { 1310 return countAllMembers(ELECTABLE_PREDICATE); 1311 } 1312 1313 /* Miscellaneous */ 1314 1315 /** 1316 * Returns the name of the group. 1317 * 1318 * @return the name of the group. 1319 */ 1320 public String getName() { 1321 return groupName; 1322 } 1323 1324 /** 1325 * Encapsulates the last known syncup state associated with a node. 1326 */ 1327 public static class BarrierState implements Serializable { 1328 1329 private static final long serialVersionUID = 1L; 1330 1331 /* 1332 * The latest sync position of this node in the replication stream. 1333 * This position is approximate and is updated on some regular basis. 1334 * It is conservative in that the node is likely to have a newer sync 1335 * point. So it represents a lower bound for its sync point. 1336 */ 1337 private final VLSN lastLocalCBVLSN; 1338 1339 /* 1340 * The time that the sync point was last recorded. Note that clocks 1341 * should be reasonably synchronized. 1342 */ 1343 private final long barrierTime; 1344 1345 public BarrierState(VLSN lastLocalCBVLSN, long barrierTime) { 1346 super(); 1347 this.lastLocalCBVLSN = lastLocalCBVLSN; 1348 this.barrierTime = barrierTime; 1349 } 1350 1351 @Override 1352 public int hashCode() { 1353 final int prime = 31; 1354 int result = 1; 1355 result = prime * result + 1356 (lastLocalCBVLSN == null ? 0 : lastLocalCBVLSN.hashCode()); 1357 result = prime * result + 1358 (int) (barrierTime ^ (barrierTime >>> 32)); 1359 return result; 1360 } 1361 1362 @Override 1363 public boolean equals(Object obj) { 1364 if (this == obj) { 1365 return true; 1366 } 1367 if (obj == null) { 1368 return false; 1369 } 1370 if (getClass() != obj.getClass()) { 1371 return false; 1372 } 1373 BarrierState other = (BarrierState) obj; 1374 if (lastLocalCBVLSN == null) { 1375 if (other.lastLocalCBVLSN != null) { 1376 return false; 1377 } 1378 } else if (!lastLocalCBVLSN.equals(other.lastLocalCBVLSN)) { 1379 return false; 1380 } 1381 if (barrierTime != other.barrierTime) { 1382 return false; 1383 } 1384 return true; 1385 } 1386 1387 public VLSN getLastCBVLSN() { 1388 return lastLocalCBVLSN; 1389 } 1390 1391 public long getBarrierTime() { 1392 return barrierTime; 1393 } 1394 1395 @Override 1396 public String toString() { 1397 return String.format("LocalCBVLSN:%,d at:%tc", 1398 lastLocalCBVLSN.getSequence(), barrierTime); 1399 } 1400 } 1401 1402 /* 1403 * An internal exception indicating that two nodes have conflicting 1404 * configurations. For example, they both use the same hostname and port. 1405 */ 1406 @SuppressWarnings("serial") 1407 public static class NodeConflictException extends DatabaseException { 1408 public NodeConflictException(String message) { 1409 super(message); 1410 } 1411 } 1412 1413 /** 1414 * Return information to the user, format nicely for ease of reading. 1415 */ 1416 @Override 1417 public String toString() { 1418 StringBuilder sb = new StringBuilder(); 1419 sb.append("Group info [").append(groupName).append("] "); 1420 sb.append(getUUID()). 1421 append("\n Format version: ").append(getFormatVersion()). 1422 append("\n Change version: ").append(getChangeVersion()). 1423 append("\n Max persist rep node ID: ").append(getNodeIdSequence()). 1424 append("\n Min JE version: ").append(minJEVersion). 1425 append("\n"); 1426 1427 synchronized (nodesById) { 1428 for (final RepNodeImpl node : nodesById.values()) { 1429 sb.append(" ").append(node); 1430 } 1431 } 1432 return sb.toString(); 1433 } 1434 } 1435