diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 01d42a1fe2..c8e20956ce 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -63,6 +63,7 @@
+
@@ -92,4 +93,4 @@
-
+
\ No newline at end of file
diff --git a/src/Particular.LicensingComponent.Contracts/IEnvironmentDataProvider.cs b/src/Particular.LicensingComponent.Contracts/IEnvironmentDataProvider.cs
new file mode 100644
index 0000000000..e7bb79f9c7
--- /dev/null
+++ b/src/Particular.LicensingComponent.Contracts/IEnvironmentDataProvider.cs
@@ -0,0 +1,9 @@
+namespace Particular.LicensingComponent.Contracts;
+
+///
+/// Provides environment data that is included in usage reports
+///
+public interface IEnvironmentDataProvider
+{
+ IEnumerable<(string key, string value)> GetData();
+}
diff --git a/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_AdditionalEnvironmentDataProvider_Tests.cs b/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_AdditionalEnvironmentDataProvider_Tests.cs
new file mode 100644
index 0000000000..8d42beea1d
--- /dev/null
+++ b/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_AdditionalEnvironmentDataProvider_Tests.cs
@@ -0,0 +1,42 @@
+namespace Particular.LicensingComponent.UnitTests;
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using NUnit.Framework;
+using Particular.LicensingComponent.Contracts;
+using Particular.LicensingComponent.UnitTests.Infrastructure;
+
+[TestFixture]
+class ThroughputCollector_AdditionalEnvironmentDataProvider_Tests : ThroughputCollectorTestFixture
+{
+ public override Task Setup()
+ {
+ SetExtraDependencies = services => services.AddSingleton();
+
+ return base.Setup();
+ }
+
+ [Test]
+ public async Task Should_include_additional_environment_data_in_throughput_report()
+ {
+ // Arrange
+ // Act
+ var report = await ThroughputCollector.GenerateThroughputReport(null, null, default);
+ // Assert
+ Assert.That(report, Is.Not.Null);
+ Assert.That(report.ReportData, Is.Not.Null);
+ Assert.That(report.ReportData.EnvironmentInformation, Is.Not.Null);
+ Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData, Is.Not.Null);
+ Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData.ContainsKey("TestKey"));
+ Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData["TestKey"], Is.EqualTo("TestValue"));
+ }
+
+ class TestAdditionalEnvironmentDataProvider : IEnvironmentDataProvider
+ {
+ public IEnumerable<(string key, string value)> GetData()
+ {
+ yield return ("TestKey", "TestValue");
+ }
+ }
+}
diff --git a/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_SanitizedNameGrouping_Tests.cs b/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_SanitizedNameGrouping_Tests.cs
index 1b1d258b57..703e842cea 100644
--- a/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_SanitizedNameGrouping_Tests.cs
+++ b/src/Particular.LicensingComponent.UnitTests/ThroughputCollector/ThroughputCollector_SanitizedNameGrouping_Tests.cs
@@ -35,7 +35,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();
- var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
+ var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
// Act
var summary = await throughputCollector.GetThroughputSummary(default);
@@ -61,7 +61,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();
- var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
+ var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
// Act
var report = await throughputCollector.GenerateThroughputReport(null, null, default);
@@ -88,7 +88,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();
- var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
+ var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
// Act
var summary = await throughputCollector.GetThroughputSummary(default);
@@ -114,7 +114,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();
- var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
+ var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
// Act
var report = await throughputCollector.GenerateThroughputReport(null, null, default);
diff --git a/src/Particular.LicensingComponent/LicensingComponentServiceCollectionExtensions.cs b/src/Particular.LicensingComponent/LicensingComponentServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..34d26ad4eb
--- /dev/null
+++ b/src/Particular.LicensingComponent/LicensingComponentServiceCollectionExtensions.cs
@@ -0,0 +1,11 @@
+namespace Particular.LicensingComponent;
+
+using Contracts;
+using Microsoft.Extensions.DependencyInjection;
+
+public static class LicensingComponentServiceCollectionExtensions
+{
+ public static IServiceCollection AddEnvironmentDataProvider(this IServiceCollection services)
+ where T : class, IEnvironmentDataProvider
+ => services.AddSingleton();
+}
diff --git a/src/Particular.LicensingComponent/ThroughputCollector.cs b/src/Particular.LicensingComponent/ThroughputCollector.cs
index f7cc882b28..958de9a6c1 100644
--- a/src/Particular.LicensingComponent/ThroughputCollector.cs
+++ b/src/Particular.LicensingComponent/ThroughputCollector.cs
@@ -12,7 +12,7 @@
using Shared;
using QueueThroughput = Report.QueueThroughput;
-public class ThroughputCollector(ILicensingDataStore dataStore, ThroughputSettings throughputSettings, IAuditQuery auditQuery, MonitoringService monitoringService, IBrokerThroughputQuery? throughputQuery = null)
+public class ThroughputCollector(ILicensingDataStore dataStore, ThroughputSettings throughputSettings, IAuditQuery auditQuery, MonitoringService monitoringService, IEnumerable environmentDataProviders, IBrokerThroughputQuery? throughputQuery = null)
: IThroughputCollector
{
public async Task GetThroughputConnectionSettingsInformation(CancellationToken cancellationToken)
@@ -179,6 +179,14 @@ public async Task GenerateThroughputReport(string spVersion, DateT
report.EnvironmentInformation.EnvironmentData[EnvironmentDataType.AuditEnabled.ToString()] = systemHasAuditEnabled.ToString();
report.EnvironmentInformation.EnvironmentData[EnvironmentDataType.MonitoringEnabled.ToString()] = systemHasMonitoringEnabled.ToString();
+ foreach (var environmentDataProvider in environmentDataProviders)
+ {
+ foreach (var (key, value) in environmentDataProvider.GetData())
+ {
+ report.EnvironmentInformation.EnvironmentData[key] = value;
+ }
+ }
+
var throughputReport = new SignedReport { ReportData = report, Signature = Signature.SignReport(report) };
return throughputReport;
diff --git a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs
index 452e955f6d..de12a681b9 100644
--- a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs
+++ b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs
@@ -119,6 +119,24 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model)
}
}
+ if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.EnableIntegratedServicePulse.Name))
+ {
+ var result = await windowManager.ShowYesNoCancelDialog("INPUT REQUIRED - INTEGRATED SERVICEPULSE",
+ "ServiceControl can host an integrated version of ServicePulse which allows you to monitor your ServiceControl instance without needing to install ServicePulse separately.",
+ "Should an integrated ServicePulse be enabled for this ServiceControl instance?",
+ "Enable integrated ServicePulse",
+ "Do NOT enable integrated ServicePulse");
+
+ if (!result.HasValue)
+ {
+ //Dialog was cancelled
+ await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances());
+ return;
+ }
+
+ upgradeOptions.EnableIntegratedServicePulse = result.Value;
+ }
+
if (await commandChecks.StopBecauseInstanceIsRunning(instance))
{
return;
diff --git a/src/ServiceControl.Config/UI/InstanceAdd/EnableIntegratedServicePulseOption.cs b/src/ServiceControl.Config/UI/InstanceAdd/EnableIntegratedServicePulseOption.cs
new file mode 100644
index 0000000000..9d16dc9942
--- /dev/null
+++ b/src/ServiceControl.Config/UI/InstanceAdd/EnableIntegratedServicePulseOption.cs
@@ -0,0 +1,8 @@
+namespace ServiceControl.Config.UI.InstanceAdd
+{
+ public class EnableIntegratedServicePulseOption
+ {
+ public string Name { get; set; }
+ public bool Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs
index 5e8eb5d97a..1d3c08009d 100644
--- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs
+++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs
@@ -76,6 +76,7 @@ async Task Add()
serviceControlNewInstance.ServiceAccount = viewModel.ServiceControl.ServiceAccount;
serviceControlNewInstance.ServiceAccountPwd = viewModel.ServiceControl.Password;
serviceControlNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControl.EnableFullTextSearchOnBodies.Value;
+ serviceControlNewInstance.EnableIntegratedServicePulse = viewModel.ServiceControl.EnableIntegratedServicePulse.Value;
}
var auditNewInstance = viewModel.InstallAuditInstance ? ServiceControlAuditNewInstance.CreateWithDefaultPersistence() : null;
diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml
index 4b09b4623c..9ace3fbd7a 100644
--- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml
+++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml
@@ -254,6 +254,12 @@
Header="FULL TEXT SEARCH ON MESSAGE BODIES"
ItemsSource="{Binding ErrorEnableFullTextSearchOnBodiesOptions}"
SelectedValue="{Binding ErrorEnableFullTextSearchOnBodies}" />
+
diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs
index f514290399..cc8bdb3d2a 100644
--- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs
+++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs
@@ -220,6 +220,15 @@ public EnableFullTextSearchOnBodiesOption ErrorEnableFullTextSearchOnBodies
set => ServiceControl.EnableFullTextSearchOnBodies = value;
}
+ public IEnumerable ErrorEnableIntegratedServicePulseOptions =>
+ ServiceControl.EnableIntegratedServicePulseOptions;
+
+ public EnableIntegratedServicePulseOption ErrorEnableIntegratedServicePulse
+ {
+ get => ServiceControl.EnableIntegratedServicePulse;
+ set => ServiceControl.EnableIntegratedServicePulse = value;
+ }
+
/* Add Audit Instance */
public string AuditInstanceName
diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs
index 843dc4bbd6..13e6d1289c 100644
--- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs
+++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs
@@ -38,6 +38,19 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent)
Value = false
}
};
+ EnableIntegratedServicePulseOptions = new[]
+ {
+ new EnableIntegratedServicePulseOption
+ {
+ Name = "On",
+ Value = true
+ },
+ new EnableIntegratedServicePulseOption
+ {
+ Name = "Off",
+ Value = false
+ }
+ };
ErrorRetention = SettingConstants.ErrorRetentionPeriodDefaultInDaysForUI;
Description = "ServiceControl Service";
HostName = "localhost";
@@ -48,6 +61,7 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent)
PortNumber = "33333";
DatabaseMaintenancePortNumber = "33334";
EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.First(p => p.Value); //Default to On.
+ EnableIntegratedServicePulse = EnableIntegratedServicePulseOptions.First(p => p.Value); //Default to On.
ViewModelParent = viewModelParent;
}
@@ -92,6 +106,10 @@ public ForwardingOption ErrorForwarding
public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies { get; set; }
+ public IEnumerable EnableIntegratedServicePulseOptions { get; }
+
+ public EnableIntegratedServicePulseOption EnableIntegratedServicePulse { get; set; }
+
protected void UpdateErrorRetention(TimeSpan value)
{
ErrorRetention = ErrorRetentionUnits == TimeSpanUnits.Days ? value.TotalDays : value.TotalHours;
@@ -122,6 +140,7 @@ public void UpdateFromInstance(ServiceControlInstance instance)
ErrorForwardingQueueName = instance.ErrorLogQueue;
UpdateErrorRetention(instance.ErrorRetentionPeriod);
EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.FirstOrDefault(p => p.Value == instance.EnableFullTextSearchOnBodies);
+ EnableIntegratedServicePulse = EnableIntegratedServicePulseOptions.FirstOrDefault(p => p.Value == instance.EnableIntegratedServicePulse);
}
ForwardingOption errorForwarding;
diff --git a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml
index 46485b62c3..dcddb94c1a 100644
--- a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml
+++ b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml
@@ -137,7 +137,7 @@
diff --git a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs
index af944eec87..57ad612610 100644
--- a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs
+++ b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs
@@ -114,6 +114,22 @@ public string BrowsableUrl
public bool HasBrowsableUrl => ServiceInstance is IURLInfo;
+ public string UrlHeading
+ {
+ get
+ {
+ if (IsServiceControlInstance)
+ {
+ if (ServiceControlInstance.EnableIntegratedServicePulse)
+ {
+ return "SERVICEPULSE";
+ }
+ }
+
+ return "URL";
+ }
+ }
+
public string InstallPath => ((IServicePaths)ServiceInstance).InstallPath;
public string DBPath => GetDBPathIfAvailable();
@@ -291,6 +307,7 @@ public Task HandleAsync(PostRefreshInstances message, CancellationToken cancella
NotifyOfPropertyChange("HasNewVersion");
NotifyOfPropertyChange("Transport");
NotifyOfPropertyChange("BrowsableUrl");
+ NotifyOfPropertyChange("UrlHeading");
return Task.CompletedTask;
}
diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs
index 86ccfe100a..f978f2f82c 100644
--- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs
+++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs
@@ -70,6 +70,7 @@ async Task Save()
instance.DatabaseMaintenancePort = !string.IsNullOrWhiteSpace(viewModel.ServiceControl.DatabaseMaintenancePortNumber) ? Convert.ToInt32(viewModel.ServiceControl.DatabaseMaintenancePortNumber) : null;
instance.VirtualDirectory = null;
instance.ForwardErrorMessages = viewModel.ServiceControl.ErrorForwarding.Value;
+ instance.EnableIntegratedServicePulse = viewModel.ServiceControl.EnableIntegratedServicePulse.Value;
instance.ErrorQueue = viewModel.ServiceControl.ErrorQueueName;
instance.ErrorLogQueue = viewModel.ServiceControl.ErrorForwardingQueueName;
instance.ErrorRetentionPeriod = viewModel.ServiceControl.ErrorRetentionPeriod;
diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml
index a81b7420e9..7565b9c214 100644
--- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml
+++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml
@@ -231,6 +231,12 @@
Header="FULL TEXT SEARCH ON MESSAGE BODIES"
ItemsSource="{Binding EnableFullTextSearchOnBodiesOptions}"
SelectedValue="{Binding EnableFullTextSearchOnBodies}" />
+
diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs
index 8ca48e5864..dcbeb0fd46 100644
--- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs
+++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs
@@ -42,6 +42,7 @@ public void UpdateInstanceFromViewModel(ServiceControlInstance instance)
instance.ConnectionString = ConnectionString;
instance.DatabaseMaintenancePort = Convert.ToInt32(ServiceControl.DatabaseMaintenancePortNumber);
instance.EnableFullTextSearchOnBodies = ServiceControl.EnableFullTextSearchOnBodies.Value;
+ instance.EnableIntegratedServicePulse = ServiceControl.EnableIntegratedServicePulse.Value;
}
public string InstanceName => ServiceControl.InstanceName;
@@ -189,6 +190,15 @@ public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies
set => ServiceControl.EnableFullTextSearchOnBodies = value;
}
+ public IEnumerable EnableIntegratedServicePulseOptions =>
+ ServiceControl.EnableIntegratedServicePulseOptions;
+
+ public EnableIntegratedServicePulseOption EnableIntegratedServicePulse
+ {
+ get => ServiceControl.EnableIntegratedServicePulse;
+ set => ServiceControl.EnableIntegratedServicePulse = value;
+ }
+
public bool SubmitAttempted { get; set; }
}
}
\ No newline at end of file
diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs
index c7e2d6cfee..66bd8e8730 100644
--- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs
+++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs
@@ -94,6 +94,9 @@ public class NewServiceControlInstance : PSCmdlet
[Parameter(Mandatory = false, HelpMessage = "Specify whether to enable full text search on error messages.")]
public SwitchParameter EnableFullTextSearchOnBodies { get; set; } = true;
+ [Parameter(Mandatory = false, HelpMessage = "Specify whether to enable integrated ServicePulse instance.")]
+ public SwitchParameter EnableIntegratedServicePulse { get; set; }
+
[Parameter(Mandatory = false, HelpMessage = "Reuse the specified log, db, and install paths even if they are not empty")]
public SwitchParameter Force { get; set; }
@@ -172,6 +175,7 @@ protected override void ProcessRecord()
details.TransportPackage = ServiceControlCoreTransports.Find(Transport);
details.SkipQueueCreation = SkipQueueCreation;
details.EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodies;
+ details.EnableIntegratedServicePulse = EnableIntegratedServicePulse;
var modulePath = Path.GetDirectoryName(MyInvocation.MyCommand.Module.Path);
diff --git a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml
index 586dc08540..42ad697714 100644
--- a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml
+++ b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml
@@ -2655,6 +2655,13 @@
SwitchParameter
+
+ EnableIntegratedServicePulse
+
+ Enable the integrated version of ServicePulse that ships with ServiceControl.
+
+ SwitchParameter
+
Force
@@ -2942,6 +2949,18 @@
+
+ EnableIntegratedServicePulse
+
+ Enable the integrated version of ServicePulse that ships with ServiceControl
+
+ SwitchParameter
+
+ SwitchParameter
+
+
+
+
@@ -2998,7 +3017,8 @@
-DisplayName 'ServiceControl Test' `
-AuditRetentionPeriod $AuditRetention `
-ErrorRetentionPeriod $ErrorRetention `
- -ForwardErrorMessages:$false
+ -ForwardErrorMessages:$false `
+ -EnableIntegratedServicePulse
Add a servicecontrol instance
diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs
index fbe99dada5..23987c6543 100644
--- a/src/ServiceControl/HostApplicationBuilderExtensions.cs
+++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs
@@ -24,6 +24,7 @@ namespace Particular.ServiceControl
using NServiceBus;
using NServiceBus.Configuration.AdvancedExtensibility;
using NServiceBus.Transport;
+ using Particular.LicensingComponent;
using ServiceBus.Management.Infrastructure;
using ServiceBus.Management.Infrastructure.Installers;
using ServiceBus.Management.Infrastructure.Settings;
@@ -54,6 +55,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S
services.AddSingleton();
services.AddSingleton(settings);
+ services.AddEnvironmentDataProvider();
services.AddHttpLogging(options =>
{
diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs
index ac5cd439b4..ebc08958cf 100644
--- a/src/ServiceControl/Hosting/Commands/RunCommand.cs
+++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs
@@ -10,6 +10,7 @@
using ServiceControl;
using ServiceControl.Hosting.Auth;
using ServiceControl.Hosting.Https;
+ using ServicePulse;
class RunCommand : AbstractCommand
{
@@ -30,6 +31,10 @@ public override async Task Execute(HostArguments args, Settings settings)
var app = hostBuilder.Build();
app.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings);
+ if (settings.EnableIntegratedServicePulse)
+ {
+ app.UseServicePulse(settings.ServicePulseSettings);
+ }
app.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled);
await app.RunAsync(settings.RootUrl);
diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs
index 939494975e..1fcdee1245 100644
--- a/src/ServiceControl/Infrastructure/Settings/Settings.cs
+++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs
@@ -15,6 +15,7 @@ namespace ServiceBus.Management.Infrastructure.Settings
using ServiceControl.Infrastructure.WebApi;
using ServiceControl.Persistence;
using ServiceControl.Transports;
+ using ServicePulse;
using JsonSerializer = System.Text.Json.JsonSerializer;
public class Settings
@@ -65,6 +66,12 @@ public Settings(
MaximumConcurrencyLevel = SettingsReader.Read(SettingsRootNamespace, "MaximumConcurrencyLevel");
RetryHistoryDepth = SettingsReader.Read(SettingsRootNamespace, "RetryHistoryDepth", 10);
AllowMessageEditing = SettingsReader.Read(SettingsRootNamespace, "AllowMessageEditing");
+ EnableIntegratedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableIntegratedServicePulse", false);
+ ServicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables() with
+ {
+ ServiceControlUrl = $"{ApiUrl}/",
+ IsIntegrated = true
+ };
NotificationsFilter = SettingsReader.Read(SettingsRootNamespace, "NotificationsFilter");
RemoteInstances = GetRemoteInstances().ToArray();
TimeToRestartErrorIngestionAfterFailure = GetTimeToRestartErrorIngestionAfterFailure();
@@ -103,6 +110,9 @@ public Settings(
public bool AllowMessageEditing { get; set; }
+ public bool EnableIntegratedServicePulse { get; set; }
+ public ServicePulseSettings ServicePulseSettings { get; set; }
+
//HINT: acceptance tests only
public Func MessageFilter { get; set; }
diff --git a/src/ServiceControl/ServiceControl.csproj b/src/ServiceControl/ServiceControl.csproj
index f4d7a787bb..d931751d34 100644
--- a/src/ServiceControl/ServiceControl.csproj
+++ b/src/ServiceControl/ServiceControl.csproj
@@ -36,6 +36,7 @@
+
diff --git a/src/ServiceControl/ServiceControlErrorInstanceEnvironmentDataProvider.cs b/src/ServiceControl/ServiceControlErrorInstanceEnvironmentDataProvider.cs
new file mode 100644
index 0000000000..b3828e8b43
--- /dev/null
+++ b/src/ServiceControl/ServiceControlErrorInstanceEnvironmentDataProvider.cs
@@ -0,0 +1,13 @@
+namespace Particular.ServiceControl;
+
+using System.Collections.Generic;
+using Particular.LicensingComponent.Contracts;
+using ServiceBus.Management.Infrastructure.Settings;
+
+class ServiceControlErrorInstanceEnvironmentDataProvider(Settings settings) : IEnvironmentDataProvider
+{
+ public IEnumerable<(string key, string value)> GetData()
+ {
+ yield return ("Features.IntegratedServicePulse", settings.EnableIntegratedServicePulse ? "Enabled" : "Disabled");
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs
index 578ef22468..aaf6fdf138 100644
--- a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs
+++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs
@@ -21,6 +21,8 @@ class FakeServiceControlInstance : IServiceControlInstance
public bool ForwardErrorMessages { get; set; }
+ public bool EnableIntegratedServicePulse { get; set; }
+
public TimeSpan ErrorRetentionPeriod { get; set; }
public TimeSpan? AuditRetentionPeriod { get; set; }
diff --git a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs
index e7651cd20e..d651da5e90 100644
--- a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs
+++ b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs
@@ -26,5 +26,12 @@ public static class SettingsList
Name = "Monitoring/ShutdownTimeout",
SupportedFrom = new SemanticVersion(6, 4, 1)
};
+
+ public static readonly SettingInfo HttpsEnabled = new()
+ {
+ Name = "Monitoring/Https.Enabled",
+ SupportedFrom = new SemanticVersion(6, 11, 0)
+ };
+
}
}
\ No newline at end of file
diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs
index c3c79743bf..3f2ef78627 100644
--- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs
+++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs
@@ -38,6 +38,7 @@ protected override void UpdateSettings()
settings.Set(ServiceControlSettings.ErrorRetentionPeriod, details.ErrorRetentionPeriod.ToString(), version);
settings.Set(ServiceControlSettings.EnableFullTextSearchOnBodies, details.EnableFullTextSearchOnBodies.ToString(), version);
settings.Set(ServiceControlSettings.RemoteInstances, RemoteInstanceConverter.ToJson(details.RemoteInstances), version);
+ settings.Set(ServiceControlSettings.EnableIntegratedServicePulse, details.EnableIntegratedServicePulse.ToString(), version);
// Windows services allow a maximum of 125 seconds when stopping a service.
// When shutting down or restarting the OS we have no control over the
diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs
index 9dc2166000..841f850f71 100644
--- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs
+++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs
@@ -113,5 +113,17 @@ public static class ServiceControlSettings
Name = "LicensingComponent/RabbitMQ/Password",
RemovedFrom = new SemanticVersion(6, 5, 0)
};
+
+ public static readonly SettingInfo EnableIntegratedServicePulse = new()
+ {
+ Name = "ServiceControl/EnableIntegratedServicePulse",
+ SupportedFrom = new SemanticVersion(6, 12, 0)
+ };
+
+ public static readonly SettingInfo HttpsEnabled = new()
+ {
+ Name = "ServiceControl/Https.Enabled",
+ SupportedFrom = new SemanticVersion(6, 11, 0)
+ };
}
}
diff --git a/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs b/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs
index 7770296479..2b3f93c4ca 100644
--- a/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs
+++ b/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs
@@ -40,7 +40,10 @@ public MonitoringInstance(WindowsServiceController service)
public bool SkipQueueCreation { get; set; }
- public string Url => $"http://{HostName}:{Port}/";
+ public string Url => $"{UrlScheme}://{HostName}:{Port}/";
+
+ public bool HttpsEnabled { get; set; }
+ string UrlScheme => HttpsEnabled ? "https" : "http";
public string BrowsableUrl
{
@@ -52,7 +55,7 @@ public string BrowsableUrl
"+" => Environment.MachineName.ToLower(),
_ => HostName,
};
- return $"http://{host}:{Port}/";
+ return $"{UrlScheme}://{host}:{Port}/";
}
}
@@ -71,6 +74,8 @@ public override void Reload()
ConnectionString = ReadConnectionString();
Description = GetDescription();
ServiceAccount = Service.Account;
+ HttpsEnabled = AppConfig.Read(SettingsList.HttpsEnabled, false);
+
}
string DefaultLogPath()
diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs
index 5e2a3a762f..a5e2294538 100644
--- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs
+++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs
@@ -85,19 +85,24 @@ public string AclMaintenanceUrl
public TimeSpan ErrorRetentionPeriod { get; set; }
public bool SkipQueueCreation { get; set; }
public bool EnableFullTextSearchOnBodies { get; set; }
+ public bool EnableIntegratedServicePulse { get; set; }
+ public bool HttpsEnabled { get; set; }
protected abstract string BaseServiceName { get; }
+ public string UrlScheme => HttpsEnabled ? "https" : "http";
+
public string Url
{
get
{
+ var suffix = EnableIntegratedServicePulse ? "" : "api/";
if (string.IsNullOrWhiteSpace(VirtualDirectory))
{
- return $"http://{HostName}:{Port}/api/";
+ return $"{UrlScheme}://{HostName}:{Port}/{suffix}";
}
- return $"http://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}api/";
+ return $"{UrlScheme}://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}";
}
}
@@ -105,6 +110,7 @@ public string BrowsableUrl
{
get
{
+ var suffix = EnableIntegratedServicePulse ? "" : "api/";
string host = HostName switch
{
"*" => "localhost",
@@ -113,10 +119,10 @@ public string BrowsableUrl
};
if (string.IsNullOrWhiteSpace(VirtualDirectory))
{
- return $"http://{host}:{Port}/api/";
+ return $"{UrlScheme}://{host}:{Port}/{suffix}";
}
- return $"http://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}api/";
+ return $"{UrlScheme}://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}/";
}
}
diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs
index 6f237b1803..49d7b73ca9 100644
--- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs
+++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs
@@ -91,6 +91,8 @@ string[] FlagFiles
public bool ForwardErrorMessages { get; set; }
+ public bool EnableIntegratedServicePulse { get; set; }
+
public TransportInfo TransportPackage { get; set; }
public string ConnectionString { get; set; }
diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs
index f500a82545..d010a64a20 100644
--- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs
+++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs
@@ -135,6 +135,9 @@ public override void Reload()
AuditLogQueue = AppConfig.Read(ServiceControlSettings.AuditLogQueue, string.IsNullOrEmpty(AuditQueue) ? null : $"{AuditQueue}.log");
}
+ EnableIntegratedServicePulse = AppConfig.Read(ServiceControlSettings.EnableIntegratedServicePulse, false);
+ HttpsEnabled = AppConfig.Read(ServiceControlSettings.HttpsEnabled, false);
+
if (TimeSpan.TryParse(AppConfig.Read(ServiceControlSettings.ErrorRetentionPeriod, (string)null), out var errorRetentionPeriod))
{
ErrorRetentionPeriod = errorRetentionPeriod;
@@ -181,6 +184,7 @@ protected override void ApplySettingsChanges(KeyValueConfigurationCollection set
settings.Set(ServiceControlSettings.ErrorLogQueue, ErrorLogQueue, Version);
settings.Set(ServiceControlSettings.EnableFullTextSearchOnBodies, EnableFullTextSearchOnBodies.ToString(), Version);
settings.Set(ServiceControlSettings.PersistenceType, PersistenceManifest.Name);
+ settings.Set(ServiceControlSettings.EnableIntegratedServicePulse, EnableIntegratedServicePulse.ToString(), Version);
if (RemoteInstances != null)
{
diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs
index d670ec50c7..5e74501c4a 100644
--- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs
+++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs
@@ -10,6 +10,7 @@ public class ServiceControlUpgradeOptions
public int? MaintenancePort { get; set; }
public bool SkipQueueCreation { get; set; }
public string RemoteUrl { get; set; }
+ public bool EnableIntegratedServicePulse { get; set; }
public bool Force { get; set; }
public void ApplyChangesToInstance(ServiceControlBaseService instance)
@@ -53,6 +54,7 @@ void ApplyChangesTo(ServiceControlInstance instance)
}
instance.SkipQueueCreation = SkipQueueCreation;
+ instance.EnableIntegratedServicePulse = EnableIntegratedServicePulse;
}
void ApplyChangesTo(ServiceControlAuditInstance instance)
diff --git a/src/ServiceControlInstaller.Engine/Interfaces.cs b/src/ServiceControlInstaller.Engine/Interfaces.cs
index 914be2cc43..5d5968b7a5 100644
--- a/src/ServiceControlInstaller.Engine/Interfaces.cs
+++ b/src/ServiceControlInstaller.Engine/Interfaces.cs
@@ -109,6 +109,7 @@ public interface IServiceControlInstance : IServiceControlBaseInstance, IURLInfo
string ErrorLogQueue { get; }
string VirtualDirectory { get; }
bool ForwardErrorMessages { get; }
+ bool EnableIntegratedServicePulse { get; }
TimeSpan ErrorRetentionPeriod { get; }
TimeSpan? AuditRetentionPeriod { get; set; }
List RemoteInstances { get; }