[k8s] Map possible Pod/Container status to ModuleStatus (#2265)

* Mapping the pod status to the module status for Pending/Failed/Unknown

* Created Kubernetes Environment & its Provider to handle the module status reports to the IOT hub

* Updated the unit tests for handling the change in pod status to module status

* Removed blank line

* Pod status checks for container status before converting it to module status

* Unit test to reflect the new model of determining module status

* Update edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironment.cs

Co-Authored-By: Denis Molokanov <dmolokanov@users.noreply.github.com>

* Updated unit tests

* fixing the braces

* fixed braces 2

* Fix build errors

* Debug mode changes to test code flow

* Updated status in debug mode

* changed moduleruntime status instead of dockerruntimestatus which gets reported to IOT

* Removed debug logs

* Resolve conflicts

* fix typo

* Fix typo in unit test

* More typo fix

* Simplified unit test

* Updated DockerModule priority parameter changes for k8s environment

* Fix typo in unit test names

* Updated the test cases with single responsbility approach

* start time format changes

* Refractoring

Co-authored-by: Denis Molokanov <dmolokanov@users.noreply.github.com>
This commit is contained in:
gaya 2020-01-21 20:21:16 -08:00 коммит произвёл GitHub
Родитель b5f814cd88
Коммит d824fb3991
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 451 добавлений и 119 удалений

Просмотреть файл

@ -567,32 +567,32 @@ Global
{08986FED-9283-47F1-B938-86D9C6E753E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08986FED-9283-47F1-B938-86D9C6E753E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08986FED-9283-47F1-B938-86D9C6E753E1}.Release|Any CPU.Build.0 = Release|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47AA5965-5826-489D-B74B-314A61629121}.Release|Any CPU.Build.0 = Release|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B736B03C-1185-4AE5-87B7-665C6790F50F}.Release|Any CPU.Build.0 = Release|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Release|Any CPU.Build.0 = Release|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
{8181EB49-62CE-495B-8078-08DCF8C30541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

Просмотреть файл

@ -82,5 +82,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
public const string EdgeK8sObjectOwnerUidKey = "EdgeK8sObjectOwnerUid";
public const string RunAsNonRootKey = "RunAsNonRoot";
public const string UnknownImage = "unknown";
}
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Docker;
using Microsoft.Azure.Devices.Edge.Agent.Docker.Models;
using Microsoft.Azure.Devices.Edge.Storage;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Extensions.Logging;
/// <summary>
/// This implementation gets the module runtime information from KubernetesRuntimeInfoProvider and
/// the configuration information from the deploymentConfig.
/// TODO: This could be made generic (not docker specific) and moved to Core.
/// </summary>
public class KubernetesEnvironment : IEnvironment
{
readonly IRuntimeInfoProvider moduleStatusProvider;
readonly IEntityStore<string, ModuleState> moduleStateStore;
readonly string operatingSystemType;
readonly string architecture;
readonly string version;
readonly DeploymentConfig deploymentConfig;
public KubernetesEnvironment(
IRuntimeInfoProvider moduleStatusProvider,
DeploymentConfig deploymentConfig,
IEntityStore<string, ModuleState> moduleStateStore,
string operatingSystemType,
string architecture,
string version)
{
this.moduleStatusProvider = moduleStatusProvider;
this.deploymentConfig = deploymentConfig;
this.moduleStateStore = moduleStateStore;
this.operatingSystemType = operatingSystemType;
this.architecture = architecture;
this.version = version;
}
public async Task<ModuleSet> GetModulesAsync(CancellationToken token)
{
IEnumerable<ModuleRuntimeInfo> moduleStatuses = await this.moduleStatusProvider.GetModules(token);
var modules = new List<IModule>();
ModuleSet moduleSet = this.deploymentConfig.GetModuleSet();
foreach (ModuleRuntimeInfo moduleRuntimeInfo in moduleStatuses)
{
if (moduleRuntimeInfo.Type != "docker" || !(moduleRuntimeInfo is ModuleRuntimeInfo<DockerReportedConfig> dockerRuntimeInfo))
{
Events.InvalidModuleType(moduleRuntimeInfo);
continue;
}
if (!moduleSet.Modules.TryGetValue(dockerRuntimeInfo.Name, out IModule configModule) || !(configModule is DockerModule dockerModule))
{
dockerModule = new DockerModule(dockerRuntimeInfo.Name, string.Empty, ModuleStatus.Unknown, Core.RestartPolicy.Unknown, new DockerConfig(Constants.UnknownImage, new CreateContainerParameters()), ImagePullPolicy.OnCreate, Core.Constants.HighestPriority, new ConfigurationInfo(), null);
}
Option<ModuleState> moduleStateOption = await this.moduleStateStore.Get(moduleRuntimeInfo.Name);
ModuleState moduleState = moduleStateOption.GetOrElse(new ModuleState(0, moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue)));
string image = !string.IsNullOrWhiteSpace(dockerRuntimeInfo.Config.Image) ? dockerRuntimeInfo.Config.Image : dockerModule.Config.Image;
var dockerReportedConfig = new DockerReportedConfig(image, dockerModule.Config.CreateOptions, dockerRuntimeInfo.Config.ImageHash);
IModule module;
switch (moduleRuntimeInfo.Name)
{
case Core.Constants.EdgeHubModuleName:
module = new EdgeHubDockerRuntimeModule(
dockerModule.DesiredStatus,
dockerModule.RestartPolicy,
dockerReportedConfig,
(int)dockerRuntimeInfo.ExitCode,
moduleRuntimeInfo.Description,
moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue),
moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue),
moduleState.RestartCount,
moduleState.LastRestartTimeUtc,
moduleRuntimeInfo.ModuleStatus,
dockerModule.ImagePullPolicy,
dockerModule.Priority,
dockerModule.ConfigurationInfo,
dockerModule.Env);
break;
case Core.Constants.EdgeAgentModuleName:
module = new EdgeAgentDockerRuntimeModule(
dockerReportedConfig,
moduleRuntimeInfo.ModuleStatus,
(int)dockerRuntimeInfo.ExitCode,
moduleRuntimeInfo.Description,
moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue),
moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue),
dockerModule.ImagePullPolicy,
dockerModule.ConfigurationInfo,
dockerModule.Env);
break;
default:
module = new DockerRuntimeModule(
moduleRuntimeInfo.Name,
dockerModule.Version,
dockerModule.DesiredStatus,
dockerModule.RestartPolicy,
dockerReportedConfig,
(int)dockerRuntimeInfo.ExitCode,
moduleRuntimeInfo.Description,
moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue),
moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue),
moduleState.RestartCount,
moduleState.LastRestartTimeUtc,
moduleRuntimeInfo.ModuleStatus,
dockerModule.ImagePullPolicy,
dockerModule.Priority,
dockerModule.ConfigurationInfo,
dockerModule.Env);
break;
}
modules.Add(module);
}
return ModuleSet.Create(modules.ToArray());
}
public Task<IRuntimeInfo> GetRuntimeInfoAsync()
{
IRuntimeInfo runtimeInfo = this.deploymentConfig.Runtime;
if (runtimeInfo?.Type == "docker")
{
var platform = new DockerPlatformInfo(this.operatingSystemType, this.architecture, this.version);
DockerRuntimeConfig config = (runtimeInfo as DockerRuntimeInfo)?.Config;
runtimeInfo = new DockerReportedRuntimeInfo(runtimeInfo.Type, config, platform);
}
else if (runtimeInfo == null || runtimeInfo is UnknownRuntimeInfo)
{
var platform = new DockerPlatformInfo(this.operatingSystemType, this.architecture, this.version);
runtimeInfo = new DockerReportedUnknownRuntimeInfo(platform);
}
return Task.FromResult(runtimeInfo);
}
static class Events
{
const int IdStart = AgentEventIds.DockerEnvironment;
static readonly ILogger Log = Logger.Factory.CreateLogger<KubernetesEnvironment>();
enum EventIds
{
InvalidModuleType = IdStart
}
public static void InvalidModuleType(ModuleRuntimeInfo moduleRuntimeInfo)
{
Log.LogWarning((int)EventIds.InvalidModuleType, $"Module {moduleRuntimeInfo.Name} has an invalid module type '{moduleRuntimeInfo.Type}'. Expected type 'docker'");
}
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Storage;
using Microsoft.Azure.Devices.Edge.Util;
public class KubernetesEnvironmentProvider : IEnvironmentProvider
{
readonly IRuntimeInfoProvider moduleStatusProvider;
readonly IEntityStore<string, ModuleState> store;
readonly string operatingSystemType;
readonly string architecture;
readonly string version;
KubernetesEnvironmentProvider(
IRuntimeInfoProvider runtimeInfoProvider,
IEntityStore<string, ModuleState> store,
string operatingSystemType,
string architecture,
string version)
{
this.moduleStatusProvider = runtimeInfoProvider;
this.store = Preconditions.CheckNotNull(store, nameof(store));
this.operatingSystemType = operatingSystemType;
this.architecture = architecture;
this.version = version;
}
public static async Task<KubernetesEnvironmentProvider> CreateAsync(
IRuntimeInfoProvider runtimeInfoProvider,
IEntityStore<string, ModuleState> store,
CancellationToken token)
{
SystemInfo systemInfo = await Preconditions.CheckNotNull(runtimeInfoProvider, nameof(runtimeInfoProvider)).GetSystemInfo(token);
return new KubernetesEnvironmentProvider(
runtimeInfoProvider,
store,
systemInfo.OperatingSystemType,
systemInfo.Architecture,
systemInfo.Version);
}
public IEnvironment Create(DeploymentConfig deploymentConfig) =>
new KubernetesEnvironment(
this.moduleStatusProvider,
deploymentConfig,
this.store,
this.operatingSystemType,
this.architecture,
this.version);
}
}

