From 06ccabc50a42662a8977be8aa54baf5d068cb74b Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Wed, 26 Mar 2025 10:28:48 +0000 Subject: [PATCH 01/13] stateful service current role imp. --- .../OmexStatefulServiceRegistrator.cs | 11 +++++ .../Internal/ServiceFabricExecutionContext.cs | 14 ++++-- src/Hosting.Services/OmexStatefulService.cs | 8 ++++ .../MockServiceFabricServices.cs | 45 ++++++++++--------- .../ServiceFabricExecutionContextTests.cs | 27 ++++++----- 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index 5699e64e6..bedd7be6a 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -19,12 +19,14 @@ public OmexStatefulServiceRegistrator( IAccessorSetter contextAccessor, IAccessorSetter partitionAccessor, IAccessorSetter stateAccessor, + IAccessorSetter roleAccessor, IEnumerable> listenerBuilders, IEnumerable> serviceActions) : base(options, contextAccessor, listenerBuilders, serviceActions) { PartitionAccessor = partitionAccessor; StateAccessor = stateAccessor; + RoleAccessor = roleAccessor; } public override Task RegisterAsync(CancellationToken cancellationToken) => @@ -33,5 +35,14 @@ public override Task RegisterAsync(CancellationToken cancellationToken) => public IAccessorSetter StateAccessor { get; } public IAccessorSetter PartitionAccessor { get; } + + public IAccessorSetter RoleAccessor { get; } + + // 'ReplicaRole' must be a reference type in order to use it as parameter 'TValue' in the generic type or method. That's why we use this wrapper class + public class ReplicaRoleWrapper + { + public ReplicaRole Role { get; set; } + } + } } diff --git a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs index 8a84ea741..3398a14f3 100644 --- a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs +++ b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs @@ -5,17 +5,25 @@ using Microsoft.Extensions.Hosting; using Microsoft.Omex.Extensions.Abstractions; using Microsoft.Omex.Extensions.Abstractions.ExecutionContext; +using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services { // TODO: should be removed after our services will set service executable version properly internal sealed class ServiceFabricExecutionContext : BaseExecutionContext { - public ServiceFabricExecutionContext(IHostEnvironment hostEnvironment, IAccessor accessor) - : base(hostEnvironment) => - accessor.OnFirstSet(UpdateState); + private readonly IAccessor m_replicaRoleAccessor; + public ServiceFabricExecutionContext(IHostEnvironment hostEnvironment, IAccessor accessor, IAccessor replicaRoleAccessor) + : base(hostEnvironment) + { + accessor.OnFirstSet(UpdateState); + m_replicaRoleAccessor = replicaRoleAccessor; + + } private void UpdateState(ServiceContext context) => BuildVersion = context.CodePackageActivationContext.CodePackageVersion; + + public ReplicaRole CurrentReplicaRole => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; } } diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index aa4505a84..74b1268a9 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; +using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services { @@ -35,6 +36,13 @@ protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken return base.OnOpenAsync(openMode, cancellationToken); } + /// + protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + return base.OnChangeRoleAsync(newRole, cancellationToken); + } + /// protected override IEnumerable CreateServiceReplicaListeners() => m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); diff --git a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs index 8df2e9efa..6baf9084e 100644 --- a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs +++ b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs @@ -10,27 +10,28 @@ namespace Microsoft.Omex.Extensions.Hosting.Services.UnitTests { - public static class MockServiceFabricServices - { - public static OmexStatelessService MockOmexStatelessService { get; } = - new OmexStatelessService( - new OmexStatelessServiceRegistrator( - Options.Create(new ServiceRegistratorOptions()), - new Accessor(), - new Accessor(), - Enumerable.Empty>(), - Enumerable.Empty>()), - MockStatelessServiceContextFactory.Default); + public static class MockServiceFabricServices + { + public static OmexStatelessService MockOmexStatelessService { get; } = + new OmexStatelessService( + new OmexStatelessServiceRegistrator( + Options.Create(new ServiceRegistratorOptions()), + new Accessor(), + new Accessor(), + Enumerable.Empty>(), + Enumerable.Empty>()), + MockStatelessServiceContextFactory.Default); - public static OmexStatefulService MockOmexStatefulService { get; } = - new OmexStatefulService( - new OmexStatefulServiceRegistrator( - Options.Create(new ServiceRegistratorOptions()), - new Accessor(), - new Accessor(), - new Accessor(new MockReliableStateManager()), - Enumerable.Empty>(), - Enumerable.Empty>()), - MockStatefulServiceContextFactory.Default); - } + public static OmexStatefulService MockOmexStatefulService { get; } = + new OmexStatefulService( + new OmexStatefulServiceRegistrator( + Options.Create(new ServiceRegistratorOptions()), + new Accessor(), + new Accessor(), + new Accessor(new MockReliableStateManager()), + new Accessor(), + Enumerable.Empty>(), + Enumerable.Empty>()), + MockStatefulServiceContextFactory.Default); + } } diff --git a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs index 38461bfed..353d28b47 100644 --- a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs +++ b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs @@ -12,18 +12,21 @@ namespace Microsoft.Omex.Extensions.Hosting.Services.UnitTests { [TestClass] - public class ServiceFabricExecutionContextTests - { - [TestMethod] - public void Constructor_InitializesPropertiesProperly() - { - ServiceContext context = MockStatelessServiceContextFactory.Default; - Accessor accessor = new(); - ((IAccessorSetter)accessor).SetValue(context); + public class ServiceFabricExecutionContextTests + { + [TestMethod] + public void Constructor_InitializesPropertiesProperly() + { + ServiceContext context = MockStatelessServiceContextFactory.Default; + Accessor accessor = new(); + ((IAccessorSetter)accessor).SetValue(context); - IExecutionContext info = new ServiceFabricExecutionContext(new Mock().Object, accessor); + Accessor replicaRoleAccessor = new(); + ((IAccessorSetter)replicaRoleAccessor).SetValue(new OmexStatefulServiceRegistrator.ReplicaRoleWrapper()); - Assert.AreEqual(context.CodePackageActivationContext.CodePackageVersion, info.BuildVersion); - } - } + IExecutionContext info = new ServiceFabricExecutionContext(new Mock().Object, accessor, replicaRoleAccessor); + + Assert.AreEqual(context.CodePackageActivationContext.CodePackageVersion, info.BuildVersion); + } + } } From de44f52a53e148572c0af87f72fd364c2cd40faa Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 10:35:08 +0100 Subject: [PATCH 02/13] updated - ServiceFabricExecutionContext expose --- .../Internal/ServiceFabricExecutionContext.cs | 6 +- src/Hosting.Services/OmexStatefulService.cs | 86 ++++++++++--------- .../ServiceFabricExecutionContextTests.cs | 7 +- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs index 3398a14f3..280defcc8 100644 --- a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs +++ b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs @@ -12,18 +12,14 @@ namespace Microsoft.Omex.Extensions.Hosting.Services // TODO: should be removed after our services will set service executable version properly internal sealed class ServiceFabricExecutionContext : BaseExecutionContext { - private readonly IAccessor m_replicaRoleAccessor; - public ServiceFabricExecutionContext(IHostEnvironment hostEnvironment, IAccessor accessor, IAccessor replicaRoleAccessor) + public ServiceFabricExecutionContext(IHostEnvironment hostEnvironment, IAccessor accessor) : base(hostEnvironment) { accessor.OnFirstSet(UpdateState); - m_replicaRoleAccessor = replicaRoleAccessor; } private void UpdateState(ServiceContext context) => BuildVersion = context.CodePackageActivationContext.CodePackageVersion; - - public ReplicaRole CurrentReplicaRole => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; } } diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 74b1268a9..f7499e973 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -6,49 +6,57 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Omex.Extensions.Abstractions; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services { - /// - /// Omex implementation of stateful service fabric service - /// - public sealed class OmexStatefulService : StatefulService, IServiceFabricService - { - private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - - internal OmexStatefulService( - OmexStatefulServiceRegistrator serviceRegistrator, - StatefulServiceContext serviceContext) - : base(serviceContext) - { - serviceRegistrator.ContextAccessor.SetValue(Context); - serviceRegistrator.StateAccessor.SetValue(StateManager); - m_serviceRegistrator = serviceRegistrator; - } - - /// - protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) - { - m_serviceRegistrator.PartitionAccessor.SetValue(Partition); - return base.OnOpenAsync(openMode, cancellationToken); - } - - /// - protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) - { - m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); - return base.OnChangeRoleAsync(newRole, cancellationToken); - } - - /// - protected override IEnumerable CreateServiceReplicaListeners() => - m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); - - /// - protected override Task RunAsync(CancellationToken cancellationToken) => - Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); - } + /// + /// Omex implementation of stateful service fabric service + /// + public sealed class OmexStatefulService : StatefulService, IServiceFabricService + { + private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; + private readonly IAccessor m_replicaRoleAccessor; + + internal OmexStatefulService( + OmexStatefulServiceRegistrator serviceRegistrator, + StatefulServiceContext serviceContext) + : base(serviceContext) + { + serviceRegistrator.ContextAccessor.SetValue(Context); + serviceRegistrator.StateAccessor.SetValue(StateManager); + m_serviceRegistrator = serviceRegistrator; + m_replicaRoleAccessor = (IAccessor)serviceRegistrator.RoleAccessor; + } + + /// + protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + { + m_serviceRegistrator.PartitionAccessor.SetValue(Partition); + return base.OnOpenAsync(openMode, cancellationToken); + } + + /// + protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + return base.OnChangeRoleAsync(newRole, cancellationToken); + } + + /// + protected override IEnumerable CreateServiceReplicaListeners() => + m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); + + /// + protected override Task RunAsync(CancellationToken cancellationToken) => + Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); + + /// + /// Gets the current replica role. + /// + public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; + } } diff --git a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs index 353d28b47..a9cfd22e1 100644 --- a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs +++ b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs @@ -21,12 +21,9 @@ public void Constructor_InitializesPropertiesProperly() Accessor accessor = new(); ((IAccessorSetter)accessor).SetValue(context); - Accessor replicaRoleAccessor = new(); - ((IAccessorSetter)replicaRoleAccessor).SetValue(new OmexStatefulServiceRegistrator.ReplicaRoleWrapper()); + IExecutionContext info = new ServiceFabricExecutionContext(new Mock().Object, accessor); - IExecutionContext info = new ServiceFabricExecutionContext(new Mock().Object, accessor, replicaRoleAccessor); - - Assert.AreEqual(context.CodePackageActivationContext.CodePackageVersion, info.BuildVersion); + Assert.AreEqual(context.CodePackageActivationContext.CodePackageVersion, info.BuildVersion); } } } From 9c1bc38c46821dac277ea0f73650d31210a32b67 Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 12:53:01 +0100 Subject: [PATCH 03/13] removed unnecessary changes --- .../Internal/ServiceFabricExecutionContext.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs index 280defcc8..8a84ea741 100644 --- a/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs +++ b/src/Hosting.Services/Internal/ServiceFabricExecutionContext.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Omex.Extensions.Abstractions; using Microsoft.Omex.Extensions.Abstractions.ExecutionContext; -using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services { @@ -13,11 +12,8 @@ namespace Microsoft.Omex.Extensions.Hosting.Services internal sealed class ServiceFabricExecutionContext : BaseExecutionContext { public ServiceFabricExecutionContext(IHostEnvironment hostEnvironment, IAccessor accessor) - : base(hostEnvironment) - { - accessor.OnFirstSet(UpdateState); - - } + : base(hostEnvironment) => + accessor.OnFirstSet(UpdateState); private void UpdateState(ServiceContext context) => BuildVersion = context.CodePackageActivationContext.CodePackageVersion; From db7ef941a340e06c88325565393435fa0bdac2eb Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 15:28:23 +0100 Subject: [PATCH 04/13] replaced - space -> tab --- src/Hosting.Services/OmexStatefulService.cs | 92 +++++++++---------- .../MockServiceFabricServices.cs | 44 ++++----- .../ServiceFabricExecutionContextTests.cs | 20 ++-- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index f7499e973..34fdd4266 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -13,50 +13,50 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - /// - /// Omex implementation of stateful service fabric service - /// - public sealed class OmexStatefulService : StatefulService, IServiceFabricService - { - private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private readonly IAccessor m_replicaRoleAccessor; - - internal OmexStatefulService( - OmexStatefulServiceRegistrator serviceRegistrator, - StatefulServiceContext serviceContext) - : base(serviceContext) - { - serviceRegistrator.ContextAccessor.SetValue(Context); - serviceRegistrator.StateAccessor.SetValue(StateManager); - m_serviceRegistrator = serviceRegistrator; - m_replicaRoleAccessor = (IAccessor)serviceRegistrator.RoleAccessor; - } - - /// - protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) - { - m_serviceRegistrator.PartitionAccessor.SetValue(Partition); - return base.OnOpenAsync(openMode, cancellationToken); - } - - /// - protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) - { - m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); - return base.OnChangeRoleAsync(newRole, cancellationToken); - } - - /// - protected override IEnumerable CreateServiceReplicaListeners() => - m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); - - /// - protected override Task RunAsync(CancellationToken cancellationToken) => - Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); - - /// - /// Gets the current replica role. - /// - public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; - } + /// + /// Omex implementation of stateful service fabric service + /// + public sealed class OmexStatefulService : StatefulService, IServiceFabricService + { + private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; + private readonly IAccessor m_replicaRoleAccessor; + + internal OmexStatefulService( + OmexStatefulServiceRegistrator serviceRegistrator, + StatefulServiceContext serviceContext) + : base(serviceContext) + { + serviceRegistrator.ContextAccessor.SetValue(Context); + serviceRegistrator.StateAccessor.SetValue(StateManager); + m_serviceRegistrator = serviceRegistrator; + m_replicaRoleAccessor = (IAccessor)serviceRegistrator.RoleAccessor; + } + + /// + protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + { + m_serviceRegistrator.PartitionAccessor.SetValue(Partition); + return base.OnOpenAsync(openMode, cancellationToken); + } + + /// + protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + return base.OnChangeRoleAsync(newRole, cancellationToken); + } + + /// + protected override IEnumerable CreateServiceReplicaListeners() => + m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); + + /// + protected override Task RunAsync(CancellationToken cancellationToken) => + Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); + + /// + /// Gets the current replica role. + /// + public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; + } } diff --git a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs index 6baf9084e..1cb452134 100644 --- a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs +++ b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs @@ -10,28 +10,28 @@ namespace Microsoft.Omex.Extensions.Hosting.Services.UnitTests { - public static class MockServiceFabricServices - { - public static OmexStatelessService MockOmexStatelessService { get; } = - new OmexStatelessService( - new OmexStatelessServiceRegistrator( - Options.Create(new ServiceRegistratorOptions()), - new Accessor(), - new Accessor(), - Enumerable.Empty>(), - Enumerable.Empty>()), - MockStatelessServiceContextFactory.Default); + public static class MockServiceFabricServices + { + public static OmexStatelessService MockOmexStatelessService { get; } = + new OmexStatelessService( + new OmexStatelessServiceRegistrator( + Options.Create(new ServiceRegistratorOptions()), + new Accessor(), + new Accessor(), + Enumerable.Empty>(), + Enumerable.Empty>()), + MockStatelessServiceContextFactory.Default); - public static OmexStatefulService MockOmexStatefulService { get; } = - new OmexStatefulService( - new OmexStatefulServiceRegistrator( - Options.Create(new ServiceRegistratorOptions()), - new Accessor(), - new Accessor(), - new Accessor(new MockReliableStateManager()), - new Accessor(), - Enumerable.Empty>(), - Enumerable.Empty>()), - MockStatefulServiceContextFactory.Default); + public static OmexStatefulService MockOmexStatefulService { get; } = + new OmexStatefulService( + new OmexStatefulServiceRegistrator( + Options.Create(new ServiceRegistratorOptions()), + new Accessor(), + new Accessor(), + new Accessor(new MockReliableStateManager()), + new Accessor(), + Enumerable.Empty>(), + Enumerable.Empty>()), + MockStatefulServiceContextFactory.Default); } } diff --git a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs index a9cfd22e1..38461bfed 100644 --- a/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs +++ b/tests/Hosting.Services.UnitTests/ServiceFabricExecutionContextTests.cs @@ -12,18 +12,18 @@ namespace Microsoft.Omex.Extensions.Hosting.Services.UnitTests { [TestClass] - public class ServiceFabricExecutionContextTests - { - [TestMethod] - public void Constructor_InitializesPropertiesProperly() - { - ServiceContext context = MockStatelessServiceContextFactory.Default; - Accessor accessor = new(); - ((IAccessorSetter)accessor).SetValue(context); + public class ServiceFabricExecutionContextTests + { + [TestMethod] + public void Constructor_InitializesPropertiesProperly() + { + ServiceContext context = MockStatelessServiceContextFactory.Default; + Accessor accessor = new(); + ((IAccessorSetter)accessor).SetValue(context); IExecutionContext info = new ServiceFabricExecutionContext(new Mock().Object, accessor); Assert.AreEqual(context.CodePackageActivationContext.CodePackageVersion, info.BuildVersion); - } - } + } + } } From ed19204784ffd4ccf91791dc7564dae1a6cf94de Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 15:37:07 +0100 Subject: [PATCH 05/13] ReplicaRoleWrapper moved to internal folder --- .../Internal/OmexStatefulServiceRegistrator.cs | 8 +------- .../Internal/ReplicaRoleWrapper.cs | 17 +++++++++++++++++ src/Hosting.Services/OmexStatefulService.cs | 10 +++++----- .../MockServiceFabricServices.cs | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 src/Hosting.Services/Internal/ReplicaRoleWrapper.cs diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index bedd7be6a..355bf7bd4 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -12,7 +12,7 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - internal sealed class OmexStatefulServiceRegistrator : OmexServiceRegistrator + internal sealed partial class OmexStatefulServiceRegistrator : OmexServiceRegistrator { public OmexStatefulServiceRegistrator( IOptions options, @@ -38,11 +38,5 @@ public override Task RegisterAsync(CancellationToken cancellationToken) => public IAccessorSetter RoleAccessor { get; } - // 'ReplicaRole' must be a reference type in order to use it as parameter 'TValue' in the generic type or method. That's why we use this wrapper class - public class ReplicaRoleWrapper - { - public ReplicaRole Role { get; set; } - } - } } diff --git a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs new file mode 100644 index 000000000..c2c111633 --- /dev/null +++ b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Fabric; + +namespace Microsoft.Omex.Extensions.Hosting.Services +{ + internal sealed partial class OmexStatefulServiceRegistrator + { + // 'ReplicaRole' must be a reference type in order to use it as parameter 'TValue' in the generic type or method. That's why we use this wrapper class + public class ReplicaRoleWrapper + { + public ReplicaRole Role { get; set; } + } + + } +} diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 34fdd4266..952422f2c 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -22,16 +22,16 @@ public sealed class OmexStatefulService : StatefulService, IServiceFabricService private readonly IAccessor m_replicaRoleAccessor; internal OmexStatefulService( - OmexStatefulServiceRegistrator serviceRegistrator, - StatefulServiceContext serviceContext) - : base(serviceContext) + OmexStatefulServiceRegistrator serviceRegistrator, + StatefulServiceContext serviceContext) + : base(serviceContext) { serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; m_replicaRoleAccessor = (IAccessor)serviceRegistrator.RoleAccessor; } - + /// protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) { @@ -49,7 +49,7 @@ protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken /// protected override IEnumerable CreateServiceReplicaListeners() => m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); - + /// protected override Task RunAsync(CancellationToken cancellationToken) => Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); diff --git a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs index 1cb452134..b2bc1ee51 100644 --- a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs +++ b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs @@ -14,7 +14,7 @@ public static class MockServiceFabricServices { public static OmexStatelessService MockOmexStatelessService { get; } = new OmexStatelessService( - new OmexStatelessServiceRegistrator( + new OmexStatelessServiceRegistrator( Options.Create(new ServiceRegistratorOptions()), new Accessor(), new Accessor(), From e1f8d655ebe4a6faff90dc4f1ab26642e1576c0c Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 15:38:07 +0100 Subject: [PATCH 06/13] space to tab --- tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs index b2bc1ee51..1ec6afbdf 100644 --- a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs +++ b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs @@ -33,5 +33,5 @@ public static class MockServiceFabricServices Enumerable.Empty>(), Enumerable.Empty>()), MockStatefulServiceContextFactory.Default); - } + } } From 4c89b659c3ca28a186e36a05b784258b5ca0a455 Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 16:44:56 +0100 Subject: [PATCH 07/13] ReplicaRoleWrapper registered --- src/Hosting.Services/HostBuilderExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Hosting.Services/HostBuilderExtensions.cs b/src/Hosting.Services/HostBuilderExtensions.cs index 31a484ded..d34b4b1fc 100644 --- a/src/Hosting.Services/HostBuilderExtensions.cs +++ b/src/Hosting.Services/HostBuilderExtensions.cs @@ -11,6 +11,7 @@ using Microsoft.Omex.Extensions.Abstractions.Accessors; using Microsoft.Omex.Extensions.Abstractions.ExecutionContext; using Microsoft.ServiceFabric.Data; +using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services { @@ -49,6 +50,7 @@ public static IServiceCollection AddOmexServiceFabricDependencies(this { collection.TryAddAccessor(); collection.TryAddAccessor(); + collection.TryAddAccessor(); } else { From 81fc2c381a0cbab72e72dc09627c83261b1d2cff Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Tue, 1 Apr 2025 22:15:07 +0100 Subject: [PATCH 08/13] test changes added --- Directory.Packages.props | 4 +- Omex.sln | 11 ++++ TestProject4/Program.cs | 64 +++++++++++++++++++ TestProject4/TestProject4.csproj | 23 +++++++ .../Internal/OmexServiceRegistrator.cs | 31 ++++++++- .../OmexStatefulServiceRegistrator.cs | 28 +++++++- .../Internal/ReplicaRoleWrapper.cs | 13 +++- .../Internal/ServiceRegistrationOptions.cs | 8 ++- src/Hosting.Services/OmexStatefulService.cs | 29 ++++++--- 9 files changed, 193 insertions(+), 18 deletions(-) create mode 100644 TestProject4/Program.cs create mode 100644 TestProject4/TestProject4.csproj diff --git a/Directory.Packages.props b/Directory.Packages.props index 132b7d69a..465b5bf28 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -64,9 +64,9 @@ - + - + \ No newline at end of file diff --git a/Omex.sln b/Omex.sln index 0eb176f2e..fd047a284 100644 --- a/Omex.sln +++ b/Omex.sln @@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.Extensions.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Omex.Extensions.ServiceFabricGuest.Abstractions.UnitTests", "tests\ServiceFabricGuest.Abstractions.UnitTests\Microsoft.Omex.Extensions.ServiceFabricGuest.Abstractions.UnitTests.csproj", "{66F677BB-D314-4DA0-9173-6CA74E88AAF2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject4", "TestProject4\TestProject4.csproj", "{8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -275,6 +277,14 @@ Global {66F677BB-D314-4DA0-9173-6CA74E88AAF2}.Release|Any CPU.Build.0 = Release|Any CPU {66F677BB-D314-4DA0-9173-6CA74E88AAF2}.Release|x64.ActiveCfg = Release|Any CPU {66F677BB-D314-4DA0-9173-6CA74E88AAF2}.Release|x64.Build.0 = Release|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Debug|x64.ActiveCfg = Debug|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Debug|x64.Build.0 = Debug|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|Any CPU.Build.0 = Release|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|x64.ActiveCfg = Release|Any CPU + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -304,6 +314,7 @@ Global {7FD8C746-9DB1-4B33-B4A6-95AF5FEF2CCD} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE} {43CE835D-5A71-4689-9297-942EF1233175} = {551C93F8-6E89-4954-8905-7F5AC7173285} {66F677BB-D314-4DA0-9173-6CA74E88AAF2} = {551C93F8-6E89-4954-8905-7F5AC7173285} + {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E6FB7BCB-BF07-4F19-ACBA-457479D421BB} diff --git a/TestProject4/Program.cs b/TestProject4/Program.cs new file mode 100644 index 000000000..4cd1aa789 --- /dev/null +++ b/TestProject4/Program.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Fabric; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Accessors; +using Microsoft.Omex.Extensions.Hosting.Services; +using Microsoft.ServiceFabric.Data; +using Microsoft.ServiceFabric.Services.Runtime; +using ServiceFabric.Mocks; + +public class Program +{ + public static void Main(string[] args) + { + // Initialize the stateful service registrator + ServiceRegistratorOptions serviceRegistratorOptions = new ServiceRegistratorOptions { ServiceTypeName = "MyServiceType" }; + IOptions options = Options.Create(serviceRegistratorOptions); + IAccessorSetter contextAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter partitionAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter stateAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter roleAccessor = new AccessorSetter(); // Initialize appropriately + IEnumerable> listenerBuilders = new List>(); // Initialize appropriately + IEnumerable> serviceActions = new List>(); // Initialize appropriately + + OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( + options, + contextAccessor, + partitionAccessor, + stateAccessor, + roleAccessor, + listenerBuilders, + serviceActions); + + // Create a mock StatefulServiceContext + NodeContext nodeContext = new NodeContext("nodeName", new NodeId(0, 0), 0, "nodeType", "ipAddress"); + ICodePackageActivationContext codePackageActivationContext = new MockCodePackageActivationContext( + "applicationName", + "applicationTypeName", + "codePackageName", + "codePackageVersion", + "contextId", + "logDirectory", + "tempDirectory", + "workDirectory", + "serviceManifestName", + "serviceManifestVersion" + ); + StatefulServiceContext context = new StatefulServiceContext(nodeContext, codePackageActivationContext, "serviceTypeName", new Uri("fabric:/AppName/ServiceName"), null, Guid.NewGuid(), long.MaxValue); + + // Initialize the OmexStatefulService + OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); + + // Get the current replica role + ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); + Console.WriteLine($"Current Replica Role: {currentRole}"); + } +} + +public class AccessorSetter : IAccessorSetter where T : class +{ + private T? m_value; + public void SetValue(T value) => m_value = value; + public T GetValue() => m_value!; +} diff --git a/TestProject4/TestProject4.csproj b/TestProject4/TestProject4.csproj new file mode 100644 index 000000000..aca8e7c0f --- /dev/null +++ b/TestProject4/TestProject4.csproj @@ -0,0 +1,23 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/src/Hosting.Services/Internal/OmexServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexServiceRegistrator.cs index 9cf3f5022..0e38372c1 100644 --- a/src/Hosting.Services/Internal/OmexServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexServiceRegistrator.cs @@ -10,18 +10,42 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - internal abstract class OmexServiceRegistrator : IOmexServiceRegistrator + /// + /// Abstract class for registering Omex services. + /// + /// The type of the service. + /// The type of the service context. + public abstract class OmexServiceRegistrator : IOmexServiceRegistrator where TService : IServiceFabricService where TContext : ServiceContext { + /// + /// Gets the options for the service registrator. + /// protected readonly ServiceRegistratorOptions Options; + /// + /// Gets the context accessor. + /// public IAccessorSetter ContextAccessor { get; } + /// + /// Gets the listener builders for the service. + /// public IEnumerable> ListenerBuilders { get; } + /// + /// Gets the service actions for the service. + /// public IEnumerable> ServiceActions { get; } + /// + /// Initializes a new instance of the class. + /// + /// The options for the service registrator. + /// The context accessor. + /// The listener builders for the service. + /// The service actions for the service. public OmexServiceRegistrator( IOptions options, IAccessorSetter contextAccessor, @@ -34,6 +58,11 @@ public OmexServiceRegistrator( ServiceActions = serviceActions; } + /// + /// Registers the service asynchronously. + /// + /// The cancellation token. + /// A task that represents the asynchronous operation. public abstract Task RegisterAsync(CancellationToken cancellationToken); } } diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index 355bf7bd4..7a55cbcd6 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -12,8 +12,18 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - internal sealed partial class OmexStatefulServiceRegistrator : OmexServiceRegistrator + public sealed partial class OmexStatefulServiceRegistrator : OmexServiceRegistrator { + /// + /// Initializes a new instance of the class. + /// + /// The service registrator options. + /// The context accessor. + /// The partition accessor. + /// The state accessor. + /// The role accessor. + /// The listener builders. + /// The service actions. public OmexStatefulServiceRegistrator( IOptions options, IAccessorSetter contextAccessor, @@ -29,13 +39,27 @@ public OmexStatefulServiceRegistrator( RoleAccessor = roleAccessor; } + /// + /// Registers the OmexStatefulService with the Service Fabric runtime. + /// + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous registration operation. public override Task RegisterAsync(CancellationToken cancellationToken) => - ServiceRuntime.RegisterServiceAsync(Options.ServiceTypeName, context => new OmexStatefulService(this, context), cancellationToken: cancellationToken); + ServiceRuntime.RegisterServiceAsync(Options.ServiceTypeName, context => new OmexStatefulService(this, context), cancellationToken: cancellationToken); + /// + /// Gets the accessor for the reliable state manager. + /// public IAccessorSetter StateAccessor { get; } + /// + /// Gets the accessor for the stateful service partition. + /// public IAccessorSetter PartitionAccessor { get; } + /// + /// Gets the accessor for the replica role wrapper. + /// public IAccessorSetter RoleAccessor { get; } } diff --git a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs index c2c111633..fa77f05aa 100644 --- a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs +++ b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs @@ -5,13 +5,20 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - internal sealed partial class OmexStatefulServiceRegistrator + /// + /// Registrator for OmexStatefulService. + /// + public sealed partial class OmexStatefulServiceRegistrator { - // 'ReplicaRole' must be a reference type in order to use it as parameter 'TValue' in the generic type or method. That's why we use this wrapper class + /// + /// Wrapper class for the ReplicaRole to be used as a parameter in generic types or methods. + /// public class ReplicaRoleWrapper { + /// + /// Gets or sets the role of the replica. + /// public ReplicaRole Role { get; set; } } - } } diff --git a/src/Hosting.Services/Internal/ServiceRegistrationOptions.cs b/src/Hosting.Services/Internal/ServiceRegistrationOptions.cs index d54d726fa..68f670149 100644 --- a/src/Hosting.Services/Internal/ServiceRegistrationOptions.cs +++ b/src/Hosting.Services/Internal/ServiceRegistrationOptions.cs @@ -3,8 +3,14 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - internal class ServiceRegistratorOptions + /// + /// Options for registering a service. + /// + public class ServiceRegistratorOptions { + /// + /// Gets or sets the name of the service type. + /// public string ServiceTypeName { get; set; } = string.Empty; } } diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 952422f2c..84524fe21 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Accessors; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; @@ -19,17 +20,22 @@ namespace Microsoft.Omex.Extensions.Hosting.Services public sealed class OmexStatefulService : StatefulService, IServiceFabricService { private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private readonly IAccessor m_replicaRoleAccessor; - - internal OmexStatefulService( - OmexStatefulServiceRegistrator serviceRegistrator, - StatefulServiceContext serviceContext) - : base(serviceContext) + private readonly IAccessorSetter m_replicaRoleAccessor; + + /// + /// Initializes a new instance of the class. + /// + /// The service registrator. + /// The stateful service context. + public OmexStatefulService( + OmexStatefulServiceRegistrator serviceRegistrator, + StatefulServiceContext serviceContext) + : base(serviceContext) { serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; - m_replicaRoleAccessor = (IAccessor)serviceRegistrator.RoleAccessor; + m_replicaRoleAccessor = serviceRegistrator.RoleAccessor; } /// @@ -53,10 +59,15 @@ protected override IEnumerable CreateServiceReplicaListe /// protected override Task RunAsync(CancellationToken cancellationToken) => Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken))); - + /// /// Gets the current replica role. /// - public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleAccessor.Value?.Role ?? ReplicaRole.Unknown; + public ReplicaRole GetCurrentReplicaRole() + { + ReplicaRoleWrapper roleWrapper = new OmexStatefulServiceRegistrator.ReplicaRoleWrapper(); + m_replicaRoleAccessor.SetValue(roleWrapper); + return roleWrapper.Role; + } } } From 3d0c89d6e0bf235cdea5384631b4b744e4121e3e Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Wed, 2 Apr 2025 17:05:18 +0100 Subject: [PATCH 09/13] test changes --- TestProject4/Program.cs | 5 +- src/Abstractions/AccessorWrapper.cs | 61 +++++++++++++++++++ .../OmexStatefulServiceRegistrator.cs | 2 +- src/Hosting.Services/OmexStatefulService.cs | 26 +++++--- 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 src/Abstractions/AccessorWrapper.cs diff --git a/TestProject4/Program.cs b/TestProject4/Program.cs index 4cd1aa789..b71ad6fba 100644 --- a/TestProject4/Program.cs +++ b/TestProject4/Program.cs @@ -10,7 +10,7 @@ public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { // Initialize the stateful service registrator ServiceRegistratorOptions serviceRegistratorOptions = new ServiceRegistratorOptions { ServiceTypeName = "MyServiceType" }; @@ -50,6 +50,9 @@ public static void Main(string[] args) // Initialize the OmexStatefulService OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); + // Call OnChangeRoleAsync with appropriate parameters + await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); + // Get the current replica role ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); Console.WriteLine($"Current Replica Role: {currentRole}"); diff --git a/src/Abstractions/AccessorWrapper.cs b/src/Abstractions/AccessorWrapper.cs new file mode 100644 index 000000000..29ef2f6a1 --- /dev/null +++ b/src/Abstractions/AccessorWrapper.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Accessors; +using Microsoft.Omex.Extensions.Hosting.Services; + +namespace Microsoft.Omex.Extensions.Hosting.Services +{ + /// + /// A wrapper class for IAccessor that provides additional functionality. + /// + /// The type of the value. + public class AccessorWrapper : IAccessor where TValue : class + { + private readonly IAccessorSetter m_accessorSetter; + + /// + /// Initializes a new instance of the class. + /// + /// The accessor setter. + public AccessorWrapper(IAccessorSetter accessorSetter) + { + m_accessorSetter = accessorSetter; + } + + /// + /// Gets the value. + /// + public TValue? Value => m_accessorSetter is IAccessor accessor ? accessor.Value : null; + + /// + /// Gets the value or throws an exception if the value is not available. + /// + /// The value. + /// Thrown when the value is not available. + public TValue GetValueOrThrow() + { + if (m_accessorSetter is IAccessor accessor) + { + return accessor.GetValueOrThrow(); + } + throw new InvalidOperationException("Value is not available."); + } + + /// + /// Registers a function to be called when the value is first set. + /// + /// The function to call. + /// Thrown when the accessor does not support OnFirstSet. + public void OnFirstSet(Action function) + { + if (m_accessorSetter is IAccessor accessor) + { + accessor.OnFirstSet(function); + } + else + { + throw new InvalidOperationException("Accessor does not support OnFirstSet."); + } + } + } +} diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index 7a55cbcd6..a8c51d0b1 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -45,7 +45,7 @@ public OmexStatefulServiceRegistrator( /// A token to monitor for cancellation requests. /// A task that represents the asynchronous registration operation. public override Task RegisterAsync(CancellationToken cancellationToken) => - ServiceRuntime.RegisterServiceAsync(Options.ServiceTypeName, context => new OmexStatefulService(this, context), cancellationToken: cancellationToken); + ServiceRuntime.RegisterServiceAsync(Options.ServiceTypeName, context => new OmexStatefulService(this, context), cancellationToken: cancellationToken); /// /// Gets the accessor for the reliable state manager. diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 84524fe21..0946c1c27 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -20,7 +20,7 @@ namespace Microsoft.Omex.Extensions.Hosting.Services public sealed class OmexStatefulService : StatefulService, IServiceFabricService { private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private readonly IAccessorSetter m_replicaRoleAccessor; + private readonly IAccessor m_replicaRoleAccessor; /// /// Initializes a new instance of the class. @@ -28,14 +28,14 @@ public sealed class OmexStatefulService : StatefulService, IServiceFabricService /// The service registrator. /// The stateful service context. public OmexStatefulService( - OmexStatefulServiceRegistrator serviceRegistrator, - StatefulServiceContext serviceContext) - : base(serviceContext) + OmexStatefulServiceRegistrator serviceRegistrator, + StatefulServiceContext serviceContext) + : base(serviceContext) { serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; - m_replicaRoleAccessor = serviceRegistrator.RoleAccessor; + m_replicaRoleAccessor = new AccessorWrapper(serviceRegistrator.RoleAccessor); } /// @@ -51,10 +51,17 @@ protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); return base.OnChangeRoleAsync(newRole, cancellationToken); } - + + /// + public Task ChangeRoleAsyncTest(ReplicaRole newRole, CancellationToken cancellationToken) + { + m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + return Task.CompletedTask; + } + /// protected override IEnumerable CreateServiceReplicaListeners() => - m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); + m_serviceRegistrator.ListenerBuilders.Select(b => new ServiceReplicaListener(c => b.Build(this), b.Name)); /// protected override Task RunAsync(CancellationToken cancellationToken) => @@ -65,9 +72,8 @@ protected override Task RunAsync(CancellationToken cancellationToken) => /// public ReplicaRole GetCurrentReplicaRole() { - ReplicaRoleWrapper roleWrapper = new OmexStatefulServiceRegistrator.ReplicaRoleWrapper(); - m_replicaRoleAccessor.SetValue(roleWrapper); - return roleWrapper.Role; + ReplicaRoleWrapper? value = m_replicaRoleAccessor.Value; + return value?.Role ?? ReplicaRole.Unknown; } } } From 2a70c437cbf197e04c6d538ef8f4947261ed4020 Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Wed, 2 Apr 2025 23:26:02 +0100 Subject: [PATCH 10/13] Test ok --- src/Abstractions/AccessorWrapper.cs | 61 --------------------- src/Hosting.Services/OmexStatefulService.cs | 22 ++++---- 2 files changed, 11 insertions(+), 72 deletions(-) delete mode 100644 src/Abstractions/AccessorWrapper.cs diff --git a/src/Abstractions/AccessorWrapper.cs b/src/Abstractions/AccessorWrapper.cs deleted file mode 100644 index 29ef2f6a1..000000000 --- a/src/Abstractions/AccessorWrapper.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.Abstractions.Accessors; -using Microsoft.Omex.Extensions.Hosting.Services; - -namespace Microsoft.Omex.Extensions.Hosting.Services -{ - /// - /// A wrapper class for IAccessor that provides additional functionality. - /// - /// The type of the value. - public class AccessorWrapper : IAccessor where TValue : class - { - private readonly IAccessorSetter m_accessorSetter; - - /// - /// Initializes a new instance of the class. - /// - /// The accessor setter. - public AccessorWrapper(IAccessorSetter accessorSetter) - { - m_accessorSetter = accessorSetter; - } - - /// - /// Gets the value. - /// - public TValue? Value => m_accessorSetter is IAccessor accessor ? accessor.Value : null; - - /// - /// Gets the value or throws an exception if the value is not available. - /// - /// The value. - /// Thrown when the value is not available. - public TValue GetValueOrThrow() - { - if (m_accessorSetter is IAccessor accessor) - { - return accessor.GetValueOrThrow(); - } - throw new InvalidOperationException("Value is not available."); - } - - /// - /// Registers a function to be called when the value is first set. - /// - /// The function to call. - /// Thrown when the accessor does not support OnFirstSet. - public void OnFirstSet(Action function) - { - if (m_accessorSetter is IAccessor accessor) - { - accessor.OnFirstSet(function); - } - else - { - throw new InvalidOperationException("Accessor does not support OnFirstSet."); - } - } - } -} diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 0946c1c27..d55630a45 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -6,10 +6,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Omex.Extensions.Abstractions; using Microsoft.Omex.Extensions.Abstractions.Accessors; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; +using Microsoft.Omex.Extensions.Hosting.Services; using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; namespace Microsoft.Omex.Extensions.Hosting.Services @@ -20,7 +20,9 @@ namespace Microsoft.Omex.Extensions.Hosting.Services public sealed class OmexStatefulService : StatefulService, IServiceFabricService { private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private readonly IAccessor m_replicaRoleAccessor; + private readonly IAccessorSetter m_replicaRoleAccessor; + // Add a default initialization for m_replicaRoleWrapper to avoid CS8618 + private ReplicaRoleWrapper m_replicaRoleWrapper = new ReplicaRoleWrapper(); /// /// Initializes a new instance of the class. @@ -35,7 +37,7 @@ public OmexStatefulService( serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; - m_replicaRoleAccessor = new AccessorWrapper(serviceRegistrator.RoleAccessor); + m_replicaRoleAccessor = serviceRegistrator.RoleAccessor; } /// @@ -44,18 +46,20 @@ protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken m_serviceRegistrator.PartitionAccessor.SetValue(Partition); return base.OnOpenAsync(openMode, cancellationToken); } - + /// protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + m_replicaRoleWrapper = new ReplicaRoleWrapper { Role = newRole }; + m_serviceRegistrator.RoleAccessor.SetValue(m_replicaRoleWrapper); return base.OnChangeRoleAsync(newRole, cancellationToken); } /// public Task ChangeRoleAsyncTest(ReplicaRole newRole, CancellationToken cancellationToken) { - m_serviceRegistrator.RoleAccessor.SetValue(new ReplicaRoleWrapper { Role = newRole }); + m_replicaRoleWrapper = new ReplicaRoleWrapper { Role = newRole }; + m_serviceRegistrator.RoleAccessor.SetValue(m_replicaRoleWrapper); return Task.CompletedTask; } @@ -70,10 +74,6 @@ protected override Task RunAsync(CancellationToken cancellationToken) => /// /// Gets the current replica role. /// - public ReplicaRole GetCurrentReplicaRole() - { - ReplicaRoleWrapper? value = m_replicaRoleAccessor.Value; - return value?.Role ?? ReplicaRole.Unknown; - } + public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleWrapper?.Role ?? ReplicaRole.Unknown; } } From 9167c72c4a6056e249301d29da257529b077838c Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Thu, 3 Apr 2025 10:38:57 +0100 Subject: [PATCH 11/13] removed unnecessary lines --- src/Hosting.Services/OmexStatefulService.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index d55630a45..278bd78a9 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Omex.Extensions.Abstractions.Accessors; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using Microsoft.Omex.Extensions.Hosting.Services; @@ -20,8 +19,6 @@ namespace Microsoft.Omex.Extensions.Hosting.Services public sealed class OmexStatefulService : StatefulService, IServiceFabricService { private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private readonly IAccessorSetter m_replicaRoleAccessor; - // Add a default initialization for m_replicaRoleWrapper to avoid CS8618 private ReplicaRoleWrapper m_replicaRoleWrapper = new ReplicaRoleWrapper(); /// @@ -37,7 +34,6 @@ public OmexStatefulService( serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; - m_replicaRoleAccessor = serviceRegistrator.RoleAccessor; } /// From 9ab70064c1941c152030a1f03b83a51863bf3cbc Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Fri, 4 Apr 2025 18:45:33 +0100 Subject: [PATCH 12/13] SF test application added and ReplicaRoleWrapper disabled --- Directory.Packages.props | 1 + Omex.sln | 26 ++ .../ApplicationManifest.xml | 16 + .../ApplicationParameters/Cloud.xml | 4 + .../ApplicationParameters/Local.1Node.xml | 6 + .../ApplicationParameters/Local.5Node.xml | 6 + ReplicaRole.TestApp/PublishProfiles/Cloud.xml | 27 ++ .../PublishProfiles/Local.1Node.xml | 12 + .../PublishProfiles/Local.5Node.xml | 12 + .../ReplicaRole.TestApp.sfproj | 51 +++ .../Scripts/Deploy-FabricApplication.ps1 | 304 ++++++++++++++++++ .../StartupServiceParameters/Cloud.xml | 8 + .../StartupServiceParameters/Local.1Node.xml | 8 + .../StartupServiceParameters/Local.5Node.xml | 8 + ReplicaRole.TestApp/StartupServices.xml | 20 ++ ReplicaRole.TestApp/packages.config | 4 + .../Controllers/WeatherForecastController.cs | 36 +++ .../PackageRoot/Config/Settings.xml | 9 + .../PackageRoot/ServiceManifest.xml | 30 ++ ReplicaTestApp/Program.cs | 101 ++++++ ReplicaTestApp/ReplicaTestApp.cs | 61 ++++ ReplicaTestApp/ReplicaTestApp.csproj | 24 ++ ReplicaTestApp/ReplicaTestApp.http | 6 + ReplicaTestApp/ServiceEventSource.cs | 190 +++++++++++ ReplicaTestApp/WeatherForecast.cs | 16 + ReplicaTestApp/appsettings.Development.json | 8 + ReplicaTestApp/appsettings.json | 9 + TestProject4/Program.cs | 72 ++--- src/Hosting.Services/HostBuilderExtensions.cs | 3 +- .../Internal/OmexStateManager.cs | 54 ++++ .../OmexStatefulServiceRegistrator.cs | 13 +- .../Internal/ReplicaRoleWrapper.cs | 28 +- src/Hosting.Services/OmexStatefulService.cs | 16 +- .../MockServiceFabricServices.cs | 3 +- 34 files changed, 1127 insertions(+), 65 deletions(-) create mode 100644 ReplicaRole.TestApp/ApplicationPackageRoot/ApplicationManifest.xml create mode 100644 ReplicaRole.TestApp/ApplicationParameters/Cloud.xml create mode 100644 ReplicaRole.TestApp/ApplicationParameters/Local.1Node.xml create mode 100644 ReplicaRole.TestApp/ApplicationParameters/Local.5Node.xml create mode 100644 ReplicaRole.TestApp/PublishProfiles/Cloud.xml create mode 100644 ReplicaRole.TestApp/PublishProfiles/Local.1Node.xml create mode 100644 ReplicaRole.TestApp/PublishProfiles/Local.5Node.xml create mode 100644 ReplicaRole.TestApp/ReplicaRole.TestApp.sfproj create mode 100644 ReplicaRole.TestApp/Scripts/Deploy-FabricApplication.ps1 create mode 100644 ReplicaRole.TestApp/StartupServiceParameters/Cloud.xml create mode 100644 ReplicaRole.TestApp/StartupServiceParameters/Local.1Node.xml create mode 100644 ReplicaRole.TestApp/StartupServiceParameters/Local.5Node.xml create mode 100644 ReplicaRole.TestApp/StartupServices.xml create mode 100644 ReplicaRole.TestApp/packages.config create mode 100644 ReplicaTestApp/Controllers/WeatherForecastController.cs create mode 100644 ReplicaTestApp/PackageRoot/Config/Settings.xml create mode 100644 ReplicaTestApp/PackageRoot/ServiceManifest.xml create mode 100644 ReplicaTestApp/Program.cs create mode 100644 ReplicaTestApp/ReplicaTestApp.cs create mode 100644 ReplicaTestApp/ReplicaTestApp.csproj create mode 100644 ReplicaTestApp/ReplicaTestApp.http create mode 100644 ReplicaTestApp/ServiceEventSource.cs create mode 100644 ReplicaTestApp/WeatherForecast.cs create mode 100644 ReplicaTestApp/appsettings.Development.json create mode 100644 ReplicaTestApp/appsettings.json create mode 100644 src/Hosting.Services/Internal/OmexStateManager.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 465b5bf28..3122e5481 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,7 @@ + diff --git a/Omex.sln b/Omex.sln index fd047a284..e5127556b 100644 --- a/Omex.sln +++ b/Omex.sln @@ -77,6 +77,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Omex.Extensions.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject4", "TestProject4\TestProject4.csproj", "{8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}" EndProject +Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "ReplicaRole.TestApp", "ReplicaRole.TestApp\ReplicaRole.TestApp.sfproj", "{D22FC4FC-DDC0-41DC-943F-D9C278A92800}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReplicaTestApp", "ReplicaTestApp\ReplicaTestApp.csproj", "{9CD4643A-3D05-4C4A-B5A8-D86074355770}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -285,6 +289,26 @@ Global {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|Any CPU.Build.0 = Release|Any CPU {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|x64.ActiveCfg = Release|Any CPU {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF}.Release|x64.Build.0 = Release|Any CPU + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|Any CPU.Build.0 = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|Any CPU.Deploy.0 = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|x64.ActiveCfg = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|x64.Build.0 = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Debug|x64.Deploy.0 = Debug|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|Any CPU.ActiveCfg = Release|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|Any CPU.Build.0 = Release|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|Any CPU.Deploy.0 = Release|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|x64.ActiveCfg = Release|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|x64.Build.0 = Release|x64 + {D22FC4FC-DDC0-41DC-943F-D9C278A92800}.Release|x64.Deploy.0 = Release|x64 + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Debug|x64.ActiveCfg = Debug|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Debug|x64.Build.0 = Debug|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Release|Any CPU.Build.0 = Release|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Release|x64.ActiveCfg = Release|Any CPU + {9CD4643A-3D05-4C4A-B5A8-D86074355770}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -315,6 +339,8 @@ Global {43CE835D-5A71-4689-9297-942EF1233175} = {551C93F8-6E89-4954-8905-7F5AC7173285} {66F677BB-D314-4DA0-9173-6CA74E88AAF2} = {551C93F8-6E89-4954-8905-7F5AC7173285} {8CBDEA04-0AB4-4590-99AB-76E26A5C88BF} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE} + {D22FC4FC-DDC0-41DC-943F-D9C278A92800} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE} + {9CD4643A-3D05-4C4A-B5A8-D86074355770} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E6FB7BCB-BF07-4F19-ACBA-457479D421BB} diff --git a/ReplicaRole.TestApp/ApplicationPackageRoot/ApplicationManifest.xml b/ReplicaRole.TestApp/ApplicationPackageRoot/ApplicationManifest.xml new file mode 100644 index 000000000..1dae82263 --- /dev/null +++ b/ReplicaRole.TestApp/ApplicationPackageRoot/ApplicationManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/ApplicationParameters/Cloud.xml b/ReplicaRole.TestApp/ApplicationParameters/Cloud.xml new file mode 100644 index 000000000..b9b44344e --- /dev/null +++ b/ReplicaRole.TestApp/ApplicationParameters/Cloud.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/ApplicationParameters/Local.1Node.xml b/ReplicaRole.TestApp/ApplicationParameters/Local.1Node.xml new file mode 100644 index 000000000..561845a8f --- /dev/null +++ b/ReplicaRole.TestApp/ApplicationParameters/Local.1Node.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/ApplicationParameters/Local.5Node.xml b/ReplicaRole.TestApp/ApplicationParameters/Local.5Node.xml new file mode 100644 index 000000000..561845a8f --- /dev/null +++ b/ReplicaRole.TestApp/ApplicationParameters/Local.5Node.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/PublishProfiles/Cloud.xml b/ReplicaRole.TestApp/PublishProfiles/Cloud.xml new file mode 100644 index 000000000..be57fcee3 --- /dev/null +++ b/ReplicaRole.TestApp/PublishProfiles/Cloud.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/PublishProfiles/Local.1Node.xml b/ReplicaRole.TestApp/PublishProfiles/Local.1Node.xml new file mode 100644 index 000000000..45cbf369c --- /dev/null +++ b/ReplicaRole.TestApp/PublishProfiles/Local.1Node.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/PublishProfiles/Local.5Node.xml b/ReplicaRole.TestApp/PublishProfiles/Local.5Node.xml new file mode 100644 index 000000000..7de39f994 --- /dev/null +++ b/ReplicaRole.TestApp/PublishProfiles/Local.5Node.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/ReplicaRole.TestApp.sfproj b/ReplicaRole.TestApp/ReplicaRole.TestApp.sfproj new file mode 100644 index 000000000..3dc5e77c5 --- /dev/null +++ b/ReplicaRole.TestApp/ReplicaRole.TestApp.sfproj @@ -0,0 +1,51 @@ + + + + + d22fc4fc-ddc0-41dc-943f-d9c278a92800 + 2.7 + 16.10 + 1.7.9 + v4.7.2 + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Service Fabric Tools\Microsoft.VisualStudio.Azure.Fabric.ApplicationProject.targets + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/Scripts/Deploy-FabricApplication.ps1 b/ReplicaRole.TestApp/Scripts/Deploy-FabricApplication.ps1 new file mode 100644 index 000000000..929595dc3 --- /dev/null +++ b/ReplicaRole.TestApp/Scripts/Deploy-FabricApplication.ps1 @@ -0,0 +1,304 @@ +<# +.SYNOPSIS +Deploys a Service Fabric application type to a cluster. + +.DESCRIPTION +This script deploys a Service Fabric application type to a cluster. It is invoked by Visual Studio when deploying a Service Fabric Application project. + +.NOTES +WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary. + +.PARAMETER PublishProfileFile +Path to the file containing the publish profile. + +.PARAMETER StartupServicesFile +Path to the file containing the startup services for debugging. + +.PARAMETER ApplicationPackagePath +Path to the folder of the packaged Service Fabric application. + +.PARAMETER DeployOnly +Indicates that the Service Fabric application should not be created or upgraded after registering the application type. + +.PARAMETER ApplicationParameter +Hashtable of the Service Fabric application parameters to be used for the application. + +.PARAMETER UnregisterUnusedApplicationVersionsAfterUpgrade +Indicates whether to unregister any unused application versions that exist after an upgrade is finished. + +.PARAMETER OverrideUpgradeBehavior +Indicates the behavior used to override the upgrade settings specified by the publish profile. +'None' indicates that the upgrade settings will not be overridden. +'ForceUpgrade' indicates that an upgrade will occur with default settings, regardless of what is specified in the publish profile. +'VetoUpgrade' indicates that an upgrade will not occur, regardless of what is specified in the publish profile. + +.PARAMETER UseExistingClusterConnection +Indicates that the script should make use of an existing cluster connection that has already been established in the PowerShell session. The cluster connection parameters configured in the publish profile are ignored. + +.PARAMETER OverwriteBehavior +Overwrite Behavior if an application exists in the cluster with the same name. Available Options are Never, Always, SameAppTypeAndVersion. This setting is not applicable when upgrading an application. +'Never' will not remove the existing application. This is the default behavior. +'Always' will remove the existing application even if its Application type and Version is different from the application being created. +'SameAppTypeAndVersion' will remove the existing application only if its Application type and Version is same as the application being created. + +.PARAMETER SkipPackageValidation +Switch signaling whether the package should be validated or not before deployment. + +.PARAMETER SecurityToken +A security token for authentication to cluster management endpoints. Used for silent authentication to clusters that are protected by Microsoft Entra ID (formerly known as Azure Active Directory). + +.PARAMETER CopyPackageTimeoutSec +Timeout in seconds for copying application package to image store. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' + +Deploy the application using the default package location for a Debug build. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -DoNotCreateApplication + +Deploy the application but do not create the application instance. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'} + +Deploy the application by providing values for parameters that are defined in the application manifest. +#> + +Param +( + [String] + $PublishProfileFile, + + [String] + $StartupServicesFile, + + [String] + $ApplicationPackagePath, + + [Switch] + $DeployOnly, + + [Hashtable] + $ApplicationParameter, + + [Boolean] + $UnregisterUnusedApplicationVersionsAfterUpgrade, + + [String] + [ValidateSet('None', 'ForceUpgrade', 'VetoUpgrade')] + $OverrideUpgradeBehavior = 'None', + + [Switch] + $UseExistingClusterConnection, + + [String] + [ValidateSet('Never', 'Always', 'SameAppTypeAndVersion')] + $OverwriteBehavior = 'Never', + + [Switch] + $SkipPackageValidation, + + [String] + $SecurityToken, + + [int] + $CopyPackageTimeoutSec, + + [int] + $RegisterApplicationTypeTimeoutSec +) + +function Read-XmlElementAsHashtable +{ + Param ( + [System.Xml.XmlElement] + $Element + ) + + $hashtable = @{} + if ($Element.Attributes) + { + $Element.Attributes | + ForEach-Object { + $boolVal = $null + if ([bool]::TryParse($_.Value, [ref]$boolVal)) { + $hashtable[$_.Name] = $boolVal + } + else { + # ServerCertThumbprints is special cased to handle parsing of comma separated thumbprints + if ($_.Name -eq "ServerCertThumbprint") + { + $hashtable[$_.Name] = $_.Value.Split(@([char]',')) + } + else + { + $hashtable[$_.Name] = $_.Value + } + } + } + } + + return $hashtable +} + +function Read-PublishProfile +{ + Param ( + [ValidateScript({Test-Path $_ -PathType Leaf})] + [String] + $PublishProfileFile + ) + + $publishProfileXml = [Xml] (Get-Content $PublishProfileFile -Encoding UTF8) + $publishProfile = @{} + + $publishProfile.ClusterConnectionParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("ClusterConnectionParameters") + $publishProfile.UpgradeDeployment = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment") + $publishProfile.CopyPackageParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("CopyPackageParameters") + $publishProfile.RegisterApplicationParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("RegisterApplicationParameters") + + if ($publishProfileXml.PublishProfile.Item("UpgradeDeployment")) + { + $publishProfile.UpgradeDeployment.Parameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment").Item("Parameters") + if ($publishProfile.UpgradeDeployment["Mode"]) + { + $publishProfile.UpgradeDeployment.Parameters[$publishProfile.UpgradeDeployment["Mode"]] = $true + } + } + + $publishProfileFolder = (Split-Path $PublishProfileFile) + $publishProfile.ApplicationParameterFile = [System.IO.Path]::Combine($PublishProfileFolder, $publishProfileXml.PublishProfile.ApplicationParameterFile.Path) + $publishProfile.StartupServiceParameterFile = [System.IO.Path]::Combine($PublishProfileFolder, $publishProfileXml.PublishProfile.StartupServiceParameterFile.Path) + + return $publishProfile +} + +$LocalFolder = (Split-Path $MyInvocation.MyCommand.Path) + +if (!$PublishProfileFile) +{ + $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml" +} + +if (!$ApplicationPackagePath) +{ + $ApplicationPackagePath = "$LocalFolder\..\pkg\Release" +} + +$ApplicationPackagePath = Resolve-Path $ApplicationPackagePath + +$publishProfile = Read-PublishProfile $PublishProfileFile + +if (-not $UseExistingClusterConnection) +{ + $ClusterConnectionParameters = $publishProfile.ClusterConnectionParameters + if ($SecurityToken) + { + $ClusterConnectionParameters["SecurityToken"] = $SecurityToken + } + + try + { + [void](Connect-ServiceFabricCluster @ClusterConnectionParameters) + } + catch [System.Fabric.FabricObjectClosedException] + { + Write-Warning "Service Fabric cluster may not be connected." + throw + } +} + +$RegKey = "HKLM:\SOFTWARE\Microsoft\Service Fabric SDK" +$ModuleFolderPath = (Get-ItemProperty -Path $RegKey -Name FabricSDKPSModulePath).FabricSDKPSModulePath +Import-Module "$ModuleFolderPath\ServiceFabricSDK.psm1" + +$IsUpgrade = ($publishProfile.UpgradeDeployment -and $publishProfile.UpgradeDeployment.Enabled -and $OverrideUpgradeBehavior -ne 'VetoUpgrade') -or $OverrideUpgradeBehavior -eq 'ForceUpgrade' + +$PublishParameters = @{ + 'ApplicationPackagePath' = $ApplicationPackagePath + 'ApplicationParameterFilePath' = $publishProfile.ApplicationParameterFile + 'ApplicationParameter' = $ApplicationParameter + 'ErrorAction' = 'Stop' +} + +if ($publishProfile.CopyPackageParameters.CopyPackageTimeoutSec) +{ + $PublishParameters['CopyPackageTimeoutSec'] = $publishProfile.CopyPackageParameters.CopyPackageTimeoutSec +} + +if ($publishProfile.CopyPackageParameters.CompressPackage) +{ + $PublishParameters['CompressPackage'] = $publishProfile.CopyPackageParameters.CompressPackage +} + +if ($publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec) +{ + $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec +} + +# CopyPackageTimeoutSec parameter overrides the value from the publish profile +if ($CopyPackageTimeoutSec) +{ + $PublishParameters['CopyPackageTimeoutSec'] = $CopyPackageTimeoutSec +} + +# RegisterApplicationTypeTimeoutSec parameter overrides the value from the publish profile +if ($RegisterApplicationTypeTimeoutSec) +{ + $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $RegisterApplicationTypeTimeoutSec +} + +if ($IsUpgrade) +{ + if ($StartupServicesFile) + { + $PublishParameters['StartupServicesFileMode'] = $true + } + + $Action = "RegisterAndUpgrade" + if ($DeployOnly) + { + $Action = "Register" + } + + $UpgradeParameters = $publishProfile.UpgradeDeployment.Parameters + + if ($OverrideUpgradeBehavior -eq 'ForceUpgrade') + { + # Warning: Do not alter these upgrade parameters. It will create an inconsistency with Visual Studio's behavior. + $UpgradeParameters = @{ UnmonitoredAuto = $true; Force = $true } + } + + $PublishParameters['Action'] = $Action + $PublishParameters['UpgradeParameters'] = $UpgradeParameters + $PublishParameters['UnregisterUnusedVersions'] = $UnregisterUnusedApplicationVersionsAfterUpgrade + + Publish-UpgradedServiceFabricApplication @PublishParameters +} +else +{ + # Pass the path to the Startup Services File if it was provided + if ($StartupServicesFile) + { + $PublishParameters['StartupServicesFilePath'] = $StartupServicesFile + + if (-not [string]::IsNullOrEmpty($publishProfile.StartupServiceParameterFile)) + { + $PublishParameters['StartupServiceParameterFilePath'] = $publishProfile.StartupServiceParameterFile + } + } + + $Action = "RegisterAndCreate" + if ($DeployOnly) + { + $Action = "Register" + } + + $PublishParameters['Action'] = $Action + $PublishParameters['OverwriteBehavior'] = $OverwriteBehavior + $PublishParameters['SkipPackageValidation'] = $SkipPackageValidation + + Publish-NewServiceFabricApplication @PublishParameters +} \ No newline at end of file diff --git a/ReplicaRole.TestApp/StartupServiceParameters/Cloud.xml b/ReplicaRole.TestApp/StartupServiceParameters/Cloud.xml new file mode 100644 index 000000000..87ac4c7eb --- /dev/null +++ b/ReplicaRole.TestApp/StartupServiceParameters/Cloud.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/StartupServiceParameters/Local.1Node.xml b/ReplicaRole.TestApp/StartupServiceParameters/Local.1Node.xml new file mode 100644 index 000000000..f77b8bcc5 --- /dev/null +++ b/ReplicaRole.TestApp/StartupServiceParameters/Local.1Node.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/StartupServiceParameters/Local.5Node.xml b/ReplicaRole.TestApp/StartupServiceParameters/Local.5Node.xml new file mode 100644 index 000000000..87ac4c7eb --- /dev/null +++ b/ReplicaRole.TestApp/StartupServiceParameters/Local.5Node.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/StartupServices.xml b/ReplicaRole.TestApp/StartupServices.xml new file mode 100644 index 000000000..ed2cd50a3 --- /dev/null +++ b/ReplicaRole.TestApp/StartupServices.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReplicaRole.TestApp/packages.config b/ReplicaRole.TestApp/packages.config new file mode 100644 index 000000000..ece21af86 --- /dev/null +++ b/ReplicaRole.TestApp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ReplicaTestApp/Controllers/WeatherForecastController.cs b/ReplicaTestApp/Controllers/WeatherForecastController.cs new file mode 100644 index 000000000..b981e1929 --- /dev/null +++ b/ReplicaTestApp/Controllers/WeatherForecastController.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.AspNetCore.Mvc; + +namespace ReplicaTestApp.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/ReplicaTestApp/PackageRoot/Config/Settings.xml b/ReplicaTestApp/PackageRoot/Config/Settings.xml new file mode 100644 index 000000000..902c747af --- /dev/null +++ b/ReplicaTestApp/PackageRoot/Config/Settings.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/ReplicaTestApp/PackageRoot/ServiceManifest.xml b/ReplicaTestApp/PackageRoot/ServiceManifest.xml new file mode 100644 index 000000000..45d93b2a5 --- /dev/null +++ b/ReplicaTestApp/PackageRoot/ServiceManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + ReplicaTestApp.exe + CodePackage + + + + + + + + + + + \ No newline at end of file diff --git a/ReplicaTestApp/Program.cs b/ReplicaTestApp/Program.cs new file mode 100644 index 000000000..c3b02d51c --- /dev/null +++ b/ReplicaTestApp/Program.cs @@ -0,0 +1,101 @@ +using Microsoft.ServiceFabric.Services.Runtime; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Omex.Extensions.Abstractions.Accessors; +using Microsoft.Omex.Extensions.Hosting.Services; +using Microsoft.Extensions.Options; +using Microsoft.ServiceFabric.Data; +using System.Fabric; +using System.Collections.Generic; +using ServiceFabric.Mocks; +using Microsoft.Omex.Extensions.Hosting.Services.Internal; + +namespace ReplicaTestApp +{ + internal static class Program + { + + public class AccessorSetter : IAccessorSetter where T : class + { + private T? m_value; + public void SetValue(T value) => m_value = value; + public T GetValue() => m_value!; + } + + /// + /// This is the entry point of the service host process. + /// + private static async Task Main() + { + try + { + // The ServiceManifest.XML file defines one or more service type names. + // Registering a service maps a service type name to a .NET type. + // When Service Fabric creates an instance of this service type, + // an instance of the class is created in this host process. + + ServiceRuntime.RegisterServiceAsync("ReplicaTestAppType", + context => new ReplicaTestApp(context)).GetAwaiter().GetResult(); + + ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(ReplicaTestApp).Name); + + // Prevents this host process from terminating so services keeps running. + + + // Initialize the stateful service registrator + ServiceRegistratorOptions serviceRegistratorOptions = new ServiceRegistratorOptions { ServiceTypeName = "MyServiceType" }; + IOptions options = Options.Create(serviceRegistratorOptions); + IAccessorSetter contextAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter partitionAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter stateAccessor = new AccessorSetter(); // Initialize appropriately + IAccessorSetter roleAccessor = new AccessorSetter(); // Initialize appropriately + IEnumerable> listenerBuilders = new List>(); // Initialize appropriately + IEnumerable> serviceActions = new List>(); // Initialize appropriately + + OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( + options, + contextAccessor, + partitionAccessor, + stateAccessor, + roleAccessor, + listenerBuilders, + serviceActions); + + // Create a mock StatefulServiceContext + NodeContext nodeContext = new NodeContext("nodeName", new NodeId(0, 0), 0, "nodeType", "ipAddress"); + ICodePackageActivationContext codePackageActivationContext = new MockCodePackageActivationContext( + "applicationName", + "applicationTypeName", + "codePackageName", + "codePackageVersion", + "contextId", + "logDirectory", + "tempDirectory", + "workDirectory", + "serviceManifestName", + "serviceManifestVersion" + ); + StatefulServiceContext context = new StatefulServiceContext(nodeContext, codePackageActivationContext, "serviceTypeName", new Uri("fabric:/AppName/ServiceName"), null, Guid.NewGuid(), long.MaxValue); + + // Initialize the OmexStatefulService + OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); + + // Call OnChangeRoleAsync with appropriate parameters + await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); + + ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); + Console.WriteLine($"Current Replica Role: {currentRole}"); + + + Thread.Sleep(Timeout.Infinite); + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + throw; + } + } + } +} diff --git a/ReplicaTestApp/ReplicaTestApp.cs b/ReplicaTestApp/ReplicaTestApp.cs new file mode 100644 index 000000000..fafe77589 --- /dev/null +++ b/ReplicaTestApp/ReplicaTestApp.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Fabric; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.ServiceFabric.Services.Communication.AspNetCore; +using Microsoft.ServiceFabric.Services.Communication.Runtime; +using Microsoft.ServiceFabric.Services.Runtime; +using Microsoft.ServiceFabric.Data; + +namespace ReplicaTestApp +{ + /// + /// The FabricRuntime creates an instance of this class for each service type instance. + /// + internal sealed class ReplicaTestApp : StatefulService + { + public ReplicaTestApp(StatefulServiceContext context) + : base(context) + { } + + /// + /// Optional override to create listeners (like tcp, http) for this service instance. + /// + /// The collection of listeners. + protected override IEnumerable CreateServiceReplicaListeners() + { + return new ServiceReplicaListener[] + { + new ServiceReplicaListener(serviceContext => + new KestrelCommunicationListener(serviceContext, (url, listener) => + { + ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}"); + + WebApplicationBuilder builder = WebApplication.CreateBuilder(); + + builder.Services + .AddSingleton(serviceContext) + .AddSingleton(StateManager); + builder.WebHost + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl) + .UseUrls(url); + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + WebApplication app = builder.Build(); + app.UseAuthorization(); + app.MapControllers(); + + return app; + + })) + }; + } + } +} diff --git a/ReplicaTestApp/ReplicaTestApp.csproj b/ReplicaTestApp/ReplicaTestApp.csproj new file mode 100644 index 000000000..1d905ca99 --- /dev/null +++ b/ReplicaTestApp/ReplicaTestApp.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + enable + enable + True + True + win-x64 + True + + + + + + + + + + + + + + diff --git a/ReplicaTestApp/ReplicaTestApp.http b/ReplicaTestApp/ReplicaTestApp.http new file mode 100644 index 000000000..8ca84ee34 --- /dev/null +++ b/ReplicaTestApp/ReplicaTestApp.http @@ -0,0 +1,6 @@ +@ReplicaTestApp_HostAddress = http://localhost:5051 + +GET {{ReplicaTestApp_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/ReplicaTestApp/ServiceEventSource.cs b/ReplicaTestApp/ServiceEventSource.cs new file mode 100644 index 000000000..ca70c1f86 --- /dev/null +++ b/ReplicaTestApp/ServiceEventSource.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Fabric; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.ServiceFabric.Services.Runtime; + +namespace ReplicaTestApp +{ + [EventSource(Name = "MyCompany-ReplicaRole.TestApp-ReplicaTestApp")] + internal sealed class ServiceEventSource : EventSource + { + public static readonly ServiceEventSource Current = new ServiceEventSource(); + + static ServiceEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + // Instance constructor is private to enforce singleton semantics + private ServiceEventSource() : base() { } + + #region Keywords + // Event keywords can be used to categorize events. + // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). + // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. + public static class Keywords + { + public const EventKeywords Requests = (EventKeywords)0x1L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; + } + #endregion + + #region Events + // Define an instance method for each event you want to record and apply an [Event] attribute to it. + // The method name is the name of the event. + // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). + // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. + // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). + // Put [NonEvent] attribute on all methods that do not define an event. + // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx + + [NonEvent] + public void Message(string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + Message(finalMessage); + } + } + + private const int MessageEventId = 1; + [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] + public void Message(string message) + { + if (this.IsEnabled()) + { + WriteEvent(MessageEventId, message); + } + } + + [NonEvent] + public void ServiceMessage(ServiceContext serviceContext, string message, params object[] args) + { + if (this.IsEnabled()) + { + + string finalMessage = string.Format(message, args); + ServiceMessage( + serviceContext.ServiceName.ToString(), + serviceContext.ServiceTypeName, + GetReplicaOrInstanceId(serviceContext), + serviceContext.PartitionId, + serviceContext.CodePackageActivationContext.ApplicationName, + serviceContext.CodePackageActivationContext.ApplicationTypeName, + serviceContext.NodeContext.NodeName, + finalMessage); + } + } + + // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. + // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. + // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. + private const int ServiceMessageEventId = 2; + [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] + private +#if UNSAFE + unsafe +#endif + void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { +#if !UNSAFE + WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); +#else + const int numArgs = 8; + fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) + { + EventData* eventData = stackalloc EventData[numArgs]; + eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; + eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; + eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; + eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; + eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; + eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; + eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; + eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; + + WriteEventCore(ServiceMessageEventId, numArgs, eventData); + } +#endif + } + + private const int ServiceTypeRegisteredEventId = 3; + [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)] + public void ServiceTypeRegistered(int hostProcessId, string serviceType) + { + WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); + } + + private const int ServiceHostInitializationFailedEventId = 4; + [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)] + public void ServiceHostInitializationFailed(string exception) + { + WriteEvent(ServiceHostInitializationFailedEventId, exception); + } + + // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. + // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, + // and other statistics. + private const int ServiceRequestStartEventId = 5; + [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] + public void ServiceRequestStart(string requestTypeName) + { + WriteEvent(ServiceRequestStartEventId, requestTypeName); + } + + private const int ServiceRequestStopEventId = 6; + [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] + public void ServiceRequestStop(string requestTypeName, string exception = "") + { + WriteEvent(ServiceRequestStopEventId, requestTypeName, exception); + } + #endregion + + #region Private methods + private static long GetReplicaOrInstanceId(ServiceContext context) + { + StatelessServiceContext? stateless = context as StatelessServiceContext; + if (stateless != null) + { + return stateless.InstanceId; + } + + StatefulServiceContext? stateful = context as StatefulServiceContext; + if (stateful != null) + { + return stateful.ReplicaId; + } + + throw new NotSupportedException("Context type not supported."); + } +#if UNSAFE + private int SizeInBytes(string s) + { + if (s == null) + { + return 0; + } + else + { + return (s.Length + 1) * sizeof(char); + } + } +#endif + #endregion + } +} diff --git a/ReplicaTestApp/WeatherForecast.cs b/ReplicaTestApp/WeatherForecast.cs new file mode 100644 index 000000000..2779e5a86 --- /dev/null +++ b/ReplicaTestApp/WeatherForecast.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace ReplicaTestApp +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/ReplicaTestApp/appsettings.Development.json b/ReplicaTestApp/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/ReplicaTestApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ReplicaTestApp/appsettings.json b/ReplicaTestApp/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/ReplicaTestApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/TestProject4/Program.cs b/TestProject4/Program.cs index b71ad6fba..a3f649711 100644 --- a/TestProject4/Program.cs +++ b/TestProject4/Program.cs @@ -10,54 +10,54 @@ public class Program { - public static async Task Main(string[] args) + public static void Main(string[] args) { // Initialize the stateful service registrator - ServiceRegistratorOptions serviceRegistratorOptions = new ServiceRegistratorOptions { ServiceTypeName = "MyServiceType" }; - IOptions options = Options.Create(serviceRegistratorOptions); - IAccessorSetter contextAccessor = new AccessorSetter(); // Initialize appropriately - IAccessorSetter partitionAccessor = new AccessorSetter(); // Initialize appropriately - IAccessorSetter stateAccessor = new AccessorSetter(); // Initialize appropriately - IAccessorSetter roleAccessor = new AccessorSetter(); // Initialize appropriately - IEnumerable> listenerBuilders = new List>(); // Initialize appropriately - IEnumerable> serviceActions = new List>(); // Initialize appropriately + //ServiceRegistratorOptions serviceRegistratorOptions = new ServiceRegistratorOptions { ServiceTypeName = "MyServiceType" }; + //IOptions options = Options.Create(serviceRegistratorOptions); + //IAccessorSetter contextAccessor = new AccessorSetter(); // Initialize appropriately + //IAccessorSetter partitionAccessor = new AccessorSetter(); // Initialize appropriately + //IAccessorSetter stateAccessor = new AccessorSetter(); // Initialize appropriately + ////IAccessorSetter roleAccessor = new AccessorSetter(); // Initialize appropriately + //IEnumerable> listenerBuilders = new List>(); // Initialize appropriately + //IEnumerable> serviceActions = new List>(); // Initialize appropriately - OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( - options, - contextAccessor, - partitionAccessor, - stateAccessor, - roleAccessor, - listenerBuilders, - serviceActions); + //OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( + // options, + // contextAccessor, + // partitionAccessor, + // stateAccessor, + // //roleAccessor, + // listenerBuilders, + // serviceActions); // Create a mock StatefulServiceContext - NodeContext nodeContext = new NodeContext("nodeName", new NodeId(0, 0), 0, "nodeType", "ipAddress"); - ICodePackageActivationContext codePackageActivationContext = new MockCodePackageActivationContext( - "applicationName", - "applicationTypeName", - "codePackageName", - "codePackageVersion", - "contextId", - "logDirectory", - "tempDirectory", - "workDirectory", - "serviceManifestName", - "serviceManifestVersion" - ); - StatefulServiceContext context = new StatefulServiceContext(nodeContext, codePackageActivationContext, "serviceTypeName", new Uri("fabric:/AppName/ServiceName"), null, Guid.NewGuid(), long.MaxValue); + //NodeContext nodeContext = new NodeContext("nodeName", new NodeId(0, 0), 0, "nodeType", "ipAddress"); + //ICodePackageActivationContext codePackageActivationContext = new MockCodePackageActivationContext( + // "applicationName", + // "applicationTypeName", + // "codePackageName", + // "codePackageVersion", + // "contextId", + // "logDirectory", + // "tempDirectory", + // "workDirectory", + // "serviceManifestName", + // "serviceManifestVersion" + //); + //StatefulServiceContext context = new StatefulServiceContext(nodeContext, codePackageActivationContext, "serviceTypeName", new Uri("fabric:/AppName/ServiceName"), null, Guid.NewGuid(), long.MaxValue); // Initialize the OmexStatefulService - OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); + //OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); // Call OnChangeRoleAsync with appropriate parameters - await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); + //await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); // Get the current replica role - ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); - Console.WriteLine($"Current Replica Role: {currentRole}"); + //ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); + Console.WriteLine($"Test"); + } } -} public class AccessorSetter : IAccessorSetter where T : class { diff --git a/src/Hosting.Services/HostBuilderExtensions.cs b/src/Hosting.Services/HostBuilderExtensions.cs index d34b4b1fc..72818c5fb 100644 --- a/src/Hosting.Services/HostBuilderExtensions.cs +++ b/src/Hosting.Services/HostBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Omex.Extensions.Abstractions; using Microsoft.Omex.Extensions.Abstractions.Accessors; using Microsoft.Omex.Extensions.Abstractions.ExecutionContext; +using Microsoft.Omex.Extensions.Hosting.Services.Internal; using Microsoft.ServiceFabric.Data; using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; @@ -50,7 +51,7 @@ public static IServiceCollection AddOmexServiceFabricDependencies(this { collection.TryAddAccessor(); collection.TryAddAccessor(); - collection.TryAddAccessor(); + collection.TryAddAccessor(); } else { diff --git a/src/Hosting.Services/Internal/OmexStateManager.cs b/src/Hosting.Services/Internal/OmexStateManager.cs new file mode 100644 index 000000000..228ad0e1b --- /dev/null +++ b/src/Hosting.Services/Internal/OmexStateManager.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Fabric; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.ServiceFabric.Data; + +namespace Microsoft.Omex.Extensions.Hosting.Services.Internal +{ + /// + /// Manages the state of the Omex service. + /// + public sealed class OmexStateManager + { + private readonly IReliableStateManager m_stateManager; + private ReplicaRole m_role; + + /// + /// Initializes a new instance of the class. + /// + /// The reliable state manager. + /// The replica role. + public OmexStateManager(IReliableStateManager stateManager, ReplicaRole role) + { + m_stateManager = stateManager; + m_role = role; + } + + /// + /// Gets the reliable state manager. + /// + public IReliableStateManager State => m_stateManager; + + /// + /// Gets a value indicating whether the state is readable. + /// + public bool IsReadable => m_role == ReplicaRole.Primary || m_role == ReplicaRole.ActiveSecondary; + + /// + /// Gets a value indicating whether the state is writable. + /// + public bool IsWritable => m_role == ReplicaRole.Primary; + + /// + /// Gets the current replica role. + /// + /// The current . + public ReplicaRole GetRole() => m_role; + } +} diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index a8c51d0b1..00fc25878 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -7,12 +7,16 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using Microsoft.Omex.Extensions.Abstractions.Accessors; +using Microsoft.Omex.Extensions.Hosting.Services.Internal; using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Services.Runtime; namespace Microsoft.Omex.Extensions.Hosting.Services { - public sealed partial class OmexStatefulServiceRegistrator : OmexServiceRegistrator + /// + /// Registers OmexStatefulService with the Service Fabric runtime. + /// + public sealed class OmexStatefulServiceRegistrator : OmexServiceRegistrator { /// /// Initializes a new instance of the class. @@ -29,7 +33,7 @@ public OmexStatefulServiceRegistrator( IAccessorSetter contextAccessor, IAccessorSetter partitionAccessor, IAccessorSetter stateAccessor, - IAccessorSetter roleAccessor, + IAccessorSetter roleAccessor, IEnumerable> listenerBuilders, IEnumerable> serviceActions) : base(options, contextAccessor, listenerBuilders, serviceActions) @@ -58,9 +62,8 @@ public override Task RegisterAsync(CancellationToken cancellationToken) => public IAccessorSetter PartitionAccessor { get; } /// - /// Gets the accessor for the replica role wrapper. + /// Gets the accessor for the Omex state manager. /// - public IAccessorSetter RoleAccessor { get; } - + public IAccessorSetter RoleAccessor { get; } } } diff --git a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs index fa77f05aa..1f003e41c 100644 --- a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs +++ b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs @@ -5,20 +5,16 @@ namespace Microsoft.Omex.Extensions.Hosting.Services { - /// - /// Registrator for OmexStatefulService. - /// - public sealed partial class OmexStatefulServiceRegistrator - { - /// - /// Wrapper class for the ReplicaRole to be used as a parameter in generic types or methods. - /// - public class ReplicaRoleWrapper - { - /// - /// Gets or sets the role of the replica. - /// - public ReplicaRole Role { get; set; } - } - } + + ///// + ///// Wrapper class for the ReplicaRole to be used as a parameter in generic types or methods. + ///// + //internal class ReplicaRoleWrapper + //{ + // /// + // /// Gets or sets the role of the replica. + // /// + // protected ReplicaRole Role { get; set; } + //} + } diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 278bd78a9..21aff5fd5 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -10,6 +10,7 @@ using Microsoft.ServiceFabric.Services.Runtime; using Microsoft.Omex.Extensions.Hosting.Services; using static Microsoft.Omex.Extensions.Hosting.Services.OmexStatefulServiceRegistrator; +using Microsoft.Omex.Extensions.Hosting.Services.Internal; namespace Microsoft.Omex.Extensions.Hosting.Services { @@ -19,7 +20,7 @@ namespace Microsoft.Omex.Extensions.Hosting.Services public sealed class OmexStatefulService : StatefulService, IServiceFabricService { private readonly OmexStatefulServiceRegistrator m_serviceRegistrator; - private ReplicaRoleWrapper m_replicaRoleWrapper = new ReplicaRoleWrapper(); + private OmexStateManager m_omexStateManager; /// /// Initializes a new instance of the class. @@ -34,6 +35,7 @@ public OmexStatefulService( serviceRegistrator.ContextAccessor.SetValue(Context); serviceRegistrator.StateAccessor.SetValue(StateManager); m_serviceRegistrator = serviceRegistrator; + m_omexStateManager = new OmexStateManager(StateManager, ReplicaRole.Unknown); } /// @@ -46,16 +48,16 @@ protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken /// protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - m_replicaRoleWrapper = new ReplicaRoleWrapper { Role = newRole }; - m_serviceRegistrator.RoleAccessor.SetValue(m_replicaRoleWrapper); + m_omexStateManager = new OmexStateManager(StateManager, newRole); + m_serviceRegistrator.RoleAccessor.SetValue(m_omexStateManager); return base.OnChangeRoleAsync(newRole, cancellationToken); } /// public Task ChangeRoleAsyncTest(ReplicaRole newRole, CancellationToken cancellationToken) { - m_replicaRoleWrapper = new ReplicaRoleWrapper { Role = newRole }; - m_serviceRegistrator.RoleAccessor.SetValue(m_replicaRoleWrapper); + m_omexStateManager = new OmexStateManager(StateManager, newRole); + m_serviceRegistrator.RoleAccessor.SetValue(m_omexStateManager); return Task.CompletedTask; } @@ -70,6 +72,8 @@ protected override Task RunAsync(CancellationToken cancellationToken) => /// /// Gets the current replica role. /// - public ReplicaRole GetCurrentReplicaRole() => m_replicaRoleWrapper?.Role ?? ReplicaRole.Unknown; + /// The current replica role. + public ReplicaRole GetCurrentReplicaRole() => m_omexStateManager?.GetRole() ?? ReplicaRole.Unknown; + } } diff --git a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs index 1ec6afbdf..2a1c7a6e6 100644 --- a/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs +++ b/tests/Hosting.Services.UnitTests/MockServiceFabricServices.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Extensions.Options; using Microsoft.Omex.Extensions.Abstractions.Accessors; +using Microsoft.Omex.Extensions.Hosting.Services.Internal; using Microsoft.ServiceFabric.Data; using ServiceFabric.Mocks; @@ -29,7 +30,7 @@ public static class MockServiceFabricServices new Accessor(), new Accessor(), new Accessor(new MockReliableStateManager()), - new Accessor(), + new Accessor(), Enumerable.Empty>(), Enumerable.Empty>()), MockStatefulServiceContextFactory.Default); From 36515667ed7febc99c623ea7007f66e4721a3ae8 Mon Sep 17 00:00:00 2001 From: Rifat Kale Date: Mon, 7 Apr 2025 12:42:55 +0100 Subject: [PATCH 13/13] GetRole and IsReadable and IsWritable test from omexStateManager --- ReplicaTestApp/Program.cs | 28 ++++++++++--------- .../OmexStatefulServiceRegistrator.cs | 2 +- src/Hosting.Services/OmexStatefulService.cs | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/ReplicaTestApp/Program.cs b/ReplicaTestApp/Program.cs index c3b02d51c..7f59ee0be 100644 --- a/ReplicaTestApp/Program.cs +++ b/ReplicaTestApp/Program.cs @@ -27,7 +27,7 @@ public class AccessorSetter : IAccessorSetter where T : class /// /// This is the entry point of the service host process. /// - private static async Task Main() + private static void Main() { try { @@ -54,14 +54,14 @@ private static async Task Main() IEnumerable> listenerBuilders = new List>(); // Initialize appropriately IEnumerable> serviceActions = new List>(); // Initialize appropriately - OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( - options, - contextAccessor, - partitionAccessor, - stateAccessor, - roleAccessor, - listenerBuilders, - serviceActions); + //OmexStatefulServiceRegistrator serviceRegistrator = new OmexStatefulServiceRegistrator( + // options, + // contextAccessor, + // partitionAccessor, + // stateAccessor, + // roleAccessor, + // listenerBuilders, + // serviceActions); // Create a mock StatefulServiceContext NodeContext nodeContext = new NodeContext("nodeName", new NodeId(0, 0), 0, "nodeType", "ipAddress"); @@ -79,14 +79,16 @@ private static async Task Main() ); StatefulServiceContext context = new StatefulServiceContext(nodeContext, codePackageActivationContext, "serviceTypeName", new Uri("fabric:/AppName/ServiceName"), null, Guid.NewGuid(), long.MaxValue); - // Initialize the OmexStatefulService - OmexStatefulService statefulService = new OmexStatefulService(serviceRegistrator, context); + IReliableStateManager reliableStateManager = new MockReliableStateManager(); + OmexStateManager omexStateManager = new(reliableStateManager, ReplicaRole.Primary); // Call OnChangeRoleAsync with appropriate parameters - await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); + //await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None); - ReplicaRole currentRole = statefulService.GetCurrentReplicaRole(); + ReplicaRole currentRole = omexStateManager.GetRole(); Console.WriteLine($"Current Replica Role: {currentRole}"); + Console.WriteLine($"IsReadable {omexStateManager.IsReadable}"); + Console.WriteLine($"IsWritablee {omexStateManager.IsWritable}"); Thread.Sleep(Timeout.Infinite); diff --git a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs index 00fc25878..dd9b1c350 100644 --- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs +++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs @@ -16,7 +16,7 @@ namespace Microsoft.Omex.Extensions.Hosting.Services /// /// Registers OmexStatefulService with the Service Fabric runtime. /// - public sealed class OmexStatefulServiceRegistrator : OmexServiceRegistrator + internal sealed class OmexStatefulServiceRegistrator : OmexServiceRegistrator { /// /// Initializes a new instance of the class. diff --git a/src/Hosting.Services/OmexStatefulService.cs b/src/Hosting.Services/OmexStatefulService.cs index 21aff5fd5..0156e2931 100644 --- a/src/Hosting.Services/OmexStatefulService.cs +++ b/src/Hosting.Services/OmexStatefulService.cs @@ -27,7 +27,7 @@ public sealed class OmexStatefulService : StatefulService, IServiceFabricService /// /// The service registrator. /// The stateful service context. - public OmexStatefulService( + internal OmexStatefulService( OmexStatefulServiceRegistrator serviceRegistrator, StatefulServiceContext serviceContext) : base(serviceContext)