This patch adds the frontend tracking abilities for followers.
It also defines the corresponding ShardDataTreeSnapshotMetadata
for use with persistence.
Change-Id: I7e2c6755c3389dcb5284f17a9c6076fb9e7ac95e
Signed-off-by: Robert Varga <rovarga@cisco.com>
Signed-off-by: Vaclav Demcak <vdemcak@cisco.com>
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.common.primitives.UnsignedLong;
+import java.util.HashMap;
+import java.util.Map;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
+import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
+import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.concepts.Identifiable;
+
+final class FrontendClientMetadataBuilder implements Builder<FrontendClientMetadata>, Identifiable<ClientIdentifier> {
+ private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
+ private final RangeSet<UnsignedLong> purgedHistories;
+ private final ClientIdentifier identifier;
+
+ FrontendClientMetadataBuilder(final ClientIdentifier identifier) {
+ this.identifier = Preconditions.checkNotNull(identifier);
+ purgedHistories = TreeRangeSet.create();
+ }
+
+ FrontendClientMetadataBuilder(final FrontendClientMetadata meta) {
+ this.identifier = Preconditions.checkNotNull(meta.getIdentifier());
+ purgedHistories = TreeRangeSet.create(meta.getPurgedHistories());
+
+ for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
+ final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(identifier, h);
+ currentHistories.put(b.getIdentifier(), b);
+ }
+ }
+
+ @Override
+ public FrontendClientMetadata build() {
+ return new FrontendClientMetadata(identifier, purgedHistories,
+ Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
+ }
+
+ @Override
+ public ClientIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ void onHistoryClosed(final LocalHistoryIdentifier historyId) {
+ ensureHistory(historyId).onHistoryClosed();
+ }
+
+ void onHistoryPurged(final LocalHistoryIdentifier historyId) {
+ currentHistories.remove(historyId);
+ // XXX: do we need to account for cookies?
+ purgedHistories.add(Range.singleton(UnsignedLong.fromLongBits(historyId.getHistoryId())));
+ }
+
+ void onTransactionCommitted(final TransactionIdentifier txId) {
+ ensureHistory(txId.getHistoryId()).onTransactionCommitted(txId);
+ }
+
+ private FrontendHistoryMetadataBuilder ensureHistory(final LocalHistoryIdentifier historyId) {
+ final FrontendHistoryMetadataBuilder existing = currentHistories.get(historyId);
+ if (existing != null) {
+ return existing;
+ }
+
+ final FrontendHistoryMetadataBuilder ret = new FrontendHistoryMetadataBuilder(historyId);
+ currentHistories.put(historyId, ret);
+ return ret;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
+import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.concepts.Identifiable;
+
+final class FrontendHistoryMetadataBuilder implements Builder<FrontendHistoryMetadata>, Identifiable<LocalHistoryIdentifier> {
+ private final LocalHistoryIdentifier identifier;
+
+ private long nextTransaction;
+ private boolean closed;
+
+ FrontendHistoryMetadataBuilder(final LocalHistoryIdentifier identifier) {
+ this.identifier = Preconditions.checkNotNull(identifier);
+ }
+
+ FrontendHistoryMetadataBuilder(final ClientIdentifier clientId, final FrontendHistoryMetadata meta) {
+ identifier = new LocalHistoryIdentifier(clientId, meta.getHistoryId(), meta.getCookie());
+ nextTransaction = meta.getNextTransaction();
+ closed = meta.isClosed();
+ }
+
+ @Override
+ public LocalHistoryIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public FrontendHistoryMetadata build() {
+ return new FrontendHistoryMetadata(identifier.getHistoryId(), identifier.getCookie(), nextTransaction, closed);
+ }
+
+ void onHistoryClosed() {
+ closed = true;
+ }
+
+ void onTransactionCommitted(final TransactionIdentifier txId) {
+ nextTransaction = txId.getTransactionId() + 1;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore;
+
+import com.google.common.collect.Collections2;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
+import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
+import org.opendaylight.controller.cluster.datastore.persisted.FrontendShardDataTreeSnapshotMetadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Frontend state as observed by a shard follower. This class is responsible for maintaining metadata state
+ * so that this can be used to seed {@link LeaderFrontendState} with proper state so that the frontend/backend
+ * conversation can continue where it left off.
+ *
+ * @author Robert Varga
+ */
+@NotThreadSafe
+final class FrontendMetadata extends ShardDataTreeMetadata<FrontendShardDataTreeSnapshotMetadata> {
+ private static final Logger LOG = LoggerFactory.getLogger(FrontendMetadata.class);
+
+ private final Map<FrontendIdentifier, FrontendClientMetadataBuilder> clients = new HashMap<>();
+
+ @Override
+ Class<FrontendShardDataTreeSnapshotMetadata> getSupportedType() {
+ return FrontendShardDataTreeSnapshotMetadata.class;
+ }
+
+ @Override
+ void reset() {
+ clients.clear();
+ }
+
+ @Override
+ void doApplySnapshot(final FrontendShardDataTreeSnapshotMetadata snapshot) {
+ clients.clear();
+
+ for (FrontendClientMetadata m : snapshot.getClients()) {
+ clients.put(m.getIdentifier().getFrontendId(), new FrontendClientMetadataBuilder(m));
+ }
+ }
+
+ @Override
+ FrontendShardDataTreeSnapshotMetadata toStapshot() {
+ return new FrontendShardDataTreeSnapshotMetadata(Collections2.transform(clients.values(),
+ FrontendClientMetadataBuilder::build));
+ }
+
+ private FrontendClientMetadataBuilder ensureClient(final ClientIdentifier id) {
+ final FrontendClientMetadataBuilder existing = clients.get(id.getFrontendId());
+ if (existing != null && id.equals(existing.getIdentifier())) {
+ return existing;
+ }
+
+ final FrontendClientMetadataBuilder client = new FrontendClientMetadataBuilder(id);
+ final FrontendClientMetadataBuilder previous = clients.put(id.getFrontendId(), client);
+ if (previous != null) {
+ LOG.debug("Replaced client {} with {}", previous, client);
+ } else {
+ LOG.debug("Added client {}", client);
+ }
+ return client;
+ }
+
+ @Override
+ void onHistoryClosed(final LocalHistoryIdentifier historyId) {
+ ensureClient(historyId.getClientId()).onHistoryClosed(historyId);
+ }
+
+ @Override
+ void onHistoryPurged(final LocalHistoryIdentifier historyId) {
+ ensureClient(historyId.getClientId()).onHistoryPurged(historyId);
+ }
+
+ @Override
+ void onTransactionCommitted(final TransactionIdentifier txId) {
+ ensureClient(txId.getHistoryId().getClientId()).onTransactionCommitted(txId);
+ }
+}
private final ShardTransactionMessageRetrySupport messageRetrySupport;
+ private final FrontendMetadata frontendMetadata = new FrontendMetadata();
+
protected Shard(final AbstractBuilder<?, ?> builder) {
super(builder.getId().toString(), builder.getPeerAddresses(),
Optional.of(builder.getDatastoreContext().getShardRaftConfig()), DataStoreVersions.CURRENT_VERSION);
private void allMetadataCommittedTransaction(final TransactionIdentifier txId) {
for (ShardDataTreeMetadata<?> m : metadata) {
- m.transactionCommitted(txId);
+ m.onTransactionCommitted(txId);
}
}
import com.google.common.base.Verify;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
import org.opendaylight.controller.cluster.datastore.persisted.ShardDataTreeSnapshotMetadata;
abstract @Nullable T toStapshot();
// Lifecycle events
- abstract void transactionCommitted(TransactionIdentifier txId);
+ abstract void onTransactionCommitted(TransactionIdentifier txId);
+ abstract void onHistoryClosed(LocalHistoryIdentifier historyId);
+ abstract void onHistoryPurged(LocalHistoryIdentifier historyId);
}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore.persisted;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableRangeSet;
+import com.google.common.collect.ImmutableRangeSet.Builder;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.primitives.UnsignedLong;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.concepts.WritableObject;
+import org.opendaylight.yangtools.concepts.WritableObjects;
+
+public final class FrontendClientMetadata implements Identifiable<ClientIdentifier>, WritableObject {
+ private final Collection<FrontendHistoryMetadata> currentHistories;
+ private final RangeSet<UnsignedLong> purgedHistories;
+ private final ClientIdentifier identifier;
+
+ public FrontendClientMetadata(final ClientIdentifier identifier, final RangeSet<UnsignedLong> purgedHistories,
+ final Collection<FrontendHistoryMetadata> currentHistories) {
+ this.identifier = Preconditions.checkNotNull(identifier);
+ this.purgedHistories = ImmutableRangeSet.copyOf(purgedHistories);
+ this.currentHistories = ImmutableList.copyOf(currentHistories);
+ }
+
+ public Collection<FrontendHistoryMetadata> getCurrentHistories() {
+ return currentHistories;
+ }
+
+ public RangeSet<UnsignedLong> getPurgedHistories() {
+ return purgedHistories;
+ }
+
+ @Override
+ public ClientIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public void writeTo(final DataOutput out) throws IOException {
+ identifier.writeTo(out);
+
+ final Set<Range<UnsignedLong>> ranges = purgedHistories.asRanges();
+ out.writeInt(ranges.size());
+ for (final Range<UnsignedLong> r : ranges) {
+ WritableObjects.writeLongs(out, r.lowerEndpoint().longValue(), r.upperEndpoint().longValue());
+ }
+
+ out.writeInt(currentHistories.size());
+ for (final FrontendHistoryMetadata h : currentHistories) {
+ h.writeTo(out);
+ }
+ }
+
+ public static FrontendClientMetadata readFrom(final DataInput in) throws IOException, ClassNotFoundException {
+ final ClientIdentifier id = ClientIdentifier.readFrom(in);
+
+ final int purgedSize = in.readInt();
+ final Builder<UnsignedLong> b = ImmutableRangeSet.builder();
+ for (int i = 0; i < purgedSize; ++i) {
+ final byte header = WritableObjects.readLongHeader(in);
+ final UnsignedLong lower = UnsignedLong.fromLongBits(WritableObjects.readFirstLong(in, header));
+ final UnsignedLong upper = UnsignedLong.fromLongBits(WritableObjects.readSecondLong(in, header));
+
+ b.add(Range.closed(lower, upper));
+ }
+
+ final int currentSize = in.readInt();
+ final Collection<FrontendHistoryMetadata> currentHistories = new ArrayList<>(currentSize);
+ for (int i = 0; i < currentSize; ++i) {
+ currentHistories.add(FrontendHistoryMetadata.readFrom(in));
+ }
+
+ return new FrontendClientMetadata(id, b.build(), currentHistories);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(FrontendClientMetadata.class).add("Identifier", identifier)
+ .add("CurrentHistory", currentHistories).add("Range", purgedHistories).toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore.persisted;
+
+import com.google.common.base.MoreObjects;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import org.opendaylight.yangtools.concepts.WritableObject;
+import org.opendaylight.yangtools.concepts.WritableObjects;
+
+public final class FrontendHistoryMetadata implements WritableObject {
+ private final long historyId;
+ private final long cookie;
+ private final long nextTransaction;
+ private final boolean closed;
+
+ public FrontendHistoryMetadata(final long historyId, final long cookie, final long nextTransaction,
+ final boolean closed) {
+ this.historyId = historyId;
+ this.cookie = cookie;
+ this.nextTransaction = nextTransaction;
+ this.closed = closed;
+ }
+
+ public long getHistoryId() {
+ return historyId;
+ }
+
+ public long getCookie() {
+ return cookie;
+ }
+
+ public long getNextTransaction() {
+ return nextTransaction;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ @Override
+ public void writeTo(final DataOutput out) throws IOException {
+ WritableObjects.writeLongs(out, historyId, cookie);
+ WritableObjects.writeLong(out, nextTransaction);
+ out.writeBoolean(closed);
+ }
+
+ public static FrontendHistoryMetadata readFrom(final DataInput in) throws IOException {
+ final byte header = WritableObjects.readLongHeader(in);
+ final long historyId = WritableObjects.readFirstLong(in, header);
+ final long cookie = WritableObjects.readSecondLong(in, header);
+ final long nextTransaction = WritableObjects.readLong(in);
+ final boolean closed = in.readBoolean();
+
+ return new FrontendHistoryMetadata(historyId, cookie, nextTransaction, closed);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(FrontendHistoryMetadata.class).add("historiId", historyId)
+ .add("cookie", cookie).add("nextTransaction", nextTransaction).add("closed", closed).toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore.persisted;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public final class FrontendShardDataTreeSnapshotMetadata extends
+ ShardDataTreeSnapshotMetadata<FrontendShardDataTreeSnapshotMetadata> {
+
+ private static final class Proxy implements Externalizable {
+ private static final long serialVersionUID = 1L;
+
+ private List<FrontendClientMetadata> clients;
+
+ public Proxy() {
+ // For Externalizable
+ }
+
+ Proxy(final FrontendShardDataTreeSnapshotMetadata metadata) {
+ this.clients = metadata.getClients();
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput out) throws IOException {
+ out.writeInt(clients.size());
+ for (final FrontendClientMetadata c : clients) {
+ c.writeTo(out);
+ }
+ }
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+ final int size = in.readInt();
+ final List<FrontendClientMetadata> readedClients = new ArrayList<>(size);
+ for (int i = 0; i < size ; ++i) {
+ readedClients.add(FrontendClientMetadata.readFrom(in));
+ }
+ this.clients = ImmutableList.copyOf(readedClients);
+ }
+
+ private Object readResolve() {
+ return new FrontendShardDataTreeSnapshotMetadata(clients);
+ }
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<FrontendClientMetadata> clients;
+
+ public FrontendShardDataTreeSnapshotMetadata(final Collection<FrontendClientMetadata> clients) {
+ this.clients = ImmutableList.copyOf(clients);
+ }
+
+ public List<FrontendClientMetadata> getClients() {
+ return clients;
+ }
+
+ @Override
+ protected Externalizable externalizableProxy() {
+ return new Proxy(this);
+ }
+
+ @Override
+ public Class<FrontendShardDataTreeSnapshotMetadata> getType() {
+ return FrontendShardDataTreeSnapshotMetadata.class;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(FrontendShardDataTreeSnapshotMetadata.class).add("clients", clients)
+ .toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.cluster.datastore.persisted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+import com.google.common.primitives.UnsignedLong;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.FrontendType;
+import org.opendaylight.controller.cluster.access.concepts.MemberName;
+
+public class FrontendShardDataTreeSnapshotMetadataTest {
+
+ @Test(expected = NullPointerException.class)
+ public final void testCreateMetadataSnapshotNullInput() {
+ new FrontendShardDataTreeSnapshotMetadata(null);
+ }
+
+ @Test
+ public final void testCreateMetadataSnapshotEmptyInput() throws Exception {
+ final FrontendShardDataTreeSnapshotMetadata emptyOrigSnapshot = createEmptyMetadataSnapshot();
+ final FrontendShardDataTreeSnapshotMetadata emptyCopySnapshot = copy(emptyOrigSnapshot);
+ testMetadataSnapshotEqual(emptyOrigSnapshot, emptyCopySnapshot);
+ }
+
+ @Test
+ public final void testSerializeMetadataSnapshotWithOneClient() throws Exception {
+ final FrontendShardDataTreeSnapshotMetadata origSnapshot = createMetadataSnapshot(1);
+ final FrontendShardDataTreeSnapshotMetadata copySnapshot = copy(origSnapshot);
+ testMetadataSnapshotEqual(origSnapshot, copySnapshot);
+ }
+
+ @Test
+ public final void testSerializeMetadataSnapshotWithMoreClients() throws Exception {
+ final FrontendShardDataTreeSnapshotMetadata origSnapshot = createMetadataSnapshot(5);
+ final FrontendShardDataTreeSnapshotMetadata copySnapshot = copy(origSnapshot);
+ testMetadataSnapshotEqual(origSnapshot, copySnapshot);
+ }
+
+ private static void testMetadataSnapshotEqual(final FrontendShardDataTreeSnapshotMetadata origSnapshot,
+ final FrontendShardDataTreeSnapshotMetadata copySnapshot) {
+
+ final List<FrontendClientMetadata> origClientList = origSnapshot.getClients();
+ final List<FrontendClientMetadata> copyClientList = copySnapshot.getClients();
+
+ assertTrue(origClientList.size() == copyClientList.size());
+
+ final Map<ClientIdentifier, FrontendClientMetadata> origIdent = new HashMap<>();
+ final Map<ClientIdentifier, FrontendClientMetadata> copyIdent = new HashMap<>();
+ origClientList.forEach(client -> origIdent.put(client.getIdentifier(), client));
+ origClientList.forEach(client -> copyIdent.put(client.getIdentifier(), client));
+
+ assertTrue(origIdent.keySet().containsAll(copyIdent.keySet()));
+ assertTrue(copyIdent.keySet().containsAll(origIdent.keySet()));
+
+ origIdent.values().forEach(client -> {
+ final FrontendClientMetadata copyClient = copyIdent.get(client.getIdentifier());
+ testObject(client.getIdentifier(), copyClient.getIdentifier());
+ assertTrue(client.getPurgedHistories().equals(copyClient.getPurgedHistories()));
+ assertTrue(client.getCurrentHistories().equals(copyClient.getCurrentHistories()));
+ });
+ }
+
+ private static FrontendShardDataTreeSnapshotMetadata createEmptyMetadataSnapshot() {
+ return new FrontendShardDataTreeSnapshotMetadata(Collections.<FrontendClientMetadata> emptyList());
+ }
+
+ private static FrontendShardDataTreeSnapshotMetadata createMetadataSnapshot(final int size) {
+ final List<FrontendClientMetadata> clients = new ArrayList<>();
+ for (long i = 0; i < size; i++) {
+ clients.add(createFrontedClientMetadata(i));
+ }
+ return new FrontendShardDataTreeSnapshotMetadata(clients);
+ }
+
+ private static FrontendClientMetadata createFrontedClientMetadata(final long i) {
+ final String index = String.valueOf(i);
+ final String indexName = "test_" + index;
+ final FrontendIdentifier frontendIdentifier = FrontendIdentifier.create(MemberName.forName(indexName),
+ FrontendType.forName(index));
+ final ClientIdentifier clientIdentifier = ClientIdentifier.create(frontendIdentifier, i);
+
+ final RangeSet<UnsignedLong> purgedHistories = TreeRangeSet.create();
+ purgedHistories.add(Range.closed(UnsignedLong.ZERO, UnsignedLong.ONE));
+
+ final Collection<FrontendHistoryMetadata> currentHistories = Collections
+ .singleton(new FrontendHistoryMetadata(i, i, i, true));
+
+ return new FrontendClientMetadata(clientIdentifier, purgedHistories, currentHistories);
+ }
+
+ private static final <T> void testObject(final T object, final T equalObject) {
+ assertEquals(object.hashCode(), equalObject.hashCode());
+ assertTrue(object.equals(object));
+ assertTrue(object.equals(equalObject));
+ assertFalse(object.equals(null));
+ assertFalse(object.equals("dummy"));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T copy(final T o) throws IOException, ClassNotFoundException {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+ oos.writeObject(o);
+ }
+
+ try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
+ return (T) ois.readObject();
+ }
+ }
+}