From 8ea38bb7a7824f74cf849af489220631c64e46ed Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Mon, 8 Apr 2024 15:19:42 +0800 Subject: [PATCH] Backport fixes to v5 (#4057) * Diagnostic improvements and bulk insert time config setting (#4049) * Add exception logging to DomainEvents * Add NServiceBus reference * Add debug logging to EndpointInstanceMonitor * Add configurable bulk insert timeout --------- Co-authored-by: WilliamBZA * Remove audit queue validation checks (#4048) * Fix small validation bugs (#4046) * Allow propogation of propertychange events * Prevent event bubbling * Switch back to invalid file chars * Add notify propogation tests * Add file path sanitization tests --------- Co-authored-by: Mike Minutillo * Fix test for primary instance convention default Original commit is here 84f61ec3d8e6aebff310e5b9eac307f2897880f6 * RavenDb License refresh --------- Co-authored-by: Szymon Pobiega Co-authored-by: WilliamBZA Co-authored-by: John Simons --- .../DatabaseConfiguration.cs | 6 +- .../RavenLicense.json | 26 ++-- .../RavenPersistenceConfiguration.cs | 34 ++++- .../UnitOfWork/RavenAuditUnitOfWorkFactory.cs | 2 +- .../SharedEmbeddedServer.cs | 2 +- .../PathNotifyPropertyChanges.cs | 129 ++++++++++++++++++ .../FilePathExtensionsTests.cs | 25 ++++ .../ServiceControlAddScreenLoadedTests.cs | 16 ++- .../AddAuditInstanceValidationTests.cs | 4 +- .../AddErrorInstanceValidationTests.cs | 4 +- .../Extensions/FilePathExtensions.cs | 13 +- .../UI/InstanceAdd/ServiceControlAddView.xaml | 8 +- .../InstanceAdd/ServiceControlAddView.xaml.cs | 23 ++-- .../InstanceAdd/ServiceControlAddViewModel.cs | 25 ++++ .../ServiceControlAddViewModelValidator.cs | 4 - ...rviceControlAuditEditViewModelValidator.cs | 4 - .../DomainEvents.cs | 25 +++- .../ServiceControl.DomainEvents.csproj | 1 + .../RavenLicense.json | 26 ++-- .../Monitoring/EndpointInstanceMonitor.cs | 5 + 20 files changed, 311 insertions(+), 71 deletions(-) create mode 100644 src/ServiceControl.Config.Tests/AddInstance/AddErrorInstance/PathNotifyPropertyChanges.cs create mode 100644 src/ServiceControl.Config.Tests/FilePathExtensionsTests.cs diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseConfiguration.cs b/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseConfiguration.cs index 7bb08d6fa8..f1cb948d5e 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseConfiguration.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/DatabaseConfiguration.cs @@ -11,7 +11,8 @@ public class DatabaseConfiguration TimeSpan auditRetentionPeriod, int maxBodySizeToStore, int minimumStorageLeftRequiredForIngestion, - ServerConfiguration serverConfiguration) + ServerConfiguration serverConfiguration, + TimeSpan bulkInsertCommitTimeout) { Name = name; ExpirationProcessTimerInSeconds = expirationProcessTimerInSeconds; @@ -19,6 +20,7 @@ public class DatabaseConfiguration AuditRetentionPeriod = auditRetentionPeriod; MaxBodySizeToStore = maxBodySizeToStore; ServerConfiguration = serverConfiguration; + BulkInsertCommitTimeout = bulkInsertCommitTimeout; MinimumStorageLeftRequiredForIngestion = minimumStorageLeftRequiredForIngestion; } @@ -37,5 +39,7 @@ public class DatabaseConfiguration public int MaxBodySizeToStore { get; } public int MinimumStorageLeftRequiredForIngestion { get; internal set; } //Setting for ATT only + + public TimeSpan BulkInsertCommitTimeout { get; } } } diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenLicense.json b/src/ServiceControl.Audit.Persistence.RavenDB/RavenLicense.json index b750d5fe08..be1b079a94 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenLicense.json +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenLicense.json @@ -2,18 +2,18 @@ "Id": "64c6a174-3f3a-4e7d-ac5d-b3eedd801460", "Name": "ParticularNservicebus (Israel)", "Keys": [ - "NKhWkUrAnwvE+M6VmSxK7J6am", - "D2JME/+XVMQq9xh18W4lClw+a", - "FDWF3ZqgA89/tIgwUyc8BZ09f", - "wSsdi8WzL/w01TOB00HuGQE4r", - "jAkg7BKr7LWIRfbg02KLhiU4M", - "Y1FQx8IA4BOBK0XP+X+aaWE48", - "fLmwuqGsSc0kg0rzFdYs9AAyU", - "GKEkFCisMLQ4vMAcREjM0NRYX", - "GhufAh8AnwIgAJ8CIwCfAiQgn", - "wIlIJ8CJiCfAicgnwIoIJ8CKS", - "CfAiognwIrIJ8CLACfAi0AnwI", - "uIJ8CLwCfAjAggQM2LjBDMEQG", - "ODk8PT6fAiEgYoFY" + "mGE1ppvgfJjJZXnMUHFXQXJuL", + "sQ1trVf0DZlVNhFSjclHNQ36p", + "0a9ZWIaCiqu8Mh4CDVd4koc2X", + "2Y18vLYbTm5JH4J91IZJhq3bP", + "/bzD806qeBpRcDCLt82ON0+RO", + "eXiLDs/DTOBU9sPsNZEPzUX+C", + "uMA5nm6QaaB85GEpFuahhAAyU", + "GKEkFCisMLQ4vMAcREjM0NRYX", + "GhufAh8AnwIgAJ8CIwCfAiQgn", + "wIlIJ8CJiCfAicgnwIoIJ8CKS", + "CfAiognwIrIJ8CLACfAi0AnwI", + "uIJ8CLwCfAjAggQM2LjBDMEQG", + "ODk8PT6fAiEgYoRa" ] } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs index b06430c881..546537093c 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs @@ -14,6 +14,7 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration public const string LogPathKey = "LogPath"; public const string RavenDbLogLevelKey = "RavenDBLogLevel"; public const string MinimumStorageLeftRequiredForIngestionKey = "MinimumStorageLeftRequiredForIngestion"; + public const string BulkInsertCommitTimeoutInSecondsKey = "BulkInsertCommitTimeoutInSeconds"; public IEnumerable ConfigurationKeys => new[]{ DatabaseNameKey, @@ -23,7 +24,8 @@ public class RavenPersistenceConfiguration : IPersistenceConfiguration ExpirationProcessTimerInSecondsKey, LogPathKey, RavenDbLogLevelKey, - MinimumStorageLeftRequiredForIngestionKey + MinimumStorageLeftRequiredForIngestionKey, + BulkInsertCommitTimeoutInSecondsKey }; public string Name => "RavenDB"; @@ -98,6 +100,8 @@ internal static DatabaseConfiguration GetDatabaseConfiguration(PersistenceSettin var expirationProcessTimerInSeconds = GetExpirationProcessTimerInSeconds(settings); + var bulkInsertTimeout = TimeSpan.FromSeconds(GetBulkInsertCommitTimeout(settings)); + return new DatabaseConfiguration( databaseName, expirationProcessTimerInSeconds, @@ -105,7 +109,8 @@ internal static DatabaseConfiguration GetDatabaseConfiguration(PersistenceSettin settings.AuditRetentionPeriod, settings.MaxBodySizeToStore, minimumStorageLeftRequiredForIngestion, - serverConfiguration); + serverConfiguration, + bulkInsertTimeout); } static int GetExpirationProcessTimerInSeconds(PersistenceSettings settings) @@ -132,8 +137,33 @@ static int GetExpirationProcessTimerInSeconds(PersistenceSettings settings) return expirationProcessTimerInSeconds; } + static int GetBulkInsertCommitTimeout(PersistenceSettings settings) + { + var bulkInsertCommitTimeoutInSeconds = BulkInsertCommitTimeoutInSecondsDefault; + + if (settings.PersisterSpecificSettings.TryGetValue(BulkInsertCommitTimeoutInSecondsKey, out var bulkInsertCommitTimeoutString)) + { + bulkInsertCommitTimeoutInSeconds = int.Parse(bulkInsertCommitTimeoutString); + } + + if (bulkInsertCommitTimeoutInSeconds < 0) + { + Logger.Error($"BulkInsertCommitTimeout cannot be negative. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}"); + return BulkInsertCommitTimeoutInSecondsDefault; + } + + if (bulkInsertCommitTimeoutInSeconds > TimeSpan.FromHours(1).TotalSeconds) + { + Logger.Error($"BulkInsertCommitTimeout cannot be larger than {TimeSpan.FromHours(1).TotalSeconds}. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}"); + return BulkInsertCommitTimeoutInSecondsDefault; + } + + return bulkInsertCommitTimeoutInSeconds; + } + static readonly ILog Logger = LogManager.GetLogger(typeof(RavenPersistenceConfiguration)); const int ExpirationProcessTimerInSecondsDefault = 600; + const int BulkInsertCommitTimeoutInSecondsDefault = 60; } } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/UnitOfWork/RavenAuditUnitOfWorkFactory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/UnitOfWork/RavenAuditUnitOfWorkFactory.cs index 751fdba882..b404ac935d 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/UnitOfWork/RavenAuditUnitOfWorkFactory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/UnitOfWork/RavenAuditUnitOfWorkFactory.cs @@ -20,7 +20,7 @@ class RavenAuditIngestionUnitOfWorkFactory : IAuditIngestionUnitOfWorkFactory public IAuditIngestionUnitOfWork StartNew(int batchSize) { - var timedCancellationSource = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var timedCancellationSource = new CancellationTokenSource(databaseConfiguration.BulkInsertCommitTimeout); var bulkInsert = documentStoreProvider.GetDocumentStore() .BulkInsert(new BulkInsertOptions { SkipOverwriteIfUnchanged = true, }, timedCancellationSource.Token); diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SharedEmbeddedServer.cs b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SharedEmbeddedServer.cs index 84252dd49c..bed5c96e23 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SharedEmbeddedServer.cs +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/SharedEmbeddedServer.cs @@ -27,7 +27,7 @@ public static async Task GetInstance(CancellationToken cancell var logsMode = "Operations"; var serverUrl = $"http://localhost:{PortUtility.FindAvailablePort(33334)}"; - embeddedDatabase = EmbeddedDatabase.Start(new DatabaseConfiguration("audit", 60, true, TimeSpan.FromMinutes(5), 120000, 5, new ServerConfiguration(dbPath, serverUrl, logPath, logsMode))); + embeddedDatabase = EmbeddedDatabase.Start(new DatabaseConfiguration("audit", 60, true, TimeSpan.FromMinutes(5), 120000, 5, new ServerConfiguration(dbPath, serverUrl, logPath, logsMode), TimeSpan.FromSeconds(60))); //make sure that the database is up while (true) diff --git a/src/ServiceControl.Config.Tests/AddInstance/AddErrorInstance/PathNotifyPropertyChanges.cs b/src/ServiceControl.Config.Tests/AddInstance/AddErrorInstance/PathNotifyPropertyChanges.cs new file mode 100644 index 0000000000..94fd5a58bc --- /dev/null +++ b/src/ServiceControl.Config.Tests/AddInstance/AddErrorInstance/PathNotifyPropertyChanges.cs @@ -0,0 +1,129 @@ +namespace ServiceControl.Config.Tests.AddInstance.AddErrorInstance +{ + using NUnit.Framework; + using ServiceControl.Config.UI.InstanceAdd; + + public class PathNotifyPropertyChangesTests + { + public ServiceControlAddViewModel Given_adding_error_instance() + { + var viewModel = new ServiceControlAddViewModel(); + + return viewModel; + } + + [Test] + public void ChangesTo_ErrorSharedServiceControlEditorViewModel_DbPath_Notifies_ServiceControlAddViewModel_DbPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.ErrorDatabasePath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControl.DatabasePath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to DatabasePath did not notify ServiceControlAddViewModel"); + } + + [Test] + public void ChangesTo_ErrorSharedServiceControlEditorViewModel_LogPath_Notifies_ServiceControlAddViewModel_LogPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.ErrorLogPath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControl.LogPath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to LogPath did not notify ServiceControlAddViewModel"); + } + + [Test] + public void ChangesTo_ErrorSharedServiceControlEditorViewModel_DestinationPath_Notifies_ServiceControlAddViewModel_DestinationPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.ErrorDestinationPath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControl.DestinationPath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to DestinationPath did not notify ServiceControlAddViewModel"); + } + + [Test] + public void ChangesTo_AuditSharedServiceControlEditorViewModel_DbPath_Notifies_ServiceControlAddViewModel_DbPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.AuditDatabasePath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControlAudit.DatabasePath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to DatabasePath did not notify ServiceControlAddViewModel"); + } + + [Test] + public void ChangesTo_AuditSharedServiceControlEditorViewModel_LogPath_Notifies_ServiceControlAddViewModel_LogPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.AuditLogPath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControlAudit.LogPath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to LogPath did not notify ServiceControlAddViewModel"); + } + + [Test] + public void ChangesTo_AuditSharedServiceControlEditorViewModel_DestinationPath_Notifies_ServiceControlAddViewModel_DestinationPath() + { + var viewModel = Given_adding_error_instance(); + + var propertyNotified = false; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(viewModel.AuditDestinationPath)) + { + propertyNotified = true; + } + }; + + viewModel.ServiceControlAudit.DestinationPath = "NewValue"; + + Assert.IsTrue(propertyNotified, "Changes to DestinationPath did not notify ServiceControlAddViewModel"); + } + } +} diff --git a/src/ServiceControl.Config.Tests/FilePathExtensionsTests.cs b/src/ServiceControl.Config.Tests/FilePathExtensionsTests.cs new file mode 100644 index 0000000000..11e75c4035 --- /dev/null +++ b/src/ServiceControl.Config.Tests/FilePathExtensionsTests.cs @@ -0,0 +1,25 @@ +namespace ServiceControl.Config.Tests +{ + using NUnit.Framework; + using ServiceControl.Config.Extensions; + + class FilePathExtensionsTests + { + [TestCase(null)] + [TestCase(@"C:\")] + [TestCase(@"C:")] + [TestCase(@"C:\foo\bar")] + //[TestCase(@"/foo/bar", @"\foo\bar")] // NOTE: Returns \foobar + [TestCase(@"C:\foo\bar", @"C:\foo\bar")] + [TestCase(@"\\foo\bar", @"\foo\bar")] + [TestCase(@"C:\foo\bar\", @"C:\foo\bar\")] + [TestCase(@"C:\foo:bar", @"C:\foobar")] + [TestCase(@"C:\foo|bar", @"C:\foobar")] + public void TestSanitization(string original, string sanitized = null) + { + var converted = FilePathExtensions.SanitizeFilePath(original); + + Assert.That(converted, Is.EqualTo(sanitized ?? original)); + } + } +} diff --git a/src/ServiceControl.Config.Tests/ServiceControlAddScreenLoadedTests.cs b/src/ServiceControl.Config.Tests/ServiceControlAddScreenLoadedTests.cs index 36b19131b6..fc9a928ba7 100644 --- a/src/ServiceControl.Config.Tests/ServiceControlAddScreenLoadedTests.cs +++ b/src/ServiceControl.Config.Tests/ServiceControlAddScreenLoadedTests.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Config.Tests { + using System; using System.ComponentModel; using NUnit.Framework; using ServiceControlInstaller.Engine.Configuration.ServiceControl; @@ -8,6 +9,9 @@ class AddErrorInstanceScreenLoadedTests { + static readonly string programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + static readonly string programX86Path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + [Test] public void Error_and_Audit_Instances_are_selected_for_install() { @@ -148,11 +152,11 @@ public void Destination_path_is_null() var errorInfo = (INotifyDataErrorInfo)viewModel; - Assert.IsNull(viewModel.ErrorDestinationPath); + Assert.That(viewModel.ErrorDestinationPath, Is.EqualTo($@"{programX86Path}\Particular Software\Particular.ServiceControl")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.ErrorDestinationPath))); - Assert.IsNull(viewModel.AuditDestinationPath); + Assert.That(viewModel.AuditDestinationPath, Is.EqualTo($@"{programX86Path}\Particular Software\Particular.ServiceControl.Audit")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.AuditDestinationPath))); } @@ -164,11 +168,11 @@ public void Log_path_is_null() var errorInfo = (INotifyDataErrorInfo)viewModel; - Assert.IsNull(viewModel.ErrorLogPath); + Assert.That(viewModel.ErrorLogPath, Is.EqualTo($@"{programDataPath}\Particular\ServiceControl\Particular.ServiceControl\Logs")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.ErrorLogPath))); - Assert.IsNull(viewModel.AuditLogPath); + Assert.That(viewModel.AuditLogPath, Is.EqualTo($@"{programDataPath}\Particular\ServiceControl\Particular.ServiceControl.Audit\Logs")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.AuditLogPath))); } @@ -181,11 +185,11 @@ public void Database_path_is_null() var errorInfo = (INotifyDataErrorInfo)viewModel; - Assert.IsNull(viewModel.ErrorDatabasePath); + Assert.That(viewModel.ErrorDatabasePath, Is.EqualTo($@"{programDataPath}\Particular\ServiceControl\Particular.ServiceControl\DB")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.ErrorDatabasePath))); - Assert.IsNull(viewModel.AuditDatabasePath); + Assert.That(viewModel.AuditDatabasePath, Is.EqualTo($@"{programDataPath}\Particular\ServiceControl\Particular.ServiceControl.Audit\DB")); Assert.IsEmpty(errorInfo.GetErrors(nameof(viewModel.AuditDatabasePath))); } diff --git a/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs index 9942032763..df5dc6b0e6 100644 --- a/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs @@ -30,9 +30,9 @@ public void Convention_name_cannot_be_empty_when_instance_names_are_not_provided var notifyErrorInfo = GetNotifyErrorInfo(viewModel); - Assert.IsFalse(instanceNamesProvided); + Assert.That(instanceNamesProvided); // Provided because the convention auto-fills them on instantiation - Assert.IsNotEmpty(notifyErrorInfo.GetErrors(nameof(viewModel.ConventionName))); + Assert.IsEmpty(notifyErrorInfo.GetErrors(nameof(viewModel.ConventionName))); } [Test] diff --git a/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs index 1fc6c18c19..1809f5246c 100644 --- a/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs @@ -30,9 +30,9 @@ public void Convention_name_cannot_be_empty_when_instance_names_are_not_provided var notifyErrorInfo = GetNotifyErrorInfo(viewModel); - Assert.IsFalse(instanceNamesProvided); + Assert.That(instanceNamesProvided); // Provided because the convention default auto-fills them on instantiation - Assert.IsNotEmpty(notifyErrorInfo.GetErrors(nameof(viewModel.ConventionName))); + Assert.IsEmpty(notifyErrorInfo.GetErrors(nameof(viewModel.ConventionName))); } [Test] diff --git a/src/ServiceControl.Config/Extensions/FilePathExtensions.cs b/src/ServiceControl.Config/Extensions/FilePathExtensions.cs index 439642c636..6580527ebe 100644 --- a/src/ServiceControl.Config/Extensions/FilePathExtensions.cs +++ b/src/ServiceControl.Config/Extensions/FilePathExtensions.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Config.Extensions { + using System; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -51,14 +52,21 @@ static string RemoveInvalidDirectoryNameCharacters(string name) var segments = name.Split('\\'); var nameBuilder = new StringBuilder(); + bool hasMoreThanRootPath = true; if (root != string.Empty) { + hasMoreThanRootPath = false; nameBuilder.Append(root); } foreach (var segment in segments) { + if (nameBuilder.Length > 0 && hasMoreThanRootPath) + { + nameBuilder.Append("\\"); + } + foreach (char character in segment) { if (Path.GetInvalidFileNameChars().Contains(character)) @@ -67,12 +75,11 @@ static string RemoveInvalidDirectoryNameCharacters(string name) } nameBuilder.Append(character); + hasMoreThanRootPath = true; } - - nameBuilder.Append("\\"); } - return nameBuilder.ToString().TrimEnd('\\'); + return nameBuilder.ToString(); } public static string SanitizeFilePath(this string name) diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml index fc24494c94..aa367448b9 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml @@ -78,9 +78,9 @@ Foreground="{StaticResource ErrorBrush}" Text="Must select either an audit or an error instance." /> - + - + - - + + chk.IsChecked = false; + if (e.Source is Expander) + { + e.Handled = e.OriginalSource is not (Ellipse or Path); + } + else + { + e.Handled = e.Source == sender; + } } } } \ No newline at end of file diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs index 66bfeeb2fb..f514290399 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs @@ -18,6 +18,31 @@ public ServiceControlAddViewModel() { DisplayName = "ADD SERVICECONTROL"; GetWindowsServiceNames = () => ServiceController.GetServices().Select(windowsService => windowsService.ServiceName).ToArray(); + ConventionName = "Particular.ServiceControl"; + OnConventionNameChanged(); + + var i = 0; + while (ConventionName != ErrorInstanceName) + { + ConventionName = $"Particular.ServiceControl.{++i}"; + // ErrorInstanceName updated via OnConventionNameChanged added by Fody + } + + ServiceControl.PropertyChanged += ServiceControl_PropertyChanged; + ServiceControlAudit.PropertyChanged += ServiceControl_PropertyChanged; + } + + void ServiceControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender == ServiceControl) + { + NotifyOfPropertyChange($"Error{e.PropertyName}"); + } + + if (sender == ServiceControlAudit) + { + NotifyOfPropertyChange($"Audit{e.PropertyName}"); + } } public Func GetWindowsServiceNames { get; set; } diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs index d4baf0686a..223d5505dd 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs @@ -224,8 +224,6 @@ public ServiceControlAddViewModelValidator() .WithMessage(string.Format(Validation.Validations.MSG_QUEUENAMES_NOT_EQUAL, "Audit", "Error Forwarding")) .MustNotBeIn(x => Validations.UsedErrorQueueNames(x.SelectedTransport, x.ErrorInstanceName, x.ConnectionString)) .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit")) - .MustNotBeIn(x => Validations.UsedAuditQueueNames(x.SelectedTransport, x.AuditInstanceName, x.ConnectionString)) - .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit")) .When(x => x.InstallAuditInstance); RuleFor(x => x.AuditForwardingQueueName) @@ -238,8 +236,6 @@ public ServiceControlAddViewModelValidator() .WithMessage(string.Format(Validation.Validations.MSG_UNIQUEQUEUENAME, "Error Forwarding")) .MustNotBeIn(x => Validations.UsedErrorQueueNames(x.SelectedTransport, x.ErrorInstanceName, x.ConnectionString)) .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit Forwarding")) - .MustNotBeIn(x => Validations.UsedAuditQueueNames(x.SelectedTransport, x.AuditInstanceName, x.ConnectionString)) - .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit Forwarding")) .When(x => x.InstallAuditInstance) .When(x => x.AuditForwarding?.Value ?? false); } diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs index 4cd60f941f..b4dd2524e0 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs @@ -69,8 +69,6 @@ public ServiceControlAuditEditViewModelValidator() .WithMessage(string.Format(Validation.Validations.MSG_UNIQUEQUEUENAME, "Audit Forwarding")) .MustNotBeIn(x => Validations.UsedErrorQueueNames(x.SelectedTransport, x.InstanceName, x.ConnectionString)) .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit")) - .MustNotBeIn(x => Validations.UsedAuditQueueNames(x.SelectedTransport, x.InstanceName, x.ConnectionString)) - .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit")) .When(x => x.SubmitAttempted); RuleFor(x => x.AuditForwardingQueueName) @@ -78,8 +76,6 @@ public ServiceControlAuditEditViewModelValidator() .WithMessage(string.Format(Validation.Validations.MSG_FORWARDINGQUEUENAME, "Audit Forwarding")) .NotEqual(x => x.AuditQueueName) .WithMessage(string.Format(Validation.Validations.MSG_QUEUENAMES_NOT_EQUAL, "Audit Forwarding", "Audit")) - .MustNotBeIn(x => Validations.UsedAuditQueueNames(x.SelectedTransport, x.InstanceName, x.ConnectionString)) - .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit Forwarding")) .MustNotBeIn(x => Validations.UsedErrorQueueNames(x.SelectedTransport, x.InstanceName, x.ConnectionString)) .WithMessage(string.Format(Validation.Validations.MSG_QUEUE_ALREADY_ASSIGNED, "Audit Forwarding")) .When(x => x.SubmitAttempted && x.AuditForwarding.Value); diff --git a/src/ServiceControl.DomainEvents/DomainEvents.cs b/src/ServiceControl.DomainEvents/DomainEvents.cs index 6df5c980c1..5714b464d1 100644 --- a/src/ServiceControl.DomainEvents/DomainEvents.cs +++ b/src/ServiceControl.DomainEvents/DomainEvents.cs @@ -3,9 +3,12 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; + using NServiceBus.Logging; public class DomainEvents : IDomainEvents { + static readonly ILog Log = LogManager.GetLogger(); + readonly IServiceProvider serviceProvider; public DomainEvents(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider; @@ -14,15 +17,31 @@ public class DomainEvents : IDomainEvents var handlers = serviceProvider.GetServices>(); foreach (var handler in handlers) { - await handler.Handle(domainEvent) - .ConfigureAwait(false); + try + { + await handler.Handle(domainEvent) + .ConfigureAwait(false); + } + catch (Exception e) + { + Log.Error($"Unexpected error publishing domain event {typeof(T)}", e); + throw; + } } var ieventHandlers = serviceProvider.GetServices>(); foreach (var handler in ieventHandlers) { - await handler.Handle(domainEvent) + try + { + await handler.Handle(domainEvent) .ConfigureAwait(false); + } + catch (Exception e) + { + Log.Error($"Unexpected error publishing domain event {typeof(T)}", e); + throw; + } } } } diff --git a/src/ServiceControl.DomainEvents/ServiceControl.DomainEvents.csproj b/src/ServiceControl.DomainEvents/ServiceControl.DomainEvents.csproj index b338efabda..a8e0ca1426 100644 --- a/src/ServiceControl.DomainEvents/ServiceControl.DomainEvents.csproj +++ b/src/ServiceControl.DomainEvents/ServiceControl.DomainEvents.csproj @@ -6,6 +6,7 @@ + diff --git a/src/ServiceControl.Persistence.RavenDB/RavenLicense.json b/src/ServiceControl.Persistence.RavenDB/RavenLicense.json index b750d5fe08..be1b079a94 100644 --- a/src/ServiceControl.Persistence.RavenDB/RavenLicense.json +++ b/src/ServiceControl.Persistence.RavenDB/RavenLicense.json @@ -2,18 +2,18 @@ "Id": "64c6a174-3f3a-4e7d-ac5d-b3eedd801460", "Name": "ParticularNservicebus (Israel)", "Keys": [ - "NKhWkUrAnwvE+M6VmSxK7J6am", - "D2JME/+XVMQq9xh18W4lClw+a", - "FDWF3ZqgA89/tIgwUyc8BZ09f", - "wSsdi8WzL/w01TOB00HuGQE4r", - "jAkg7BKr7LWIRfbg02KLhiU4M", - "Y1FQx8IA4BOBK0XP+X+aaWE48", - "fLmwuqGsSc0kg0rzFdYs9AAyU", - "GKEkFCisMLQ4vMAcREjM0NRYX", - "GhufAh8AnwIgAJ8CIwCfAiQgn", - "wIlIJ8CJiCfAicgnwIoIJ8CKS", - "CfAiognwIrIJ8CLACfAi0AnwI", - "uIJ8CLwCfAjAggQM2LjBDMEQG", - "ODk8PT6fAiEgYoFY" + "mGE1ppvgfJjJZXnMUHFXQXJuL", + "sQ1trVf0DZlVNhFSjclHNQ36p", + "0a9ZWIaCiqu8Mh4CDVd4koc2X", + "2Y18vLYbTm5JH4J91IZJhq3bP", + "/bzD806qeBpRcDCLt82ON0+RO", + "eXiLDs/DTOBU9sPsNZEPzUX+C", + "uMA5nm6QaaB85GEpFuahhAAyU", + "GKEkFCisMLQ4vMAcREjM0NRYX", + "GhufAh8AnwIgAJ8CIwCfAiQgn", + "wIlIJ8CJiCfAicgnwIoIJ8CKS", + "CfAiognwIrIJ8CLACfAi0AnwI", + "uIJ8CLwCfAjAggQM2LjBDMEQG", + "ODk8PT6fAiEgYoRa" ] } \ No newline at end of file diff --git a/src/ServiceControl/Monitoring/EndpointInstanceMonitor.cs b/src/ServiceControl/Monitoring/EndpointInstanceMonitor.cs index 2941c43b9b..e055fe46ce 100644 --- a/src/ServiceControl/Monitoring/EndpointInstanceMonitor.cs +++ b/src/ServiceControl/Monitoring/EndpointInstanceMonitor.cs @@ -5,6 +5,8 @@ namespace ServiceControl.Monitoring using Contracts.HeartbeatMonitoring; using EndpointControl.Contracts; using Infrastructure.DomainEvents; + using NLog.Fluent; + using NServiceBus.Logging; using ServiceControl.Operations; using ServiceControl.Persistence; @@ -38,6 +40,7 @@ public async Task UpdateStatus(HeartbeatStatus newStatus, DateTime? latestTimest if (newStatus != status) { await RaiseStateChangeEvents(newStatus, latestTimestamp); + Log.DebugFormat("Endpoint {0} status updated from {1} to {2}", Id.LogicalName, status, newStatus); } lastSeen = latestTimestamp; @@ -132,6 +135,8 @@ public KnownEndpointsView GetKnownView() }; } + static readonly ILog Log = LogManager.GetLogger(); + IDomainEvents domainEvents; DateTime? lastSeen; HeartbeatStatus status;