Просмотреть файл

@ -16,7 +16,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment;
using Microsoft.Azure.Devices.Edge.Util;
using AgentDocker = Microsoft.Azure.Devices.Edge.Agent.Docker;
public class KubernetesRuntimeInfoProvider : IRuntimeInfoProvider, IRuntimeInfoSource
{
readonly string deviceNamespace;

Просмотреть файл

@ -14,7 +14,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment
public static ModuleRuntimeInfo ConvertToRuntime(this V1Pod pod, string name)
{
Option<V1ContainerStatus> containerStatus = GetContainerByName(name, pod);
ReportedModuleStatus moduleStatus = ConvertPodStatusToModuleStatus(Option.Maybe(pod.Status));
ReportedModuleStatus moduleStatus = ConvertPodStatusToModuleStatus(Option.Maybe(pod.Status), containerStatus);
RuntimeData runtimeData = GetRuntimeData(containerStatus.OrDefault());
string moduleName = string.Empty;
@ -43,7 +43,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment
return Option.Maybe(status);
}
static ReportedModuleStatus ConvertPodStatusToModuleStatus(Option<V1PodStatus> podStatus)
static ReportedModuleStatus ConvertPodStatusToModuleStatus(Option<V1PodStatus> podStatus, Option<V1ContainerStatus> containerStatus)
{
return podStatus.Map(
status =>
@ -51,17 +51,59 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment
switch (status.Phase)
{
case "Running":
return new ReportedModuleStatus(ModuleStatus.Running, $"Started at {status.StartTime}");
case "Failed":
{
return containerStatus.Map(c =>
{
if (c.State.Waiting != null)
{
return new ReportedModuleStatus(ModuleStatus.Backoff, $"Module in Back-off reason: {c.State.Waiting.Reason}");
}
else if (c.State.Terminated != null)
{
if (c.State.Terminated.ExitCode != 0)
return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {c.State.Terminated.Reason}");
else
return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {c.State.Terminated.Reason}");
}
else
{
return new ReportedModuleStatus(ModuleStatus.Running, $"Started at {c.State.Running.StartedAt}");
}
}).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed with container status Unknown More Info: K8s reason: {status.Reason} with message: {status.Message}"));
}
case "Pending":
{
return containerStatus.Map(c =>
{
if (c.State.Waiting != null)
{
return new ReportedModuleStatus(ModuleStatus.Backoff, $"Module in Back-off reason: {c.State.Waiting.Reason}");
}
else if (c.State.Terminated != null)
{
if (c.State.Terminated.ExitCode != 0)
return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {c.State.Terminated.Reason}");
else
return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {c.State.Terminated.Reason}");
}
else
{
return new ReportedModuleStatus(ModuleStatus.Backoff, $"Started at {c.State.Running.StartedAt}");
}
}).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed with container status Unknown More Info: K8s reason: {status.Reason} with message: {status.Message}"));
}
case "Unknown":
return new ReportedModuleStatus(ModuleStatus.Failed, status.Reason);
return new ReportedModuleStatus(ModuleStatus.Unknown, $"Module status Unknown reason: {status.Reason}");
case "Succeeded":
return new ReportedModuleStatus(ModuleStatus.Stopped, status.Reason);
return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {status.Reason} with message: {status.Message}");
case "Failed":
return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {status.Reason} with message: {status.Message}");
default:
throw new InvalidOperationException($"Invalid pod status {status.Phase}");
}
}).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Failed, "Unable to get pod status"));
}).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Unknown, "Unable to get pod status"));
}
static RuntimeData GetRuntimeData(V1ContainerStatus status)

