2021-02-10 22:06:34 +03:00
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using Microsoft.VisualStudio.TestTools.UnitTesting ;
using FabricHealer.Repair ;
using Guan.Logic ;
using System.Collections.Generic ;
using System.Threading.Tasks ;
using System ;
using System.Fabric ;
using System.Threading ;
using FabricHealer.Repair.Guan ;
using System.IO ;
using System.Linq ;
using FabricHealer.Utilities.Telemetry ;
using FabricHealer.Utilities ;
2021-11-14 23:31:09 +03:00
using FabricHealer ;
2022-01-20 23:00:36 +03:00
using System.Fabric.Repair ;
using System.Diagnostics ;
2022-04-14 23:11:41 +03:00
using System.Fabric.Health ;
2022-06-20 23:25:20 +03:00
using SupportedErrorCodes = FabricHealer . Utilities . SupportedErrorCodes ;
2022-06-21 02:07:32 +03:00
using EntityType = FabricHealer . EntityType ;
2021-02-10 22:06:34 +03:00
namespace FHTest
{
2022-07-07 20:35:30 +03:00
/// <summary>
/// NOTE: Run these tests on your machine with a local SF dev cluster running.
/// TODO: More code coverage.
/// </summary>
2022-06-20 23:25:20 +03:00
2021-02-10 22:06:34 +03:00
[TestClass]
public class FHUnitTests
{
private static readonly Uri ServiceName = new Uri ( "fabric:/app/service" ) ;
2022-01-20 23:00:36 +03:00
private static readonly FabricClient fabricClient = new FabricClient ( ) ;
2021-02-10 22:06:34 +03:00
private static readonly ICodePackageActivationContext CodePackageContext
= new MockCodePackageActivationContext (
2022-01-20 23:00:36 +03:00
ServiceName . AbsoluteUri ,
"applicationType" ,
"Code" ,
"1.0.0.0" ,
Guid . NewGuid ( ) . ToString ( ) ,
@"C:\Log" ,
@"C:\Temp" ,
@"C:\Work" ,
"ServiceManifest" ,
"1.0.0.0" ) ;
2021-02-10 22:06:34 +03:00
private readonly StatelessServiceContext context
= new StatelessServiceContext (
2022-01-20 23:00:36 +03:00
new NodeContext ( "Node0" , new NodeId ( 0 , 1 ) , 0 , "NodeType1" , "TEST.MACHINE" ) ,
CodePackageContext ,
"FabricHealer.FabricHealerType" ,
ServiceName ,
null ,
Guid . NewGuid ( ) ,
long . MaxValue ) ;
2021-02-10 22:06:34 +03:00
2021-04-27 23:20:20 +03:00
private readonly CancellationToken token = new CancellationToken ( ) ;
2021-02-10 22:06:34 +03:00
2022-06-20 23:25:20 +03:00
// This is the name of the node used on your local dev machine's SF cluster. If you customize this, then change it.
private const string NodeName = "_Node_0" ;
private const string FHProxyId = "FabricHealerProxy" ;
2021-02-10 22:06:34 +03:00
// Set this to the full path to your Rules directory in the FabricHealer project's PackageRoot\Config directory.
2021-09-29 22:47:21 +03:00
// e.g., if developing on Windows, then something like @"C:\Users\[me]\source\repos\service-fabric-healer\FabricHealer\PackageRoot\Config\LogicRules\";
2022-01-20 23:00:36 +03:00
private const string FHRulesDirectory = @"C:\Users\ctorre\source\repos\service-fabric-healer\FabricHealer\PackageRoot\Config\LogicRules\" ;
private static bool IsLocalSFRuntimePresent ( )
{
try
{
var ps = Process . GetProcessesByName ( "Fabric" ) ;
return ps . Length ! = 0 ;
}
catch ( InvalidOperationException )
{
return false ;
}
}
2022-02-15 23:51:42 +03:00
/// <summary>
/// This function cancels the local repair tasks created by the tests.
/// </summary>
/// <returns></returns>
2022-01-20 23:00:36 +03:00
private static async Task CleanupTestRepairJobsAsync ( )
{
// Complete (Cancel) any existing Test Repair Jobs.
try
{
var repairTasks = await fabricClient . RepairManager . GetRepairTaskListAsync ( ) ;
var testRepairTasks = repairTasks . Where ( r = > r . TaskId . EndsWith ( "TEST_0" ) ) ;
2022-02-15 23:51:42 +03:00
2022-01-20 23:00:36 +03:00
foreach ( var repairTask in testRepairTasks )
{
if ( repairTask . State ! = RepairTaskState . Completed )
{
await FabricRepairTasks . CancelRepairTaskAsync ( repairTask , fabricClient ) ;
}
}
}
catch ( FabricException )
{
throw ;
}
}
[ClassCleanup]
public static async Task TestClassCleanupAsync ( )
{
await CleanupTestRepairJobsAsync ( ) ;
2022-06-21 02:07:32 +03:00
// Ensure FHProxy cleans up its health reports.
FabricHealerProxy . Instance . Close ( ) ;
2022-01-20 23:00:36 +03:00
}
2021-02-10 22:06:34 +03:00
/* GuanLogic Tests */
2022-02-15 23:51:42 +03:00
// Currently, the tests below validate logic rules and the successful scheduling of related local repair jobs.
2021-02-10 22:06:34 +03:00
2022-02-15 23:51:42 +03:00
// This test ensures your shipping rule files (the guan files located in Config/LogicRules folder)
2022-02-17 02:25:49 +03:00
// contain correctly written rules and that the related local repair job is successfully created.
2021-02-10 22:06:34 +03:00
[TestMethod]
public async Task TestGuanLogic_AllRules_FabricHealer_EnsureWellFormedRules_QueryInitialized ( )
{
2022-01-20 23:00:36 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2021-11-14 23:31:09 +03:00
FabricHealerManager . ConfigSettings = new ConfigSettings ( context )
{
TelemetryEnabled = false
} ;
2022-04-14 23:11:41 +03:00
// This will be the data used to create a repair task.
var repairData = new TelemetryData
2021-02-10 22:06:34 +03:00
{
2022-01-20 23:00:36 +03:00
ApplicationName = "fabric:/test" ,
2021-02-10 22:06:34 +03:00
NodeName = "TEST_0" ,
2022-04-14 23:11:41 +03:00
Code = SupportedErrorCodes . AppErrorMemoryMB ,
HealthState = HealthState . Warning ,
2022-01-20 23:00:36 +03:00
ServiceName = "fabric:/test0/service0" ,
2022-06-20 23:25:20 +03:00
Value = 1024.0 ,
RepairPolicy = new RepairPolicy
{
RepairId = $"Test42_{SupportedErrorCodes.AppErrorMemoryMB}"
}
2022-05-03 21:50:49 +03:00
} ;
2021-04-27 23:20:20 +03:00
var executorData = new RepairExecutorData
2021-02-10 22:06:34 +03:00
{
2022-05-03 21:50:49 +03:00
RepairData = repairData
2021-02-10 22:06:34 +03:00
} ;
foreach ( var file in Directory . GetFiles ( FHRulesDirectory ) )
{
2022-02-17 02:25:49 +03:00
List < string > repairRules = ParseRulesFile ( await File . ReadAllLinesAsync ( file , token ) ) ;
2021-02-10 22:06:34 +03:00
try
{
2022-04-14 23:11:41 +03:00
await TestInitializeGuanAndRunQuery ( repairData , repairRules , executorData ) ;
2021-02-10 22:06:34 +03:00
}
2021-02-20 01:07:11 +03:00
catch ( GuanException ge )
2021-02-10 22:06:34 +03:00
{
2022-02-17 02:25:49 +03:00
throw new AssertFailedException ( ge . Message , ge ) ;
2021-02-10 22:06:34 +03:00
}
}
}
2022-02-17 02:25:49 +03:00
// This test ensures your test rules housed in testrules_wellformed file or in fact correct.
2021-02-10 22:06:34 +03:00
[TestMethod]
public async Task TestGuanLogicRule_GoodRule_QueryInitialized ( )
{
2022-01-20 23:00:36 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2021-11-14 23:31:09 +03:00
FabricHealerManager . ConfigSettings = new ConfigSettings ( context )
{
TelemetryEnabled = false
} ;
2022-01-20 23:00:36 +03:00
2021-02-10 22:06:34 +03:00
string testRulesFilePath = Path . Combine ( Environment . CurrentDirectory , "testrules_wellformed" ) ;
2022-06-02 02:26:11 +03:00
string [ ] rules = await File . ReadAllLinesAsync ( testRulesFilePath , token ) ;
2022-02-15 23:51:42 +03:00
List < string > repairRules = ParseRulesFile ( rules ) ;
2022-04-14 23:11:41 +03:00
var repairData = new TelemetryData
2021-02-10 22:06:34 +03:00
{
ApplicationName = "fabric:/test0" ,
NodeName = "TEST_0" ,
Metric = "Memory" ,
2022-04-14 23:11:41 +03:00
HealthState = HealthState . Warning ,
Code = SupportedErrorCodes . AppErrorMemoryMB ,
2022-01-20 23:00:36 +03:00
ServiceName = "fabric:/test0/service0" ,
2021-02-10 22:06:34 +03:00
Value = 42 ,
2021-07-14 21:30:05 +03:00
ReplicaId = default ,
2022-04-14 23:11:41 +03:00
PartitionId = default ,
2022-06-20 23:25:20 +03:00
RepairPolicy = new RepairPolicy
{
RepairId = $"Test42_{SupportedErrorCodes.AppErrorMemoryMB}"
}
2022-05-03 21:50:49 +03:00
} ;
2021-04-27 23:20:20 +03:00
var executorData = new RepairExecutorData
2021-02-10 22:06:34 +03:00
{
2022-05-03 21:50:49 +03:00
RepairData = repairData
2021-02-10 22:06:34 +03:00
} ;
2022-02-17 02:25:49 +03:00
try
{
2022-04-14 23:11:41 +03:00
await TestInitializeGuanAndRunQuery ( repairData , repairRules , executorData ) ;
2022-02-17 02:25:49 +03:00
}
catch ( GuanException ge )
{
throw new AssertFailedException ( ge . Message , ge ) ;
}
2021-02-10 22:06:34 +03:00
}
2022-02-17 02:25:49 +03:00
// This test ensures your test rules housed in testrules_malformed file or in fact incorrect.
2021-02-10 22:06:34 +03:00
[TestMethod]
public async Task TestGuanLogicRule_BadRule_ShouldThrowGuanException ( )
{
2022-01-20 23:00:36 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2021-11-14 23:31:09 +03:00
FabricHealerManager . ConfigSettings = new ConfigSettings ( context )
{
TelemetryEnabled = false
} ;
2022-01-20 23:00:36 +03:00
2022-06-02 02:26:11 +03:00
string [ ] rules = await File . ReadAllLinesAsync ( Path . Combine ( Environment . CurrentDirectory , "testrules_malformed" ) , token ) ;
2022-02-15 23:51:42 +03:00
List < string > repairAction = ParseRulesFile ( rules ) ;
2021-02-10 22:06:34 +03:00
2022-04-14 23:11:41 +03:00
var repairData = new TelemetryData
2021-02-10 22:06:34 +03:00
{
ApplicationName = "fabric:/test0" ,
NodeName = "TEST_0" ,
Metric = "Memory" ,
2022-04-14 23:11:41 +03:00
HealthState = HealthState . Warning ,
Code = SupportedErrorCodes . AppErrorMemoryMB ,
2022-01-20 23:00:36 +03:00
ServiceName = "fabric:/test0/service0" ,
2021-02-10 22:06:34 +03:00
Value = 42 ,
2021-07-14 21:30:05 +03:00
ReplicaId = default ,
2022-04-14 23:11:41 +03:00
PartitionId = default ,
2022-06-20 23:25:20 +03:00
RepairPolicy = new RepairPolicy
{
RepairId = $"Test42_{SupportedErrorCodes.AppErrorMemoryMB}"
}
2022-05-03 21:50:49 +03:00
} ;
2021-04-27 23:20:20 +03:00
var executorData = new RepairExecutorData
2021-02-10 22:06:34 +03:00
{
2022-05-03 21:50:49 +03:00
RepairData = repairData
2021-02-10 22:06:34 +03:00
} ;
2022-04-14 23:11:41 +03:00
await Assert . ThrowsExceptionAsync < GuanException > ( async ( ) = > { await TestInitializeGuanAndRunQuery ( repairData , repairAction , executorData ) ; } ) ;
2021-02-10 22:06:34 +03:00
}
/* private Helpers */
2022-04-14 23:11:41 +03:00
private async Task TestInitializeGuanAndRunQuery ( TelemetryData repairData , List < string > repairRules , RepairExecutorData executorData )
2021-02-10 22:06:34 +03:00
{
2022-07-07 20:35:30 +03:00
_ = FabricHealerManager . Instance ( context , token ) ;
var repairTaskManager = new RepairTaskManager ( context , token ) ;
var repairTaskEngine = new RepairTaskEngine ( ) ;
2021-02-10 22:06:34 +03:00
2021-11-14 23:31:09 +03:00
// Add predicate types to functor table. Note that all health information data from FO are automatically passed to all predicates.
// This enables access to various health state values in any query. See Mitigate() in rules files, for examples.
2021-02-10 22:06:34 +03:00
FunctorTable functorTable = new FunctorTable ( ) ;
// Add external helper predicates.
2022-04-14 23:11:41 +03:00
functorTable . Add ( CheckFolderSizePredicateType . Singleton ( RepairConstants . CheckFolderSize , repairTaskManager , repairData ) ) ;
functorTable . Add ( GetRepairHistoryPredicateType . Singleton ( RepairConstants . GetRepairHistory , repairTaskManager , repairData ) ) ;
functorTable . Add ( GetHealthEventHistoryPredicateType . Singleton ( RepairConstants . GetHealthEventHistory , repairTaskManager , repairData ) ) ;
functorTable . Add ( CheckInsideRunIntervalPredicateType . Singleton ( RepairConstants . CheckInsideRunInterval , repairTaskManager , repairData ) ) ;
2021-11-14 23:31:09 +03:00
functorTable . Add ( EmitMessagePredicateType . Singleton ( RepairConstants . EmitMessage , repairTaskManager ) ) ;
2021-02-10 22:06:34 +03:00
// Add external repair predicates.
2022-04-14 23:11:41 +03:00
functorTable . Add ( DeleteFilesPredicateType . Singleton ( RepairConstants . DeleteFiles , repairTaskManager , repairData ) ) ;
functorTable . Add ( RestartCodePackagePredicateType . Singleton ( RepairConstants . RestartCodePackage , repairTaskManager , repairData ) ) ;
functorTable . Add ( RestartFabricNodePredicateType . Singleton ( RepairConstants . RestartFabricNode , repairTaskManager , executorData , repairTaskEngine , repairData ) ) ;
functorTable . Add ( RestartFabricSystemProcessPredicateType . Singleton ( RepairConstants . RestartFabricSystemProcess , repairTaskManager , repairData ) ) ;
functorTable . Add ( RestartReplicaPredicateType . Singleton ( RepairConstants . RestartReplica , repairTaskManager , repairData ) ) ;
2022-04-22 04:21:01 +03:00
functorTable . Add ( RestartMachinePredicateType . Singleton ( RepairConstants . RestartVM , repairTaskManager , repairData ) ) ;
2021-02-10 22:06:34 +03:00
// Parse rules
2021-11-15 05:25:04 +03:00
Module module = Module . Parse ( "external" , repairRules , functorTable ) ;
2021-02-10 22:06:34 +03:00
2021-11-15 22:27:41 +03:00
// Create guan query dispatcher.
2022-01-20 23:00:36 +03:00
var queryDispatcher = new GuanQueryDispatcher ( module ) ;
2021-11-15 05:25:04 +03:00
/* Bind default arguments to goal (Mitigate). */
List < CompoundTerm > compoundTerms = new List < CompoundTerm > ( ) ;
// Mitigate is the head of the rules used in FH. It's the Goal that Guan will try to accomplish based on the logical expressions (or subgoals) that form a given rule.
CompoundTerm compoundTerm = new CompoundTerm ( "Mitigate" ) ;
2021-02-10 22:06:34 +03:00
2021-02-20 01:07:11 +03:00
// The type of metric that led FO to generate the unhealthy evaluation for the entity (App, Node, VM, Replica, etc).
2021-11-15 05:25:04 +03:00
// We rename these for brevity for simplified use in logic rule composition (e;g., MetricName="Threads" instead of MetricName="Total Thread Count").
2022-04-14 23:11:41 +03:00
repairData . Metric = SupportedErrorCodes . GetMetricNameFromErrorCode ( repairData . Code ) ;
2021-02-20 01:07:11 +03:00
2021-11-15 05:25:04 +03:00
// These args hold the related values supplied by FO and are available anywhere Mitigate is used as a rule head.
2022-04-14 23:11:41 +03:00
compoundTerm . AddArgument ( new Constant ( repairData . ApplicationName ) , RepairConstants . AppName ) ;
compoundTerm . AddArgument ( new Constant ( repairData . Code ) , RepairConstants . ErrorCode ) ;
compoundTerm . AddArgument ( new Constant ( Enum . GetName ( typeof ( HealthState ) , repairData . HealthState ) ) , RepairConstants . HealthState ) ;
compoundTerm . AddArgument ( new Constant ( repairData . Metric ) , RepairConstants . MetricName ) ;
compoundTerm . AddArgument ( new Constant ( repairData . NodeName ) , RepairConstants . NodeName ) ;
compoundTerm . AddArgument ( new Constant ( repairData . NodeType ) , RepairConstants . NodeType ) ;
compoundTerm . AddArgument ( new Constant ( repairData . ObserverName ) , RepairConstants . ObserverName ) ;
compoundTerm . AddArgument ( new Constant ( repairData . OS ) , RepairConstants . OS ) ;
compoundTerm . AddArgument ( new Constant ( repairData . ServiceName ) , RepairConstants . ServiceName ) ;
2022-07-07 20:35:30 +03:00
compoundTerm . AddArgument ( new Constant ( repairData . ProcessName ) , RepairConstants . ProcessName ) ;
2022-04-14 23:11:41 +03:00
compoundTerm . AddArgument ( new Constant ( repairData . PartitionId ) , RepairConstants . PartitionId ) ;
compoundTerm . AddArgument ( new Constant ( repairData . ReplicaId ) , RepairConstants . ReplicaOrInstanceId ) ;
compoundTerm . AddArgument ( new Constant ( Convert . ToInt64 ( repairData . Value ) ) , RepairConstants . MetricValue ) ;
2021-11-15 05:25:04 +03:00
compoundTerms . Add ( compoundTerm ) ;
2022-04-16 00:46:27 +03:00
await queryDispatcher . RunQueryAsync ( compoundTerms ) ;
2021-02-10 22:06:34 +03:00
}
2022-02-15 23:51:42 +03:00
private static List < string > ParseRulesFile ( string [ ] rules )
2021-02-10 22:06:34 +03:00
{
var repairRules = new List < string > ( ) ;
2022-02-15 23:51:42 +03:00
int ptr1 = 0 , ptr2 = 0 ;
rules = rules . Where ( s = > ! string . IsNullOrWhiteSpace ( s ) ) . ToArray ( ) ;
2021-02-10 22:06:34 +03:00
2022-02-15 23:51:42 +03:00
while ( ptr1 < rules . Length & & ptr2 < rules . Length )
2021-02-10 22:06:34 +03:00
{
// Single line comments removal.
2021-02-22 22:31:19 +03:00
if ( rules [ ptr2 ] . TrimStart ( ) . StartsWith ( "##" ) )
2021-02-10 22:06:34 +03:00
{
ptr1 + + ;
ptr2 + + ;
continue ;
}
if ( rules [ ptr2 ] . EndsWith ( "." ) )
{
if ( ptr1 = = ptr2 )
{
repairRules . Add ( rules [ ptr2 ] . Remove ( rules [ ptr2 ] . Length - 1 , 1 ) ) ;
}
else
{
string rule = rules [ ptr1 ] . TrimEnd ( ' ' ) ;
for ( int i = ptr1 + 1 ; i < = ptr2 ; i + + )
{
rule = rule + ' ' + rules [ i ] . Replace ( '\t' , ' ' ) . TrimStart ( ' ' ) ;
}
2022-02-15 23:51:42 +03:00
2021-02-10 22:06:34 +03:00
repairRules . Add ( rule . Remove ( rule . Length - 1 , 1 ) ) ;
}
ptr2 + + ;
ptr1 = ptr2 ;
}
else
{
ptr2 + + ;
}
}
return repairRules ;
}
2022-06-20 23:25:20 +03:00
2022-06-21 02:07:32 +03:00
private static async Task < ( bool , TelemetryData data ) >
2022-06-20 23:25:20 +03:00
IsEntityInWarningStateAsync ( string appName = null , string serviceName = null , string nodeName = null )
{
EntityHealth healthData = null ;
if ( appName ! = null )
{
healthData = await fabricClient . HealthManager . GetApplicationHealthAsync ( new Uri ( appName ) ) ;
}
else if ( serviceName ! = null )
{
healthData = await fabricClient . HealthManager . GetServiceHealthAsync ( new Uri ( serviceName ) ) ;
}
else if ( nodeName ! = null )
{
healthData = await fabricClient . HealthManager . GetNodeHealthAsync ( nodeName ) ;
}
else
{
return ( false , null ) ;
}
if ( healthData = = null )
{
return ( false , null ) ;
}
bool isInWarning = healthData . HealthEvents . Any ( h = > h ? . HealthInformation ? . HealthState = = HealthState . Warning ) ;
if ( ! isInWarning )
{
return ( false , null ) ;
}
HealthEvent healthEventWarning = healthData . HealthEvents . FirstOrDefault ( h = > h . HealthInformation ? . HealthState = = HealthState . Warning ) ;
_ = JsonSerializationUtility . TryDeserialize ( healthEventWarning . HealthInformation . Description , out TelemetryData data ) ;
return ( true , data ) ;
}
// FabricHealearProxy tests \\
// This specifies that you want FabricHealer to repair a service instance deployed to a Fabric node named NodeName.
// FabricHealer supports both Replica and CodePackage restarts of services. The logic rules will dictate which one of these happens,
// so make sure to craft a specific logic rule that makes sense for you (and use some logic!).
// Note that, out of the box, FabricHealer's AppRules.guan file located in the FabricHealer project's PackageRoot/Config/LogicRules folder
// already has a restart replica catch-all (applies to any service) rule that will restart the primary replica of
// the specified service below, deployed to the a specified Fabric node.
// By default, if you only supply NodeName and ServiceName, then FabricHealerProxy assumes the target EntityType is Service. This is a convience to limit how many facts
// you must supply in a RepairFacts instance. For any type of repair, NodeName is always required.
static readonly RepairFacts RepairFactsServiceTarget = new RepairFacts
{
ServiceName = "fabric:/GettingStartedApplication/MyActorService" ,
NodeName = NodeName ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/test"
} ;
// This specifies that you want FabricHealer to repair a Fabric node named _Node_0. The only supported Fabric node repair in FabricHealer is a Restart.
// Related rules can be found in FabricNodeRules.guan file in the FabricHealer project's PackageRoot/Config/LogicRules folder.
// So, implicitly, this means you want FabricHealer to restart _Node_0. By default, if you only supply NodeName, then FabricHealerProxy assumes the target EntityType is Node.
static readonly RepairFacts RepairFactsNodeTarget = new RepairFacts
{
NodeName = NodeName ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
// Initiate a reboot of the machine hosting the specified Fabric node, _Node_4. This will be executed by the InfrastructureService for the related node type.
// The related logic rules for this repair target are housed in FabricHealer's MachineRules.guan file.
static readonly RepairFacts RepairFactsMachineTarget = new RepairFacts
{
NodeName = NodeName ,
2022-06-21 02:07:32 +03:00
EntityType = EntityType . Machine ,
2022-06-20 23:25:20 +03:00
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
// Restart system service process.
static readonly RepairFacts SystemServiceRepairFacts = new RepairFacts
{
ApplicationName = "fabric:/System" ,
NodeName = NodeName ,
2022-07-07 20:35:30 +03:00
ProcessName = "FabricDCA" ,
2022-06-20 23:25:20 +03:00
ProcessId = 73588 ,
Code = SupportedErrorCodes . AppWarningMemoryMB ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
// Disk - Delete files. This only works if FabricHealer instance is present on the same target node.
// Note the rules in FabricHealer\PackageRoot\LogicRules\DiskRules.guan file in the FabricHealer project.
static readonly RepairFacts DiskRepairFacts = new RepairFacts
{
NodeName = NodeName ,
2022-06-21 02:07:32 +03:00
EntityType = EntityType . Disk ,
2022-06-20 23:25:20 +03:00
Metric = SupportedMetricNames . DiskSpaceUsageMb ,
Code = SupportedErrorCodes . NodeWarningDiskSpaceMB ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
// For use in the IEnumerable<RepairFacts> RepairEntityAsync overload.
static readonly List < RepairFacts > RepairFactsList = new List < RepairFacts >
{
DiskRepairFacts ,
RepairFactsMachineTarget ,
RepairFactsNodeTarget ,
RepairFactsServiceTarget ,
2022-06-21 02:07:32 +03:00
SystemServiceRepairFacts
2022-06-20 23:25:20 +03:00
} ;
[TestMethod]
2022-06-21 02:07:32 +03:00
public async Task FHProxy_Service_Facts_Generate_Entity_Health_Warning ( )
2022-06-20 23:25:20 +03:00
{
2022-06-20 23:29:12 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2022-06-20 23:25:20 +03:00
// This will put the entity into Warning with a specially-crafted Health Event description (serialized instance of ITelemetryData type).
2022-06-21 02:07:32 +03:00
await FabricHealerProxy . Instance . RepairEntityAsync ( RepairFactsServiceTarget , token ) ;
2022-06-20 23:25:20 +03:00
// FHProxy creates or renames Source with trailing id ("FabricHealerProxy");
Assert . IsTrue ( RepairFactsServiceTarget . Source . EndsWith ( FHProxyId ) ) ;
2022-06-21 02:07:32 +03:00
var ( generatedWarning , data ) = await IsEntityInWarningStateAsync ( null , RepairFactsServiceTarget . ServiceName ) ;
2022-06-20 23:25:20 +03:00
Assert . IsTrue ( generatedWarning ) ;
2022-07-07 20:35:30 +03:00
Assert . IsTrue ( data is TelemetryData ) ;
2022-06-20 23:25:20 +03:00
}
[TestMethod]
2022-06-21 02:07:32 +03:00
public async Task FHProxy_Node_Facts_Generates_Entity_Health_Warning ( )
2022-06-20 23:25:20 +03:00
{
2022-06-20 23:29:12 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2022-06-20 23:25:20 +03:00
// This will put the entity into Warning with a specially-crafted Health Event description (serialized instance of ITelemetryData type).
2022-06-21 02:07:32 +03:00
await FabricHealerProxy . Instance . RepairEntityAsync ( RepairFactsNodeTarget , token ) ;
2022-06-20 23:25:20 +03:00
// FHProxy creates or renames Source with trailing id ("FabricHealerProxy");
Assert . IsTrue ( RepairFactsNodeTarget . Source . EndsWith ( FHProxyId ) ) ;
2022-06-21 02:07:32 +03:00
var ( generatedWarning , data ) = await IsEntityInWarningStateAsync ( null , null , NodeName ) ;
2022-06-20 23:25:20 +03:00
Assert . IsTrue ( generatedWarning ) ;
2022-07-07 20:35:30 +03:00
Assert . IsTrue ( data is TelemetryData ) ;
2022-06-20 23:25:20 +03:00
}
[TestMethod]
2022-06-21 02:07:32 +03:00
public async Task FHProxy_Missing_Fact_Generates_MissingRepairFactsException ( )
2022-06-20 23:25:20 +03:00
{
2022-06-20 23:29:12 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2022-06-20 23:25:20 +03:00
var repairFacts = new RepairFacts
{
ServiceName = "fabric:/foo/bar" ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
2022-06-21 02:07:32 +03:00
await Assert . ThrowsExceptionAsync < MissingRepairFactsException > ( async ( ) = >
{
try
{
await FabricHealerProxy . Instance . RepairEntityAsync ( repairFacts , token ) ;
}
finally
{
// Ensure FHProxy cleans up its health reports.
FabricHealerProxy . Instance . Close ( ) ;
}
} ) ;
2022-06-20 23:25:20 +03:00
}
[TestMethod]
2022-06-21 02:07:32 +03:00
public async Task FHProxy_Missing_Fact_Generates_ServiceNotFoundException ( )
2022-06-20 23:25:20 +03:00
{
2022-06-20 23:29:12 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2022-06-20 23:25:20 +03:00
var repairFacts = new RepairFacts
{
ServiceName = "fabric:/foo/bar" ,
NodeName = NodeName ,
// Specifying Source is Required for unit tests.
// For unit tests, there is no FabricRuntime static, so FHProxy, which utilizes this type, will fail unless Source is provided here.
Source = "fabric:/Test"
} ;
2022-06-21 02:07:32 +03:00
await Assert . ThrowsExceptionAsync < ServiceNotFoundException > ( async ( ) = >
{
await FabricHealerProxy . Instance . RepairEntityAsync ( repairFacts , token ) ;
} ) ;
2022-06-20 23:25:20 +03:00
}
[TestMethod]
2022-06-21 02:07:32 +03:00
public async Task FHProxy_Missing_Fact_Generates_NodeNotFoundException ( )
2022-06-20 23:25:20 +03:00
{
2022-06-20 23:29:12 +03:00
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
2022-06-20 23:25:20 +03:00
var repairFacts = new RepairFacts
{
NodeName = "_Node_007x" ,
// No need for Source here as an invalid node will be detected before the Source value matters.
} ;
2022-06-21 02:07:32 +03:00
await Assert . ThrowsExceptionAsync < NodeNotFoundException > ( async ( ) = >
{
await FabricHealerProxy . Instance . RepairEntityAsync ( repairFacts , token ) ;
} ) ;
}
[TestMethod]
public async Task FHProxy_Multiple_Entity_Repair_Facts_Generate_Warnings ( )
{
if ( ! IsLocalSFRuntimePresent ( ) )
{
throw new InternalTestFailureException ( "You must run this test with an active local (dev) SF cluster." ) ;
}
// This will put the entity into Warning with a specially-crafted Health Event description (serialized instance of ITelemetryData type).
await FabricHealerProxy . Instance . RepairEntityAsync ( RepairFactsList , token ) ;
foreach ( var repair in RepairFactsList )
{
if ( repair . ServiceName ! = null )
{
var ( generatedWarningService , sdata ) = await IsEntityInWarningStateAsync ( null , repair . ServiceName ) ;
Assert . IsTrue ( generatedWarningService ) ;
2022-07-07 20:35:30 +03:00
Assert . IsTrue ( sdata is TelemetryData ) ;
2022-06-21 02:07:32 +03:00
}
else if ( repair . EntityType = = EntityType . Disk | | repair . EntityType = = EntityType . Machine | | repair . EntityType = = EntityType . Node )
{
var ( generatedWarningNode , ndata ) = await IsEntityInWarningStateAsync ( null , null , NodeName ) ;
Assert . IsTrue ( generatedWarningNode ) ;
2022-07-07 20:35:30 +03:00
Assert . IsTrue ( ndata is TelemetryData ) ;
2022-06-21 02:07:32 +03:00
}
// FHProxy creates or renames Source with trailing id ("FabricHealerProxy");
Assert . IsTrue ( repair . Source . EndsWith ( FHProxyId ) ) ;
}
2022-06-20 23:25:20 +03:00
}
2021-02-10 22:06:34 +03:00
}
}