BUG-5280: use MemberName instead of String
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / shardmanager / ShardManager.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.cluster.datastore.shardmanager;
10
11 import static akka.pattern.Patterns.ask;
12 import akka.actor.ActorRef;
13 import akka.actor.Address;
14 import akka.actor.Cancellable;
15 import akka.actor.OneForOneStrategy;
16 import akka.actor.PoisonPill;
17 import akka.actor.Status;
18 import akka.actor.SupervisorStrategy;
19 import akka.actor.SupervisorStrategy.Directive;
20 import akka.cluster.ClusterEvent;
21 import akka.cluster.Member;
22 import akka.dispatch.Futures;
23 import akka.dispatch.OnComplete;
24 import akka.japi.Function;
25 import akka.pattern.Patterns;
26 import akka.persistence.RecoveryCompleted;
27 import akka.persistence.SaveSnapshotFailure;
28 import akka.persistence.SaveSnapshotSuccess;
29 import akka.persistence.SnapshotOffer;
30 import akka.persistence.SnapshotSelectionCriteria;
31 import akka.util.Timeout;
32 import com.google.common.annotations.VisibleForTesting;
33 import com.google.common.base.Preconditions;
34 import java.io.ByteArrayInputStream;
35 import java.io.ObjectInputStream;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 import java.util.function.Supplier;
48 import javax.annotation.Nonnull;
49 import javax.annotation.Nullable;
50 import org.apache.commons.lang3.SerializationUtils;
51 import org.opendaylight.controller.cluster.access.concepts.MemberName;
52 import org.opendaylight.controller.cluster.common.actor.AbstractUntypedPersistentActorWithMetering;
53 import org.opendaylight.controller.cluster.datastore.ClusterWrapper;
54 import org.opendaylight.controller.cluster.datastore.DatastoreContext;
55 import org.opendaylight.controller.cluster.datastore.DatastoreContextFactory;
56 import org.opendaylight.controller.cluster.datastore.Shard;
57 import org.opendaylight.controller.cluster.datastore.config.Configuration;
58 import org.opendaylight.controller.cluster.datastore.config.ModuleShardConfiguration;
59 import org.opendaylight.controller.cluster.datastore.exceptions.AlreadyExistsException;
60 import org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException;
61 import org.opendaylight.controller.cluster.datastore.exceptions.NotInitializedException;
62 import org.opendaylight.controller.cluster.datastore.exceptions.PrimaryNotFoundException;
63 import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier;
64 import org.opendaylight.controller.cluster.datastore.messages.ActorInitialized;
65 import org.opendaylight.controller.cluster.datastore.messages.AddShardReplica;
66 import org.opendaylight.controller.cluster.datastore.messages.CreateShard;
67 import org.opendaylight.controller.cluster.datastore.messages.DatastoreSnapshot;
68 import org.opendaylight.controller.cluster.datastore.messages.FindLocalShard;
69 import org.opendaylight.controller.cluster.datastore.messages.FindPrimary;
70 import org.opendaylight.controller.cluster.datastore.messages.LocalPrimaryShardFound;
71 import org.opendaylight.controller.cluster.datastore.messages.LocalShardFound;
72 import org.opendaylight.controller.cluster.datastore.messages.LocalShardNotFound;
73 import org.opendaylight.controller.cluster.datastore.messages.RemoteFindPrimary;
74 import org.opendaylight.controller.cluster.datastore.messages.RemotePrimaryShardFound;
75 import org.opendaylight.controller.cluster.datastore.messages.RemoveShardReplica;
76 import org.opendaylight.controller.cluster.datastore.messages.ShardLeaderStateChanged;
77 import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext;
78 import org.opendaylight.controller.cluster.datastore.utils.Dispatchers;
79 import org.opendaylight.controller.cluster.datastore.utils.PrimaryShardInfoFutureCache;
80 import org.opendaylight.controller.cluster.notifications.RegisterRoleChangeListener;
81 import org.opendaylight.controller.cluster.notifications.RoleChangeNotification;
82 import org.opendaylight.controller.cluster.raft.base.messages.FollowerInitialSyncUpStatus;
83 import org.opendaylight.controller.cluster.raft.base.messages.SwitchBehavior;
84 import org.opendaylight.controller.cluster.raft.client.messages.GetSnapshot;
85 import org.opendaylight.controller.cluster.raft.client.messages.Shutdown;
86 import org.opendaylight.controller.cluster.raft.messages.AddServer;
87 import org.opendaylight.controller.cluster.raft.messages.AddServerReply;
88 import org.opendaylight.controller.cluster.raft.messages.RemoveServer;
89 import org.opendaylight.controller.cluster.raft.messages.RemoveServerReply;
90 import org.opendaylight.controller.cluster.raft.messages.ServerChangeStatus;
91 import org.opendaylight.controller.cluster.raft.messages.ServerRemoved;
92 import org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy;
93 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
94 import org.slf4j.Logger;
95 import org.slf4j.LoggerFactory;
96 import scala.concurrent.ExecutionContext;
97 import scala.concurrent.Future;
98 import scala.concurrent.duration.Duration;
99 import scala.concurrent.duration.FiniteDuration;
100
101 /**
102  * The ShardManager has the following jobs,
103  * <ul>
104  * <li> Create all the local shard replicas that belong on this cluster member
105  * <li> Find the address of the local shard
106  * <li> Find the primary replica for any given shard
107  * <li> Monitor the cluster members and store their addresses
108  * <ul>
109  */
110 class ShardManager extends AbstractUntypedPersistentActorWithMetering {
111
112     private static final Logger LOG = LoggerFactory.getLogger(ShardManager.class);
113
114     // Stores a mapping between a shard name and it's corresponding information
115     // Shard names look like inventory, topology etc and are as specified in
116     // configuration
117     private final Map<String, ShardInformation> localShards = new HashMap<>();
118
119     // The type of a ShardManager reflects the type of the datastore itself
120     // A data store could be of type config/operational
121     private final String type;
122
123     private final ClusterWrapper cluster;
124
125     private final Configuration configuration;
126
127     private final String shardDispatcherPath;
128
129     private final ShardManagerInfo mBean;
130
131     private DatastoreContextFactory datastoreContextFactory;
132
133     private final CountDownLatch waitTillReadyCountdownLatch;
134
135     private final PrimaryShardInfoFutureCache primaryShardInfoCache;
136
137     private final ShardPeerAddressResolver peerAddressResolver;
138
139     private SchemaContext schemaContext;
140
141     private DatastoreSnapshot restoreFromSnapshot;
142
143     private ShardManagerSnapshot currentSnapshot;
144
145     private final Set<String> shardReplicaOperationsInProgress = new HashSet<>();
146
147     private final String persistenceId;
148
149     ShardManager(AbstractShardManagerCreator<?> builder) {
150         this.cluster = builder.getCluster();
151         this.configuration = builder.getConfiguration();
152         this.datastoreContextFactory = builder.getDatastoreContextFactory();
153         this.type = datastoreContextFactory.getBaseDatastoreContext().getDataStoreName();
154         this.shardDispatcherPath =
155                 new Dispatchers(context().system().dispatchers()).getDispatcherPath(Dispatchers.DispatcherType.Shard);
156         this.waitTillReadyCountdownLatch = builder.getWaitTillReadyCountdownLatch();
157         this.primaryShardInfoCache = builder.getPrimaryShardInfoCache();
158         this.restoreFromSnapshot = builder.getRestoreFromSnapshot();
159
160         String possiblePersistenceId = datastoreContextFactory.getBaseDatastoreContext().getShardManagerPersistenceId();
161         persistenceId = possiblePersistenceId != null ? possiblePersistenceId : "shard-manager-" + type;
162
163         peerAddressResolver = new ShardPeerAddressResolver(type, cluster.getCurrentMemberName());
164
165         // Subscribe this actor to cluster member events
166         cluster.subscribeToMemberEvents(getSelf());
167
168         mBean = new ShardManagerInfo(getSelf(), cluster.getCurrentMemberName(), "shard-manager-" + this.type,
169                 datastoreContextFactory.getBaseDatastoreContext().getDataStoreMXBeanType());
170         mBean.registerMBean();
171     }
172
173     @Override
174     public void postStop() {
175         LOG.info("Stopping ShardManager {}", persistenceId());
176
177         mBean.unregisterMBean();
178     }
179
180     @Override
181     public void handleCommand(Object message) throws Exception {
182         if (message  instanceof FindPrimary) {
183             findPrimary((FindPrimary)message);
184         } else if(message instanceof FindLocalShard){
185             findLocalShard((FindLocalShard) message);
186         } else if (message instanceof UpdateSchemaContext) {
187             updateSchemaContext(message);
188         } else if(message instanceof ActorInitialized) {
189             onActorInitialized(message);
190         } else if (message instanceof ClusterEvent.MemberUp){
191             memberUp((ClusterEvent.MemberUp) message);
192         } else if (message instanceof ClusterEvent.MemberExited){
193             memberExited((ClusterEvent.MemberExited) message);
194         } else if(message instanceof ClusterEvent.MemberRemoved) {
195             memberRemoved((ClusterEvent.MemberRemoved) message);
196         } else if(message instanceof ClusterEvent.UnreachableMember) {
197             memberUnreachable((ClusterEvent.UnreachableMember)message);
198         } else if(message instanceof ClusterEvent.ReachableMember) {
199             memberReachable((ClusterEvent.ReachableMember) message);
200         } else if(message instanceof DatastoreContextFactory) {
201             onDatastoreContextFactory((DatastoreContextFactory)message);
202         } else if(message instanceof RoleChangeNotification) {
203             onRoleChangeNotification((RoleChangeNotification) message);
204         } else if(message instanceof FollowerInitialSyncUpStatus){
205             onFollowerInitialSyncStatus((FollowerInitialSyncUpStatus) message);
206         } else if(message instanceof ShardNotInitializedTimeout) {
207             onShardNotInitializedTimeout((ShardNotInitializedTimeout)message);
208         } else if(message instanceof ShardLeaderStateChanged) {
209             onLeaderStateChanged((ShardLeaderStateChanged) message);
210         } else if(message instanceof SwitchShardBehavior){
211             onSwitchShardBehavior((SwitchShardBehavior) message);
212         } else if(message instanceof CreateShard) {
213             onCreateShard((CreateShard)message);
214         } else if(message instanceof AddShardReplica){
215             onAddShardReplica((AddShardReplica)message);
216         } else if(message instanceof ForwardedAddServerReply) {
217             ForwardedAddServerReply msg = (ForwardedAddServerReply)message;
218             onAddServerReply(msg.shardInfo, msg.addServerReply, getSender(), msg.leaderPath,
219                     msg.removeShardOnFailure);
220         } else if(message instanceof ForwardedAddServerFailure) {
221             ForwardedAddServerFailure msg = (ForwardedAddServerFailure)message;
222             onAddServerFailure(msg.shardName, msg.failureMessage, msg.failure, getSender(), msg.removeShardOnFailure);
223         } else if(message instanceof PrimaryShardFoundForContext) {
224             PrimaryShardFoundForContext primaryShardFoundContext = (PrimaryShardFoundForContext)message;
225             onPrimaryShardFoundContext(primaryShardFoundContext);
226         } else if(message instanceof RemoveShardReplica) {
227             onRemoveShardReplica((RemoveShardReplica) message);
228         } else if(message instanceof WrappedShardResponse){
229             onWrappedShardResponse((WrappedShardResponse) message);
230         } else if(message instanceof GetSnapshot) {
231             onGetSnapshot();
232         } else if(message instanceof ServerRemoved){
233             onShardReplicaRemoved((ServerRemoved) message);
234         } else if(message instanceof SaveSnapshotSuccess) {
235             onSaveSnapshotSuccess((SaveSnapshotSuccess)message);
236         } else if(message instanceof SaveSnapshotFailure) {
237             LOG.error("{}: SaveSnapshotFailure received for saving snapshot of shards",
238                     persistenceId(), ((SaveSnapshotFailure) message).cause());
239         } else if(message instanceof Shutdown) {
240             onShutDown();
241         } else if (message instanceof GetLocalShardIds) {
242             onGetLocalShardIds();
243         } else {
244             unknownMessage(message);
245         }
246     }
247
248     private void onShutDown() {
249         List<Future<Boolean>> stopFutures = new ArrayList<>(localShards.size());
250         for (ShardInformation info : localShards.values()) {
251             if (info.getActor() != null) {
252                 LOG.debug("{}: Issuing gracefulStop to shard {}", persistenceId(), info.getShardId());
253
254                 FiniteDuration duration = info.getDatastoreContext().getShardRaftConfig().getElectionTimeOutInterval().$times(2);
255                 stopFutures.add(Patterns.gracefulStop(info.getActor(), duration, Shutdown.INSTANCE));
256             }
257         }
258
259         LOG.info("Shutting down ShardManager {} - waiting on {} shards", persistenceId(), stopFutures.size());
260
261         ExecutionContext dispatcher = new Dispatchers(context().system().dispatchers()).getDispatcher(Dispatchers.DispatcherType.Client);
262         Future<Iterable<Boolean>> combinedFutures = Futures.sequence(stopFutures, dispatcher);
263
264         combinedFutures.onComplete(new OnComplete<Iterable<Boolean>>() {
265             @Override
266             public void onComplete(Throwable failure, Iterable<Boolean> results) {
267                 LOG.debug("{}: All shards shutdown - sending PoisonPill to self", persistenceId());
268
269                 self().tell(PoisonPill.getInstance(), self());
270
271                 if(failure != null) {
272                     LOG.warn("{}: An error occurred attempting to shut down the shards", persistenceId(), failure);
273                 } else {
274                     int nfailed = 0;
275                     for(Boolean r: results) {
276                         if(!r) {
277                             nfailed++;
278                         }
279                     }
280
281                     if(nfailed > 0) {
282                         LOG.warn("{}: {} shards did not shut down gracefully", persistenceId(), nfailed);
283                     }
284                 }
285             }
286         }, dispatcher);
287     }
288
289     private void onWrappedShardResponse(WrappedShardResponse message) {
290         if (message.getResponse() instanceof RemoveServerReply) {
291             onRemoveServerReply(getSender(), message.getShardId(), (RemoveServerReply) message.getResponse(),
292                     message.getLeaderPath());
293         }
294     }
295
296     private void onRemoveServerReply(ActorRef originalSender, ShardIdentifier shardId, RemoveServerReply replyMsg,
297             String leaderPath) {
298         shardReplicaOperationsInProgress.remove(shardId);
299
300         LOG.debug ("{}: Received {} for shard {}", persistenceId(), replyMsg, shardId.getShardName());
301
302         if (replyMsg.getStatus() == ServerChangeStatus.OK) {
303             LOG.debug ("{}: Leader shard successfully removed the replica shard {}", persistenceId(),
304                     shardId.getShardName());
305             originalSender.tell(new Status.Success(null), getSelf());
306         } else {
307             LOG.warn ("{}: Leader failed to remove shard replica {} with status {}",
308                     persistenceId(), shardId, replyMsg.getStatus());
309
310             Exception failure = getServerChangeException(RemoveServer.class, replyMsg.getStatus(), leaderPath, shardId);
311             originalSender.tell(new Status.Failure(failure), getSelf());
312         }
313     }
314
315     private void onPrimaryShardFoundContext(PrimaryShardFoundForContext primaryShardFoundContext) {
316         if(primaryShardFoundContext.getContextMessage() instanceof AddShardReplica) {
317             addShard(primaryShardFoundContext.getShardName(), primaryShardFoundContext.getRemotePrimaryShardFound(),
318                     getSender());
319         } else if(primaryShardFoundContext.getContextMessage() instanceof RemoveShardReplica){
320             removeShardReplica((RemoveShardReplica) primaryShardFoundContext.getContextMessage(),
321                     primaryShardFoundContext.getShardName(), primaryShardFoundContext.getPrimaryPath(), getSender());
322         }
323     }
324
325     private void removeShardReplica(RemoveShardReplica contextMessage, final String shardName, final String primaryPath,
326             final ActorRef sender) {
327         if(isShardReplicaOperationInProgress(shardName, sender)) {
328             return;
329         }
330
331         shardReplicaOperationsInProgress.add(shardName);
332
333         final ShardIdentifier shardId = getShardIdentifier(contextMessage.getMemberName(), shardName);
334
335         final DatastoreContext datastoreContext = newShardDatastoreContextBuilder(shardName).build();
336
337         //inform ShardLeader to remove this shard as a replica by sending an RemoveServer message
338         LOG.debug ("{}: Sending RemoveServer message to peer {} for shard {}", persistenceId(),
339                 primaryPath, shardId);
340
341         Timeout removeServerTimeout = new Timeout(datastoreContext.getShardLeaderElectionTimeout().
342                 duration());
343         Future<Object> futureObj = ask(getContext().actorSelection(primaryPath),
344                 new RemoveServer(shardId.toString()), removeServerTimeout);
345
346         futureObj.onComplete(new OnComplete<Object>() {
347             @Override
348             public void onComplete(Throwable failure, Object response) {
349                 if (failure != null) {
350                     String msg = String.format("RemoveServer request to leader %s for shard %s failed",
351                             primaryPath, shardName);
352
353                     LOG.debug ("{}: {}", persistenceId(), msg, failure);
354
355                     // FAILURE
356                     sender.tell(new Status.Failure(new RuntimeException(msg, failure)), self());
357                 } else {
358                     // SUCCESS
359                     self().tell(new WrappedShardResponse(shardId, response, primaryPath), sender);
360                 }
361             }
362         }, new Dispatchers(context().system().dispatchers()).getDispatcher(Dispatchers.DispatcherType.Client));
363     }
364
365     private void onShardReplicaRemoved(ServerRemoved message) {
366         final ShardIdentifier shardId = new ShardIdentifier.Builder().fromShardIdString(message.getServerId()).build();
367         final ShardInformation shardInformation = localShards.remove(shardId.getShardName());
368         if(shardInformation == null) {
369             LOG.debug("{} : Shard replica {} is not present in list", persistenceId(), shardId.toString());
370             return;
371         } else if(shardInformation.getActor() != null) {
372             LOG.debug("{} : Sending Shutdown to Shard actor {}", persistenceId(), shardInformation.getActor());
373             shardInformation.getActor().tell(Shutdown.INSTANCE, self());
374         }
375         LOG.debug("{} : Local Shard replica for shard {} has been removed", persistenceId(), shardId.getShardName());
376         persistShardList();
377     }
378
379     private void onGetSnapshot() {
380         LOG.debug("{}: onGetSnapshot", persistenceId());
381
382         List<String> notInitialized = null;
383         for(ShardInformation shardInfo: localShards.values()) {
384             if(!shardInfo.isShardInitialized()) {
385                 if(notInitialized == null) {
386                     notInitialized = new ArrayList<>();
387                 }
388
389                 notInitialized.add(shardInfo.getShardName());
390             }
391         }
392
393         if(notInitialized != null) {
394             getSender().tell(new Status.Failure(new IllegalStateException(String.format(
395                     "%d shard(s) %s are not initialized", notInitialized.size(), notInitialized))), getSelf());
396             return;
397         }
398
399         byte[] shardManagerSnapshot = null;
400         if(currentSnapshot != null) {
401             shardManagerSnapshot = SerializationUtils.serialize(currentSnapshot);
402         }
403
404         ActorRef replyActor = getContext().actorOf(ShardManagerGetSnapshotReplyActor.props(
405                 new ArrayList<>(localShards.keySet()), type, shardManagerSnapshot , getSender(), persistenceId(),
406                 datastoreContextFactory.getBaseDatastoreContext().getShardInitializationTimeout().duration()));
407
408         for(ShardInformation shardInfo: localShards.values()) {
409             shardInfo.getActor().tell(GetSnapshot.INSTANCE, replyActor);
410         }
411     }
412
413     private void onCreateShard(CreateShard createShard) {
414         LOG.debug("{}: onCreateShard: {}", persistenceId(), createShard);
415
416         Object reply;
417         try {
418             String shardName = createShard.getModuleShardConfig().getShardName();
419             if(localShards.containsKey(shardName)) {
420                 LOG.debug("{}: Shard {} already exists", persistenceId(), shardName);
421                 reply = new Status.Success(String.format("Shard with name %s already exists", shardName));
422             } else {
423                 doCreateShard(createShard);
424                 reply = new Status.Success(null);
425             }
426         } catch (Exception e) {
427             LOG.error("{}: onCreateShard failed", persistenceId(), e);
428             reply = new Status.Failure(e);
429         }
430
431         if(getSender() != null && !getContext().system().deadLetters().equals(getSender())) {
432             getSender().tell(reply, getSelf());
433         }
434     }
435
436     private void doCreateShard(CreateShard createShard) {
437         ModuleShardConfiguration moduleShardConfig = createShard.getModuleShardConfig();
438         String shardName = moduleShardConfig.getShardName();
439
440         configuration.addModuleShardConfiguration(moduleShardConfig);
441
442         DatastoreContext shardDatastoreContext = createShard.getDatastoreContext();
443         if(shardDatastoreContext == null) {
444             shardDatastoreContext = newShardDatastoreContext(shardName);
445         } else {
446             shardDatastoreContext = DatastoreContext.newBuilderFrom(shardDatastoreContext).shardPeerAddressResolver(
447                     peerAddressResolver).build();
448         }
449
450         ShardIdentifier shardId = getShardIdentifier(cluster.getCurrentMemberName(), shardName);
451
452         boolean shardWasInRecoveredSnapshot = currentSnapshot != null &&
453                 currentSnapshot.getShardList().contains(shardName);
454
455         Map<String, String> peerAddresses;
456         boolean isActiveMember;
457         if(shardWasInRecoveredSnapshot || configuration.getMembersFromShardName(shardName).
458                 contains(cluster.getCurrentMemberName())) {
459             peerAddresses = getPeerAddresses(shardName);
460             isActiveMember = true;
461         } else {
462             // The local member is not in the static shard member configuration and the shard did not
463             // previously exist (ie !shardWasInRecoveredSnapshot). In this case we'll create
464             // the shard with no peers and with elections disabled so it stays as follower. A
465             // subsequent AddServer request will be needed to make it an active member.
466             isActiveMember = false;
467             peerAddresses = Collections.emptyMap();
468             shardDatastoreContext = DatastoreContext.newBuilderFrom(shardDatastoreContext).
469                     customRaftPolicyImplementation(DisableElectionsRaftPolicy.class.getName()).build();
470         }
471
472         LOG.debug("{} doCreateShard: shardId: {}, memberNames: {}, peerAddresses: {}, isActiveMember: {}",
473                 persistenceId(), shardId, moduleShardConfig.getShardMemberNames(), peerAddresses,
474                 isActiveMember);
475
476         ShardInformation info = new ShardInformation(shardName, shardId, peerAddresses,
477                 shardDatastoreContext, createShard.getShardBuilder(), peerAddressResolver);
478         info.setActiveMember(isActiveMember);
479         localShards.put(info.getShardName(), info);
480
481         if(schemaContext != null) {
482             info.setActor(newShardActor(schemaContext, info));
483         }
484     }
485
486     private DatastoreContext.Builder newShardDatastoreContextBuilder(String shardName) {
487         return DatastoreContext.newBuilderFrom(datastoreContextFactory.getShardDatastoreContext(shardName)).
488                 shardPeerAddressResolver(peerAddressResolver);
489     }
490
491     private DatastoreContext newShardDatastoreContext(String shardName) {
492         return newShardDatastoreContextBuilder(shardName).build();
493     }
494
495     private void checkReady(){
496         if (isReadyWithLeaderId()) {
497             LOG.info("{}: All Shards are ready - data store {} is ready, available count is {}",
498                     persistenceId(), type, waitTillReadyCountdownLatch.getCount());
499
500             waitTillReadyCountdownLatch.countDown();
501         }
502     }
503
504     private void onLeaderStateChanged(ShardLeaderStateChanged leaderStateChanged) {
505         LOG.info("{}: Received LeaderStateChanged message: {}", persistenceId(), leaderStateChanged);
506
507         ShardInformation shardInformation = findShardInformation(leaderStateChanged.getMemberId());
508         if(shardInformation != null) {
509             shardInformation.setLocalDataTree(leaderStateChanged.getLocalShardDataTree());
510             shardInformation.setLeaderVersion(leaderStateChanged.getLeaderPayloadVersion());
511             if(shardInformation.setLeaderId(leaderStateChanged.getLeaderId())) {
512                 primaryShardInfoCache.remove(shardInformation.getShardName());
513             }
514
515             checkReady();
516         } else {
517             LOG.debug("No shard found with member Id {}", leaderStateChanged.getMemberId());
518         }
519     }
520
521     private void onShardNotInitializedTimeout(ShardNotInitializedTimeout message) {
522         ShardInformation shardInfo = message.getShardInfo();
523
524         LOG.debug("{}: Received ShardNotInitializedTimeout message for shard {}", persistenceId(),
525                 shardInfo.getShardName());
526
527         shardInfo.removeOnShardInitialized(message.getOnShardInitialized());
528
529         if(!shardInfo.isShardInitialized()) {
530             LOG.debug("{}: Returning NotInitializedException for shard {}", persistenceId(), shardInfo.getShardName());
531             message.getSender().tell(createNotInitializedException(shardInfo.getShardId()), getSelf());
532         } else {
533             LOG.debug("{}: Returning NoShardLeaderException for shard {}", persistenceId(), shardInfo.getShardName());
534             message.getSender().tell(createNoShardLeaderException(shardInfo.getShardId()), getSelf());
535         }
536     }
537
538     private void onFollowerInitialSyncStatus(FollowerInitialSyncUpStatus status) {
539         LOG.info("{} Received follower initial sync status for {} status sync done {}", persistenceId(),
540                 status.getName(), status.isInitialSyncDone());
541
542         ShardInformation shardInformation = findShardInformation(status.getName());
543
544         if(shardInformation != null) {
545             shardInformation.setFollowerSyncStatus(status.isInitialSyncDone());
546
547             mBean.setSyncStatus(isInSync());
548         }
549
550     }
551
552     private void onRoleChangeNotification(RoleChangeNotification roleChanged) {
553         LOG.info("{}: Received role changed for {} from {} to {}", persistenceId(), roleChanged.getMemberId(),
554                 roleChanged.getOldRole(), roleChanged.getNewRole());
555
556         ShardInformation shardInformation = findShardInformation(roleChanged.getMemberId());
557         if(shardInformation != null) {
558             shardInformation.setRole(roleChanged.getNewRole());
559             checkReady();
560             mBean.setSyncStatus(isInSync());
561         }
562     }
563
564
565     private ShardInformation findShardInformation(String memberId) {
566         for(ShardInformation info : localShards.values()){
567             if(info.getShardId().toString().equals(memberId)){
568                 return info;
569             }
570         }
571
572         return null;
573     }
574
575     private boolean isReadyWithLeaderId() {
576         boolean isReady = true;
577         for (ShardInformation info : localShards.values()) {
578             if(!info.isShardReadyWithLeaderId()){
579                 isReady = false;
580                 break;
581             }
582         }
583         return isReady;
584     }
585
586     private boolean isInSync(){
587         for (ShardInformation info : localShards.values()) {
588             if(!info.isInSync()){
589                 return false;
590             }
591         }
592         return true;
593     }
594
595     private void onActorInitialized(Object message) {
596         final ActorRef sender = getSender();
597
598         if (sender == null) {
599             return; //why is a non-actor sending this message? Just ignore.
600         }
601
602         String actorName = sender.path().name();
603         //find shard name from actor name; actor name is stringified shardId
604         ShardIdentifier shardId = ShardIdentifier.builder().fromShardIdString(actorName).build();
605
606         if (shardId.getShardName() == null) {
607             return;
608         }
609
610         markShardAsInitialized(shardId.getShardName());
611     }
612
613     private void markShardAsInitialized(String shardName) {
614         LOG.debug("{}: Initializing shard [{}]", persistenceId(), shardName);
615
616         ShardInformation shardInformation = localShards.get(shardName);
617         if (shardInformation != null) {
618             shardInformation.setActorInitialized();
619
620             shardInformation.getActor().tell(new RegisterRoleChangeListener(), self());
621         }
622     }
623
624     @Override
625     protected void handleRecover(Object message) throws Exception {
626         if (message instanceof RecoveryCompleted) {
627             onRecoveryCompleted();
628         } else if (message instanceof SnapshotOffer) {
629             applyShardManagerSnapshot((ShardManagerSnapshot)((SnapshotOffer) message).snapshot());
630         }
631     }
632
633     private void onRecoveryCompleted() {
634         LOG.info("Recovery complete : {}", persistenceId());
635
636         // We no longer persist SchemaContext modules so delete all the prior messages from the akka
637         // journal on upgrade from Helium.
638         deleteMessages(lastSequenceNr());
639
640         if(currentSnapshot == null && restoreFromSnapshot != null &&
641                 restoreFromSnapshot.getShardManagerSnapshot() != null) {
642             try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
643                     restoreFromSnapshot.getShardManagerSnapshot()))) {
644                 ShardManagerSnapshot snapshot = (ShardManagerSnapshot) ois.readObject();
645
646                 LOG.debug("{}: Deserialized restored ShardManagerSnapshot: {}", persistenceId(), snapshot);
647
648                 applyShardManagerSnapshot(snapshot);
649             } catch(Exception e) {
650                 LOG.error("{}: Error deserializing restored ShardManagerSnapshot", persistenceId(), e);
651             }
652         }
653
654         createLocalShards();
655     }
656
657     private void findLocalShard(FindLocalShard message) {
658         final ShardInformation shardInformation = localShards.get(message.getShardName());
659
660         if(shardInformation == null){
661             getSender().tell(new LocalShardNotFound(message.getShardName()), getSelf());
662             return;
663         }
664
665         sendResponse(shardInformation, message.isWaitUntilInitialized(), false, () -> new LocalShardFound(shardInformation.getActor()));
666     }
667
668     private void sendResponse(ShardInformation shardInformation, boolean doWait,
669             boolean wantShardReady, final Supplier<Object> messageSupplier) {
670         if (!shardInformation.isShardInitialized() || (wantShardReady && !shardInformation.isShardReadyWithLeaderId())) {
671             if(doWait) {
672                 final ActorRef sender = getSender();
673                 final ActorRef self = self();
674
675                 Runnable replyRunnable = () -> sender.tell(messageSupplier.get(), self);
676
677                 OnShardInitialized onShardInitialized = wantShardReady ? new OnShardReady(replyRunnable) :
678                     new OnShardInitialized(replyRunnable);
679
680                 shardInformation.addOnShardInitialized(onShardInitialized);
681
682                 FiniteDuration timeout = shardInformation.getDatastoreContext().getShardInitializationTimeout().duration();
683                 if(shardInformation.isShardInitialized()) {
684                     // If the shard is already initialized then we'll wait enough time for the shard to
685                     // elect a leader, ie 2 times the election timeout.
686                     timeout = FiniteDuration.create(shardInformation.getDatastoreContext().getShardRaftConfig()
687                             .getElectionTimeOutInterval().toMillis() * 2, TimeUnit.MILLISECONDS);
688                 }
689
690                 LOG.debug("{}: Scheduling {} ms timer to wait for shard {}", persistenceId(), timeout.toMillis(),
691                         shardInformation.getShardName());
692
693                 Cancellable timeoutSchedule = getContext().system().scheduler().scheduleOnce(
694                         timeout, getSelf(),
695                         new ShardNotInitializedTimeout(shardInformation, onShardInitialized, sender),
696                         getContext().dispatcher(), getSelf());
697
698                 onShardInitialized.setTimeoutSchedule(timeoutSchedule);
699
700             } else if (!shardInformation.isShardInitialized()) {
701                 LOG.debug("{}: Returning NotInitializedException for shard {}", persistenceId(),
702                         shardInformation.getShardName());
703                 getSender().tell(createNotInitializedException(shardInformation.getShardId()), getSelf());
704             } else {
705                 LOG.debug("{}: Returning NoShardLeaderException for shard {}", persistenceId(),
706                         shardInformation.getShardName());
707                 getSender().tell(createNoShardLeaderException(shardInformation.getShardId()), getSelf());
708             }
709
710             return;
711         }
712
713         getSender().tell(messageSupplier.get(), getSelf());
714     }
715
716     private static NoShardLeaderException createNoShardLeaderException(ShardIdentifier shardId) {
717         return new NoShardLeaderException(null, shardId.toString());
718     }
719
720     private static NotInitializedException createNotInitializedException(ShardIdentifier shardId) {
721         return new NotInitializedException(String.format(
722                 "Found primary shard %s but it's not initialized yet. Please try again later", shardId));
723     }
724
725     @VisibleForTesting
726     static MemberName memberToName(final Member member) {
727         return MemberName.forName(member.roles().iterator().next());
728     }
729
730     private void memberRemoved(ClusterEvent.MemberRemoved message) {
731         MemberName memberName = memberToName(message.member());
732
733         LOG.debug("{}: Received MemberRemoved: memberName: {}, address: {}", persistenceId(), memberName,
734                 message.member().address());
735
736         peerAddressResolver.removePeerAddress(memberName);
737
738         for(ShardInformation info : localShards.values()){
739             info.peerDown(memberName, getShardIdentifier(memberName, info.getShardName()).toString(), getSelf());
740         }
741     }
742
743     private void memberExited(ClusterEvent.MemberExited message) {
744         MemberName memberName = memberToName(message.member());
745
746         LOG.debug("{}: Received MemberExited: memberName: {}, address: {}", persistenceId(), memberName,
747                 message.member().address());
748
749         peerAddressResolver.removePeerAddress(memberName);
750
751         for(ShardInformation info : localShards.values()){
752             info.peerDown(memberName, getShardIdentifier(memberName, info.getShardName()).toString(), getSelf());
753         }
754     }
755
756     private void memberUp(ClusterEvent.MemberUp message) {
757         MemberName memberName = memberToName(message.member());
758
759         LOG.debug("{}: Received MemberUp: memberName: {}, address: {}", persistenceId(), memberName,
760                 message.member().address());
761
762         addPeerAddress(memberName, message.member().address());
763
764         checkReady();
765     }
766
767     private void addPeerAddress(MemberName memberName, Address address) {
768         peerAddressResolver.addPeerAddress(memberName, address);
769
770         for(ShardInformation info : localShards.values()){
771             String shardName = info.getShardName();
772             String peerId = getShardIdentifier(memberName, shardName).toString();
773             info.updatePeerAddress(peerId, peerAddressResolver.getShardActorAddress(shardName, memberName), getSelf());
774
775             info.peerUp(memberName, peerId, getSelf());
776         }
777     }
778
779     private void memberReachable(ClusterEvent.ReachableMember message) {
780         MemberName memberName = memberToName(message.member());
781         LOG.debug("Received ReachableMember: memberName {}, address: {}", memberName, message.member().address());
782
783         addPeerAddress(memberName, message.member().address());
784
785         markMemberAvailable(memberName);
786     }
787
788     private void memberUnreachable(ClusterEvent.UnreachableMember message) {
789         MemberName memberName = memberToName(message.member());
790         LOG.debug("Received UnreachableMember: memberName {}, address: {}", memberName, message.member().address());
791
792         markMemberUnavailable(memberName);
793     }
794
795     private void markMemberUnavailable(final MemberName memberName) {
796         final String memberStr = memberName.getName();
797         for (ShardInformation info : localShards.values()) {
798             String leaderId = info.getLeaderId();
799             // XXX: why are we using String#contains() here?
800             if (leaderId != null && leaderId.contains(memberStr)) {
801                 LOG.debug("Marking Leader {} as unavailable.", leaderId);
802                 info.setLeaderAvailable(false);
803
804                 primaryShardInfoCache.remove(info.getShardName());
805             }
806
807             info.peerDown(memberName, getShardIdentifier(memberName, info.getShardName()).toString(), getSelf());
808         }
809     }
810
811     private void markMemberAvailable(final MemberName memberName) {
812         final String memberStr = memberName.getName();
813         for (ShardInformation info : localShards.values()) {
814             String leaderId = info.getLeaderId();
815             // XXX: why are we using String#contains() here?
816             if (leaderId != null && leaderId.contains(memberStr)) {
817                 LOG.debug("Marking Leader {} as available.", leaderId);
818                 info.setLeaderAvailable(true);
819             }
820
821             info.peerUp(memberName, getShardIdentifier(memberName, info.getShardName()).toString(), getSelf());
822         }
823     }
824
825     private void onDatastoreContextFactory(DatastoreContextFactory factory) {
826         datastoreContextFactory = factory;
827         for (ShardInformation info : localShards.values()) {
828             info.setDatastoreContext(newShardDatastoreContext(info.getShardName()), getSelf());
829         }
830     }
831
832     private void onGetLocalShardIds() {
833         final List<String> response = new ArrayList<>(localShards.size());
834
835         for (ShardInformation info : localShards.values()) {
836             response.add(info.getShardId().toString());
837         }
838
839         getSender().tell(new Status.Success(response), getSelf());
840     }
841
842     private void onSwitchShardBehavior(final SwitchShardBehavior message) {
843         final ShardIdentifier identifier = message.getShardId();
844
845         if (identifier != null) {
846             final ShardInformation info = localShards.get(identifier.getShardName());
847             if (info == null) {
848                 getSender().tell(new Status.Failure(
849                     new IllegalArgumentException("Shard " + identifier + " is not local")), getSelf());
850                 return;
851             }
852
853             switchShardBehavior(info, new SwitchBehavior(message.getNewState(), message.getTerm()));
854         } else {
855             for (ShardInformation info : localShards.values()) {
856                 switchShardBehavior(info, new SwitchBehavior(message.getNewState(), message.getTerm()));
857             }
858         }
859
860         getSender().tell(new Status.Success(null), getSelf());
861     }
862
863     private void switchShardBehavior(final ShardInformation info, final SwitchBehavior switchBehavior) {
864         final ActorRef actor = info.getActor();
865         if (actor != null) {
866             actor.tell(switchBehavior, getSelf());
867           } else {
868             LOG.warn("Could not switch the behavior of shard {} to {} - shard is not yet available",
869                 info.getShardName(), switchBehavior.getNewState());
870         }
871     }
872
873     /**
874      * Notifies all the local shards of a change in the schema context
875      *
876      * @param message
877      */
878     private void updateSchemaContext(final Object message) {
879         schemaContext = ((UpdateSchemaContext) message).getSchemaContext();
880
881         LOG.debug("Got updated SchemaContext: # of modules {}", schemaContext.getAllModuleIdentifiers().size());
882
883         for (ShardInformation info : localShards.values()) {
884             if (info.getActor() == null) {
885                 LOG.debug("Creating Shard {}", info.getShardId());
886                 info.setActor(newShardActor(schemaContext, info));
887             } else {
888                 info.getActor().tell(message, getSelf());
889             }
890         }
891     }
892
893     @VisibleForTesting
894     protected ClusterWrapper getCluster() {
895         return cluster;
896     }
897
898     @VisibleForTesting
899     protected ActorRef newShardActor(final SchemaContext schemaContext, ShardInformation info) {
900         return getContext().actorOf(info.newProps(schemaContext)
901                 .withDispatcher(shardDispatcherPath), info.getShardId().toString());
902     }
903
904     private void findPrimary(FindPrimary message) {
905         LOG.debug("{}: In findPrimary: {}", persistenceId(), message);
906
907         final String shardName = message.getShardName();
908         final boolean canReturnLocalShardState = !(message instanceof RemoteFindPrimary);
909
910         // First see if the there is a local replica for the shard
911         final ShardInformation info = localShards.get(shardName);
912         if (info != null && info.isActiveMember()) {
913             sendResponse(info, message.isWaitUntilReady(), true, () -> {
914                 String primaryPath = info.getSerializedLeaderActor();
915                 Object found = canReturnLocalShardState && info.isLeader() ?
916                         new LocalPrimaryShardFound(primaryPath, info.getLocalShardDataTree().get()) :
917                             new RemotePrimaryShardFound(primaryPath, info.getLeaderVersion());
918
919                         if(LOG.isDebugEnabled()) {
920                             LOG.debug("{}: Found primary for {}: {}", persistenceId(), shardName, found);
921                         }
922
923                         return found;
924             });
925
926             return;
927         }
928
929         final Collection<String> visitedAddresses;
930         if(message instanceof RemoteFindPrimary) {
931             visitedAddresses = ((RemoteFindPrimary)message).getVisitedAddresses();
932         } else {
933             visitedAddresses = new ArrayList<>(1);
934         }
935
936         visitedAddresses.add(peerAddressResolver.getShardManagerActorPathBuilder(cluster.getSelfAddress()).toString());
937
938         for(String address: peerAddressResolver.getShardManagerPeerActorAddresses()) {
939             if(visitedAddresses.contains(address)) {
940                 continue;
941             }
942
943             LOG.debug("{}: findPrimary for {} forwarding to remote ShardManager {}, visitedAddresses: {}",
944                     persistenceId(), shardName, address, visitedAddresses);
945
946             getContext().actorSelection(address).forward(new RemoteFindPrimary(shardName,
947                     message.isWaitUntilReady(), visitedAddresses), getContext());
948             return;
949         }
950
951         LOG.debug("{}: No shard found for {}", persistenceId(), shardName);
952
953         getSender().tell(new PrimaryNotFoundException(
954                 String.format("No primary shard found for %s.", shardName)), getSelf());
955     }
956
957     /**
958      * Construct the name of the shard actor given the name of the member on
959      * which the shard resides and the name of the shard
960      *
961      * @param memberName
962      * @param shardName
963      * @return
964      */
965     private ShardIdentifier getShardIdentifier(MemberName memberName, String shardName){
966         return peerAddressResolver.getShardIdentifier(memberName, shardName);
967     }
968
969     /**
970      * Create shards that are local to the member on which the ShardManager
971      * runs
972      *
973      */
974     private void createLocalShards() {
975         MemberName memberName = this.cluster.getCurrentMemberName();
976         Collection<String> memberShardNames = this.configuration.getMemberShardNames(memberName);
977
978         Map<String, DatastoreSnapshot.ShardSnapshot> shardSnapshots = new HashMap<>();
979         if(restoreFromSnapshot != null)
980         {
981             for(DatastoreSnapshot.ShardSnapshot snapshot: restoreFromSnapshot.getShardSnapshots()) {
982                 shardSnapshots.put(snapshot.getName(), snapshot);
983             }
984         }
985
986         restoreFromSnapshot = null; // null out to GC
987
988         for(String shardName : memberShardNames){
989             ShardIdentifier shardId = getShardIdentifier(memberName, shardName);
990
991             LOG.debug("{}: Creating local shard: {}", persistenceId(), shardId);
992
993             Map<String, String> peerAddresses = getPeerAddresses(shardName);
994             localShards.put(shardName, new ShardInformation(shardName, shardId, peerAddresses,
995                     newShardDatastoreContext(shardName), Shard.builder().restoreFromSnapshot(
996                         shardSnapshots.get(shardName)), peerAddressResolver));
997         }
998     }
999
1000     /**
1001      * Given the name of the shard find the addresses of all it's peers
1002      *
1003      * @param shardName
1004      */
1005     private Map<String, String> getPeerAddresses(String shardName) {
1006         Collection<MemberName> members = configuration.getMembersFromShardName(shardName);
1007         Map<String, String> peerAddresses = new HashMap<>();
1008
1009         MemberName currentMemberName = this.cluster.getCurrentMemberName();
1010
1011         for (MemberName memberName : members) {
1012             if (!currentMemberName.equals(memberName)) {
1013                 ShardIdentifier shardId = getShardIdentifier(memberName, shardName);
1014                 String address = peerAddressResolver.getShardActorAddress(shardName, memberName);
1015                 peerAddresses.put(shardId.toString(), address);
1016             }
1017         }
1018         return peerAddresses;
1019     }
1020
1021     @Override
1022     public SupervisorStrategy supervisorStrategy() {
1023
1024         return new OneForOneStrategy(10, Duration.create("1 minute"),
1025                 (Function<Throwable, Directive>) t -> {
1026                     LOG.warn("Supervisor Strategy caught unexpected exception - resuming", t);
1027                     return SupervisorStrategy.resume();
1028                 }
1029                 );
1030
1031     }
1032
1033     @Override
1034     public String persistenceId() {
1035         return persistenceId;
1036     }
1037
1038     @VisibleForTesting
1039     ShardManagerInfoMBean getMBean(){
1040         return mBean;
1041     }
1042
1043     private boolean isShardReplicaOperationInProgress(final String shardName, final ActorRef sender) {
1044         if (shardReplicaOperationsInProgress.contains(shardName)) {
1045             String msg = String.format("A shard replica operation for %s is already in progress", shardName);
1046             LOG.debug ("{}: {}", persistenceId(), msg);
1047             sender.tell(new Status.Failure(new IllegalStateException(msg)), getSelf());
1048             return true;
1049         }
1050
1051         return false;
1052     }
1053
1054     private void onAddShardReplica (final AddShardReplica shardReplicaMsg) {
1055         final String shardName = shardReplicaMsg.getShardName();
1056
1057         LOG.debug("{}: onAddShardReplica: {}", persistenceId(), shardReplicaMsg);
1058
1059         // verify the shard with the specified name is present in the cluster configuration
1060         if (!(this.configuration.isShardConfigured(shardName))) {
1061             String msg = String.format("No module configuration exists for shard %s", shardName);
1062             LOG.debug ("{}: {}", persistenceId(), msg);
1063             getSender().tell(new Status.Failure(new IllegalArgumentException(msg)), getSelf());
1064             return;
1065         }
1066
1067         // Create the localShard
1068         if (schemaContext == null) {
1069             String msg = String.format(
1070                   "No SchemaContext is available in order to create a local shard instance for %s", shardName);
1071             LOG.debug ("{}: {}", persistenceId(), msg);
1072             getSender().tell(new Status.Failure(new IllegalStateException(msg)), getSelf());
1073             return;
1074         }
1075
1076         findPrimary(shardName, new AutoFindPrimaryFailureResponseHandler(getSender(), shardName, persistenceId(), getSelf()) {
1077             @Override
1078             public void onRemotePrimaryShardFound(RemotePrimaryShardFound response) {
1079                 getSelf().tell(new PrimaryShardFoundForContext(getShardName(), shardReplicaMsg, response), getTargetActor());
1080             }
1081
1082             @Override
1083             public void onLocalPrimaryFound(LocalPrimaryShardFound message) {
1084                 sendLocalReplicaAlreadyExistsReply(getShardName(), getTargetActor());
1085             }
1086
1087         });
1088     }
1089
1090     private void sendLocalReplicaAlreadyExistsReply(String shardName, ActorRef sender) {
1091         String msg = String.format("Local shard %s already exists", shardName);
1092         LOG.debug ("{}: {}", persistenceId(), msg);
1093         sender.tell(new Status.Failure(new AlreadyExistsException(msg)), getSelf());
1094     }
1095
1096     private void addShard(final String shardName, final RemotePrimaryShardFound response, final ActorRef sender) {
1097         if(isShardReplicaOperationInProgress(shardName, sender)) {
1098             return;
1099         }
1100
1101         shardReplicaOperationsInProgress.add(shardName);
1102
1103         final ShardInformation shardInfo;
1104         final boolean removeShardOnFailure;
1105         ShardInformation existingShardInfo = localShards.get(shardName);
1106         if(existingShardInfo == null) {
1107             removeShardOnFailure = true;
1108             ShardIdentifier shardId = getShardIdentifier(cluster.getCurrentMemberName(), shardName);
1109
1110             DatastoreContext datastoreContext = newShardDatastoreContextBuilder(shardName).customRaftPolicyImplementation(
1111                     DisableElectionsRaftPolicy.class.getName()).build();
1112
1113             shardInfo = new ShardInformation(shardName, shardId, getPeerAddresses(shardName), datastoreContext,
1114                     Shard.builder(), peerAddressResolver);
1115             shardInfo.setActiveMember(false);
1116             localShards.put(shardName, shardInfo);
1117             shardInfo.setActor(newShardActor(schemaContext, shardInfo));
1118         } else {
1119             removeShardOnFailure = false;
1120             shardInfo = existingShardInfo;
1121         }
1122
1123         String localShardAddress = peerAddressResolver.getShardActorAddress(shardName, cluster.getCurrentMemberName());
1124
1125         //inform ShardLeader to add this shard as a replica by sending an AddServer message
1126         LOG.debug ("{}: Sending AddServer message to peer {} for shard {}", persistenceId(),
1127                 response.getPrimaryPath(), shardInfo.getShardId());
1128
1129         Timeout addServerTimeout = new Timeout(shardInfo.getDatastoreContext().getShardLeaderElectionTimeout().
1130                 duration());
1131         Future<Object> futureObj = ask(getContext().actorSelection(response.getPrimaryPath()),
1132             new AddServer(shardInfo.getShardId().toString(), localShardAddress, true), addServerTimeout);
1133
1134         futureObj.onComplete(new OnComplete<Object>() {
1135             @Override
1136             public void onComplete(Throwable failure, Object addServerResponse) {
1137                 if (failure != null) {
1138                     LOG.debug ("{}: AddServer request to {} for {} failed", persistenceId(),
1139                             response.getPrimaryPath(), shardName, failure);
1140
1141                     String msg = String.format("AddServer request to leader %s for shard %s failed",
1142                             response.getPrimaryPath(), shardName);
1143                     self().tell(new ForwardedAddServerFailure(shardName, msg, failure, removeShardOnFailure), sender);
1144                 } else {
1145                     self().tell(new ForwardedAddServerReply(shardInfo, (AddServerReply)addServerResponse,
1146                             response.getPrimaryPath(), removeShardOnFailure), sender);
1147                 }
1148             }
1149         }, new Dispatchers(context().system().dispatchers()).getDispatcher(Dispatchers.DispatcherType.Client));
1150     }
1151
1152     private void onAddServerFailure(String shardName, String message, Throwable failure, ActorRef sender,
1153             boolean removeShardOnFailure) {
1154         shardReplicaOperationsInProgress.remove(shardName);
1155
1156         if(removeShardOnFailure) {
1157             ShardInformation shardInfo = localShards.remove(shardName);
1158             if (shardInfo.getActor() != null) {
1159                 shardInfo.getActor().tell(PoisonPill.getInstance(), getSelf());
1160             }
1161         }
1162
1163         sender.tell(new Status.Failure(message == null ? failure :
1164             new RuntimeException(message, failure)), getSelf());
1165     }
1166
1167     private void onAddServerReply(ShardInformation shardInfo, AddServerReply replyMsg, ActorRef sender,
1168             String leaderPath, boolean removeShardOnFailure) {
1169         String shardName = shardInfo.getShardName();
1170         shardReplicaOperationsInProgress.remove(shardName);
1171
1172         LOG.debug ("{}: Received {} for shard {} from leader {}", persistenceId(), replyMsg, shardName, leaderPath);
1173
1174         if (replyMsg.getStatus() == ServerChangeStatus.OK) {
1175             LOG.debug ("{}: Leader shard successfully added the replica shard {}", persistenceId(), shardName);
1176
1177             // Make the local shard voting capable
1178             shardInfo.setDatastoreContext(newShardDatastoreContext(shardName), getSelf());
1179             shardInfo.setActiveMember(true);
1180             persistShardList();
1181
1182             sender.tell(new Status.Success(null), getSelf());
1183         } else if(replyMsg.getStatus() == ServerChangeStatus.ALREADY_EXISTS) {
1184             sendLocalReplicaAlreadyExistsReply(shardName, sender);
1185         } else {
1186             LOG.warn ("{}: Leader failed to add shard replica {} with status {}",
1187                     persistenceId(), shardName, replyMsg.getStatus());
1188
1189             Exception failure = getServerChangeException(AddServer.class, replyMsg.getStatus(), leaderPath, shardInfo.getShardId());
1190
1191             onAddServerFailure(shardName, null, failure, sender, removeShardOnFailure);
1192         }
1193     }
1194
1195     private static Exception getServerChangeException(Class<?> serverChange, ServerChangeStatus serverChangeStatus,
1196                                                String leaderPath, ShardIdentifier shardId) {
1197         Exception failure;
1198         switch (serverChangeStatus) {
1199             case TIMEOUT:
1200                 failure = new TimeoutException(String.format(
1201                         "The shard leader %s timed out trying to replicate the initial data to the new shard %s." +
1202                         "Possible causes - there was a problem replicating the data or shard leadership changed while replicating the shard data",
1203                         leaderPath, shardId.getShardName()));
1204                 break;
1205             case NO_LEADER:
1206                 failure = createNoShardLeaderException(shardId);
1207                 break;
1208             case NOT_SUPPORTED:
1209                 failure = new UnsupportedOperationException(String.format("%s request is not supported for shard %s",
1210                         serverChange.getSimpleName(), shardId.getShardName()));
1211                 break;
1212             default :
1213                 failure = new RuntimeException(String.format(
1214                         "%s request to leader %s for shard %s failed with status %s",
1215                         serverChange.getSimpleName(), leaderPath, shardId.getShardName(), serverChangeStatus));
1216         }
1217         return failure;
1218     }
1219
1220     private void onRemoveShardReplica (final RemoveShardReplica shardReplicaMsg) {
1221         LOG.debug("{}: onRemoveShardReplica: {}", persistenceId(), shardReplicaMsg);
1222
1223         findPrimary(shardReplicaMsg.getShardName(), new AutoFindPrimaryFailureResponseHandler(getSender(),
1224                 shardReplicaMsg.getShardName(), persistenceId(), getSelf()) {
1225             @Override
1226             public void onRemotePrimaryShardFound(RemotePrimaryShardFound response) {
1227                 getSelf().tell(new PrimaryShardFoundForContext(getShardName(), shardReplicaMsg, response), getTargetActor());
1228             }
1229
1230             @Override
1231             public void onLocalPrimaryFound(LocalPrimaryShardFound response) {
1232                 getSelf().tell(new PrimaryShardFoundForContext(getShardName(), shardReplicaMsg, response), getTargetActor());
1233             }
1234         });
1235     }
1236
1237     private void persistShardList() {
1238         List<String> shardList = new ArrayList<>(localShards.keySet());
1239         for (ShardInformation shardInfo : localShards.values()) {
1240             if (!shardInfo.isActiveMember()) {
1241                 shardList.remove(shardInfo.getShardName());
1242             }
1243         }
1244         LOG.debug ("{}: persisting the shard list {}", persistenceId(), shardList);
1245         saveSnapshot(updateShardManagerSnapshot(shardList));
1246     }
1247
1248     private ShardManagerSnapshot updateShardManagerSnapshot(List<String> shardList) {
1249         currentSnapshot = new ShardManagerSnapshot(shardList);
1250         return currentSnapshot;
1251     }
1252
1253     private void applyShardManagerSnapshot(ShardManagerSnapshot snapshot) {
1254         currentSnapshot = snapshot;
1255
1256         LOG.debug ("{}: onSnapshotOffer: {}", persistenceId(), currentSnapshot);
1257
1258         final MemberName currentMember = cluster.getCurrentMemberName();
1259         Set<String> configuredShardList =
1260             new HashSet<>(configuration.getMemberShardNames(currentMember));
1261         for (String shard : currentSnapshot.getShardList()) {
1262             if (!configuredShardList.contains(shard)) {
1263                 // add the current member as a replica for the shard
1264                 LOG.debug ("{}: adding shard {}", persistenceId(), shard);
1265                 configuration.addMemberReplicaForShard(shard, currentMember);
1266             } else {
1267                 configuredShardList.remove(shard);
1268             }
1269         }
1270         for (String shard : configuredShardList) {
1271             // remove the member as a replica for the shard
1272             LOG.debug ("{}: removing shard {}", persistenceId(), shard);
1273             configuration.removeMemberReplicaForShard(shard, currentMember);
1274         }
1275     }
1276
1277     private void onSaveSnapshotSuccess (SaveSnapshotSuccess successMessage) {
1278         LOG.debug ("{} saved ShardManager snapshot successfully. Deleting the prev snapshot if available",
1279             persistenceId());
1280         deleteSnapshots(new SnapshotSelectionCriteria(scala.Long.MaxValue(), successMessage.metadata().timestamp() - 1,
1281             0, 0));
1282     }
1283
1284     private static final class ForwardedAddServerReply {
1285         ShardInformation shardInfo;
1286         AddServerReply addServerReply;
1287         String leaderPath;
1288         boolean removeShardOnFailure;
1289
1290         ForwardedAddServerReply(ShardInformation shardInfo, AddServerReply addServerReply, String leaderPath,
1291                 boolean removeShardOnFailure) {
1292             this.shardInfo = shardInfo;
1293             this.addServerReply = addServerReply;
1294             this.leaderPath = leaderPath;
1295             this.removeShardOnFailure = removeShardOnFailure;
1296         }
1297     }
1298
1299     private static final class ForwardedAddServerFailure {
1300         String shardName;
1301         String failureMessage;
1302         Throwable failure;
1303         boolean removeShardOnFailure;
1304
1305         ForwardedAddServerFailure(String shardName, String failureMessage, Throwable failure,
1306                 boolean removeShardOnFailure) {
1307             this.shardName = shardName;
1308             this.failureMessage = failureMessage;
1309             this.failure = failure;
1310             this.removeShardOnFailure = removeShardOnFailure;
1311         }
1312     }
1313
1314     static class OnShardInitialized {
1315         private final Runnable replyRunnable;
1316         private Cancellable timeoutSchedule;
1317
1318         OnShardInitialized(Runnable replyRunnable) {
1319             this.replyRunnable = replyRunnable;
1320         }
1321
1322         Runnable getReplyRunnable() {
1323             return replyRunnable;
1324         }
1325
1326         Cancellable getTimeoutSchedule() {
1327             return timeoutSchedule;
1328         }
1329
1330         void setTimeoutSchedule(Cancellable timeoutSchedule) {
1331             this.timeoutSchedule = timeoutSchedule;
1332         }
1333     }
1334
1335     static class OnShardReady extends OnShardInitialized {
1336         OnShardReady(Runnable replyRunnable) {
1337             super(replyRunnable);
1338         }
1339     }
1340
1341     private void findPrimary(final String shardName, final FindPrimaryResponseHandler handler) {
1342         Timeout findPrimaryTimeout = new Timeout(datastoreContextFactory.getBaseDatastoreContext().
1343                 getShardInitializationTimeout().duration().$times(2));
1344
1345
1346         Future<Object> futureObj = ask(getSelf(), new FindPrimary(shardName, true), findPrimaryTimeout);
1347         futureObj.onComplete(new OnComplete<Object>() {
1348             @Override
1349             public void onComplete(Throwable failure, Object response) {
1350                 if (failure != null) {
1351                     handler.onFailure(failure);
1352                 } else {
1353                     if(response instanceof RemotePrimaryShardFound) {
1354                         handler.onRemotePrimaryShardFound((RemotePrimaryShardFound) response);
1355                     } else if(response instanceof LocalPrimaryShardFound) {
1356                         handler.onLocalPrimaryFound((LocalPrimaryShardFound) response);
1357                     } else {
1358                         handler.onUnknownResponse(response);
1359                     }
1360                 }
1361             }
1362         }, new Dispatchers(context().system().dispatchers()).getDispatcher(Dispatchers.DispatcherType.Client));
1363     }
1364
1365     /**
1366      * The FindPrimaryResponseHandler provides specific callback methods which are invoked when a response to the
1367      * a remote or local find primary message is processed
1368      */
1369     private static interface FindPrimaryResponseHandler {
1370         /**
1371          * Invoked when a Failure message is received as a response
1372          *
1373          * @param failure
1374          */
1375         void onFailure(Throwable failure);
1376
1377         /**
1378          * Invoked when a RemotePrimaryShardFound response is received
1379          *
1380          * @param response
1381          */
1382         void onRemotePrimaryShardFound(RemotePrimaryShardFound response);
1383
1384         /**
1385          * Invoked when a LocalPrimaryShardFound response is received
1386          * @param response
1387          */
1388         void onLocalPrimaryFound(LocalPrimaryShardFound response);
1389
1390         /**
1391          * Invoked when an unknown response is received. This is another type of failure.
1392          *
1393          * @param response
1394          */
1395         void onUnknownResponse(Object response);
1396     }
1397
1398     /**
1399      * The AutoFindPrimaryFailureResponseHandler automatically processes Failure responses when finding a primary
1400      * replica and sends a wrapped Failure response to some targetActor
1401      */
1402     private static abstract class AutoFindPrimaryFailureResponseHandler implements FindPrimaryResponseHandler {
1403         private final ActorRef targetActor;
1404         private final String shardName;
1405         private final String persistenceId;
1406         private final ActorRef shardManagerActor;
1407
1408         /**
1409          * @param targetActor The actor to whom the Failure response should be sent when a FindPrimary failure occurs
1410          * @param shardName The name of the shard for which the primary replica had to be found
1411          * @param persistenceId The persistenceId for the ShardManager
1412          * @param shardManagerActor The ShardManager actor which triggered the call to FindPrimary
1413          */
1414         protected AutoFindPrimaryFailureResponseHandler(ActorRef targetActor, String shardName, String persistenceId, ActorRef shardManagerActor){
1415             this.targetActor = Preconditions.checkNotNull(targetActor);
1416             this.shardName = Preconditions.checkNotNull(shardName);
1417             this.persistenceId = Preconditions.checkNotNull(persistenceId);
1418             this.shardManagerActor = Preconditions.checkNotNull(shardManagerActor);
1419         }
1420
1421         public ActorRef getTargetActor() {
1422             return targetActor;
1423         }
1424
1425         public String getShardName() {
1426             return shardName;
1427         }
1428
1429         @Override
1430         public void onFailure(Throwable failure) {
1431             LOG.debug ("{}: Received failure from FindPrimary for shard {}", persistenceId, shardName, failure);
1432             targetActor.tell(new Status.Failure(new RuntimeException(
1433                     String.format("Failed to find leader for shard %s", shardName), failure)), shardManagerActor);
1434         }
1435
1436         @Override
1437         public void onUnknownResponse(Object response) {
1438             String msg = String.format("Failed to find leader for shard %s: received response: %s",
1439                     shardName, response);
1440             LOG.debug ("{}: {}", persistenceId, msg);
1441             targetActor.tell(new Status.Failure(response instanceof Throwable ? (Throwable) response :
1442                     new RuntimeException(msg)), shardManagerActor);
1443         }
1444     }
1445
1446     /**
1447      * The PrimaryShardFoundForContext is a DTO which puts together a message (aka 'Context' message) which needs to be
1448      * forwarded to the primary replica of a shard and the message (aka 'PrimaryShardFound' message) that is received
1449      * as a successful response to find primary.
1450      */
1451     private static class PrimaryShardFoundForContext {
1452         private final String shardName;
1453         private final Object contextMessage;
1454         private final RemotePrimaryShardFound remotePrimaryShardFound;
1455         private final LocalPrimaryShardFound localPrimaryShardFound;
1456
1457         public PrimaryShardFoundForContext(@Nonnull String shardName, @Nonnull Object contextMessage,
1458                 @Nonnull Object primaryFoundMessage) {
1459             this.shardName = Preconditions.checkNotNull(shardName);
1460             this.contextMessage = Preconditions.checkNotNull(contextMessage);
1461             Preconditions.checkNotNull(primaryFoundMessage);
1462             this.remotePrimaryShardFound = (primaryFoundMessage instanceof RemotePrimaryShardFound) ?
1463                     (RemotePrimaryShardFound) primaryFoundMessage : null;
1464             this.localPrimaryShardFound = (primaryFoundMessage instanceof LocalPrimaryShardFound) ?
1465                     (LocalPrimaryShardFound) primaryFoundMessage : null;
1466         }
1467
1468         @Nonnull
1469         String getPrimaryPath(){
1470             if(remotePrimaryShardFound != null) {
1471                 return remotePrimaryShardFound.getPrimaryPath();
1472             }
1473             return localPrimaryShardFound.getPrimaryPath();
1474         }
1475
1476         @Nonnull
1477         Object getContextMessage() {
1478             return contextMessage;
1479         }
1480
1481         @Nullable
1482         RemotePrimaryShardFound getRemotePrimaryShardFound() {
1483             return remotePrimaryShardFound;
1484         }
1485
1486         @Nonnull
1487         String getShardName() {
1488             return shardName;
1489         }
1490     }
1491
1492     /**
1493      * The WrappedShardResponse class wraps a response from a Shard.
1494      */
1495     private static final class WrappedShardResponse {
1496         private final ShardIdentifier shardId;
1497         private final Object response;
1498         private final String leaderPath;
1499
1500         WrappedShardResponse(ShardIdentifier shardId, Object response, String leaderPath) {
1501             this.shardId = shardId;
1502             this.response = response;
1503             this.leaderPath = leaderPath;
1504         }
1505
1506         ShardIdentifier getShardId() {
1507             return shardId;
1508         }
1509
1510         Object getResponse() {
1511             return response;
1512         }
1513
1514         String getLeaderPath() {
1515             return leaderPath;
1516         }
1517     }
1518
1519     private static final class ShardNotInitializedTimeout {
1520         private final ActorRef sender;
1521         private final ShardInformation shardInfo;
1522         private final OnShardInitialized onShardInitialized;
1523
1524         ShardNotInitializedTimeout(ShardInformation shardInfo, OnShardInitialized onShardInitialized, ActorRef sender) {
1525             this.sender = sender;
1526             this.shardInfo = shardInfo;
1527             this.onShardInitialized = onShardInitialized;
1528         }
1529
1530         ActorRef getSender() {
1531             return sender;
1532         }
1533
1534         ShardInformation getShardInfo() {
1535             return shardInfo;
1536         }
1537
1538         OnShardInitialized getOnShardInitialized() {
1539             return onShardInitialized;
1540         }
1541     }
1542 }
1543
1544
1545