예제 #1
0
        protected override async Task <Option <object> > HandleRequestInternal(Option <RestartRequest> payloadOption, CancellationToken cancellationToken)
        {
            RestartRequest payload = payloadOption.Expect(() => new ArgumentException("Request payload not found"));

            if (ExpectedSchemaVersion.CompareMajorVersion(payload.SchemaVersion, "restart module request schema") != 0)
            {
                Events.MismatchedMinorVersions(payload.SchemaVersion, ExpectedSchemaVersion);
            }

            Events.ProcessingRequest(payload);

            DeploymentConfigInfo deploymentConfigInfo = await this.configSource.GetDeploymentConfigInfoAsync();

            IEnvironment environment = this.environmentProvider.Create(deploymentConfigInfo.DeploymentConfig);
            ModuleSet    modules     = await environment.GetModulesAsync(cancellationToken);

            if (!modules.TryGetModule(payload.Id, out IModule module))
            {
                throw new InvalidOperationException($"Module {payload.Id} not found in the current environment");
            }

            Events.RestartingModule(payload.Id);
            ICommand restartCommand = await this.commandFactory.RestartAsync(module);

            await restartCommand.ExecuteAsync(cancellationToken);

            Events.RestartedModule(payload.Id);
            return(Option.None <object>());
        }
예제 #2
0
        public void TestApplyDiff(ModuleSet starting, Diff diff, ModuleSet expected)
        {
            ModuleSet updated = starting.ApplyDiff(diff);

            Assert.Equal(expected.Modules.Count, updated.Modules.Count);

            foreach (KeyValuePair <string, IModule> module in expected.Modules)
            {
                Assert.True(updated.TryGetModule(module.Key, out IModule updatedMod));
                Assert.Equal(module.Value, updatedMod);
            }
        }
예제 #3
0
        DiffState ProcessDiff(ModuleSet desired, ModuleSet current)
        {
            Diff diff = desired.Diff(current);

            IList <IModule>        added   = diff.Updated.Where(m => !current.TryGetModule(m.Name, out _)).ToList();
            IList <IRuntimeModule> removed = diff.Removed.Select(name => (IRuntimeModule)current.Modules[name]).ToList();

            // We are interested in 2 kinds of "updated" modules:
            //
            //  [1] someone pushed a new deployment for this device that changed something
            //      for an existing module
            //  [2] something changed in the runtime state of the module - for example, it
            //      had a tragic untimely death
            //
            // We need to be able to distinguish between the two cases because for the latter
            // we want to apply the restart policy and for the former we want to simply
            // re-deploy.
            IList <IModule> updateDeployed = diff.Updated.Except(added).ToList(); // TODO: Should we do module name comparisons below instead of object comparisons? Faster?

            IList <IRuntimeModule> currentRuntimeModules = current.Modules.Values
                                                           .Select(m => (IRuntimeModule)m)
                                                           .Except(removed) // TODO: Should we do module name comparisons below instead of object comparisons? Faster?
                                                           .Except(updateDeployed.Select(m => current.Modules[m.Name] as IRuntimeModule)).ToList();

            IList <IRuntimeModule> updateStateChanged = currentRuntimeModules
                                                        .Where(m => m.DesiredStatus == ModuleStatus.Running && m.RuntimeStatus != ModuleStatus.Running).ToList();

            // Apart from all of the lists above, there can be modules in "current" where neither
            // the desired state has changed nor the runtime state has changed. For example, a module
            // that is expected to be "running" continues to run just fine. This won't show up in
            // any of the lists above. But we are still interested in these because we want to clear
            // the restart stats on them when they have been behaving well for "intensiveCareTime".
            //
            // Note that we are only interested in "running" modules. If there's a module that was
            // expected to be in the "stopped" state and continues to be in the "stopped" state, that
            // is not very interesting to us.
            IList <IRuntimeModule> runningGreat = currentRuntimeModules
                                                  .Where(m => m.DesiredStatus == ModuleStatus.Running && m.RuntimeStatus == ModuleStatus.Running).ToList();

            return(added, updateDeployed, updateStateChanged, removed, runningGreat);
        }
