Add the ability to report known connected clients 73/85373/8
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 24 Oct 2019 11:54:38 +0000 (13:54 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 25 Oct 2019 11:08:44 +0000 (13:08 +0200)
In order to allow from-scratch recovery, we need the ability to
query all existing shards to provide the identifiers of clients
they know about. This patch adds the models to support such a query
as well as implementing it in cluster-admin.

JIRA: CONTROLLER-1626
Change-Id: Id69aeb9021c8111dad10930620c9eaacfccd0d94
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
12 files changed:
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java
opendaylight/md-sal/cds-access-api/src/main/yang/odl-controller-cds-types.yang [new file with mode: 0644]
opendaylight/md-sal/sal-cluster-admin-api/pom.xml
opendaylight/md-sal/sal-cluster-admin-api/src/main/yang/cluster-admin.yang
opendaylight/md-sal/sal-cluster-admin-impl/src/main/java/org/opendaylight/controller/cluster/datastore/admin/ClusterAdminRpcService.java
opendaylight/md-sal/sal-cluster-admin-impl/src/main/java/org/opendaylight/controller/cluster/datastore/admin/ShardIdentifier.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/FrontendMetadata.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClients.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClientsReply.java [new file with mode: 0644]

index e97626b7ebcb607d1780795d1c70fd4da41cc411..c317ac31b6985710e4e25477656880033d47a2b6 100644 (file)
@@ -17,8 +17,11 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.cds.types.rev191024.ClientGeneration;
 import org.opendaylight.yangtools.concepts.WritableIdentifier;
 import org.opendaylight.yangtools.concepts.WritableObjects;
+import org.opendaylight.yangtools.yang.common.Uint64;
 
 /**
  * A cluster-wide unique identifier of a frontend instance. This identifier discerns between individual incarnations
@@ -63,7 +66,8 @@ public final class ClientIdentifier implements WritableIdentifier {
     }
 
     private static final long serialVersionUID = 1L;
-    private final FrontendIdentifier frontendId;
+
+    private final @NonNull FrontendIdentifier frontendId;
     private final long generation;
 
     ClientIdentifier(final FrontendIdentifier frontendId, final long generation) {
@@ -71,12 +75,12 @@ public final class ClientIdentifier implements WritableIdentifier {
         this.generation = generation;
     }
 
-    public static ClientIdentifier create(final FrontendIdentifier frontendId,
+    public static @NonNull ClientIdentifier create(final FrontendIdentifier frontendId,
             final long generation) {
         return new ClientIdentifier(frontendId, generation);
     }
 
-    public static ClientIdentifier readFrom(final DataInput in) throws IOException {
+    public static @NonNull ClientIdentifier readFrom(final DataInput in) throws IOException {
         final FrontendIdentifier frontendId = FrontendIdentifier.readFrom(in);
         return new ClientIdentifier(frontendId, WritableObjects.readLong(in));
     }
@@ -87,7 +91,7 @@ public final class ClientIdentifier implements WritableIdentifier {
         WritableObjects.writeLong(out, generation);
     }
 
-    public FrontendIdentifier getFrontendId() {
+    public @NonNull FrontendIdentifier getFrontendId() {
         return frontendId;
     }
 
@@ -95,6 +99,10 @@ public final class ClientIdentifier implements WritableIdentifier {
         return generation;
     }
 
+    public @NonNull ClientGeneration getYangGeneration() {
+        return new ClientGeneration(Uint64.fromLongBits(generation));
+    }
+
     @Override
     public int hashCode() {
         return frontendId.hashCode() * 31 + Long.hashCode(generation);
index b6a7b4b260e4bb5ce1cb94c0302e25c6df53dc3a..2a2a5b2b30af2d26bd4c39e75844401611d32129 100644 (file)
@@ -23,6 +23,7 @@ import java.io.ObjectInput;
 import java.io.ObjectOutput;
 import java.nio.charset.StandardCharsets;
 import java.util.regex.Pattern;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.Identifier;
 import org.opendaylight.yangtools.concepts.WritableIdentifier;
 
@@ -71,7 +72,8 @@ public final class FrontendType implements Comparable<FrontendType>, WritableIde
     private static final String SIMPLE_STRING_REGEX = "^[a-zA-Z0-9-_.*+:=,!~';]+$";
     private static final Pattern SIMPLE_STRING_PATTERN = Pattern.compile(SIMPLE_STRING_REGEX);
     private static final long serialVersionUID = 1L;
-    private final String name;
+
+    private final @NonNull String name;
 
     @SuppressFBWarnings(value = "VO_VOLATILE_REFERENCE_TO_ARRAY",
             justification = "The array elements are non-volatile but we don't access them.")
@@ -96,14 +98,14 @@ public final class FrontendType implements Comparable<FrontendType>, WritableIde
      * @return A {@link FrontendType} instance
      * @throws IllegalArgumentException if the string is null, empty or contains invalid characters
      */
-    public static FrontendType forName(final String name) {
+    public static @NonNull FrontendType forName(final String name) {
         checkArgument(!Strings.isNullOrEmpty(name));
         checkArgument(SIMPLE_STRING_PATTERN.matcher(name).matches(),
             "Supplied name %s does not patch pattern %s", name, SIMPLE_STRING_REGEX);
         return new FrontendType(name);
     }
 
-    public static FrontendType readFrom(final DataInput in) throws IOException {
+    public static @NonNull FrontendType readFrom(final DataInput in) throws IOException {
         final byte[] serialized = new byte[in.readInt()];
         in.readFully(serialized);
         return new FrontendType(new String(serialized, StandardCharsets.UTF_8));
@@ -116,10 +118,16 @@ public final class FrontendType implements Comparable<FrontendType>, WritableIde
         out.write(local);
     }
 
-    public String getName() {
+    public @NonNull String getName() {
         return name;
     }
 
+    public org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.cds.types.rev191024
+        . @NonNull FrontendType toYang() {
+        return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.cds.types.rev191024
+                .FrontendType(name);
+    }
+
     @Override
     public int hashCode() {
         return name.hashCode();
index 70df622b9bde76d4685d1cd805e8def05cd6db07..8a1123da520f9ff74d7a504d6839e0ec0d07be20 100644 (file)
@@ -103,6 +103,12 @@ public final class MemberName implements Comparable<MemberName>, WritableIdentif
         return name;
     }
 
+    public org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.cds.types.rev191024
+        .MemberName toYang() {
+        return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.cds.types.rev191024
+                .MemberName(name);
+    }
+
     @Override
     public int hashCode() {
         return name.hashCode();
diff --git a/opendaylight/md-sal/cds-access-api/src/main/yang/odl-controller-cds-types.yang b/opendaylight/md-sal/cds-access-api/src/main/yang/odl-controller-cds-types.yang
new file mode 100644 (file)
index 0000000..58f3b9e
--- /dev/null
@@ -0,0 +1,53 @@
+module odl-controller-cds-types {
+  yang-version 1;
+  namespace "urn:opendaylight:params:xml:ns:yang:controller:cds:types";
+  prefix "cdst";
+
+  organization "The OpenDaylight Project";
+
+  description "Common type definitions related to clustered data store.";
+
+  revision 2019-10-24 {
+    description "Initial revision.";
+  }
+
+  typedef member-name {
+    description "Cluster member name.";
+    type string;
+  }
+
+  typedef frontend-type {
+    description "Frontend type.";
+    type string {
+      pattern "";
+    }
+  }
+
+  typedef client-generation {
+    description "Client generation.";
+    type uint64;
+  }
+
+  grouping frontend-identifier {
+    description "Identifier of a particular frontend.";
+    leaf member {
+      type member-name;
+      mandatory true;
+    }
+
+    leaf type {
+      type frontend-type;
+      mandatory true;
+    }
+  }
+
+  grouping client-identifier {
+    description "Identifier of a particular client.";
+    uses frontend-identifier;
+    leaf generation {
+      type client-generation;
+      mandatory true;
+    }
+  }
+}
+
index d456c0ee62a2a4f5d5ebaa8edaa3366f6036a76b..b4e0e5776b0475072921e0fa97c8a6b032fa7216 100644 (file)
   <version>1.11.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>cds-access-api</artifactId>
+    </dependency>
+  </dependencies>
+
 </project>
index bb0eed38b28e59f45a1f5fe3985d4fddca4b82fc..109845c86f6420b03ca1040487391d979f4053b8 100644 (file)
@@ -10,6 +10,8 @@ module cluster-admin {
         description "Initial revision.";
     }
 
+    import odl-controller-cds-types { prefix cds; }
+
     typedef data-store-type {
         type enumeration {
             enum config {
@@ -299,4 +301,22 @@ module cluster-admin {
 
         description "Returns the current role for the requested module shard.";
     }
+
+    rpc get-known-clients-for-all-shards {
+        description "Request all shards to report their known frontend clients. This is useful for determining what
+                     generation should a resurrected member node should use.";
+
+        output {
+            uses shard-result-output {
+                augment shard-result {
+                    list known-clients {
+                        when "../succeeded = true";
+
+                        uses cds:client-identifier;
+                        key "member type";
+                    }
+                }
+            }
+        }
+    }
 }
index 7e5c798e82dc10e749576df4123fd5f3e45bb86b..3e14a376453cb62b4f0e81154ee4267e162e093e 100644 (file)
@@ -15,6 +15,7 @@ import akka.pattern.Patterns;
 import akka.util.Timeout;
 import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -30,8 +31,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.SerializationUtils;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.controller.cluster.access.concepts.MemberName;
@@ -40,6 +43,8 @@ import org.opendaylight.controller.cluster.datastore.messages.AddPrefixShardRepl
 import org.opendaylight.controller.cluster.datastore.messages.AddShardReplica;
 import org.opendaylight.controller.cluster.datastore.messages.ChangeShardMembersVotingStatus;
 import org.opendaylight.controller.cluster.datastore.messages.FlipShardMembersVotingStatus;
+import org.opendaylight.controller.cluster.datastore.messages.GetKnownClients;
+import org.opendaylight.controller.cluster.datastore.messages.GetKnownClientsReply;
 import org.opendaylight.controller.cluster.datastore.messages.GetShardRole;
 import org.opendaylight.controller.cluster.datastore.messages.GetShardRoleReply;
 import org.opendaylight.controller.cluster.datastore.messages.MakeLeaderLocal;
@@ -75,6 +80,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.FlipMemberVotingStatesForAllShardsInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.FlipMemberVotingStatesForAllShardsOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.FlipMemberVotingStatesForAllShardsOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetKnownClientsForAllShardsInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetKnownClientsForAllShardsOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetKnownClientsForAllShardsOutputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetPrefixShardRoleInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetPrefixShardRoleOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.GetPrefixShardRoleOutputBuilder;
@@ -96,6 +104,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.RemoveShardReplicaInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.RemoveShardReplicaOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.RemoveShardReplicaOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.get.known.clients._for.all.shards.output.ShardResult1;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.get.known.clients._for.all.shards.output.ShardResult1Builder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.get.known.clients._for.all.shards.output.shard.result.KnownClientsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.locate.shard.output.member.node.LeaderActorRefBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.locate.shard.output.member.node.LocalBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.member.voting.states.input.MemberVotingState;
@@ -295,7 +306,7 @@ public class ClusterAdminRpcService implements ClusterAdminService {
         }, actorUtils.getClientDispatcher());
 
         final SettableFuture<RpcResult<MakeLeaderLocalOutput>> future = SettableFuture.create();
-        makeLeaderLocalAsk.future().onComplete(new OnComplete<Object>() {
+        makeLeaderLocalAsk.future().onComplete(new OnComplete<>() {
             @Override
             public void onComplete(final Throwable failure, final Object success) {
                 if (failure != null) {
@@ -626,6 +637,52 @@ public class ClusterAdminRpcService implements ClusterAdminService {
         return returnFuture;
     }
 
+
+    @Override
+    public ListenableFuture<RpcResult<GetKnownClientsForAllShardsOutput>> getKnownClientsForAllShards(
+            final GetKnownClientsForAllShardsInput input) {
+        final ImmutableMap<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> allShardReplies =
+                getAllShardLeadersClients();
+        return Futures.whenAllComplete(allShardReplies.values()).call(() -> processReplies(allShardReplies),
+            MoreExecutors.directExecutor());
+    }
+
+    private static RpcResult<GetKnownClientsForAllShardsOutput> processReplies(
+            final ImmutableMap<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> allShardReplies) {
+        final List<ShardResult> result = new ArrayList<>(allShardReplies.size());
+        for (Entry<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> entry : allShardReplies.entrySet()) {
+            final ListenableFuture<GetKnownClientsReply> future = entry.getValue();
+            final ShardResultBuilder builder = new ShardResultBuilder()
+                    .setDataStoreType(entry.getKey().getDataStoreType())
+                    .setShardName(entry.getKey().getShardName());
+
+            final GetKnownClientsReply reply;
+            try {
+                reply = Futures.getDone(future);
+            } catch (ExecutionException e) {
+                LOG.debug("Shard {} failed to answer", entry.getKey(), e);
+                result.add(builder.setSucceeded(Boolean.FALSE).setErrorMessage(e.getCause().getMessage()).build());
+                continue;
+            }
+
+            result.add(builder
+                .setSucceeded(Boolean.TRUE)
+                .addAugmentation(ShardResult1.class, new ShardResult1Builder()
+                    .setKnownClients(reply.getClients().stream()
+                        .map(client -> new KnownClientsBuilder()
+                            .setMember(client.getFrontendId().getMemberName().toYang())
+                            .setType(client.getFrontendId().getClientType().toYang())
+                            .setGeneration(client.getYangGeneration())
+                            .build())
+                        .collect(Collectors.toList()))
+                    .build())
+                .build());
+        }
+
+        return RpcResultBuilder.success(new GetKnownClientsForAllShardsOutputBuilder().setShardResult(result).build())
+                .build();
+    }
+
     private static ChangeShardMembersVotingStatus toChangeShardMembersVotingStatus(final String shardName,
             final List<MemberVotingState> memberVotingStatus) {
         Map<String, Boolean> serverVotingStatusMap = new HashMap<>();
@@ -756,6 +813,38 @@ public class ClusterAdminRpcService implements ClusterAdminService {
         return returnFuture;
     }
 
+    private ImmutableMap<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> getAllShardLeadersClients() {
+        final ImmutableMap.Builder<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> builder =
+                ImmutableMap.builder();
+
+        addAllShardsClients(builder, DataStoreType.Config, configDataStore.getActorUtils());
+        addAllShardsClients(builder, DataStoreType.Operational, operDataStore.getActorUtils());
+
+        return builder.build();
+    }
+
+    private static void addAllShardsClients(
+            final ImmutableMap.Builder<ShardIdentifier, ListenableFuture<GetKnownClientsReply>> builder,
+            final DataStoreType type, final ActorUtils utils) {
+        for (String shardName : utils.getConfiguration().getAllShardNames()) {
+            final SettableFuture<GetKnownClientsReply> future = SettableFuture.create();
+            builder.put(new ShardIdentifier(type, shardName), future);
+
+            utils.findPrimaryShardAsync(shardName).flatMap(
+                info -> Patterns.ask(info.getPrimaryShardActor(), GetKnownClients.INSTANCE, SHARD_MGR_TIMEOUT),
+                utils.getClientDispatcher()).onComplete(new OnComplete<>() {
+                    @Override
+                    public void onComplete(final Throwable failure, final Object success) {
+                        if (failure == null) {
+                            future.set((GetKnownClientsReply) success);
+                        } else {
+                            future.setException(failure);
+                        }
+                    }
+                }, utils.getClientDispatcher());
+        }
+    }
+
     private static <T> ListenableFuture<RpcResult<T>> newFailedRpcResultFuture(final String message) {
         return ClusterAdminRpcService.<T>newFailedRpcResultBuilder(message).buildFuture();
     }
diff --git a/opendaylight/md-sal/sal-cluster-admin-impl/src/main/java/org/opendaylight/controller/cluster/datastore/admin/ShardIdentifier.java b/opendaylight/md-sal/sal-cluster-admin-impl/src/main/java/org/opendaylight/controller/cluster/datastore/admin/ShardIdentifier.java
new file mode 100644 (file)
index 0000000..af3a111
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2015 Brocade Communications 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.admin;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.DataStoreType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.cluster.admin.rev151013.DatastoreShardId;
+import org.opendaylight.yangtools.concepts.Identifier;
+
+final class ShardIdentifier implements Identifier {
+    private static final long serialVersionUID = 1L;
+
+    private final @NonNull String shardName;
+    private final @NonNull DataStoreType type;
+
+    ShardIdentifier(final DataStoreType type, final String shardName) {
+        this.type = requireNonNull(type);
+        this.shardName = requireNonNull(shardName);
+    }
+
+    ShardIdentifier(final DatastoreShardId id) {
+        this(id.getDataStoreType(), id.getShardName());
+    }
+
+    public @NonNull String getShardName() {
+        return shardName;
+    }
+
+    public @NonNull DataStoreType getDataStoreType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        return type.hashCode() * 31 + shardName.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof ShardIdentifier)) {
+            return false;
+        }
+        final ShardIdentifier other = (ShardIdentifier) obj;
+        return type.equals(other.type) && shardName.equals(other.shardName);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this).add("type", type).add("shardName", shardName).toString();
+    }
+}
index 0bf23e776c563c8e42fe4bb8e3cae79f71a784d5..d5ecbf6ea416c6c283f0e06b5537293bbdef99e8 100644 (file)
@@ -11,6 +11,7 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import java.util.HashMap;
 import java.util.Map;
@@ -149,4 +150,10 @@ final class FrontendMetadata extends ShardDataTreeMetadata<FrontendShardDataTree
 
         verify(clients.replace(frontendId, client, new FrontendClientMetadataBuilder.Disabled(shardName, clientId)));
     }
+
+    ImmutableSet<ClientIdentifier> getClients() {
+        return clients.values().stream()
+                .map(FrontendClientMetadataBuilder::getIdentifier)
+                .collect(ImmutableSet.toImmutableSet());
+    }
 }
index 8c671cc7da41abca57071902052852832e81bd4d..5fa9192ac0d4c727a72f2d3af8904b021c46aa80 100644 (file)
@@ -25,6 +25,7 @@ import com.google.common.base.Ticker;
 import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Range;
 import java.io.IOException;
 import java.util.Arrays;
@@ -76,6 +77,8 @@ import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction;
 import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction;
 import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
 import org.opendaylight.controller.cluster.datastore.messages.ForwardedReadyTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.GetKnownClients;
+import org.opendaylight.controller.cluster.datastore.messages.GetKnownClientsReply;
 import org.opendaylight.controller.cluster.datastore.messages.GetShardDataTree;
 import org.opendaylight.controller.cluster.datastore.messages.MakeLeaderLocal;
 import org.opendaylight.controller.cluster.datastore.messages.OnDemandShardState;
@@ -371,6 +374,8 @@ public class Shard extends RaftActor {
                 onMakeLeaderLocal();
             } else if (RESUME_NEXT_PENDING_TRANSACTION.equals(message)) {
                 store.resumeNextPendingTransaction();
+            } else if (GetKnownClients.INSTANCE.equals(message)) {
+                handleGetKnownClients();
             } else if (!responseMessageSlicer.handleMessage(message)) {
                 super.handleNonRaftCommand(message);
             }
@@ -597,6 +602,18 @@ public class Shard extends RaftActor {
         }
     }
 
+    private void handleGetKnownClients() {
+        final ImmutableSet<ClientIdentifier> clients;
+        if (isLeader()) {
+            clients = knownFrontends.values().stream()
+                    .map(LeaderFrontendState::getIdentifier)
+                    .collect(ImmutableSet.toImmutableSet());
+        } else {
+            clients = frontendMetadata.getClients();
+        }
+        sender().tell(new GetKnownClientsReply(clients), self());
+    }
+
     private boolean hasLeader() {
         return getLeaderId() != null;
     }
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClients.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClients.java
new file mode 100644 (file)
index 0000000..f1e7fb7
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.messages;
+
+import java.io.Serializable;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * Request a shard to report the clients it knows about. Shard is required to respond with {@link GetKnownClientsReply}.
+ */
+public final class GetKnownClients implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static final @NonNull GetKnownClients INSTANCE = new GetKnownClients();
+
+    private GetKnownClients() {
+
+    }
+
+    private Object readResolve() {
+        return INSTANCE;
+    }
+}
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClientsReply.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/GetKnownClientsReply.java
new file mode 100644 (file)
index 0000000..2864141
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.messages;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.Serializable;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+
+/**
+ * Reply to {@link GetKnownClients}.
+ */
+public final class GetKnownClientsReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final @NonNull ImmutableSet<ClientIdentifier> clients;
+
+    public GetKnownClientsReply(final ImmutableSet<ClientIdentifier> clients) {
+        this.clients = requireNonNull(clients);
+    }
+
+    public @NonNull ImmutableSet<ClientIdentifier> getClients() {
+        return clients;
+    }
+}