diff --git a/docs/packaging.md b/docs/packaging.md index 28bc624912..47b4032b87 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -8,7 +8,9 @@ The zip files minimize duplication to control the overall file size of each inst - `ServiceControl.Audit.zip` - `ServiceControl.Monitoring.zip` - `InstanceShared.zip` - Contains the transport assemblies that are shared by each instance, as well as any other assemblies that are shared across all 3 instance zip files, such as `NServiceBus.Core.dll`. -- `RavenDBServer.zip` - Contains the RavenDB server assemblies used by ServiceControl and Monitoring instances +- `RavenDBServer.zip` - Contains the RavenDB server assemblies used by ServiceControl and Audit instances + +`RavenDBServer.zip` excludes the native Kafka dependencies `librdkafka.dll`, `librdkafkacpp.dll`, and `libcurl.dll`. ServiceControl does not configure RavenDB's Kafka ETL or sink tasks. The Windows installation engine tests extract this archive and verify document persistence and indexing across a server restart without these libraries. ## The mechanics diff --git a/docs/testing.md b/docs/testing.md index a3140e48dd..af20a9d4f9 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -20,6 +20,8 @@ Installation engine tests run partial installations and check: - That the generated configuration is correct. - That transport and persistence are correctly extracted. +On Windows, [PackagedRavenDBTests](../src/ServiceControlInstaller.Engine.UnitTests/Validation/PackagedRavenDBTests.cs) starts the server extracted from the installer's embedded `RavenDBServer.zip`. It verifies the native Kafka dependencies are absent and checks document persistence and indexing before and after a restart. These tests run in the `Windows-DefaultCore` CI job; the `Windows-Raven` tests use the separate server files supplied by `RavenDB.Embedded`. + ## Persistence tests Persistence tests check assumptions at the persistence seam level by exercising each persister. diff --git a/src/Custom.Build.props b/src/Custom.Build.props index 450f2d830e..dc994fdaba 100644 --- a/src/Custom.Build.props +++ b/src/Custom.Build.props @@ -12,7 +12,7 @@ - 10.0.11 + 10.0.13 Disable diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8b5fef92b2..d5cab607dd 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -103,6 +103,7 @@ + diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/PackagedRavenDBTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/PackagedRavenDBTests.cs new file mode 100644 index 0000000000..01de0bd0cb --- /dev/null +++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/PackagedRavenDBTests.cs @@ -0,0 +1,180 @@ +namespace ServiceControlInstaller.Engine.UnitTests.Validation +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using FileSystem; + using NUnit.Framework; + using Raven.Client.Documents; + using Raven.Client.Documents.Indexes; + using Raven.Client.Documents.Operations.Indexes; + using Raven.Client.ServerWide; + using Raven.Client.ServerWide.Operations; + using TestHelper; + + [TestFixture] + [Platform("Win")] + [NonParallelizable] + class PackagedRavenDBTests + { + [Test] + public async Task CanPersistAndIndexDocumentsWithoutNativeKafkaDependencies() + { + var root = Path.Combine(Path.GetTempPath(), "PackagedRavenDB", Guid.NewGuid().ToString("N")); + var serverDirectory = Path.Combine(root, "Server"); + var dataDirectory = Path.Combine(root, "Data"); + var serverUrl = $"http://127.0.0.1:{PortUtility.FindAvailablePort(33400)}"; + using var timeout = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CurrentContext.CancellationToken); + timeout.CancelAfter(TimeSpan.FromMinutes(3)); + var cancellationToken = timeout.Token; + Process server = null; + Task standardOutput = null; + Task standardError = null; + + try + { + FileUtils.UnzipToSubdirectory("RavenDBServer.zip", serverDirectory); + + using (Assert.EnterMultipleScope()) + { + Assert.That(Path.Combine(serverDirectory, "libcurl.dll"), Does.Not.Exist); + Assert.That(Path.Combine(serverDirectory, "librdkafka.dll"), Does.Not.Exist); + Assert.That(Path.Combine(serverDirectory, "librdkafkacpp.dll"), Does.Not.Exist); + Assert.That(Path.Combine(serverDirectory, "Raven.Server.exe"), Does.Exist); + } + + await StartServer(); + + const string databaseName = "PackagingSmokeTest"; + using var store = new DocumentStore + { + Urls = [serverUrl], + Database = databaseName, + Conventions = { DisableTopologyUpdates = true } + }.Initialize(); + + await store.Maintenance.Server.SendAsync(new CreateDatabaseOperation(new DatabaseRecord(databaseName)), cancellationToken); + await store.Maintenance.SendAsync(new PutIndexesOperation(new IndexDefinition + { + Name = indexName, + Maps = ["from document in docs select new { document.Name }"] + }), cancellationToken); + + using (var session = store.OpenAsyncSession()) + { + await session.StoreAsync(new PackagedDocument { Name = documentName }, documentId, cancellationToken); + await session.SaveChangesAsync(cancellationToken); + } + + await AssertDocumentAndIndex(store, cancellationToken); + await StopServer(); + await StartServer(); + await AssertDocumentAndIndex(store, cancellationToken); + } + finally + { + await StopServer(); + if (Directory.Exists(root)) + { + FileUtils.DeleteDirectory(root, recursive: true, contentsOnly: false); + } + } + + async Task StartServer() + { + var startInfo = new ProcessStartInfo(Path.Combine(serverDirectory, "Raven.Server.exe")) + { + WorkingDirectory = serverDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + startInfo.ArgumentList.Add("--non-interactive"); + startInfo.ArgumentList.Add("--log-to-console"); + startInfo.ArgumentList.Add($"--ServerUrl={serverUrl}"); + startInfo.ArgumentList.Add($"--DataDir={dataDirectory}"); + startInfo.ArgumentList.Add($"--Logs.Path={Path.Combine(root, "Logs")}"); + startInfo.ArgumentList.Add("--Setup.Mode=None"); + startInfo.ArgumentList.Add("--Security.UnsecuredAccessAllowed=Local"); + startInfo.ArgumentList.Add("--License.Eula.Accepted=true"); + startInfo.ArgumentList.Add($"--License.Path={Path.Combine(TestContext.CurrentContext.TestDirectory, "RavenLicense.json")}"); + startInfo.ArgumentList.Add("--License.DisableAutoUpdate=true"); + startInfo.ArgumentList.Add("--License.DisableLicenseSupportCheck=true"); + + server = Process.Start(startInfo); + standardOutput = server.StandardOutput.ReadToEndAsync(CancellationToken.None); + standardError = server.StandardError.ReadToEndAsync(CancellationToken.None); + + using var client = new HttpClient { Timeout = Timeout.InfiniteTimeSpan }; + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + Assert.That(server.HasExited, Is.False, "The packaged RavenDB server exited before accepting requests."); + try + { + using var response = await client.GetAsync($"{serverUrl}/build/version", cancellationToken); + if (response.IsSuccessStatusCode) + { + return; + } + } + catch (HttpRequestException) + { + } + await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); + } + } + + async Task StopServer() + { + if (server == null) + { + return; + } + + try + { + if (!server.HasExited) + { + server.Kill(entireProcessTree: true); + } + + await server.WaitForExitAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(15)); + TestContext.Out.WriteLine(await standardOutput.WaitAsync(TimeSpan.FromSeconds(15))); + TestContext.Out.WriteLine(await standardError.WaitAsync(TimeSpan.FromSeconds(15))); + } + finally + { + server.Dispose(); + server = null; + } + } + } + + static async Task AssertDocumentAndIndex(IDocumentStore store, CancellationToken cancellationToken) + { + using var session = store.OpenAsyncSession(); + var document = await session.LoadAsync(documentId, cancellationToken); + Assert.That(document?.Name, Is.EqualTo(documentName)); + + var indexedDocument = await session.Advanced.AsyncDocumentQuery(indexName) + .WaitForNonStaleResults(TimeSpan.FromSeconds(30)) + .WhereEquals(nameof(PackagedDocument.Name), documentName) + .SingleAsync(cancellationToken); + Assert.That(indexedDocument.Name, Is.EqualTo(documentName)); + } + + class PackagedDocument + { + public string Name { get; set; } + } + + const string documentId = "documents/1"; + const string documentName = "Packaged RavenDB"; + const string indexName = "Packaging/ByName"; + } +} diff --git a/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj b/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj index fd416f3082..7a4136f323 100644 --- a/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj +++ b/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj @@ -45,7 +45,8 @@ - +