예제 #4
0
        async Task <IList <(ICommand command, string module)> > ProcessDesiredStatusChangedModules(IList <IModule> desiredStatusChanged, ModuleSet current)
        {
            var commands = new List <(ICommand command, string module)>();

            foreach (IModule module in desiredStatusChanged)
            {
                if (current.TryGetModule(module.Name, out IModule currentModule))
                {
                    if (module.DesiredStatus != ((IRuntimeModule)currentModule).RuntimeStatus)
                    {
                        if (module.DesiredStatus == ModuleStatus.Running)
                        {
                            ICommand startCommand = await this.commandFactory.StartAsync(currentModule);

                            commands.Add((startCommand, module.Name));
                        }
                        else if (module.DesiredStatus == ModuleStatus.Stopped)
                        {
                            ICommand stopCommand = await this.commandFactory.StopAsync(currentModule);

                            commands.Add((stopCommand, module.Name));
                        }
                        else
                        {
                            Events.InvalidDesiredState(module);
                        }
                    }
                }
                else
                {
                    Events.CurrentModuleNotFound(module.Name);
                }
            }

            return(commands);
        }
예제 #5
0
        public async Task <Plan> PlanAsync(ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo,
                                           IImmutableDictionary <string, IModuleIdentity> moduleIdentities)
        {
            Events.LogDesired(desired);
            Events.LogCurrent(current);
            // extract list of modules that need attention
            var(added, updateDeployed, updateStateChanged, removed, runningGreat) = this.ProcessDiff(desired, current);

            var     updateRuntimeCommands = new List <ICommand>();
            IModule edgeAgentModule       = updateDeployed.FirstOrDefault(m => m.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase));

            if (edgeAgentModule != null)
            {
                updateDeployed.Remove(edgeAgentModule);
                ICommand updateEdgeAgentCommand = await this.commandFactory.UpdateEdgeAgentAsync(new ModuleWithIdentity(edgeAgentModule, moduleIdentities.GetValueOrDefault(edgeAgentModule.Name)), runtimeInfo);

                updateRuntimeCommands.Add(updateEdgeAgentCommand);
            }

            // create "stop" commands for modules that have been updated/removed
            IEnumerable <Task <ICommand> > stopTasks = updateDeployed
                                                       .Concat(removed)
                                                       .Select(m => this.commandFactory.StopAsync(m));
            IEnumerable <ICommand> stop = await Task.WhenAll(stopTasks);

            // create "remove" commands for modules that are being deleted in this deployment
            IEnumerable <Task <ICommand> > removeTasks = removed.Select(m => this.commandFactory.RemoveAsync(m));
            IEnumerable <ICommand>         remove      = await Task.WhenAll(removeTasks);

            // remove any saved state we might have for modules that are being removed or
            // are being updated because of a deployment
            IEnumerable <Task <ICommand> > removeStateTasks = removed
                                                              .Concat(updateDeployed)
                                                              .Select(m => this.commandFactory.WrapAsync(new RemoveFromStoreCommand <ModuleState>(this.store, m.Name)));
            IEnumerable <ICommand> removeState = await Task.WhenAll(removeStateTasks);

            // create pull, create, update and start commands for added/updated modules
            IEnumerable <ICommand> addedCommands = await this.ProcessAddedUpdatedModules(
                added,
                m => this.commandFactory.CreateAsync(new ModuleWithIdentity(m, moduleIdentities.GetValueOrDefault(m.Name)), runtimeInfo)
                );

            IEnumerable <ICommand> updatedCommands = await this.ProcessAddedUpdatedModules(
                updateDeployed,
                m =>
            {
                current.TryGetModule(m.Name, out IModule currentModule);
                return(this.commandFactory.UpdateAsync(
                           currentModule,
                           new ModuleWithIdentity(m, moduleIdentities.GetValueOrDefault(m.Name)),
                           runtimeInfo));
            }
                );

            // apply restart policy for modules that are not in the deployment list and aren't running
            IEnumerable <Task <ICommand> > restartTasks = this.ApplyRestartPolicy(updateStateChanged.Where(m => !m.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)));
            IEnumerable <ICommand>         restart      = await Task.WhenAll(restartTasks);

            // clear the "restartCount" and "lastRestartTime" values for running modules that have been up
            // for more than "IntensiveCareTime" & still have an entry for them in the store
            IEnumerable <ICommand> resetHealthStatus = await this.ResetStatsForHealthyModulesAsync(runningGreat);

            IList <ICommand> commands = updateRuntimeCommands
                                        .Concat(stop)
                                        .Concat(remove)
                                        .Concat(removeState)
                                        .Concat(addedCommands)
                                        .Concat(updatedCommands)
                                        .Concat(restart)
                                        .Concat(resetHealthStatus)
                                        .ToList();

            Events.PlanCreated(commands);
            return(new Plan(commands));
        }