Просмотреть файл

@ -227,17 +227,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules
.As<IRuntimeInfoSource>()
.SingleInstance();
// Task<IBackupSource>
builder.Register(
c =>
{
var serde = c.Resolve<ISerde<DeploymentConfigInfo>>();
IDeploymentBackupSource backupSource = new DeploymentSecretBackup(Constants.EdgeAgentBackupName, this.deviceNamespace, this.moduleOwner, serde, c.Resolve<IKubernetes>());
return Task.FromResult(backupSource);
})
.As<Task<IDeploymentBackupSource>>()
.SingleInstance();
// KubernetesDeploymentProvider
builder.Register(
c => new KubernetesDeploymentMapper(
@ -330,10 +319,9 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules
{
CancellationTokenSource tokenSource = new CancellationTokenSource(SystemInfoTimeout);
var moduleStateStore = await c.Resolve<Task<IEntityStore<string, ModuleState>>>();
var restartPolicyManager = c.Resolve<IRestartPolicyManager>();
IRuntimeInfoProvider runtimeInfoProvider = c.Resolve<IRuntimeInfoProvider>();
IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager, tokenSource.Token);
return dockerEnvironmentProvider;
IEnvironmentProvider kubernetesEnvironmentProvider = await KubernetesEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, tokenSource.Token);
return kubernetesEnvironmentProvider;
})
.As<Task<IEnvironmentProvider>>()
.SingleInstance();

