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>()); }
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); } }
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); }
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); }
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)); }
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)); }
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);