示例#1
0
 public ResourceManager(ZooKeeperService zooKeeperService,
                        ILogger logger,
                        OnChangeActions onChangeActions)
 {
     this.zooKeeperService = zooKeeperService;
     this.logger           = logger;
     this.resources        = new List <string>();
     this.assignmentStatus = AssignmentStatus.NoAssignmentYet;
     this.onChangeActions  = onChangeActions;
 }
示例#2
0
        private Task StartRoleTask(CancellationToken token,
                                   OnChangeActions onChangeActions,
                                   BlockingCollection <ClientEvent> clientEvents)
        {
            return(Task.Run(async() =>
            {
                while (!token.IsCancellationRequested && !clientEvents.IsAddingCompleted)
                {
                    try
                    {
                        // take the most recent event, if multiple are queued up then we only need the latest
                        ClientEvent clientEvent = null;
                        while (clientEvents.Any())
                        {
                            try
                            {
                                clientEvent = clientEvents.Take(token);
                            }
                            catch (OperationCanceledException) { }
                        }

                        // if there was an event then call the appropriate role beahvaiour
                        if (clientEvent != null)
                        {
                            if (clientEvent.EventType == EventType.Coordinator)
                            {
                                //await this.coordinator.ExecuteCoordinatorRoleAsync(this.clientId,
                                //        clientEvent,
                                //        onChangeActions,
                                //        token);
                            }
                            else
                            {
                                //await this.follower.ExecuteFollowerRoleAsync(this.clientId,
                                //        clientEvent,
                                //        onChangeActions,
                                //        token);
                            }
                        }
                        else
                        {
                            await WaitFor(TimeSpan.FromSeconds(1), token);
                        }
                    }
                    catch (Exception ex)
                    {
                        this.logger.Error(ex);
                        await WaitFor(TimeSpan.FromSeconds(1), token);
                    }
                }
            }));
        }
示例#3
0
        public async Task ExecuteCoordinatorRoleAsync(Guid coordinatorClientId,
                                                      ClientEvent clientEvent,
                                                      OnChangeActions onChangeActions,
                                                      CancellationToken token)
        {
            currentFencingToken = clientEvent.FencingToken;
            Client self = await clientService.KeepAliveAsync(coordinatorClientId);

            List <string> resourcesNow = (await resourceService.GetResourcesAsync(clientEvent.ResourceGroup))
                                         .OrderBy(x => x)
                                         .ToList();
            List <Client> clientsNow = await GetLiveClientsAsync(clientEvent, coordinatorClientId);

            List <Guid> clientIds = clientsNow.Select(x => x.ClientId).ToList();

            clientIds.Add(coordinatorClientId);

            if (clientsNow.Any(x => x.FencingToken > clientEvent.FencingToken))
            {
                clientEvent.CoordinatorToken.FencingTokenViolation = true;
                return;
            }

            if (self.ClientStatus == ClientStatus.Terminated || stoppedDueToInternalErrorFlag)
            {
                stoppedDueToInternalErrorFlag = false;
                await clientService.SetClientStatusAsync(coordinatorClientId, ClientStatus.Waiting);

                logger.Debug(coordinatorClientId.ToString(),
                             "Status change: COORDINATOR was terminated due to an error");
                await TriggerRebalancingAsync(coordinatorClientId, clientEvent, clientsNow, resourcesNow,
                                              onChangeActions,
                                              token);
            }
            else if (!resources.OrderBy(x => x).SequenceEqual(resourcesNow.OrderBy(x => x)))
            {
                logger.Debug(coordinatorClientId.ToString(),
                             $"Resource change: Old: {string.Join(",", resources.OrderBy(x => x))} New: {string.Join(",", resourcesNow.OrderBy(x => x))}");
                await TriggerRebalancingAsync(coordinatorClientId, clientEvent, clientsNow, resourcesNow,
                                              onChangeActions,
                                              token);
            }
            else if (!clients.OrderBy(x => x).SequenceEqual(clientIds.OrderBy(x => x)))
            {
                logger.Debug(coordinatorClientId.ToString(),
                             $"Client change: Old: {string.Join(",", clients.OrderBy(x => x))} New: {string.Join(",", clientIds.OrderBy(x => x))}");
                await TriggerRebalancingAsync(coordinatorClientId, clientEvent, clientsNow, resourcesNow,
                                              onChangeActions,
                                              token);
            }
        }
 private void InvokeOnStop(OnChangeActions onChangeActions)
 {
     try
     {
         foreach (var onErrorAction in onChangeActions.OnStopActions)
         {
             onErrorAction.Invoke();
         }
     }
     catch (Exception ex)
     {
         this.logger.Error(ex.ToString());
     }
 }
 private void InvokeOnError(OnChangeActions onChangeActions, string message, bool autoRecoveryEnabled, Exception exception)
 {
     try
     {
         foreach (var onErrorAction in onChangeActions.OnErrorActions)
         {
             onErrorAction.Invoke(message, autoRecoveryEnabled, exception);
         }
     }
     catch (Exception ex)
     {
         this.logger.Error(ex.ToString());
     }
 }
        public async Task StartAsync(string resourceGroup,
                                     OnChangeActions onChangeActions,
                                     CancellationToken token,
                                     ClientOptions clientOptions)
        {
            // just in case someone tries to start the client twice (with some concurrency)
            lock (startLockObj)
            {
                if (this.started)
                {
                    throw new RebalanserException("Client already started");
                }

                this.started = true;
            }

            this.resourceManager = new ResourceManager(this.zooKeeperService, this.logger, onChangeActions, this.rebalancingMode);
            SetStateToNoSession();
            this.resourceGroup = resourceGroup;
            this.onStartDelay  = clientOptions.OnAssignmentDelay;

            mainTask = Task.Run(async() => await RunStateMachine(token, clientOptions));
            await Task.Yield();
        }
