From 18f5c86395996cae906b70102e6460fab4333d9c Mon Sep 17 00:00:00 2001
From: Jake Friedman <31931010+spaceisfun@users.noreply.github.com>
Date: Wed, 23 Sep 2020 13:00:46 -0700
Subject: [PATCH] Get container info from FactoryOS registry instead of cmdiag;
Create service events for container status (#20)
* Get container info from FactoryOS registry instead of cmdiag; Create service events for container status
---
src/App/App.xaml.cs | 29 +-
src/CoreLibrary/IPCInterface.cs | 16 +-
.../Resources/Resources.Designer.cs | 18 ++
src/CoreLibrary/Resources/Resources.resx | 8 +
src/Service/ServiceExe.cs | 252 ++++++++++++------
src/custom.props | 2 +-
6 files changed, 215 insertions(+), 110 deletions(-)
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index 5aedc6a1..08b86fba 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -433,28 +433,6 @@ public void OnIpcConnected()
await Task.Delay(1000);
}
});
-
-
- Task.Run(async () =>
- {
- while (true)
- {
- try
- {
- IsContainerRunning = await Client.IsContainerRunning();
- }
- catch (FactoryOrchestratorConnectionException)
- {
- OnConnectionFailure();
- while (OnConnectionPage || (!Client.IsConnected))
- {
- await Task.Delay(1000);
- }
- }
-
- await Task.Delay(10000);
- }
- });
}
}
@@ -546,8 +524,15 @@ private async Task HandleServiceEvents()
await HandleExternalTaskRunAsync(run);
}
}
+ break;
+ case ServiceEventType.ContainerConnected:
+ IsContainerRunning = true;
+ break;
+ case ServiceEventType.ContainerDisconnected:
+ IsContainerRunning = false;
break;
default:
+ // Ignore other events
break;
}
}
diff --git a/src/CoreLibrary/IPCInterface.cs b/src/CoreLibrary/IPCInterface.cs
index a5e2584b..db83f276 100644
--- a/src/CoreLibrary/IPCInterface.cs
+++ b/src/CoreLibrary/IPCInterface.cs
@@ -38,19 +38,27 @@ public enum ServiceEventType
///
WaitingForContainerTaskRun,
///
- /// The Factory Orchestrator Serivce threw an exception.
+ /// The Factory Orchestrator Service threw an exception.
///
ServiceError,
///
- /// The Factory Orchestrator Serivce is starting. It can now communicate with clients, but boot tasks may not be complete.
+ /// The Factory Orchestrator Service is starting. It can now communicate with clients, but boot tasks may not be complete.
///
ServiceStart,
///
- /// The Factory Orchestrator Serivce is fully started. Boot tasks are completed.
+ /// The Factory Orchestrator Service is fully started. Boot tasks are completed.
///
BootTasksComplete,
///
- /// An unknown Factory Orchestrator Serivce event occurred.
+ /// The Factory Orchestrator Service is connected to a container also running a compatible version of Factory Orchestrator Service.
+ ///
+ ContainerConnected,
+ ///
+ /// The Factory Orchestrator Service is disconnected from a container also running a compatible version of Factory Orchestrator Service.
+ ///
+ ContainerDisconnected,
+ ///
+ /// An unknown Factory Orchestrator Service event occurred.
///
Unknown = int.MaxValue
}
diff --git a/src/CoreLibrary/Resources/Resources.Designer.cs b/src/CoreLibrary/Resources/Resources.Designer.cs
index c5e4afa5..9de17ed1 100644
--- a/src/CoreLibrary/Resources/Resources.Designer.cs
+++ b/src/CoreLibrary/Resources/Resources.Designer.cs
@@ -168,6 +168,24 @@ public static string ClientNotConnected {
}
}
+ ///
+ /// Looks up a localized string similar to Factory Orchestrator Service is connected to a container running a compatible version of Factory Orchestrator Service..
+ ///
+ public static string ContainerConnected {
+ get {
+ return ResourceManager.GetString("ContainerConnected", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Factory Orchestrator Service is disconnected from a container running a compatible version of Factory Orchestrator Service..
+ ///
+ public static string ContainerDisconnected {
+ get {
+ return ResourceManager.GetString("ContainerDisconnected", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unable to get file from container!.
///
diff --git a/src/CoreLibrary/Resources/Resources.resx b/src/CoreLibrary/Resources/Resources.resx
index 959cdc72..797d1f42 100644
--- a/src/CoreLibrary/Resources/Resources.resx
+++ b/src/CoreLibrary/Resources/Resources.resx
@@ -423,4 +423,12 @@
Duplicate Guid(s) {0}in FactoryOrchestratorXML!
+
+ Factory Orchestrator Service is connected to a container running a compatible version of Factory Orchestrator Service.
+ container refers to a Windows container running on the host device (eg https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/)
+
+
+ Factory Orchestrator Service is disconnected from a container running a compatible version of Factory Orchestrator Service.
+ container refers to a Windows container running on the host device (eg https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/)
+
\ No newline at end of file
diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs
index 93093db6..5b6b4125 100644
--- a/src/Service/ServiceExe.cs
+++ b/src/Service/ServiceExe.cs
@@ -4,7 +4,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using PeterKottas.DotNetCore.WindowsService;
-using JKang.IpcServiceFramework;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
@@ -24,9 +23,8 @@
using Microsoft.FactoryOrchestrator.Client;
using System.Globalization;
using JKang.IpcServiceFramework.Hosting;
-using Microsoft.Extensions.Hosting;
-using JKang.IpcServiceFramework.Hosting.Tcp;
-
+using Microsoft.Extensions.Hosting;
+
namespace Microsoft.FactoryOrchestrator.Service
{
class FOServiceExe
@@ -1209,11 +1207,13 @@ public List GetContainerIpAddresses()
try
{
FOService.Instance.ServiceLogger.LogDebug($"{Resources.Start}: GetContainerIpAddresses");
- var ips = FOService.Instance.GetContainerIpAddresses();
+
+ // As of now, there is only one IP for the container but keep this as a list in case that changes
+ var ip = FOService.Instance.ContainerIpAddress;
var ipStrings = new List();
- foreach (var ip in ips)
- {
- ipStrings.Add(ip.ToString());
+ if (ip != null)
+ {
+ ipStrings.Add(ip.ToString());
}
FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: GetContainerIpAddresses");
@@ -1270,16 +1270,7 @@ public bool IsContainerRunning()
try
{
FOService.Instance.ServiceLogger.LogDebug($"{Resources.Start}: IsContainerRunning");
- bool ret = false;
- if (!String.IsNullOrWhiteSpace(FOService.Instance.GetContainerId()))
- {
- ret = true;
- }
- else
- {
- ret = false;
- }
-
+ bool ret = FOService.Instance.IsContainerConnected;
FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: IsContainerRunning");
return ret;
}
@@ -1315,7 +1306,16 @@ internal enum RegKeyType
private readonly string _mutableServiceRegKey = @"OSDATA\CurrentControlSet\Control\FactoryOrchestrator";
private readonly string _volatileServiceRegKey = @"SYSTEM\CurrentControlSet\Control\FactoryOrchestrator\EveryBootTaskStatus";
- private readonly string _loopbackEnabledValue = @"UWPLocalLoopbackEnabled";
+ // FactoryOS specific registry
+ private readonly string _volatileFactoryOSContainerRegKey = @"SYSTEM\CurrentControlSet\Control\FactoryUserManager";
+ private readonly string _factoryOSContainerGuidValue = @"ContainerGuid";
+ private readonly string _factoryOSContainerIpv4AddressValue = @"ContainerIPv4Address";
+
+ ///
+ /// Prevents service from polling container status.
+ ///
+ private readonly string _disableContainerValue = @"DisableContainerSupport";
+ private readonly string _loopbackEnabledValue = @"UWPLocalLoopbackEnabled";
// OEM Customization registry values
private readonly string _disableNetworkAccessValue = @"DisableNetworkAccess";
@@ -1407,9 +1407,12 @@ public TaskManager TaskExecutionManager
public bool DisableNetworkAccess { get; private set; }
public bool EnableNetworkAccess { get; private set; }
public int ServiceNetworkPort { get; private set; }
- public bool RunInitialTaskListsOnFirstBoot { get; private set; }
-
-
+ public bool RunInitialTaskListsOnFirstBoot { get; private set; }
+
+ public bool IsContainerConnected => _containerClient?.IsConnected ?? false;
+ public Guid ContainerGuid { get; private set; }
+ public IPAddress ContainerIpAddress { get; private set; }
+ private System.Threading.CancellationTokenSource _containerHeartbeatToken;
///
/// List of apps to enable local loopback on.
@@ -1426,8 +1429,8 @@ public static FOService Instance
{
return _singleton;
}
- }
-
+ }
+
///
/// Returns build number of FactoryOrchestratorService.
///
@@ -1494,14 +1497,18 @@ public FOService(ILogger logger)
DisableUWPAppsPage = false;
DisableManageTasklistsPage = false;
DisableFileTransferPage = false;
- DisableNetworkAccess = false;
- EnableNetworkAccess = false;
+ DisableNetworkAccess = true;
+ EnableNetworkAccess = false;
LocalLoopbackApps = new List();
TaskManagerLogFolder = _defaultLogFolder;
IsExecutingBootTasks = true;
- _containerClient = null;
ServiceNetworkPort = 45684;
_openedFiles = new Dictionary();
+
+ ContainerGuid = Guid.Empty;
+ ContainerIpAddress = null;
+ _containerHeartbeatToken = null;
+ _containerClient = null;
}
}
@@ -1623,8 +1630,12 @@ private bool LoadFirstBootStateFile(bool force)
///
public void Stop()
{
- // Disable inter process communication interface
+ // Disable inter process communication interfaces
_ipcCancellationToken?.Cancel();
+ _containerHeartbeatToken.Cancel();
+ _containerClient = null;
+ ContainerGuid = Guid.Empty;
+ ContainerIpAddress = null;
// Abort everything that's running, except persisted background tasks
_taskExecutionManager?.AbortAll();
@@ -1785,77 +1796,136 @@ private void RunTaskRunInContainer(ServerTaskRun hostRun)
});
}
+ public async Task TryVerifyContainerConnection()
+ {
+ try
+ {
+ await VerifyContainerConnection();
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public async Task VerifyContainerConnection()
+ {
+ var previousContainerStatus = IsContainerConnected;
+ var previousContainerGuid = ContainerGuid;
+
+ try
+ {
+ UpdateContainerId();
+ UpdateContainerIpAddress();
+
+ await ConnectToContainer();
+
+ if (previousContainerStatus == IsContainerConnected)
+ {
+ // Verify it is still working properly
+ await _containerClient.GetServiceVersionString();
+ }
+ else
+ {
+ // Initial connection made or reconnected
+ LogServiceEvent(new ServiceEvent(ServiceEventType.ContainerConnected, ContainerGuid, Resources.ContainerConnected));
+ }
+ }
+ catch (Exception)
+ {
+ if (previousContainerStatus == IsContainerConnected)
+ {
+ // Connection lost
+ LogServiceEvent(new ServiceEvent(ServiceEventType.ContainerDisconnected, previousContainerGuid, Resources.ContainerDisconnected));
+ }
+
+ throw;
+ }
+ }
+
public async Task ConnectToContainer()
{
- if (_containerClient == null || !_containerClient.IsConnected)
- {
- foreach (var ip in GetContainerIpAddresses())
- {
- _containerClient = new FactoryOrchestratorClient(ip, ServiceNetworkPort);
- if (await _containerClient.TryConnect())
- {
- return;
- }
- else
- {
- _containerClient = null;
- }
- }
- }
- else
- {
- return;
- }
-
- throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound);
+ if (ContainerGuid != null)
+ {
+ if (ContainerIpAddress != null)
+ {
+ if ((_containerClient == null || !_containerClient.IsConnected))
+ {
+ _containerClient = new FactoryOrchestratorClient(ContainerIpAddress, ServiceNetworkPort);
+ if (await _containerClient.TryConnect())
+ {
+ return;
+ }
+ else
+ {
+ _containerClient = null;
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound);
+ }
+
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIdFound);
}
- public string GetContainerId()
+ public void UpdateContainerId()
{
try
- {
- // TODO: replace with an API call
- var cmdiag = RunProcessViaCmd("cmdiag.exe", "list", 5000);
- var containerGuidString = cmdiag.TaskOutput.Where(x => x.Contains("cmscontainerstaterunning", StringComparison.InvariantCultureIgnoreCase)).DefaultIfEmpty(null).FirstOrDefault();
-
- if (containerGuidString != null)
- {
- return containerGuidString.Split(',', StringSplitOptions.RemoveEmptyEntries).First().Trim();
+ {
+ // FactoryOS puts the container guid in SYSTEM\CurrentControlSet\Control\FactoryUserManager
+ using var reg = Registry.LocalMachine.OpenSubKey(_volatileFactoryOSContainerRegKey, false);
+
+ var guidStr = (string)reg.GetValue(_factoryOSContainerGuidValue, String.Empty);
+ if (!string.IsNullOrEmpty(guidStr))
+ {
+ ContainerGuid = Guid.Parse(guidStr);
+ }
+ else
+ {
+ ContainerGuid = Guid.Empty;
+ _containerClient = null;
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIdFound);
}
}
- catch (Exception e)
+ catch (Exception)
{
- throw new FactoryOrchestratorContainerException(Resources.NoContainerIdFound, null, e);
- }
-
- throw new FactoryOrchestratorContainerException(Resources.NoContainerIdFound);
+ ContainerGuid = Guid.Empty;
+ _containerClient = null;
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIdFound);
+ }
}
- public List GetContainerIpAddresses()
+ public void UpdateContainerIpAddress()
{
- List ips = new List();
try
- {
- var containerGuid = GetContainerId();
- var cmdiag = RunProcessViaCmd("cmdiag.exe", $"exec {containerGuid} -runas administrator -command \"ipconfig.exe\"", 5000);
- var ipStrings = cmdiag.TaskOutput.Where(x => x.Contains("IPv4 address", StringComparison.InvariantCultureIgnoreCase));
-
- foreach (var ipString in ipStrings)
- {
- if (ipString != null)
- {
- ips.Add(IPAddress.Parse(ipString.Split(':', StringSplitOptions.RemoveEmptyEntries).Last().Trim()));
- }
+ {
+ // FactoryOS puts the container IP in SYSTEM\CurrentControlSet\Control\FactoryUserManager
+ using var reg = Registry.LocalMachine.OpenSubKey(_volatileFactoryOSContainerRegKey, false);
+
+ var ipStr = (string)reg.GetValue(_factoryOSContainerIpv4AddressValue, String.Empty);
+ if (!string.IsNullOrEmpty(ipStr))
+ {
+ ContainerIpAddress = IPAddress.Parse(ipStr);
+ }
+ else
+ {
+ ContainerIpAddress = null;
+ _containerClient = null;
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound);
}
-
- return ips;
}
- catch (Exception e)
+ catch (Exception)
{
- throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound, null, e);
+ ContainerIpAddress = null;
+ _containerClient = null;
+ throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound);
}
-
- throw new FactoryOrchestratorContainerException(Resources.NoContainerIpFound);
}
@@ -2163,7 +2233,22 @@ public void ExecuteServerBootTasks()
_taskExecutionManager = new TaskManager(TaskManagerLogFolder, Path.Combine(FOServiceExe.ServiceLogFolder, "FactoryOrchestratorKnownTaskLists.xml"));
- _taskExecutionManager.OnTaskManagerEvent += HandleTaskManagerEvent;
+ _taskExecutionManager.OnTaskManagerEvent += HandleTaskManagerEvent;
+
+ if (!Convert.ToBoolean(GetValueFromRegistry(_disableContainerValue, false), CultureInfo.InvariantCulture))
+ {
+ // Start container heartbeat thread
+ _containerHeartbeatToken = new System.Threading.CancellationTokenSource();
+ Task.Run(async () =>
+ {
+ while (!_containerHeartbeatToken.Token.IsCancellationRequested)
+ {
+ // Poll for container running Factory Orchestrator Service
+ await TryVerifyContainerConnection();
+ await Task.Delay(2000);
+ }
+ });
+ }
// Enable local loopback every boot.
EnableUWPLocalLoopback();
@@ -2595,6 +2680,7 @@ protected virtual void Dispose(bool disposing)
if (disposing)
{
_ipcCancellationToken?.Dispose();
+ _containerHeartbeatToken?.Dispose();
_mutableKey?.Dispose();
_nonMutableKey?.Dispose();
_volatileKey?.Dispose();
diff --git a/src/custom.props b/src/custom.props
index 36f6d721..b0205db3 100644
--- a/src/custom.props
+++ b/src/custom.props
@@ -3,7 +3,7 @@
7
- 0
+ 1
Factory Orchestrator
\ No newline at end of file