Просмотреть файл

@ -160,108 +160,189 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test
}
}
[Fact]
public async Task ReturnsLastKnowModuleState()
public static V1Pod CreatePodInPhaseWithContainerStatus(string podPhase, V1ContainerState containerState)
=> new V1Pod
{
Metadata = new V1ObjectMeta
{
Name = "module-a-abc123",
Labels = new Dictionary<string, string>
{
[KubernetesConstants.K8sEdgeModuleLabel] = "module-a"
},
Annotations = new Dictionary<string, string>
{
[KubernetesConstants.K8sEdgeOriginalModuleId] = "Module-A"
}
},
Status = new V1PodStatus
{
Phase = podPhase,
ContainerStatuses = new List<V1ContainerStatus>()
{
new V1ContainerStatus
{
Name = "module-a",
State = containerState,
}
}
}
};
public static V1Pod CreatePodWithPodParametersOnly(string podPhase, string podReason, string podMessage)
=> new V1Pod
{
Metadata = new V1ObjectMeta
{
Name = "module-a-abc123",
Labels = new Dictionary<string, string>
{
[KubernetesConstants.K8sEdgeModuleLabel] = "module-a"
},
Annotations = new Dictionary<string, string>
{
[KubernetesConstants.K8sEdgeOriginalModuleId] = "Module-A"
}
},
Status = new V1PodStatus { Phase = podPhase, Reason = podReason, Message = podMessage },
};
public static IEnumerable<object[]> GetListOfPodsInRunningPhase()
{
return new[]
{
new object[]
{
CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(waiting: new V1ContainerStateWaiting("Waiting", "CrashBackLoopOff"))),
"Module in Back-off reason: CrashBackLoopOff",
ModuleStatus.Backoff
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(terminated: new V1ContainerStateTerminated(0, reason: "Completed"))),
"Module Stopped reason: Completed",
ModuleStatus.Stopped
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(terminated: new V1ContainerStateTerminated(139, reason: "Segmentation Fault"))),
"Module Failed reason: Segmentation Fault",
ModuleStatus.Failed
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(running: new V1ContainerStateRunning(startedAt: DateTime.Parse("2019-06-12T16:11:22Z")))),
"Started at " + DateTime.Parse("2019-06-12T16:11:22Z"),
ModuleStatus.Running
}
};
}
[Theory]
[MemberData(nameof(GetListOfPodsInRunningPhase))]
public async Task ReturnModuleStatusWhenPodIsRunning(V1Pod pod, string description, ModuleStatus status)
{
var client = new Mock<IKubernetes>(MockBehavior.Strict);
var moduleManager = new Mock<IModuleManager>(MockBehavior.Strict);
var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object);
foreach (V1Pod pod in BuildPodList().Values)
{
runtimeInfo.CreateOrUpdateAddPodInfo(pod);
ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single();
Assert.Equal(status, info.ModuleStatus);
Assert.Equal(description, info.Description);
}
Dictionary<string, V1Pod> modified = BuildPodList();
DateTime agentStartTime = new DateTime(2019, 6, 11);
modified["edgeagent"].Status.Phase = "Running";
modified["edgeagent"].Status.StartTime = agentStartTime;
string pendingDescription = "0/1 node available";
modified["edgehub"].Status.Phase = "Pending";
modified["edgehub"].Status.Reason = pendingDescription;
string finishedDescription = "Pod finished";
modified["simulatedtemperaturesensor"].Status.Phase = "Succeeded";
modified["simulatedtemperaturesensor"].Status.Reason = finishedDescription;
modified["simulatedtemperaturesensor"].Status.ContainerStatuses[1].State.Running = null;
modified["simulatedtemperaturesensor"].Status.ContainerStatuses[1].State.Terminated = new V1ContainerStateTerminated(139, finishedAt: DateTime.Parse("2019-06-12T16:13:07Z"), startedAt: DateTime.Parse("2019-06-12T16:11:22Z"));
foreach (V1Pod pod in modified.Values)
public static IEnumerable<object[]> GetListOfPodsInPendingPhase()
{
return new[]
{
new object[]
{
CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(waiting: new V1ContainerStateWaiting("Waiting", "CrashBackLoopOff"))),
"Module in Back-off reason: CrashBackLoopOff",
ModuleStatus.Backoff
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(terminated: new V1ContainerStateTerminated(0, reason: "Completed"))),
"Module Stopped reason: Completed",
ModuleStatus.Stopped
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(terminated: new V1ContainerStateTerminated(139, reason: "Segmentation Fault"))),
"Module Failed reason: Segmentation Fault",
ModuleStatus.Failed
},
new object[]
{
CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(running: new V1ContainerStateRunning(startedAt: DateTime.Parse("2019-06-12T16:11:22Z")))),
"Started at " + DateTime.Parse("2019-06-12T16:11:22Z"),
ModuleStatus.Backoff
}
};
}
[Theory]
[MemberData(nameof(GetListOfPodsInPendingPhase))]
public async Task ReturnModuleStatusWhenPodIsPending(V1Pod pod, string description, ModuleStatus status)
{
var client = new Mock<IKubernetes>(MockBehavior.Strict);
var moduleManager = new Mock<IModuleManager>(MockBehavior.Strict);
var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object);
runtimeInfo.CreateOrUpdateAddPodInfo(pod);
ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single();
Assert.Equal(status, info.ModuleStatus);
Assert.Equal(description, info.Description);
}
var modules = (await runtimeInfo.GetModules(CancellationToken.None)).ToList();
Assert.Equal(3, modules.Count);
// Normal operation statuses
foreach (var i in modules)
public static IEnumerable<object[]> GetListOfPodsInAbnormalPhase()
{
if (string.Equals("edgeAgent", i.Name))
return new[]
{
Assert.Equal(ModuleStatus.Running, i.ModuleStatus);
Assert.Equal($"Started at {agentStartTime.ToString()}", i.Description);
new object[]
{
CreatePodWithPodParametersOnly("Running", "Unschedulable", "persistentvolumeclaim module-a-pvc not found"),
"Module Failed with container status Unknown More Info: K8s reason: Unschedulable with message: persistentvolumeclaim module-a-pvc not found",
ModuleStatus.Failed
},
new object[]
{
CreatePodWithPodParametersOnly("Unknown", "Unknown", "Unknown"),
"Module status Unknown reason: Unknown",
ModuleStatus.Unknown
},
new object[]
{
CreatePodWithPodParametersOnly("Failed", "Terminated", "Non-zero exit code"),
"Module Failed reason: Terminated with message: Non-zero exit code",
ModuleStatus.Failed
},
new object[]
{
CreatePodWithPodParametersOnly("Succeeded", "Completed", "Zero exit code"),
"Module Stopped reason: Completed with message: Zero exit code",
ModuleStatus.Stopped
}
else if (string.Equals("edgeHub", i.Name))
{
Assert.Equal(ModuleStatus.Failed, i.ModuleStatus);
Assert.Equal(pendingDescription, i.Description);
Assert.Equal(Option.None<DateTime>(), i.ExitTime);
}
else if (string.Equals("SimulatedTemperatureSensor", i.Name))
{
Assert.Equal(ModuleStatus.Stopped, i.ModuleStatus);
Assert.Equal(finishedDescription, i.Description);
Assert.Equal(new DateTime(2019, 6, 12), i.StartTime.OrDefault().Date);
Assert.Equal(new DateTime(2019, 6, 12), i.ExitTime.OrDefault().Date);
}
else
{
Assert.True(false, $"Missing module {i.Name} in validation");
};
}
if (i is ModuleRuntimeInfo<DockerReportedConfig> d)
{
Assert.NotEqual("unknown:unknown", d.Config.Image);
}
}
string unknownDescription = "Could not reach pod";
modified["edgeagent"].Status.Phase = "Unknown";
modified["edgeagent"].Status.Reason = unknownDescription;
modified["edgehub"].Status = null;
foreach (V1Pod pod in modified.Values)
[Theory]
[MemberData(nameof(GetListOfPodsInAbnormalPhase))]
public async Task ReturnModuleStatusWhenPodIsAbnormal(V1Pod pod, string description, ModuleStatus status)
{
var client = new Mock<IKubernetes>(MockBehavior.Strict);
var moduleManager = new Mock<IModuleManager>(MockBehavior.Strict);
var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object);
runtimeInfo.CreateOrUpdateAddPodInfo(pod);
}
var abnormalModules = (await runtimeInfo.GetModules(CancellationToken.None)).ToList();
Assert.Equal(3, modules.Count);
ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single();
// Abnormal operation statuses
foreach (var i in abnormalModules)
{
if (string.Equals("edgeAgent", i.Name))
{
Assert.Equal(ModuleStatus.Failed, i.ModuleStatus);
Assert.Equal(unknownDescription, i.Description);
}
else if (string.Equals("edgeHub", i.Name))
{
Assert.Equal(ModuleStatus.Failed, i.ModuleStatus);
Assert.Equal("Unable to get pod status", i.Description);
}
else if (string.Equals("SimulatedTemperatureSensor", i.Name))
{
}
else
{
Assert.True(false, $"Missing module {i.Name} in validation");
}
}
Assert.Equal(status, info.ModuleStatus);
Assert.Equal(description, info.Description);
}
static Dictionary<string, V1Pod> BuildPodList()