예제 #6
0
        public async Task <Plan> PlanAsync(
            ModuleSet desired,
            ModuleSet current,
            IRuntimeInfo runtimeInfo,
            IImmutableDictionary <string, IModuleIdentity> moduleIdentities)
        {
            Events.LogDesired(desired);
            Events.LogCurrent(current);
            // extract list of modules that need attention
            (IList <IModule> added, IList <IModule> updateDeployed, IList <IModule> desiredStatusChanged, IList <IRuntimeModule> updateStateChanged, IList <IRuntimeModule> removed, IList <IRuntimeModule> runningGreat) = this.ProcessDiff(desired, current);

            List <ICommand> updateRuntimeCommands = await this.GetUpdateRuntimeCommands(updateDeployed, moduleIdentities, runtimeInfo);

            // create "stop" commands for modules that have been removed
            IEnumerable <Task <ICommand> > stopTasks = removed
                                                       .Select(m => this.commandFactory.StopAsync(m));
            IEnumerable <ICommand> stop = await Task.WhenAll(stopTasks);

            // create "remove" commands for modules that are being deleted in this deployment
            IEnumerable <Task <ICommand> > removeTasks = removed.Select(m => this.commandFactory.RemoveAsync(m));
            IEnumerable <ICommand>         remove      = await Task.WhenAll(removeTasks);

            // create pull, create, update and start commands for added/updated modules
            IEnumerable <ICommand> addedCommands = await this.ProcessAddedUpdatedModules(
                added,
                moduleIdentities,
                m => this.commandFactory.CreateAsync(m, runtimeInfo));

            IEnumerable <ICommand> updatedCommands = await this.ProcessAddedUpdatedModules(
                updateDeployed,
                moduleIdentities,
                m =>
            {
                current.TryGetModule(m.Module.Name, out IModule currentModule);
                return(this.commandFactory.UpdateAsync(
                           currentModule,
                           m,
                           runtimeInfo));
            });

            // Get commands to start / stop modules whose desired status has changed.
            IList <(ICommand command, string module)> desiredStatedChangedCommands = await this.ProcessDesiredStatusChangedModules(desiredStatusChanged, current);

            // Get commands for modules that have stopped/failed
            IEnumerable <ICommand> stateChangedCommands = await this.ProcessStateChangedModules(updateStateChanged.Where(m => !m.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)).ToList());

            // remove any saved state we might have for modules that are being removed or
            // are being updated because of a deployment
            IEnumerable <Task <ICommand> > removeStateTasks = removed
                                                              .Concat(updateDeployed)
                                                              .Select(m => m.Name)
                                                              .Concat(desiredStatedChangedCommands.Select(d => d.module))
                                                              .Select(m => this.commandFactory.WrapAsync(new RemoveFromStoreCommand <ModuleState>(this.store, m)));
            IEnumerable <ICommand> removeState = await Task.WhenAll(removeStateTasks);

            // clear the "restartCount" and "lastRestartTime" values for running modules that have been up
            // for more than "IntensiveCareTime" & still have an entry for them in the store
            IEnumerable <ICommand> resetHealthStatus = await this.ResetStatsForHealthyModulesAsync(runningGreat);

            IList <ICommand> commands = updateRuntimeCommands
                                        .Concat(stop)
                                        .Concat(remove)
                                        .Concat(removeState)
                                        .Concat(addedCommands)
                                        .Concat(updatedCommands)
                                        .Concat(stateChangedCommands)
                                        .Concat(desiredStatedChangedCommands.Select(d => d.command))
                                        .Concat(resetHealthStatus)
                                        .ToList();

            Events.PlanCreated(commands);
            return(new Plan(commands));
        }
예제 #7
0
 async Task <ICommand> CreateOrUpdate(ModuleSet current, IModule desiredMod, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> moduleIdentities) =>
 current.TryGetModule(desiredMod.Name, out IModule currentMod)
         ? await this.commandFactory.UpdateAsync(currentMod, new ModuleWithIdentity(desiredMod, moduleIdentities.GetValueOrDefault(desiredMod.Name)), runtimeInfo)
         : await this.commandFactory.CreateAsync(new ModuleWithIdentity(desiredMod, moduleIdentities.GetValueOrDefault(desiredMod.Name)), runtimeInfo);