示例#7
0
        public async Task ExecuteCoordinatorRoleAsync(Guid coordinatorClientId,
                                                      ClientEvent clientEvent,
                                                      OnChangeActions onChangeActions,
                                                      CancellationToken token)
        {
            currentFencingToken = clientEvent.FencingToken;
            var self = await clientService.KeepAliveAsync(coordinatorClientId);

            var resourcesNow = (await resourceService.GetResourcesAsync(clientEvent.ResourceGroup)).OrderBy(x => x).ToList();
            var clientsNow   = await GetLiveClientsAsync(clientEvent, coordinatorClientId);

            var clientIds = clientsNow.Select(x => x.ClientId).ToList();

            clientIds.Add(coordinatorClientId);

            if (clientsNow.Any(x => x.FencingToken > clientEvent.FencingToken))
            {
                clientEvent.CoordinatorToken.FencingTokenViolation = true;
                return;
            }

            if (!resources.OrderBy(x => x).SequenceEqual(resourcesNow.OrderBy(x => x)))
            {
                logger.Debug($"Resource change: Old: {string.Join(",", resources.OrderBy(x => x))} New: {string.Join(",", resourcesNow.OrderBy(x => x))}");
                await TriggerRebalancingAsync(coordinatorClientId, clientEvent, clientsNow, resourcesNow, onChangeActions, token);
            }
            else if (!clients.OrderBy(x => x).SequenceEqual(clientIds.OrderBy(x => x)))
            {
                logger.Debug($"Client change: Old: {string.Join(",", clients.OrderBy(x => x))} New: {string.Join(",", clientIds.OrderBy(x => x))}");
                await TriggerRebalancingAsync(coordinatorClientId, clientEvent, clientsNow, resourcesNow, onChangeActions, token);
            }
            else
            {
                // no change, do nothing
            }
        }
        public async Task StartAsync(string resourceGroup,
                                     OnChangeActions onChangeActions,
                                     CancellationToken parentToken,
                                     ContextOptions contextOptions)
        {
            // just in case someone does some concurrency
            lock (startLockObj)
            {
                if (this.started)
                {
                    throw new RebalanserException("Context already started");
                }
            }

            this.resourceGroup = resourceGroup;
            await this.clientService.CreateClientAsync(this.resourceGroup, this.clientId);

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            mainTask = Task.Run(async() =>
            {
                while (!parentToken.IsCancellationRequested)
                {
                    var childTaskCts = new CancellationTokenSource();
                    try
                    {
                        var clientEvents = new BlockingCollection <ClientEvent>();

                        var leaderElectionTask = StartLeadershipTask(childTaskCts.Token, clientEvents);
                        var roleTask           = StartRoleTask(childTaskCts.Token, onChangeActions, clientEvents);

                        while (!parentToken.IsCancellationRequested &&
                               !leaderElectionTask.IsCompleted &&
                               !clientEvents.IsCompleted)
                        {
                            await Task.Delay(100);
                        }

                        // cancel child tasks
                        childTaskCts.Cancel();

                        if (parentToken.IsCancellationRequested)
                        {
                            logger.Info("Context shutting down due to cancellation");
                        }
                        else
                        {
                            if (leaderElectionTask.IsFaulted)
                            {
                                await NotifyOfErrorAsync(leaderElectionTask, "Shutdown due to leader election task fault", contextOptions.AutoRecoveryOnError, onChangeActions);
                            }
                            else if (roleTask.IsFaulted)
                            {
                                await NotifyOfErrorAsync(roleTask, "Shutdown due to coordinator/follower task fault", contextOptions.AutoRecoveryOnError, onChangeActions);
                            }
                            else
                            {
                                NotifyOfError(onChangeActions, "Unknown shutdown reason", contextOptions.AutoRecoveryOnError, null);
                            }

                            if (contextOptions.AutoRecoveryOnError)
                            {
                                await WaitFor(contextOptions.RestartDelay, parentToken);
                            }
                            else
                            {
                                break;
                            }
                        }

                        await leaderElectionTask;
                        await roleTask;

                        this.clientService.SetClientStatusAsync(this.clientId, ClientStatus.Terminated);

                        if (this.isCoordinator)
                        {
                            this.leaseService.RelinquishLeaseAsync(new RelinquishLeaseRequest()
                            {
                                ClientId      = this.clientId,
                                FencingToken  = this.coordinator.GetCurrentFencingToken(),
                                ResourceGroup = this.resourceGroup
                            });
                        }
                    }
                    catch (Exception ex)
                    {
                        NotifyOfError(onChangeActions, $"An unexpected error has caused shutdown. Automatic restart is set to {contextOptions.AutoRecoveryOnError}", contextOptions.AutoRecoveryOnError, ex);

                        if (contextOptions.AutoRecoveryOnError)
                        {
                            await WaitFor(contextOptions.RestartDelay, parentToken);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }
 private async Task InvokeOnErrorAsync(Task faultedTask, string message, bool autoRecoveryEnabled, OnChangeActions onChangeActions)
 {
     try
     {
         await faultedTask;
     }
     catch (Exception ex)
     {
         InvokeOnError(onChangeActions, message, autoRecoveryEnabled, ex);
     }
 }
 private void NotifyOfError(OnChangeActions onChangeActions, string message, bool autoRecoveryEnabled, Exception exception)
 {
     InvokeOnError(onChangeActions, message, autoRecoveryEnabled, exception);
     InvokeOnStop(onChangeActions);
 }
        private async Task NotifyOfErrorAsync(Task faultedTask, string message, bool autoRecoveryEnabled, OnChangeActions onChangeActions)
        {
            await InvokeOnErrorAsync(faultedTask, message, autoRecoveryEnabled, onChangeActions);

            InvokeOnStop(onChangeActions);
        }
示例#12
0
        private async Task TriggerRebalancingAsync(Guid coordinatorClientId,
                                                   ClientEvent clientEvent,
                                                   List <Client> clients,
                                                   List <string> resources,
                                                   OnChangeActions onChangeActions,
                                                   CancellationToken token)
        {
            logger.Info(coordinatorClientId.ToString(), "---------- Rebalancing triggered -----------");

            // request stop of all clients
            logger.Info(coordinatorClientId.ToString(), "COORDINATOR: Requested stop");
            if (clients.Any())
            {
                ModifyClientResult result = await clientService.StopActivityAsync(clientEvent.FencingToken, clients);

                if (result == ModifyClientResult.FencingTokenViolation)
                {
                    clientEvent.CoordinatorToken.FencingTokenViolation = true;
                    return;
                }

                if (result == ModifyClientResult.Error)
                {
                    logger.Error(coordinatorClientId.ToString(), "COORDINATOR: Rebalancing error");
                    return;
                }
            }

            // stop all resource activity in local coordinator client
            foreach (Action onStopAction in onChangeActions.OnStopActions)
            {
                onStopAction.Invoke();
            }

            // wait for all live clients to confirm stopped
            bool          allClientsWaiting = false;
            List <Client> clientsNow        = null;

            while (!allClientsWaiting && !token.IsCancellationRequested)
            {
                await WaitFor(TimeSpan.FromSeconds(5), token);

                clientsNow = await GetLiveClientsAsync(clientEvent, coordinatorClientId);

                if (!clientsNow.Any())
                {
                    allClientsWaiting = true;
                }
                else
                {
                    allClientsWaiting = clientsNow.All(x => x.ClientStatus == ClientStatus.Waiting);
                }
            }

            logger.Info(coordinatorClientId.ToString(), "COORDINATOR: Stop confirmed");

            // assign resources first to coordinator then to other live clients
            if (token.IsCancellationRequested)
            {
                return;
            }

            if (allClientsWaiting)
            {
                Queue <string>            resourcesToAssign   = new(resources);
                List <ClientStartRequest> clientStartRequests = new();
                int remainingClients   = clientsNow.Count + 1;
                int resourcesPerClient = Math.Max(1, resourcesToAssign.Count / remainingClients);

                ClientStartRequest coordinatorRequest = new() { ClientId = coordinatorClientId };
                while (coordinatorRequest.AssignedResources.Count < resourcesPerClient && resourcesToAssign.Any())
                {
                    coordinatorRequest.AssignedResources.Add(resourcesToAssign.Dequeue());
                }

                clientStartRequests.Add(coordinatorRequest);
                remainingClients--;

                foreach (Client client in clientsNow)
                {
                    resourcesPerClient = Math.Max(1, resourcesToAssign.Count / remainingClients);

                    ClientStartRequest request = new() { ClientId = client.ClientId };

                    while (request.AssignedResources.Count < resourcesPerClient && resourcesToAssign.Any())
                    {
                        request.AssignedResources.Add(resourcesToAssign.Dequeue());
                    }

                    clientStartRequests.Add(request);
                    remainingClients--;
                }

                if (token.IsCancellationRequested)
                {
                    return;
                }

                logger.Info(coordinatorClientId.ToString(), "COORDINATOR: Resources assigned");
                ModifyClientResult startResult =
                    await clientService.StartActivityAsync(clientEvent.FencingToken, clientStartRequests);

                if (startResult == ModifyClientResult.FencingTokenViolation)
                {
                    clientEvent.CoordinatorToken.FencingTokenViolation = true;
                    return;
                }

                if (startResult == ModifyClientResult.Error)
                {
                    logger.Error(coordinatorClientId.ToString(), "COORDINATOR: Rebalancing error");
                    return;
                }

                store.SetResources(new SetResourcesRequest
                {
                    AssignmentStatus = AssignmentStatus.ResourcesAssigned,
                    Resources        = coordinatorRequest.AssignedResources
                });
                foreach (Action <IList <string> > onStartAction in onChangeActions.OnStartActions)
                {
                    onStartAction.Invoke(coordinatorRequest.AssignedResources);
                }

                logger.Debug(coordinatorClientId.ToString(), "COORDINATOR: Local client started");

                List <Guid> clientIds = clientsNow.Select(x => x.ClientId).ToList();
                clientIds.Add(coordinatorClientId);
                this.clients   = clientIds;
                this.resources = resources;
                logger.Info(coordinatorClientId.ToString(), "---------- Activity Started -----------");
            }
            else
            {
                // log it
                logger.Info(coordinatorClientId.ToString(), "!!!");
            }
        }
示例#13
0
        public async Task ExecuteFollowerRoleAsync(Guid followerClientId,
                                                   ClientEvent clientEvent,
                                                   OnChangeActions onChangeActions,
                                                   CancellationToken token)
        {
            var self = await clientService.KeepAliveAsync(followerClientId);

            this.logger.Debug($"FOLLOWER : Keep Alive sent. Coordinator: {self.CoordinatorStatus} Client: {self.ClientStatus}");
            if (self.CoordinatorStatus == CoordinatorStatus.StopActivity)
            {
                if (self.ClientStatus == ClientStatus.Active)
                {
                    logger.Info("-------------- Stopping activity ---------------");
                    logger.Debug("FOLLOWER : Invoking on stop actions");
                    foreach (var stopAction in onChangeActions.OnStopActions)
                    {
                        stopAction.Invoke();
                    }

                    this.store.SetResources(new SetResourcesRequest()
                    {
                        AssignmentStatus = AssignmentStatus.AssignmentInProgress, Resources = new List <string>()
                    });
                    await clientService.SetClientStatusAsync(followerClientId, ClientStatus.Waiting);

                    logger.Info($"FOLLOWER : State= {self.ClientStatus} -> WAITING");
                }
                else
                {
                    logger.Debug($"FOLLOWER : State= {self.ClientStatus}");
                }
            }
            else if (self.CoordinatorStatus == CoordinatorStatus.ResourcesGranted)
            {
                if (self.ClientStatus == ClientStatus.Waiting)
                {
                    if (self.AssignedResources.Any())
                    {
                        this.store.SetResources(new SetResourcesRequest()
                        {
                            AssignmentStatus = AssignmentStatus.ResourcesAssigned, Resources = self.AssignedResources
                        });
                    }
                    else
                    {
                        this.store.SetResources(new SetResourcesRequest()
                        {
                            AssignmentStatus = AssignmentStatus.NoResourcesAssigned, Resources = new List <string>()
                        });
                    }

                    await clientService.SetClientStatusAsync(followerClientId, ClientStatus.Active);

                    if (self.AssignedResources.Any())
                    {
                        logger.Info($"FOLLOWER : Granted resources={string.Join(",", self.AssignedResources)}");
                    }
                    else
                    {
                        logger.Info("FOLLOWER : No resources available to be assigned.");
                    }
                    foreach (var startAction in onChangeActions.OnStartActions)
                    {
                        startAction.Invoke();
                    }

                    logger.Info($"FOLLOWER : State={self.ClientStatus} -> ACTIVE");
                    logger.Info("-------------- Activity started ---------------");
                }
                else
                {
                    logger.Debug($"FOLLOWER : State= {self.ClientStatus}");
                }
            }
        }
示例#14
0
 public async Task StartAsync(string group, OnChangeActions onChangeActions, CancellationToken token, ContextOptions contextOptions)
 {
     throw new NotImplementedException();
 }