diff --git a/.github/workflows/build-db-container.yml b/.github/workflows/build-db-container.yml
index 9bcc8ac4e5..b8180b9e20 100644
--- a/.github/workflows/build-db-container.yml
+++ b/.github/workflows/build-db-container.yml
@@ -26,6 +26,8 @@ jobs:
version: ${{ env.MinVerVersion }}
- name: Log in to GitHub container registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
+ - name: Install Docker arm64 emulation
+ run: docker run --privileged --rm tonistiigi/binfmt --install arm64
- name: Build images
env:
TAG_NAME: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || env.MinVerVersion }}
diff --git a/.github/workflows/container-integration-test.yml b/.github/workflows/container-integration-test.yml
index c35b016c8e..49d029b001 100644
--- a/.github/workflows/container-integration-test.yml
+++ b/.github/workflows/container-integration-test.yml
@@ -57,6 +57,11 @@ jobs:
uses: Particular/run-minver-action@v1.0.0
- name: Log in to GitHub container registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3.3.0
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Azure Login
if: ${{ matrix.name == 'asb' || matrix.name == 'asq' }}
uses: azure/login@v2.2.0
@@ -101,13 +106,20 @@ jobs:
}
Write-Output "healthy-containers=$numHealthy" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf-8 -Append
- name: Dump Docker container list
+ if: ${{ always() }}
run: docker ps --all
- name: Dump Primary instance logs
+ if: ${{ always() }}
run: docker logs service-platform-servicecontrol-1
- name: Dump Audit instance logs
+ if: ${{ always() }}
run: docker logs service-platform-servicecontrol-audit-1
- name: Dump Monitoring instance logs
+ if: ${{ always() }}
run: docker logs service-platform-servicecontrol-monitoring-1
+ - name: Dump Database logs
+ if: ${{ always() }}
+ run: docker logs service-platform-servicecontrol-db-1
- name: Return status
shell: pwsh
run: |
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 9a98173468..854b8d86ac 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -60,7 +60,7 @@
-
+
diff --git a/src/ServiceControl.AcceptanceTests.RavenDB/Recoverability/MessageFailures/FailedMessageRetriesController.cs b/src/ServiceControl.AcceptanceTests.RavenDB/Recoverability/MessageFailures/FailedMessageRetriesController.cs
index 8ca6705b59..b511a9db28 100644
--- a/src/ServiceControl.AcceptanceTests.RavenDB/Recoverability/MessageFailures/FailedMessageRetriesController.cs
+++ b/src/ServiceControl.AcceptanceTests.RavenDB/Recoverability/MessageFailures/FailedMessageRetriesController.cs
@@ -10,7 +10,7 @@
public class FailedMessageRetriesCountReponse
{
- public int Count { get; set; }
+ public long Count { get; set; }
}
[ApiController]
diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseSetup.cs b/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseSetup.cs
index 2e1a6148de..cb31bf977a 100644
--- a/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseSetup.cs
+++ b/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseSetup.cs
@@ -1,17 +1,17 @@
namespace ServiceControl.Audit.Persistence.RavenDB
{
+ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
using Raven.Client.Documents.Indexes;
- using Raven.Client.Documents.Operations;
using Raven.Client.Documents.Operations.Expiration;
using Raven.Client.Documents.Operations.Indexes;
using Raven.Client.Exceptions;
- using Raven.Client.Exceptions.Database;
using Raven.Client.ServerWide;
using Raven.Client.ServerWide.Operations;
+ using Raven.Client.ServerWide.Operations.Configuration;
using ServiceControl.Audit.Persistence.RavenDB.Indexes;
using ServiceControl.SagaAudit;
@@ -19,51 +19,55 @@ class DatabaseSetup(DatabaseConfiguration configuration)
{
public async Task Execute(IDocumentStore documentStore, CancellationToken cancellationToken)
{
- try
- {
- await documentStore.Maintenance.ForDatabase(configuration.Name).SendAsync(new GetStatisticsOperation(), cancellationToken);
- }
- catch (DatabaseDoesNotExistException)
+ await CreateDatabase(documentStore, configuration.Name, cancellationToken);
+ await UpdateDatabaseSettings(documentStore, configuration.Name, cancellationToken);
+
+ await CreateIndexes(documentStore, cancellationToken);
+
+ await ConfigureExpiration(documentStore, cancellationToken);
+ }
+
+ async Task CreateDatabase(IDocumentStore documentStore, string databaseName, CancellationToken cancellationToken)
+ {
+ var dbRecord = await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName), cancellationToken);
+
+ if (dbRecord is null)
{
try
{
- await documentStore.Maintenance.Server
- .SendAsync(new CreateDatabaseOperation(new DatabaseRecord(configuration.Name)), cancellationToken);
+ var databaseRecord = new DatabaseRecord(databaseName);
+ databaseRecord.Settings.Add("Indexing.Auto.SearchEngineType", "Corax");
+ databaseRecord.Settings.Add("Indexing.Static.SearchEngineType", "Corax");
+
+ await documentStore.Maintenance.Server.SendAsync(new CreateDatabaseOperation(databaseRecord), cancellationToken);
}
catch (ConcurrencyException)
{
// The database was already created before calling CreateDatabaseOperation
}
}
+ }
- var indexList = new List {
- new FailedAuditImportIndex(),
- new SagaDetailsIndex()
- };
-
- await DeleteLegacySagaDetailsIndex(documentStore, cancellationToken);
+ async Task UpdateDatabaseSettings(IDocumentStore documentStore, string databaseName, CancellationToken cancellationToken)
+ {
+ var dbRecord = await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName), cancellationToken);
- if (configuration.EnableFullTextSearch)
+ if (dbRecord is null)
{
- indexList.Add(new MessagesViewIndexWithFullTextSearch());
- await documentStore.Maintenance.SendAsync(new DeleteIndexOperation("MessagesViewIndex"), cancellationToken);
- }
- else
- {
- indexList.Add(new MessagesViewIndex());
- await documentStore.Maintenance
- .SendAsync(new DeleteIndexOperation("MessagesViewIndexWithFullTextSearch"), cancellationToken);
+ throw new InvalidOperationException($"Database '{databaseName}' does not exist.");
}
- await IndexCreation.CreateIndexesAsync(indexList, documentStore, null, null, cancellationToken);
+ var updated = false;
- var expirationConfig = new ExpirationConfiguration
- {
- Disabled = false,
- DeleteFrequencyInSec = configuration.ExpirationProcessTimerInSeconds
- };
+ updated |= dbRecord.Settings.TryAdd("Indexing.Auto.SearchEngineType", "Corax");
+ updated |= dbRecord.Settings.TryAdd("Indexing.Static.SearchEngineType", "Corax");
- await documentStore.Maintenance.SendAsync(new ConfigureExpirationOperation(expirationConfig), cancellationToken);
+ if (updated)
+ {
+ await documentStore.Maintenance.ForDatabase(databaseName).SendAsync(new PutDatabaseSettingsOperation(databaseName, dbRecord.Settings), cancellationToken);
+ await documentStore.Maintenance.Server.SendAsync(new ToggleDatabasesStateOperation(databaseName, true), cancellationToken);
+ await documentStore.Maintenance.Server.SendAsync(new ToggleDatabasesStateOperation(databaseName, false), cancellationToken);
+ }
}
public static async Task DeleteLegacySagaDetailsIndex(IDocumentStore documentStore, CancellationToken cancellationToken)
@@ -81,5 +85,36 @@ public static async Task DeleteLegacySagaDetailsIndex(IDocumentStore documentSto
await documentStore.Maintenance.SendAsync(new DeleteIndexOperation("SagaDetailsIndex"), cancellationToken);
}
}
+
+ async Task CreateIndexes(IDocumentStore documentStore, CancellationToken cancellationToken)
+ {
+ await DeleteLegacySagaDetailsIndex(documentStore, cancellationToken);
+
+ List indexList = [new FailedAuditImportIndex(), new SagaDetailsIndex()];
+
+ if (configuration.EnableFullTextSearch)
+ {
+ indexList.Add(new MessagesViewIndexWithFullTextSearch());
+ await documentStore.Maintenance.SendAsync(new DeleteIndexOperation("MessagesViewIndex"), cancellationToken);
+ }
+ else
+ {
+ indexList.Add(new MessagesViewIndex());
+ await documentStore.Maintenance.SendAsync(new DeleteIndexOperation("MessagesViewIndexWithFullTextSearch"), cancellationToken);
+ }
+
+ await IndexCreation.CreateIndexesAsync(indexList, documentStore, null, null, cancellationToken);
+ }
+
+ async Task ConfigureExpiration(IDocumentStore documentStore, CancellationToken cancellationToken)
+ {
+ var expirationConfig = new ExpirationConfiguration
+ {
+ Disabled = false,
+ DeleteFrequencyInSec = configuration.ExpirationProcessTimerInSeconds
+ };
+
+ await documentStore.Maintenance.SendAsync(new ConfigureExpirationOperation(expirationConfig), cancellationToken);
+ }
}
}
diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenAuditDataStore.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenAuditDataStore.cs
index d966fc0d1b..a963d6015b 100644
--- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenAuditDataStore.cs
+++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenAuditDataStore.cs
@@ -175,9 +175,9 @@ public async Task>> QueryAuditCounts(string endpoi
.Take(0)
.ToArrayAsync();
- if (stats.LongTotalResults > 0)
+ if (stats.TotalResults > 0)
{
- results.Add(new AuditCount { UtcDate = date, Count = stats.LongTotalResults });
+ results.Add(new AuditCount { UtcDate = date, Count = stats.TotalResults });
}
}
}
diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
index 9909fea599..02b386354c 100644
--- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
+++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
@@ -70,9 +70,6 @@ public async Task Initialize(CancellationToken cancellationToken = default)
var databaseSetup = new DatabaseSetup(configuration);
await databaseSetup.Execute(store, cancellationToken);
-
- // Must go after the database setup, as database must exist
- StartupChecks.EnsureSingleNodeTopology(store);
}
finally
{
diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SagaHistoryIndexTests.cs b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SagaHistoryIndexTests.cs
deleted file mode 100644
index e37c6b8537..0000000000
--- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SagaHistoryIndexTests.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-namespace ServiceControl.Audit.Persistence.Tests
-{
- using System;
- using System.Threading.Tasks;
- using NUnit.Framework;
- using Raven.Client.Documents;
- using ServiceControl.SagaAudit;
-
- [TestFixture]
- class SagaHistoryIndexTests : PersistenceTestFixture
- {
- [Test]
- public async Task Should_only_index_saga_id_property()
- {
- var sagaType = "MySagaType";
- var sagaState = "some-saga-state";
-
- await IngestSagaAudits(new SagaSnapshot
- {
- SagaId = Guid.NewGuid(),
- SagaType = sagaType,
- Status = SagaStateChangeStatus.New,
- StateAfterChange = sagaState
- });
-
- await configuration.CompleteDBOperation();
-
- using (var session = configuration.DocumentStore.OpenAsyncSession())
- {
- var sagaTypeResults = await session.Query()
- .Search(s => s.SagaType, sagaType)
- .ToListAsync();
-
- Assert.That(sagaTypeResults.Count, Is.EqualTo(0));
-
- var sagaChangeResults = await session.Query()
- .Search(s => s.Changes, sagaState)
- .ToListAsync();
-
- Assert.That(sagaChangeResults.Count, Is.EqualTo(0));
- }
- }
-
- async Task IngestSagaAudits(params SagaSnapshot[] snapshots)
- {
- var unitOfWork = await StartAuditUnitOfWork(snapshots.Length);
- foreach (var snapshot in snapshots)
- {
- await unitOfWork.RecordSagaSnapshot(snapshot);
- }
- await unitOfWork.DisposeAsync();
- await configuration.CompleteDBOperation();
- }
- }
-}
\ No newline at end of file
diff --git a/src/ServiceControl.Audit.Persistence/Infrastructure/QueryStatsInfo.cs b/src/ServiceControl.Audit.Persistence/Infrastructure/QueryStatsInfo.cs
index bae5067730..7e21326bd6 100644
--- a/src/ServiceControl.Audit.Persistence/Infrastructure/QueryStatsInfo.cs
+++ b/src/ServiceControl.Audit.Persistence/Infrastructure/QueryStatsInfo.cs
@@ -3,10 +3,10 @@ namespace ServiceControl.Audit.Auditing.MessagesView
public struct QueryStatsInfo
{
public readonly string ETag;
- public readonly int TotalCount;
- public readonly int HighestTotalCountOfAllTheInstances;
+ public readonly long TotalCount;
+ public readonly long HighestTotalCountOfAllTheInstances;
- public QueryStatsInfo(string eTag, int totalCount, int? highestTotalCountOfAllTheInstances = null)
+ public QueryStatsInfo(string eTag, long totalCount, long? highestTotalCountOfAllTheInstances = null)
{
ETag = eTag;
TotalCount = totalCount;
diff --git a/src/ServiceControl.Audit/Infrastructure/WebApi/HttpResponseExtensions.cs b/src/ServiceControl.Audit/Infrastructure/WebApi/HttpResponseExtensions.cs
index 2d0f9f9078..f4af3b765d 100644
--- a/src/ServiceControl.Audit/Infrastructure/WebApi/HttpResponseExtensions.cs
+++ b/src/ServiceControl.Audit/Infrastructure/WebApi/HttpResponseExtensions.cs
@@ -12,7 +12,7 @@ namespace ServiceControl.Audit.Infrastructure.WebApi
static class HttpResponseExtensions
{
- public static void WithTotalCount(this HttpResponse response, int totalCount) => response.WithHeader("Total-Count", totalCount.ToString(CultureInfo.InvariantCulture));
+ public static void WithTotalCount(this HttpResponse response, long totalCount) => response.WithHeader("Total-Count", totalCount.ToString(CultureInfo.InvariantCulture));
public static void WithEtag(this HttpResponse response, StringValues value) => response.Headers.ETag = value;
@@ -35,7 +35,7 @@ public static void WithDeterministicEtag(this HttpResponse response, string data
static void WithHeader(this HttpResponse response, string header, StringValues value) => response.Headers.Append(header, value);
- public static void WithPagingLinks(this HttpResponse response, PagingInfo pageInfo, int highestTotalCountOfAllInstances, int totalResults)
+ public static void WithPagingLinks(this HttpResponse response, PagingInfo pageInfo, long highestTotalCountOfAllInstances, long totalResults)
{
if (totalResults <= PagingInfo.DefaultPageSize)
{
@@ -96,7 +96,7 @@ public static void WithQueryStatsAndPagingInfo(this HttpResponse response, Query
}
public static void WithPagingLinksAndTotalCount(this HttpResponse response,
- PagingInfo pagingInfo, int totalCount, int highestTotalCountOfAllInstances = 1)
+ PagingInfo pagingInfo, long totalCount, long highestTotalCountOfAllInstances = 1)
{
response.WithTotalCount(totalCount);
response.WithPagingLinks(pagingInfo, highestTotalCountOfAllInstances, totalCount);
diff --git a/src/ServiceControl.Persistence.RavenDB/DatabaseSetup.cs b/src/ServiceControl.Persistence.RavenDB/DatabaseSetup.cs
index 8525e82f2f..89ea46ec3a 100644
--- a/src/ServiceControl.Persistence.RavenDB/DatabaseSetup.cs
+++ b/src/ServiceControl.Persistence.RavenDB/DatabaseSetup.cs
@@ -1,5 +1,6 @@
namespace ServiceControl.Persistence.RavenDB
{
+ using System;
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
@@ -8,6 +9,7 @@ namespace ServiceControl.Persistence.RavenDB
using Raven.Client.Exceptions;
using Raven.Client.ServerWide;
using Raven.Client.ServerWide.Operations;
+ using Raven.Client.ServerWide.Operations.Configuration;
class DatabaseSetup(RavenPersisterSettings settings, IDocumentStore documentStore)
{
@@ -16,29 +18,27 @@ public async Task Execute(CancellationToken cancellationToken)
await CreateDatabase(settings.DatabaseName, cancellationToken);
await CreateDatabase(settings.ThroughputDatabaseName, cancellationToken);
- await IndexCreation.CreateIndexesAsync(typeof(DatabaseSetup).Assembly, documentStore, null, null, cancellationToken);
+ await UpdateDatabaseSettings(settings.DatabaseName, cancellationToken);
+ await UpdateDatabaseSettings(settings.ThroughputDatabaseName, cancellationToken);
- var expirationConfig = new ExpirationConfiguration
- {
- Disabled = false,
- DeleteFrequencyInSec = settings.ExpirationProcessTimerInSeconds
- };
+ await IndexCreation.CreateIndexesAsync(typeof(DatabaseSetup).Assembly, documentStore, null, null, cancellationToken);
- await documentStore.Maintenance.SendAsync(new ConfigureExpirationOperation(expirationConfig), cancellationToken);
+ await ConfigureExpiration(settings, cancellationToken);
}
async Task CreateDatabase(string databaseName, CancellationToken cancellationToken)
{
- DatabaseRecordWithEtag dbRecord =
- await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName),
- cancellationToken);
+ var dbRecord = await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName), cancellationToken);
- if (dbRecord == null)
+ if (dbRecord is null)
{
try
{
- await documentStore.Maintenance.Server.SendAsync(
- new CreateDatabaseOperation(new DatabaseRecord(databaseName)), cancellationToken);
+ var databaseRecord = new DatabaseRecord(databaseName);
+ databaseRecord.Settings.Add("Indexing.Auto.SearchEngineType", "Corax");
+ databaseRecord.Settings.Add("Indexing.Static.SearchEngineType", "Corax");
+
+ await documentStore.Maintenance.Server.SendAsync(new CreateDatabaseOperation(databaseRecord), cancellationToken);
}
catch (ConcurrencyException)
{
@@ -46,5 +46,38 @@ await documentStore.Maintenance.Server.SendAsync(
}
}
}
+
+ async Task UpdateDatabaseSettings(string databaseName, CancellationToken cancellationToken)
+ {
+ var dbRecord = await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName), cancellationToken);
+
+ if (dbRecord is null)
+ {
+ throw new InvalidOperationException($"Database '{databaseName}' does not exist.");
+ }
+
+ var updated = false;
+
+ updated |= dbRecord.Settings.TryAdd("Indexing.Auto.SearchEngineType", "Corax");
+ updated |= dbRecord.Settings.TryAdd("Indexing.Static.SearchEngineType", "Corax");
+
+ if (updated)
+ {
+ await documentStore.Maintenance.ForDatabase(databaseName).SendAsync(new PutDatabaseSettingsOperation(databaseName, dbRecord.Settings), cancellationToken);
+ await documentStore.Maintenance.Server.SendAsync(new ToggleDatabasesStateOperation(databaseName, true), cancellationToken);
+ await documentStore.Maintenance.Server.SendAsync(new ToggleDatabasesStateOperation(databaseName, false), cancellationToken);
+ }
+ }
+
+ async Task ConfigureExpiration(RavenPersisterSettings settings, CancellationToken cancellationToken)
+ {
+ var expirationConfig = new ExpirationConfiguration
+ {
+ Disabled = false,
+ DeleteFrequencyInSec = settings.ExpirationProcessTimerInSeconds
+ };
+
+ await documentStore.Maintenance.SendAsync(new ConfigureExpirationOperation(expirationConfig), cancellationToken);
+ }
}
}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.RavenDB/EventLogDataStore.cs b/src/ServiceControl.Persistence.RavenDB/EventLogDataStore.cs
index ec9e171875..f11461c94e 100644
--- a/src/ServiceControl.Persistence.RavenDB/EventLogDataStore.cs
+++ b/src/ServiceControl.Persistence.RavenDB/EventLogDataStore.cs
@@ -15,7 +15,7 @@ public async Task Add(EventLogItem logItem)
await session.SaveChangesAsync();
}
- public async Task<(IList, int, string)> GetEventLogItems(PagingInfo pagingInfo)
+ public async Task<(IList, long, string)> GetEventLogItems(PagingInfo pagingInfo)
{
using var session = await sessionProvider.OpenSession();
var results = await session
diff --git a/src/ServiceControl.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs b/src/ServiceControl.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
index 83473e7dc8..41f6e0ce04 100644
--- a/src/ServiceControl.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
+++ b/src/ServiceControl.Persistence.RavenDB/RavenExternalPersistenceLifecycle.cs
@@ -65,9 +65,6 @@ public async Task Initialize(CancellationToken cancellationToken)
var databaseSetup = new DatabaseSetup(settings, store);
await databaseSetup.Execute(cancellationToken).ConfigureAwait(false);
-
- // Must go after the database setup, as database must exist
- StartupChecks.EnsureSingleNodeTopology(store);
}
finally
{
diff --git a/src/ServiceControl.Persistence/IEventLogDataStore.cs b/src/ServiceControl.Persistence/IEventLogDataStore.cs
index b9725cac47..33d71b7a68 100644
--- a/src/ServiceControl.Persistence/IEventLogDataStore.cs
+++ b/src/ServiceControl.Persistence/IEventLogDataStore.cs
@@ -8,6 +8,6 @@
public interface IEventLogDataStore
{
Task Add(EventLogItem logItem);
- Task<(IList items, int total, string version)> GetEventLogItems(PagingInfo pagingInfo);
+ Task<(IList items, long total, string version)> GetEventLogItems(PagingInfo pagingInfo);
}
}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence/Infrastructure/QueryStatsInfo.cs b/src/ServiceControl.Persistence/Infrastructure/QueryStatsInfo.cs
index 18d14e63d4..91abfcef4b 100644
--- a/src/ServiceControl.Persistence/Infrastructure/QueryStatsInfo.cs
+++ b/src/ServiceControl.Persistence/Infrastructure/QueryStatsInfo.cs
@@ -3,11 +3,11 @@ namespace ServiceControl.Persistence.Infrastructure
public struct QueryStatsInfo
{
public readonly string ETag;
- public readonly int TotalCount;
- public readonly int HighestTotalCountOfAllTheInstances;
+ public readonly long TotalCount;
+ public readonly long HighestTotalCountOfAllTheInstances;
public readonly bool IsStale;
- public QueryStatsInfo(string eTag, int totalCount, bool isStale, int? highestTotalCountOfAllTheInstances = null)
+ public QueryStatsInfo(string eTag, long totalCount, bool isStale, long? highestTotalCountOfAllTheInstances = null)
{
ETag = eTag;
TotalCount = totalCount;
diff --git a/src/ServiceControl.RavenDB/Container-README.md b/src/ServiceControl.RavenDB/Container-README.md
index 6f791f4cd3..0c7b42a517 100644
--- a/src/ServiceControl.RavenDB/Container-README.md
+++ b/src/ServiceControl.RavenDB/Container-README.md
@@ -20,7 +20,8 @@ This is the most basic way to start the container using `docker run`:
```shell
docker run -d --name servicecontrol-db \
- -v :/opt/RavenDB/Server/RavenData \
+ -v db-config:/etc/ravendb \
+ -v db-data:/var/lib/ravendb/data \
particular/servicecontrol-ravendb:latest
```
diff --git a/src/ServiceControl.RavenDB/Dockerfile b/src/ServiceControl.RavenDB/Dockerfile
index 1d73893aea..2d11600e2e 100644
--- a/src/ServiceControl.RavenDB/Dockerfile
+++ b/src/ServiceControl.RavenDB/Dockerfile
@@ -2,10 +2,15 @@ ARG BASETAG
ARG VERSION
FROM ravendb/ravendb:$BASETAG AS ravendb
-COPY src/ServiceControl.RavenDB/RavenLicense.json /opt/RavenDB/servicecontrol-license.json
+COPY src/ServiceControl.RavenDB/RavenLicense.json /usr/lib/ravendb/servicecontrol-license.json
+COPY src/ServiceControl.RavenDB/sc-container-startup.sh /usr/lib/ravendb/scripts/
+
+USER root
+RUN chown 999:999 /var/lib/ravendb/data
+USER ravendb
ENV RAVEN_License_Eula_Accepted=true \
- RAVEN_License_Path=/opt/RavenDB/servicecontrol-license.json \
+ RAVEN_License_Path=/usr/lib/ravendb/servicecontrol-license.json \
RAVEN_Setup_Mode=None
LABEL org.opencontainers.image.source=https://github.com/Particular/ServiceControl \
@@ -15,3 +20,5 @@ LABEL org.opencontainers.image.source=https://github.com/Particular/ServiceContr
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.title=ServiceControl.RavenDB \
org.opencontainers.image.description="ServiceControl database storage using RavenDB"
+
+CMD [ "/bin/bash", "/usr/lib/ravendb/scripts/sc-container-startup.sh" ]
diff --git a/src/ServiceControl.RavenDB/EmbeddedDatabase.cs b/src/ServiceControl.RavenDB/EmbeddedDatabase.cs
index 3d093434a8..9e4b711b0b 100644
--- a/src/ServiceControl.RavenDB/EmbeddedDatabase.cs
+++ b/src/ServiceControl.RavenDB/EmbeddedDatabase.cs
@@ -57,18 +57,19 @@ public static EmbeddedDatabase Start(EmbeddedDatabaseConfiguration databaseConfi
{
CommandLineArgs =
[
- $"--License.Path=\"{licenseFileNameAndServerDirectory.LicenseFileName}\"",
$"--Logs.Mode={databaseConfiguration.LogsMode}",
// HINT: If this is not set, then Raven will pick a default location relative to the server binaries
// See https://github.com/ravendb/ravendb/issues/15694
$"--Indexing.NuGetPackagesPath=\"{nugetPackagesPath}\""
],
- AcceptEula = true,
DataDirectory = databaseConfiguration.DbPath,
ServerUrl = databaseConfiguration.ServerUrl,
LogsPath = databaseConfiguration.LogPath
};
+ serverOptions.Licensing.EulaAccepted = true;
+ serverOptions.Licensing.LicensePath = licenseFileNameAndServerDirectory.LicenseFileName;
+
if (!string.IsNullOrWhiteSpace(licenseFileNameAndServerDirectory.ServerDirectory))
{
serverOptions.ServerDirectory = licenseFileNameAndServerDirectory.ServerDirectory;
@@ -146,7 +147,8 @@ public async Task Connect(CancellationToken cancellationToken)
Conventions = new DocumentConventions
{
SaveEnumsAsIntegers = true
- }
+ },
+ SkipCreatingDatabase = true
};
if (configuration.FindClrType != null)
diff --git a/src/ServiceControl.RavenDB/StartupChecks.cs b/src/ServiceControl.RavenDB/StartupChecks.cs
index c3538937c0..46558850e9 100644
--- a/src/ServiceControl.RavenDB/StartupChecks.cs
+++ b/src/ServiceControl.RavenDB/StartupChecks.cs
@@ -3,32 +3,21 @@
using System.Reflection;
using System.Threading;
using Raven.Client.Documents;
- using Raven.Client.Documents.Conventions;
- using Raven.Client.Http;
using Raven.Client.ServerWide.Operations;
- using Sparrow.Json;
public static class StartupChecks
{
public static async Task EnsureServerVersion(IDocumentStore store, CancellationToken cancellationToken = default)
{
var build = await store.Maintenance.Server.SendAsync(new GetBuildNumberOperation(), cancellationToken);
- var embeddedVersion = typeof(Raven.Embedded.EmbeddedServer).Assembly.GetCustomAttributes().First().InformationalVersion;
- if (embeddedVersion != build.FullVersion)
- {
- throw new Exception($"ServiceControl expects RavenDB Server version {embeddedVersion} but the server is using {build.FullVersion}.");
- }
- }
- // Must go after the database setup, as database must exist
- public static void EnsureSingleNodeTopology(IDocumentStore store)
- {
- using (var session = store.OpenAsyncSession())
+ var clientVersion = typeof(Raven.Client.Constants).Assembly.GetCustomAttributes().First().InformationalVersion;
+ var parts = clientVersion.Split('.');
+ var clientProductVersion = $"{parts[0]}.{parts[1]}";
+
+ if (clientProductVersion != build.ProductVersion)
{
- if (session.Advanced.RequestExecutor.Topology.Nodes.Count > 1)
- {
- throw new Exception("ServiceControl expects to run against a single-node RavenDB database. Cluster configurations are not supported.");
- }
+ throw new Exception($"ServiceControl expects RavenDB Server version {clientProductVersion} but the server is using {build.ProductVersion}.");
}
}
}
diff --git a/src/ServiceControl.RavenDB/sc-container-startup.sh b/src/ServiceControl.RavenDB/sc-container-startup.sh
new file mode 100755
index 0000000000..cd317bbcb5
--- /dev/null
+++ b/src/ServiceControl.RavenDB/sc-container-startup.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+LEGACY_PATH="/opt/RavenDB/Server/RavenData"
+
+if [[ -d "$LEGACY_PATH" ]]; then
+ echo "ERROR: RavenDB data is being mounted to the wrong location. It should be mounted to $RAVEN_DataDir. Refer to the article 'Upgrade ServiceControl from Version 5 to Version 6' in the documentation for more details about the steps that must be taken to update ServiceControl to this version."
+ exit 1
+fi
+
+chmod -R u+rwX $RAVEN_DataDir 2> /dev/null
+
+if [[ $? -ne 0 ]]; then
+ echo "ERROR: RavenDB data permissions are not correct. The owner and group should be set to ID 999. Refer to the article 'Upgrade ServiceControl from Version 5 to Version 6' in the documentation for more details about the steps that must be taken to update ServiceControl to this version."
+ exit 1
+fi
+
+source /usr/lib/ravendb/scripts/run-raven.sh
diff --git a/src/ServiceControl/Infrastructure/WebApi/HttpResponseExtensions.cs b/src/ServiceControl/Infrastructure/WebApi/HttpResponseExtensions.cs
index af2fcfd345..6d33e4394e 100644
--- a/src/ServiceControl/Infrastructure/WebApi/HttpResponseExtensions.cs
+++ b/src/ServiceControl/Infrastructure/WebApi/HttpResponseExtensions.cs
@@ -11,7 +11,7 @@ namespace ServiceControl.Infrastructure.WebApi
static class HttpResponseExtensions
{
- public static void WithTotalCount(this HttpResponse response, int totalCount) => response.WithHeader("Total-Count", totalCount.ToString(CultureInfo.InvariantCulture));
+ public static void WithTotalCount(this HttpResponse response, long totalCount) => response.WithHeader("Total-Count", totalCount.ToString(CultureInfo.InvariantCulture));
public static void WithEtag(this HttpResponse response, StringValues value) => response.Headers.ETag = value;
@@ -34,7 +34,7 @@ public static void WithDeterministicEtag(this HttpResponse response, string data
static void WithHeader(this HttpResponse response, string header, StringValues value) => response.Headers.Append(header, value);
- public static void WithPagingLinks(this HttpResponse response, PagingInfo pageInfo, int highestTotalCountOfAllInstances, int totalResults)
+ public static void WithPagingLinks(this HttpResponse response, PagingInfo pageInfo, long highestTotalCountOfAllInstances, long totalResults)
{
if (totalResults <= PagingInfo.DefaultPageSize)
{
@@ -95,7 +95,7 @@ public static void WithQueryStatsAndPagingInfo(this HttpResponse response, Query
}
public static void WithPagingLinksAndTotalCount(this HttpResponse response,
- PagingInfo pagingInfo, int totalCount, int highestTotalCountOfAllInstances = 1)
+ PagingInfo pagingInfo, long totalCount, long highestTotalCountOfAllInstances = 1)
{
response.WithTotalCount(totalCount);
response.WithPagingLinks(pagingInfo, highestTotalCountOfAllInstances, totalCount);
diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/SelfContainedRavenDBTest.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/SelfContainedRavenDBTest.cs
index e1a825e1f0..9e1b1582be 100644
--- a/src/ServiceControlInstaller.Engine.UnitTests/Validation/SelfContainedRavenDBTest.cs
+++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/SelfContainedRavenDBTest.cs
@@ -15,16 +15,16 @@ public void CheckForSelfContainedRavenDB()
var ravenServerPath = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "..", "deploy", "RavenDBServer"));
var ravenStudio = Path.Combine(ravenServerPath, "Raven.Studio.zip");
- var runtimes = Path.Combine(ravenServerPath, "runtimes");
- var systemCollections = Path.Combine(ravenServerPath, "System.Collections.dll");
- var aspNetCoreHttp = Path.Combine(ravenServerPath, "Microsoft.AspNetCore.Http.dll");
+ var ravenServerDll = Path.Combine(ravenServerPath, "Raven.Server.dll");
+ var ravenServerExe = Path.Combine(ravenServerPath, "Raven.Server.exe");
+ var offlineOperationsUtility = Path.Combine(ravenServerPath, "rvn.exe");
Assert.Multiple(() =>
{
Assert.That(ravenStudio, Does.Exist); // No matter what
- Assert.That(Directory.Exists(runtimes), Is.EqualTo(isLocal)); // Only in local development
- Assert.That(File.Exists(systemCollections), Is.EqualTo(isCI)); // Only on CI
- Assert.That(File.Exists(aspNetCoreHttp), Is.EqualTo(isCI)); // Only on CI
+ Assert.That(File.Exists(ravenServerDll), Is.EqualTo(isLocal)); // Only in local development
+ Assert.That(File.Exists(ravenServerExe), Is.EqualTo(isCI)); // Only on CI
+ Assert.That(File.Exists(offlineOperationsUtility), Is.EqualTo(isCI)); // Only on CI
});
}
}
diff --git a/src/container-integration-test/servicecontrol.yml b/src/container-integration-test/servicecontrol.yml
index f821418489..19b2c0df91 100644
--- a/src/container-integration-test/servicecontrol.yml
+++ b/src/container-integration-test/servicecontrol.yml
@@ -20,8 +20,8 @@ services:
ports:
- "8080:8080"
volumes:
- - ./volumes/db-data:/opt/RavenDB/Server/RavenData
- - ./volumes/db-config:/opt/RavenDB/config
+ - db-config:/etc/ravendb
+ - db-data:/var/lib/ravendb/data
servicecontrol-audit:
image: ghcr.io/particular/servicecontrol-audit:${SERVICECONTROL_TAG}
@@ -41,3 +41,7 @@ services:
command: --setup-and-run
ports:
- "33633:33633"
+
+volumes:
+ db-config:
+ db-data: