diff --git a/Directory.Packages.props b/Directory.Packages.props
index 132b7d69a..3122e5481 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -20,6 +20,7 @@
+
@@ -64,9 +65,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/Omex.sln b/Omex.sln
index 0eb176f2e..e5127556b 100644
--- a/Omex.sln
+++ b/Omex.sln
@@ -75,6 +75,12 @@ 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
+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
@@ -275,6 +281,34 @@ 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
+ {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
@@ -304,6 +338,9 @@ 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}
+ {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..7f59ee0be
--- /dev/null
+++ b/ReplicaTestApp/Program.cs
@@ -0,0 +1,103 @@
+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 void 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);
+
+ IReliableStateManager reliableStateManager = new MockReliableStateManager();
+ OmexStateManager omexStateManager = new(reliableStateManager, ReplicaRole.Primary);
+
+ // Call OnChangeRoleAsync with appropriate parameters
+ //await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None);
+
+ ReplicaRole currentRole = omexStateManager.GetRole();
+ Console.WriteLine($"Current Replica Role: {currentRole}");
+ Console.WriteLine($"IsReadable {omexStateManager.IsReadable}");
+ Console.WriteLine($"IsWritablee {omexStateManager.IsWritable}");
+
+
+ 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
new file mode 100644
index 000000000..a3f649711
--- /dev/null
+++ b/TestProject4/Program.cs
@@ -0,0 +1,67 @@
+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);
+
+ // Call OnChangeRoleAsync with appropriate parameters
+ //await statefulService.ChangeRoleAsyncTest(ReplicaRole.Primary, CancellationToken.None);
+
+ // Get the current replica role
+ //ReplicaRole currentRole = statefulService.GetCurrentReplicaRole();
+ Console.WriteLine($"Test");
+ }
+ }
+
+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/HostBuilderExtensions.cs b/src/Hosting.Services/HostBuilderExtensions.cs
index 31a484ded..72818c5fb 100644
--- a/src/Hosting.Services/HostBuilderExtensions.cs
+++ b/src/Hosting.Services/HostBuilderExtensions.cs
@@ -10,7 +10,9 @@
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;
namespace Microsoft.Omex.Extensions.Hosting.Services
{
@@ -49,6 +51,7 @@ public static IServiceCollection AddOmexServiceFabricDependencies(this
{
collection.TryAddAccessor();
collection.TryAddAccessor();
+ collection.TryAddAccessor();
}
else
{
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/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 5699e64e6..dd9b1c350 100644
--- a/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs
+++ b/src/Hosting.Services/Internal/OmexStatefulServiceRegistrator.cs
@@ -7,31 +7,63 @@
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
{
+ ///
+ /// Registers OmexStatefulService with the Service Fabric runtime.
+ ///
internal sealed 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,
IAccessorSetter partitionAccessor,
IAccessorSetter stateAccessor,
+ IAccessorSetter roleAccessor,
IEnumerable> listenerBuilders,
IEnumerable> serviceActions)
: base(options, contextAccessor, listenerBuilders, serviceActions)
{
PartitionAccessor = partitionAccessor;
StateAccessor = stateAccessor;
+ 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);
+ ///
+ /// 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 Omex state manager.
+ ///
+ public IAccessorSetter RoleAccessor { get; }
}
}
diff --git a/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs
new file mode 100644
index 000000000..1f003e41c
--- /dev/null
+++ b/src/Hosting.Services/Internal/ReplicaRoleWrapper.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Fabric;
+
+namespace Microsoft.Omex.Extensions.Hosting.Services
+{
+
+ /////
+ ///// 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/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 aa4505a84..0156e2931 100644
--- a/src/Hosting.Services/OmexStatefulService.cs
+++ b/src/Hosting.Services/OmexStatefulService.cs
@@ -8,6 +8,9 @@
using System.Threading.Tasks;
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;
+using Microsoft.Omex.Extensions.Hosting.Services.Internal;
namespace Microsoft.Omex.Extensions.Hosting.Services
{
@@ -17,15 +20,22 @@ namespace Microsoft.Omex.Extensions.Hosting.Services
public sealed class OmexStatefulService : StatefulService, IServiceFabricService
{
private readonly OmexStatefulServiceRegistrator m_serviceRegistrator;
+ private OmexStateManager m_omexStateManager;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service registrator.
+ /// The stateful service context.
internal OmexStatefulService(
OmexStatefulServiceRegistrator serviceRegistrator,
StatefulServiceContext serviceContext)
- : base(serviceContext)
+ : base(serviceContext)
{
serviceRegistrator.ContextAccessor.SetValue(Context);
serviceRegistrator.StateAccessor.SetValue(StateManager);
m_serviceRegistrator = serviceRegistrator;
+ m_omexStateManager = new OmexStateManager(StateManager, ReplicaRole.Unknown);
}
///
@@ -35,12 +45,35 @@ protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken
return base.OnOpenAsync(openMode, cancellationToken);
}
+ ///
+ protected override Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken)
+ {
+ 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_omexStateManager = new OmexStateManager(StateManager, newRole);
+ m_serviceRegistrator.RoleAccessor.SetValue(m_omexStateManager);
+ 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) =>
Task.WhenAll(m_serviceRegistrator.ServiceActions.Select(r => r.RunAsync(this, cancellationToken)));
+
+ ///
+ /// Gets the current replica role.
+ ///
+ /// 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 8df2e9efa..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,6 +30,7 @@ public static class MockServiceFabricServices
new Accessor(),
new Accessor(),
new Accessor(new MockReliableStateManager()),
+ new Accessor(),
Enumerable.Empty>(),
Enumerable.Empty>()),
MockStatefulServiceContextFactory.Default);