From 73c7e28a3332ec85fecc5c76181be28957bf68f9 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 7 Jun 2019 15:26:03 +0200 Subject: [PATCH] SEBWIN-301: Started working on service architecture. --- .../Communication/Hosts/IServiceHost.cs | 17 +++ .../SafeExamBrowser.Contracts.csproj | 1 + .../Operations/ConfigurationOperationTests.cs | 32 +++-- .../{ => Testdata}/SebClientSettings.seb | 0 .../Operations/WRONG/SebClientSettings.seb | 1 - .../SafeExamBrowser.Runtime.UnitTests.csproj | 5 +- SafeExamBrowser.Runtime/SessionContext.cs | 16 +-- .../SafeExamBrowser.Service.UnitTests.csproj | 28 +++++ .../ServiceControllerTests.cs | 109 ++++++++++++++++++ .../packages.config | 4 + .../Communication/ServiceHost.cs | 43 +++++++ SafeExamBrowser.Service/CompositionRoot.cs | 33 +++++- SafeExamBrowser.Service/Installer.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 + .../SafeExamBrowser.Service.csproj | 13 +++ SafeExamBrowser.Service/Service.cs | 2 +- SafeExamBrowser.Service/ServiceController.cs | 41 ++++++- SafeExamBrowser.Service/SessionContext.cs | 26 +++++ 18 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs rename SafeExamBrowser.Runtime.UnitTests/Operations/{ => Testdata}/SebClientSettings.seb (100%) delete mode 100644 SafeExamBrowser.Runtime.UnitTests/Operations/WRONG/SebClientSettings.seb create mode 100644 SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs create mode 100644 SafeExamBrowser.Service/Communication/ServiceHost.cs create mode 100644 SafeExamBrowser.Service/SessionContext.cs diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs new file mode 100644 index 00000000..c19bdf4c --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Contracts.Communication.Hosts +{ + /// + /// Defines the functionality of the communication host for the service application component. + /// + public interface IServiceHost : ICommunicationHost + { + } +} diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 88d2ab3a..24cd39b1 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -59,6 +59,7 @@ + diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs index f139e73e..806351ba 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs @@ -58,7 +58,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); appConfig.AppDataFilePath = location; appConfig.ProgramDataFilePath = location; @@ -76,7 +76,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void Perform_MustUseProgramDataAs2ndPrio() { - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); var settings = default(Settings); appConfig.ProgramDataFilePath = location; @@ -93,7 +93,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void Perform_MustUseAppDataAs3rdPrio() { - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); var settings = default(Settings); appConfig.AppDataFilePath = location; @@ -107,7 +107,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Perform_MustCorrectlyHandleBrowserResource() + public void Perform_MustTestdatalyHandleBrowserResource() { var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam }; var url = @"http://www.safeexambrowser.org/whatever.seb"; @@ -281,7 +281,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations var settings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, It.IsAny())).Returns(LoadStatus.Success); repository.Setup(r => r.TryLoadSettings(It.Is(u => u.LocalPath.Contains(FILE_NAME)), out localSettings, It.IsAny())).Returns(LoadStatus.Success); @@ -331,16 +331,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Perform_MustSucceedIfAdminPasswordCorrect() + public void Perform_MustSucceedIfAdminPasswordTestdata() { var password = "test"; var currentSettings = new Settings { AdminPasswordHash = "1234", ConfigurationMode = ConfigurationMode.ConfigureClient }; var nextSettings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); nextSession.SetupGet(s => s.Settings).Returns(nextSettings); - hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is(p => p == password))).Returns(currentSettings.AdminPasswordHash); repository.Setup(r => r.TryLoadSettings(It.IsAny(), out currentSettings, It.IsAny())).Returns(LoadStatus.Success); repository.Setup(r => r.TryLoadSettings(It.Is(u => u.AbsoluteUri == url), out nextSettings, It.IsAny())).Returns(LoadStatus.Success); @@ -370,9 +368,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations var nextSettings = new Settings { AdminPasswordHash = "1234", ConfigurationMode = ConfigurationMode.ConfigureClient }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); nextSession.SetupGet(s => s.Settings).Returns(nextSettings); - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out currentSettings, It.IsAny())).Returns(LoadStatus.Success); repository.Setup(r => r.TryLoadSettings(It.Is(u => u.AbsoluteUri == url), out nextSettings, It.IsAny())).Returns(LoadStatus.Success); repository.Setup(r => r.ConfigureClientWith(It.IsAny(), It.IsAny())).Returns(SaveStatus.Success); @@ -394,7 +390,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Perform_MustSucceedIfSettingsPasswordCorrect() + public void Perform_MustSucceedIfSettingsPasswordTestdata() { var password = "test"; var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam }; @@ -424,7 +420,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations public void Perform_MustUseCurrentPasswordIfAvailable() { var url = @"http://www.safeexambrowser.org/whatever.seb"; - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); var settings = new Settings { AdminPasswordHash = "1234", ConfigurationMode = ConfigurationMode.Exam }; appConfig.AppDataFilePath = location; @@ -455,7 +451,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations var nextSettings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME); + appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME); nextSession.SetupGet(s => s.Settings).Returns(nextSettings); hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is(p => p == password))).Returns(currentSettings.AdminPasswordHash); @@ -501,11 +497,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Repeat_MustPerformForExamWithCorrectUri() + public void Repeat_MustPerformForExamWithTestdataUri() { var currentSettings = new Settings(); var location = Path.GetDirectoryName(GetType().Assembly.Location); - var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME)); + var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME)); var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam }; currentSession.SetupGet(s => s.Settings).Returns(currentSettings); @@ -523,11 +519,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Repeat_MustPerformForClientConfigurationWithCorrectUri() + public void Repeat_MustPerformForClientConfigurationWithTestdataUri() { var currentSettings = new Settings(); var location = Path.GetDirectoryName(GetType().Assembly.Location); - var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME)); + var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME)); var settings = new Settings { ConfigurationMode = ConfigurationMode.ConfigureClient }; currentSession.SetupGet(s => s.Settings).Returns(currentSettings); @@ -572,7 +568,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { var currentSettings = new Settings(); var location = Path.GetDirectoryName(GetType().Assembly.Location); - var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME)); + var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME)); var settings = new Settings { ConfigurationMode = ConfigurationMode.ConfigureClient }; currentSession.SetupGet(s => s.Settings).Returns(currentSettings); diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/SebClientSettings.seb b/SafeExamBrowser.Runtime.UnitTests/Operations/Testdata/SebClientSettings.seb similarity index 100% rename from SafeExamBrowser.Runtime.UnitTests/Operations/SebClientSettings.seb rename to SafeExamBrowser.Runtime.UnitTests/Operations/Testdata/SebClientSettings.seb diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/WRONG/SebClientSettings.seb b/SafeExamBrowser.Runtime.UnitTests/Operations/WRONG/SebClientSettings.seb deleted file mode 100644 index 5f282702..00000000 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/WRONG/SebClientSettings.seb +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj index cdca3e7f..ecf0bb3c 100644 --- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj +++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj @@ -121,10 +121,7 @@ - - Always - - + Always diff --git a/SafeExamBrowser.Runtime/SessionContext.cs b/SafeExamBrowser.Runtime/SessionContext.cs index 5b64eb4d..4efeeb89 100644 --- a/SafeExamBrowser.Runtime/SessionContext.cs +++ b/SafeExamBrowser.Runtime/SessionContext.cs @@ -21,41 +21,41 @@ namespace SafeExamBrowser.Runtime /// /// The currently active . /// - public KioskMode? ActiveMode { get; set; } + internal KioskMode? ActiveMode { get; set; } /// /// The currently running client process. /// - public IProcess ClientProcess { get; set; } + internal IProcess ClientProcess { get; set; } /// /// The communication proxy for the currently running client process. /// - public IClientProxy ClientProxy { get; set; } + internal IClientProxy ClientProxy { get; set; } /// /// The configuration of the currently active session. /// - public ISessionConfiguration Current { get; set; } + internal ISessionConfiguration Current { get; set; } /// /// The new desktop, if is currently active. /// - public IDesktop NewDesktop { get; set; } + internal IDesktop NewDesktop { get; set; } /// /// The configuration of the next session to be activated. /// - public ISessionConfiguration Next { get; set; } + internal ISessionConfiguration Next { get; set; } /// /// The original desktop, if is currently active. /// - public IDesktop OriginalDesktop { get; set; } + internal IDesktop OriginalDesktop { get; set; } /// /// The path of the configuration file to be used for reconfiguration. /// - public string ReconfigurationFilePath { get; set; } + internal string ReconfigurationFilePath { get; set; } } } diff --git a/SafeExamBrowser.Service.UnitTests/SafeExamBrowser.Service.UnitTests.csproj b/SafeExamBrowser.Service.UnitTests/SafeExamBrowser.Service.UnitTests.csproj index 39cae989..79b9e3ac 100644 --- a/SafeExamBrowser.Service.UnitTests/SafeExamBrowser.Service.UnitTests.csproj +++ b/SafeExamBrowser.Service.UnitTests/SafeExamBrowser.Service.UnitTests.csproj @@ -57,21 +57,49 @@ MinimumRecommendedRules.ruleset + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Moq.4.11.0\lib\net45\Moq.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + + + {47da5933-bef8-4729-94e6-abde2db12262} + SafeExamBrowser.Contracts + + + {fa3c6692-dfed-4afa-bd58-9a3da2753c78} + SafeExamBrowser.Service + + + + + + diff --git a/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs new file mode 100644 index 00000000..f96e0ca4 --- /dev/null +++ b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SafeExamBrowser.Contracts.Communication.Hosts; +using SafeExamBrowser.Contracts.Core.OperationModel; + +namespace SafeExamBrowser.Service.UnitTests +{ + [TestClass] + public class ServiceControllerTests + { + private Mock bootstrapSequence; + private SessionContext sessionContext; + private Mock sessionSequence; + private Mock serviceHost; + private ServiceController sut; + + [TestInitialize] + public void Initialize() + { + bootstrapSequence = new Mock(); + sessionContext = new SessionContext(); + sessionSequence = new Mock(); + serviceHost = new Mock(); + + sut = new ServiceController(bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext); + } + + [TestMethod] + public void Start_MustOnlyPerformBootstrapSequence() + { + bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); + sessionSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); + sessionContext.Current = null; + + var success = sut.TryStart(); + + bootstrapSequence.Verify(b => b.TryPerform(), Times.Once); + bootstrapSequence.Verify(b => b.TryRevert(), Times.Never); + sessionSequence.Verify(b => b.TryPerform(), Times.Never); + sessionSequence.Verify(b => b.TryRepeat(), Times.Never); + sessionSequence.Verify(b => b.TryRevert(), Times.Never); + + Assert.IsTrue(success); + } + + [TestMethod] + public void Stop_MustRevertSessionThenBootstrapSequence() + { + var order = 0; + var bootstrap = 0; + var session = 0; + + sut.TryStart(); + + bootstrapSequence.Reset(); + sessionSequence.Reset(); + + bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); + sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); + + sut.Terminate(); + + bootstrapSequence.Verify(b => b.TryPerform(), Times.Never); + bootstrapSequence.Verify(b => b.TryRevert(), Times.Once); + sessionSequence.Verify(b => b.TryPerform(), Times.Never); + sessionSequence.Verify(b => b.TryRepeat(), Times.Never); + sessionSequence.Verify(b => b.TryRevert(), Times.Once); + + Assert.AreEqual(1, session); + Assert.AreEqual(2, bootstrap); + } + + [TestMethod] + public void Stop_MustNotRevertSessionSequenceIfNoSessionRunning() + { + var order = 0; + var bootstrap = 0; + var session = 0; + + sut.TryStart(); + + bootstrapSequence.Reset(); + sessionSequence.Reset(); + + bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); + sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); + sessionContext.Current = null; + + sut.Terminate(); + + bootstrapSequence.Verify(b => b.TryPerform(), Times.Never); + bootstrapSequence.Verify(b => b.TryRevert(), Times.Once); + sessionSequence.Verify(b => b.TryPerform(), Times.Never); + sessionSequence.Verify(b => b.TryRepeat(), Times.Never); + sessionSequence.Verify(b => b.TryRevert(), Times.Never); + + Assert.AreEqual(0, session); + Assert.AreEqual(1, bootstrap); + } + } +} diff --git a/SafeExamBrowser.Service.UnitTests/packages.config b/SafeExamBrowser.Service.UnitTests/packages.config index 2f7c5a18..26fb3b97 100644 --- a/SafeExamBrowser.Service.UnitTests/packages.config +++ b/SafeExamBrowser.Service.UnitTests/packages.config @@ -1,5 +1,9 @@  + + + + \ No newline at end of file diff --git a/SafeExamBrowser.Service/Communication/ServiceHost.cs b/SafeExamBrowser.Service/Communication/ServiceHost.cs new file mode 100644 index 00000000..624ddcbd --- /dev/null +++ b/SafeExamBrowser.Service/Communication/ServiceHost.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Communication.Data; +using SafeExamBrowser.Contracts.Communication.Hosts; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Service.Communication +{ + internal class ServiceHost : BaseHost, IServiceHost + { + internal ServiceHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) + { + } + + protected override bool OnConnect(Guid? token) + { + throw new NotImplementedException(); + } + + protected override void OnDisconnect() + { + throw new NotImplementedException(); + } + + protected override Response OnReceive(Message message) + { + throw new NotImplementedException(); + } + + protected override Response OnReceive(SimpleMessagePurport message) + { + throw new NotImplementedException(); + } + } +} diff --git a/SafeExamBrowser.Service/CompositionRoot.cs b/SafeExamBrowser.Service/CompositionRoot.cs index fa14dedf..c3880af2 100644 --- a/SafeExamBrowser.Service/CompositionRoot.cs +++ b/SafeExamBrowser.Service/CompositionRoot.cs @@ -7,10 +7,16 @@ */ using System; +using System.Collections.Generic; using System.IO; +using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Service; +using SafeExamBrowser.Core.OperationModel; +using SafeExamBrowser.Core.Operations; using SafeExamBrowser.Logging; +using SafeExamBrowser.Service.Communication; namespace SafeExamBrowser.Service { @@ -22,11 +28,30 @@ namespace SafeExamBrowser.Service internal void BuildObjectGraph() { - logger = new Logger(); + const string SERVICE_ADDRESS = "net.pipe://localhost/safeexambrowser/service"; + const int FIVE_SECONDS = 5000; InitializeLogging(); - ServiceController = new ServiceController(); + var serviceHost = new ServiceHost(SERVICE_ADDRESS, new HostObjectFactory(), new ModuleLogger(logger, nameof(ServiceHost)), FIVE_SECONDS); + var sessionContext = new SessionContext(); + + var bootstrapOperations = new Queue(); + var sessionOperations = new Queue(); + + // TODO: bootstrapOperations.Enqueue(new RestoreOperation()); + bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger)); + + // sessionOperations.Enqueue(new RuntimeConnectionOperation()); + // sessionOperations.Enqueue(new LogOperation()); + // sessionOperations.Enqueue(new RegistryOperation()); + // sessionOperations.Enqueue(new WindowsUpdateOperation()); + // sessionOperations.Enqueue(new SessionActivationOperation()); + + var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); + var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations); + + ServiceController = new ServiceController(bootstrapSequence, sessionSequence, serviceHost, sessionContext); } internal void LogStartupInformation() @@ -37,6 +62,7 @@ namespace SafeExamBrowser.Service internal void LogShutdownInformation() { + logger?.Log(string.Empty); logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } @@ -48,9 +74,10 @@ namespace SafeExamBrowser.Service var logFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Service.log"); var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath); - logFileWriter.Initialize(); + logger = new Logger(); logger.LogLevel = LogLevel.Debug; logger.Subscribe(logFileWriter); + logFileWriter.Initialize(); } } } diff --git a/SafeExamBrowser.Service/Installer.cs b/SafeExamBrowser.Service/Installer.cs index 99efedb9..12eae4c7 100644 --- a/SafeExamBrowser.Service/Installer.cs +++ b/SafeExamBrowser.Service/Installer.cs @@ -25,7 +25,7 @@ namespace SafeExamBrowser.Service service = new ServiceInstaller(); service.Description = "Performs operations which require elevated privileges."; service.DisplayName = "Safe Exam Browser Service"; - service.ServiceName = nameof(SafeExamBrowser.Service); + service.ServiceName = nameof(SafeExamBrowser); service.StartType = ServiceStartMode.Automatic; Installers.Add(process); diff --git a/SafeExamBrowser.Service/Properties/AssemblyInfo.cs b/SafeExamBrowser.Service/Properties/AssemblyInfo.cs index 22ac2c99..340618bb 100644 --- a/SafeExamBrowser.Service/Properties/AssemblyInfo.cs +++ b/SafeExamBrowser.Service/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -14,6 +15,7 @@ using System.Runtime.InteropServices; // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("SafeExamBrowser.Service.UnitTests")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("fa3c6692-dfed-4afa-bd58-9a3da2753c78")] diff --git a/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj b/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj index 1a193fed..60c8368f 100644 --- a/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj +++ b/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj @@ -59,6 +59,7 @@ + Component @@ -68,19 +69,31 @@ + + + {c9416a62-0623-4d38-96aa-92516b32f02f} + SafeExamBrowser.Communication + {47da5933-bef8-4729-94e6-abde2db12262} SafeExamBrowser.Contracts + + {3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc} + SafeExamBrowser.Core + {e107026c-2011-4552-a7d8-3a0d37881df6} SafeExamBrowser.Logging + + + \ No newline at end of file diff --git a/SafeExamBrowser.Service/Service.cs b/SafeExamBrowser.Service/Service.cs index 5e218414..e8b2b38a 100644 --- a/SafeExamBrowser.Service/Service.cs +++ b/SafeExamBrowser.Service/Service.cs @@ -18,7 +18,7 @@ namespace SafeExamBrowser.Service public Service() { CanPauseAndContinue = false; - ServiceName = nameof(SafeExamBrowser.Service); + ServiceName = nameof(SafeExamBrowser); } public static void Main() diff --git a/SafeExamBrowser.Service/ServiceController.cs b/SafeExamBrowser.Service/ServiceController.cs index 847dece5..1bcd22a6 100644 --- a/SafeExamBrowser.Service/ServiceController.cs +++ b/SafeExamBrowser.Service/ServiceController.cs @@ -6,20 +6,59 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using SafeExamBrowser.Contracts.Communication.Hosts; +using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Service; namespace SafeExamBrowser.Service { internal class ServiceController : IServiceController { + private IOperationSequence bootstrapSequence; + private IRepeatableOperationSequence sessionSequence; + private IServiceHost serviceHost; + private SessionContext sessionContext; + + private object Session + { + get { return sessionContext.Current; } + } + + private bool SessionIsRunning + { + get { return Session != null; } + } + + public ServiceController( + IOperationSequence bootstrapSequence, + IRepeatableOperationSequence sessionSequence, + IServiceHost serviceHost, + SessionContext sessionContext) + { + this.bootstrapSequence = bootstrapSequence; + this.sessionSequence = sessionSequence; + this.serviceHost = serviceHost; + this.sessionContext = sessionContext; + } + public bool TryStart() { - return true; + var result = bootstrapSequence.TryPerform(); + var success = result == OperationResult.Success; + + return success; } public void Terminate() { + var result = default(OperationResult); + if (SessionIsRunning) + { + result = sessionSequence.TryRevert(); + } + + result = bootstrapSequence.TryRevert(); } } } diff --git a/SafeExamBrowser.Service/SessionContext.cs b/SafeExamBrowser.Service/SessionContext.cs new file mode 100644 index 00000000..3600d69b --- /dev/null +++ b/SafeExamBrowser.Service/SessionContext.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Service +{ + /// + /// Holds all configuration and runtime data required for the session handling. + /// + internal class SessionContext + { + /// + /// The configuration of the currently active session. + /// + internal object Current { get; set; } + + /// + /// The configuration of the next session to be activated. + /// + internal object Next { get; set; } + } +}