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