From edd61d79da614388134b0e0a618010c91e9c91bd Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 3 Aug 2016 02:52:20 +0200 Subject: [PATCH 1/1] BUG-5280: add FrontendMetadata 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 Signed-off-by: Vaclav Demcak --- .../FrontendClientMetadataBuilder.java | 81 +++++++++++ .../FrontendHistoryMetadataBuilder.java | 51 +++++++ .../cluster/datastore/FrontendMetadata.java | 91 ++++++++++++ .../controller/cluster/datastore/Shard.java | 2 + .../cluster/datastore/ShardDataTree.java | 2 +- .../datastore/ShardDataTreeMetadata.java | 5 +- .../persisted/FrontendClientMetadata.java | 97 +++++++++++++ .../persisted/FrontendHistoryMetadata.java | 69 +++++++++ ...FrontendShardDataTreeSnapshotMetadata.java | 86 +++++++++++ ...tendShardDataTreeSnapshotMetadataTest.java | 134 ++++++++++++++++++ 10 files changed, 616 insertions(+), 2 deletions(-) create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendClientMetadataBuilder.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendHistoryMetadataBuilder.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendMetadata.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendClientMetadata.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendHistoryMetadata.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadata.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadataTest.java diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendClientMetadataBuilder.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendClientMetadataBuilder.java new file mode 100644 index 0000000000..0dab830584 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendClientMetadataBuilder.java @@ -0,0 +1,81 @@ +/* + * 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, Identifiable { + private final Map currentHistories = new HashMap<>(); + private final RangeSet 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; + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendHistoryMetadataBuilder.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendHistoryMetadataBuilder.java new file mode 100644 index 0000000000..d8d7bdda21 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendHistoryMetadataBuilder.java @@ -0,0 +1,51 @@ +/* + * 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, Identifiable { + 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; + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendMetadata.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendMetadata.java new file mode 100644 index 0000000000..164e7a9aa8 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendMetadata.java @@ -0,0 +1,91 @@ +/* + * 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 { + private static final Logger LOG = LoggerFactory.getLogger(FrontendMetadata.class); + + private final Map clients = new HashMap<>(); + + @Override + Class 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); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java index fbd7c89b6f..1b12462a4a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java @@ -129,6 +129,8 @@ public class Shard extends RaftActor { 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); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java index f1d37872fd..d43602acea 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java @@ -346,7 +346,7 @@ public class ShardDataTree extends ShardDataTreeTransactionParent { private void allMetadataCommittedTransaction(final TransactionIdentifier txId) { for (ShardDataTreeMetadata m : metadata) { - m.transactionCommitted(txId); + m.onTransactionCommitted(txId); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeMetadata.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeMetadata.java index 8a62a2bae0..1fce1445b9 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeMetadata.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeMetadata.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.datastore; 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; @@ -29,5 +30,7 @@ abstract class ShardDataTreeMetadata> 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); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendClientMetadata.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendClientMetadata.java new file mode 100644 index 0000000000..91c81edea4 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendClientMetadata.java @@ -0,0 +1,97 @@ +/* + * 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, WritableObject { + private final Collection currentHistories; + private final RangeSet purgedHistories; + private final ClientIdentifier identifier; + + public FrontendClientMetadata(final ClientIdentifier identifier, final RangeSet purgedHistories, + final Collection currentHistories) { + this.identifier = Preconditions.checkNotNull(identifier); + this.purgedHistories = ImmutableRangeSet.copyOf(purgedHistories); + this.currentHistories = ImmutableList.copyOf(currentHistories); + } + + public Collection getCurrentHistories() { + return currentHistories; + } + + public RangeSet getPurgedHistories() { + return purgedHistories; + } + + @Override + public ClientIdentifier getIdentifier() { + return identifier; + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + identifier.writeTo(out); + + final Set> ranges = purgedHistories.asRanges(); + out.writeInt(ranges.size()); + for (final Range 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 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 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(); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendHistoryMetadata.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendHistoryMetadata.java new file mode 100644 index 0000000000..2a52eccb94 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendHistoryMetadata.java @@ -0,0 +1,69 @@ +/* + * 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(); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadata.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadata.java new file mode 100644 index 0000000000..55d9757371 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadata.java @@ -0,0 +1,86 @@ +/* + * 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 { + + private static final class Proxy implements Externalizable { + private static final long serialVersionUID = 1L; + + private List 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 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 clients; + + public FrontendShardDataTreeSnapshotMetadata(final Collection clients) { + this.clients = ImmutableList.copyOf(clients); + } + + public List getClients() { + return clients; + } + + @Override + protected Externalizable externalizableProxy() { + return new Proxy(this); + } + + @Override + public Class getType() { + return FrontendShardDataTreeSnapshotMetadata.class; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(FrontendShardDataTreeSnapshotMetadata.class).add("clients", clients) + .toString(); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadataTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadataTest.java new file mode 100644 index 0000000000..f99cf6fc41 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/persisted/FrontendShardDataTreeSnapshotMetadataTest.java @@ -0,0 +1,134 @@ +/* + * 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 origClientList = origSnapshot.getClients(); + final List copyClientList = copySnapshot.getClients(); + + assertTrue(origClientList.size() == copyClientList.size()); + + final Map origIdent = new HashMap<>(); + final Map 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. emptyList()); + } + + private static FrontendShardDataTreeSnapshotMetadata createMetadataSnapshot(final int size) { + final List 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 purgedHistories = TreeRangeSet.create(); + purgedHistories.add(Range.closed(UnsignedLong.ZERO, UnsignedLong.ONE)); + + final Collection currentHistories = Collections + .singleton(new FrontendHistoryMetadata(i, i, i, true)); + + return new FrontendClientMetadata(clientIdentifier, purgedHistories, currentHistories); + } + + private static final 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 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(); + } + } +} -- 2.36.6